Skip to content

Commit 6ad4d8c

Browse files
committed
Expand value references to packages to their underlying package objects
A package object can be seen as the facade of a package. For instance, it is the logical place where we want to write doc comments that explain a package. So far references to packages cannot be used as values. But if the package has a package object, it would make sense to allow the package reference with the meaning that it refers to this object. For instance, let's say we have ```scala package a object b ``` Of course, we can use `a.b` as a value. But if we change that to ```scala package a package object b ``` we can't anymore. This PR changes that so that we still allow a reference `a.b` as a value to mean the package object. Due to the way package objects are encoded the `a.b` reference expands to `a.b.package`.
1 parent b989790 commit 6ad4d8c

File tree

10 files changed

+81
-46
lines changed

10 files changed

+81
-46
lines changed

Diff for: compiler/src/dotty/tools/dotc/config/Feature.scala

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ object Feature:
3838
val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors")
3939
val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions")
4040
val betterFors = experimental("betterFors")
41+
val packageObjectValues = experimental("packageObjectValues")
4142

4243
def experimentalAutoEnableFeatures(using Context): List[TermName] =
4344
defn.languageExperimentalFeatures

Diff for: compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
132132
case MissingCompanionForStaticID // errorNumber: 116
133133
case PolymorphicMethodMissingTypeInParentID // errorNumber: 117
134134
case ParamsNoInlineID // errorNumber: 118
135-
case JavaSymbolIsNotAValueID // errorNumber: 119
135+
case SymbolIsNotAValueID // errorNumber: 119
136136
case DoubleDefinitionID // errorNumber: 120
137137
case MatchCaseOnlyNullWarningID // errorNumber: 121
138138
case ImportedTwiceID // errorNumber: 122

Diff for: compiler/src/dotty/tools/dotc/reporting/messages.scala

+6-6
Original file line numberDiff line numberDiff line change
@@ -2317,7 +2317,7 @@ class ParamsNoInline(owner: Symbol)(using Context)
23172317
def explain(using Context) = ""
23182318
}
23192319

2320-
class JavaSymbolIsNotAValue(symbol: Symbol)(using Context) extends TypeMsg(JavaSymbolIsNotAValueID) {
2320+
class SymbolIsNotAValue(symbol: Symbol)(using Context) extends TypeMsg(SymbolIsNotAValueID) {
23212321
def msg(using Context) =
23222322
val kind =
23232323
if symbol is Package then i"$symbol"
@@ -3331,14 +3331,14 @@ final class QuotedTypeMissing(tpe: Type)(using Context) extends StagingMessage(Q
33313331

33323332
private def witness = defn.QuotedTypeClass.typeRef.appliedTo(tpe)
33333333

3334-
override protected def msg(using Context): String =
3334+
override protected def msg(using Context): String =
33353335
i"Reference to $tpe within quotes requires a given ${witness} in scope"
33363336

33373337
override protected def explain(using Context): String =
3338-
i"""Referencing `$tpe` inside a quoted expression requires a `${witness}` to be in scope.
3338+
i"""Referencing `$tpe` inside a quoted expression requires a `${witness}` to be in scope.
33393339
|Since Scala is subject to erasure at runtime, the type information will be missing during the execution of the code.
3340-
|`${witness}` is therefore needed to carry `$tpe`'s type information into the quoted code.
3341-
|Without an implicit `${witness}`, the type `$tpe` cannot be properly referenced within the expression.
3340+
|`${witness}` is therefore needed to carry `$tpe`'s type information into the quoted code.
3341+
|Without an implicit `${witness}`, the type `$tpe` cannot be properly referenced within the expression.
33423342
|To resolve this, ensure that a `${witness}` is available, either through a context-bound or explicitly.
33433343
|"""
33443344

@@ -3350,7 +3350,7 @@ final class AmbiguousNamedTupleAssignment(key: Name, value: untpd.Tree)(using Co
33503350
|not as an assignment.
33513351
|
33523352
|To assign a value, use curly braces: `{${key} = ${value}}`."""
3353-
3353+
33543354
override protected def explain(using Context): String = ""
33553355

33563356
class AmbiguousNamedTupleInfixApply()(using Context) extends SyntaxMsg(AmbiguousNamedTupleInfixApplyID):

Diff for: compiler/src/dotty/tools/dotc/transform/Erasure.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import typer.NoChecking
2424
import inlines.Inlines
2525
import typer.ProtoTypes.*
2626
import typer.ErrorReporting.errorTree
27-
import typer.Checking.checkValue
2827
import core.TypeErasure.*
2928
import core.Decorators.*
3029
import dotty.tools.dotc.ast.{tpd, untpd}
@@ -676,7 +675,7 @@ object Erasure {
676675
if tree.name == nme.apply && integrateSelect(tree) then
677676
return typed(tree.qualifier, pt)
678677

679-
val qual1 = typed(tree.qualifier, AnySelectionProto)
678+
var qual1 = typed(tree.qualifier, AnySelectionProto)
680679

681680
def mapOwner(sym: Symbol): Symbol =
682681
if !sym.exists && tree.name == nme.apply then
@@ -725,7 +724,8 @@ object Erasure {
725724

726725
assert(sym.exists, i"no owner from $owner/${origSym.showLocated} in $tree")
727726

728-
if owner == defn.ObjectClass then checkValue(qual1)
727+
if owner == defn.ObjectClass then
728+
qual1 = checkValue(qual1)
729729

730730
def select(qual: Tree, sym: Symbol): Tree =
731731
untpd.cpy.Select(tree)(qual, sym.name).withType(NamedType(qual.tpe, sym))

Diff for: compiler/src/dotty/tools/dotc/typer/Checking.scala

-18
Original file line numberDiff line numberDiff line change
@@ -781,24 +781,6 @@ object Checking {
781781
else "Cannot override non-inline parameter with an inline parameter",
782782
p1.srcPos)
783783

784-
def checkValue(tree: Tree)(using Context): Unit =
785-
val sym = tree.tpe.termSymbol
786-
if sym.isNoValue && !ctx.isJava then
787-
report.error(JavaSymbolIsNotAValue(sym), tree.srcPos)
788-
789-
/** Check that `tree` refers to a value, unless `tree` is selected or applied
790-
* (singleton types x.type don't count as selections).
791-
*/
792-
def checkValue(tree: Tree, proto: Type)(using Context): tree.type =
793-
tree match
794-
case tree: RefTree if tree.name.isTermName =>
795-
proto match
796-
case _: SelectionProto if proto ne SingletonTypeProto => // no value check
797-
case _: FunOrPolyProto => // no value check
798-
case _ => checkValue(tree)
799-
case _ =>
800-
tree
801-
802784
/** Check that experimental language imports in `trees`
803785
* are done only in experimental scopes. For top-level
804786
* experimental imports, all top-level definitions are transformed

Diff for: compiler/src/dotty/tools/dotc/typer/Typer.scala

+36-15
Original file line numberDiff line numberDiff line change
@@ -609,10 +609,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
609609
// Shortcut for the root package, this is not just a performance
610610
// optimization, it also avoids forcing imports thus potentially avoiding
611611
// cyclic references.
612-
if (name == nme.ROOTPKG)
613-
val tree2 = tree.withType(defn.RootPackage.termRef)
614-
checkLegalValue(tree2, pt)
615-
return tree2
612+
if name == nme.ROOTPKG then
613+
return checkLegalValue(tree.withType(defn.RootPackage.termRef), pt)
616614

617615
val rawType =
618616
val saved1 = unimported
@@ -672,9 +670,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
672670
cpy.Ident(tree)(tree.name.unmangleClassName).withType(checkedType)
673671
else
674672
tree.withType(checkedType)
675-
val tree2 = toNotNullTermRef(tree1, pt)
676-
checkLegalValue(tree2, pt)
677-
tree2
673+
checkLegalValue(toNotNullTermRef(tree1, pt), pt)
678674

679675
def isLocalExtensionMethodRef: Boolean = rawType match
680676
case rawType: TermRef =>
@@ -714,21 +710,47 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
714710
errorTree(tree, MissingIdent(tree, kind, name, pt))
715711
end typedIdent
716712

713+
def checkValue(tree: Tree)(using Context): Tree =
714+
val sym = tree.tpe.termSymbol
715+
if sym.isNoValue && !ctx.isJava then
716+
if sym.is(Package)
717+
&& Feature.enabled(Feature.packageObjectValues)
718+
&& tree.tpe.member(nme.PACKAGE).hasAltWith(_.symbol.isPackageObject)
719+
then
720+
typed(untpd.Select(untpd.TypedSplice(tree), nme.PACKAGE))
721+
else
722+
report.error(SymbolIsNotAValue(sym), tree.srcPos)
723+
tree
724+
else tree
725+
726+
/** Check that `tree` refers to a value, unless `tree` is selected or applied
727+
* (singleton types x.type don't count as selections).
728+
*/
729+
def checkValue(tree: Tree, proto: Type)(using Context): Tree =
730+
tree match
731+
case tree: RefTree if tree.name.isTermName =>
732+
proto match
733+
case _: SelectionProto if proto ne SingletonTypeProto => tree // no value check
734+
case _: FunOrPolyProto => tree // no value check
735+
case _ => checkValue(tree)
736+
case _ => tree
737+
717738
/** (1) If this reference is neither applied nor selected, check that it does
718739
* not refer to a package or Java companion object.
719740
* (2) Check that a stable identifier pattern is indeed stable (SLS 8.1.5)
720741
*/
721-
private def checkLegalValue(tree: Tree, pt: Type)(using Context): Unit =
722-
checkValue(tree, pt)
742+
private def checkLegalValue(tree: Tree, pt: Type)(using Context): Tree =
743+
val tree1 = checkValue(tree, pt)
723744
if ctx.mode.is(Mode.Pattern)
724-
&& !tree.isType
745+
&& !tree1.isType
725746
&& !pt.isInstanceOf[ApplyingProto]
726-
&& !tree.tpe.match
747+
&& !tree1.tpe.match
727748
case tp: NamedType => tp.denot.hasAltWith(_.symbol.isStableMember && tp.prefix.isStable || tp.info.isStable)
728749
case tp => tp.isStable
729-
&& !isWildcardArg(tree)
750+
&& !isWildcardArg(tree1)
730751
then
731-
report.error(StableIdentPattern(tree, pt), tree.srcPos)
752+
report.error(StableIdentPattern(tree1, pt), tree1.srcPos)
753+
tree1
732754

733755
def typedSelectWithAdapt(tree0: untpd.Select, pt: Type, qual: Tree)(using Context): Tree =
734756
val selName = tree0.name
@@ -742,8 +764,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
742764
if checkedType.exists then
743765
val select = toNotNullTermRef(assignType(tree, checkedType), pt)
744766
if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix")
745-
checkLegalValue(select, pt)
746-
ConstFold(select)
767+
ConstFold(checkLegalValue(select, pt))
747768
else EmptyTree
748769

749770
// Otherwise, simplify `m.apply(...)` to `m(...)`

Diff for: library/src/scala/runtime/stdLibPatches/language.scala

+5
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ object language:
141141
*/
142142
@compileTimeOnly("`betterFors` can only be used at compile time in import statements")
143143
object betterFors
144+
145+
/** Experimental support for package object values
146+
*/
147+
@compileTimeOnly("`packageObjectValues` can only be used at compile time in import statements")
148+
object packageObjectValues
144149
end experimental
145150

146151
/** The deprecated object contains features that are no longer officially suypported in Scala.

Diff for: tests/neg/i21696.check

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
|---------------------------------------------------------------------------------------------------------------------
66
| Explanation (enabled by `-explain`)
77
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
8-
| Referencing `T` inside a quoted expression requires a `scala.quoted.Type[T]` to be in scope.
8+
| Referencing `T` inside a quoted expression requires a `scala.quoted.Type[T]` to be in scope.
99
| Since Scala is subject to erasure at runtime, the type information will be missing during the execution of the code.
10-
| `scala.quoted.Type[T]` is therefore needed to carry `T`'s type information into the quoted code.
11-
| Without an implicit `scala.quoted.Type[T]`, the type `T` cannot be properly referenced within the expression.
10+
| `scala.quoted.Type[T]` is therefore needed to carry `T`'s type information into the quoted code.
11+
| Without an implicit `scala.quoted.Type[T]`, the type `T` cannot be properly referenced within the expression.
1212
| To resolve this, ensure that a `scala.quoted.Type[T]` is available, either through a context-bound or explicitly.
1313
---------------------------------------------------------------------------------------------------------------------

Diff for: tests/run/pkgobjvals.check

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Foo was created
2+
Foo was created
3+
Foo was created
4+
Foo was created

Diff for: tests/run/pkgobjvals.scala

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import language.experimental.packageObjectValues
2+
3+
package a:
4+
package object b:
5+
class Foo:
6+
println("Foo was created")
7+
8+
def foo() = Foo()
9+
end b
10+
11+
def test =
12+
val bb = b
13+
bb.foo()
14+
new bb.Foo()
15+
end a
16+
17+
@main def Test =
18+
a.test
19+
val ab: a.b.type = a.b
20+
ab.foo()
21+
new ab.Foo()
22+

0 commit comments

Comments
 (0)