Skip to content

Commit ce25907

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 a61c12e commit ce25907

File tree

9 files changed

+73
-38
lines changed

9 files changed

+73
-38
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ object Feature:
3939
val betterMatchTypeExtractors = experimental("betterMatchTypeExtractors")
4040
val quotedPatternsWithPolymorphicFunctions = experimental("quotedPatternsWithPolymorphicFunctions")
4141
val betterFors = experimental("betterFors")
42+
val packageObjectValues = experimental("packageObjectValues")
4243

4344
def experimentalAutoEnableFeatures(using Context): List[TermName] =
4445
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

+1-1
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"

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
@@ -804,24 +804,6 @@ object Checking {
804804
else "Cannot override non-inline parameter with an inline parameter",
805805
p1.srcPos)
806806

807-
def checkValue(tree: Tree)(using Context): Unit =
808-
val sym = tree.tpe.termSymbol
809-
if sym.isNoValue && !ctx.isJava then
810-
report.error(JavaSymbolIsNotAValue(sym), tree.srcPos)
811-
812-
/** Check that `tree` refers to a value, unless `tree` is selected or applied
813-
* (singleton types x.type don't count as selections).
814-
*/
815-
def checkValue(tree: Tree, proto: Type)(using Context): tree.type =
816-
tree match
817-
case tree: RefTree if tree.name.isTermName =>
818-
proto match
819-
case _: SelectionProto if proto ne SingletonTypeProto => // no value check
820-
case _: FunOrPolyProto => // no value check
821-
case _ => checkValue(tree)
822-
case _ =>
823-
tree
824-
825807
/** Check that experimental language imports in `trees`
826808
* are done only in experimental scopes. For top-level
827809
* 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
@@ -618,10 +618,8 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
618618
// Shortcut for the root package, this is not just a performance
619619
// optimization, it also avoids forcing imports thus potentially avoiding
620620
// cyclic references.
621-
if (name == nme.ROOTPKG)
622-
val tree2 = tree.withType(defn.RootPackage.termRef)
623-
checkLegalValue(tree2, pt)
624-
return tree2
621+
if name == nme.ROOTPKG then
622+
return checkLegalValue(tree.withType(defn.RootPackage.termRef), pt)
625623

626624
val rawType =
627625
val saved1 = unimported
@@ -681,9 +679,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
681679
cpy.Ident(tree)(tree.name.unmangleClassName).withType(checkedType)
682680
else
683681
tree.withType(checkedType)
684-
val tree2 = toNotNullTermRef(tree1, pt)
685-
checkLegalValue(tree2, pt)
686-
tree2
682+
checkLegalValue(toNotNullTermRef(tree1, pt), pt)
687683

688684
def isLocalExtensionMethodRef: Boolean = rawType match
689685
case rawType: TermRef =>
@@ -723,21 +719,47 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
723719
errorTree(tree, MissingIdent(tree, kind, name, pt))
724720
end typedIdent
725721

722+
def checkValue(tree: Tree)(using Context): Tree =
723+
val sym = tree.tpe.termSymbol
724+
if sym.isNoValue && !ctx.isJava then
725+
if sym.is(Package)
726+
&& Feature.enabled(Feature.packageObjectValues)
727+
&& tree.tpe.member(nme.PACKAGE).hasAltWith(_.symbol.isPackageObject)
728+
then
729+
typed(untpd.Select(untpd.TypedSplice(tree), nme.PACKAGE))
730+
else
731+
report.error(SymbolIsNotAValue(sym), tree.srcPos)
732+
tree
733+
else tree
734+
735+
/** Check that `tree` refers to a value, unless `tree` is selected or applied
736+
* (singleton types x.type don't count as selections).
737+
*/
738+
def checkValue(tree: Tree, proto: Type)(using Context): Tree =
739+
tree match
740+
case tree: RefTree if tree.name.isTermName =>
741+
proto match
742+
case _: SelectionProto if proto ne SingletonTypeProto => tree // no value check
743+
case _: FunOrPolyProto => tree // no value check
744+
case _ => checkValue(tree)
745+
case _ => tree
746+
726747
/** (1) If this reference is neither applied nor selected, check that it does
727748
* not refer to a package or Java companion object.
728749
* (2) Check that a stable identifier pattern is indeed stable (SLS 8.1.5)
729750
*/
730-
private def checkLegalValue(tree: Tree, pt: Type)(using Context): Unit =
731-
checkValue(tree, pt)
751+
private def checkLegalValue(tree: Tree, pt: Type)(using Context): Tree =
752+
val tree1 = checkValue(tree, pt)
732753
if ctx.mode.is(Mode.Pattern)
733-
&& !tree.isType
754+
&& !tree1.isType
734755
&& !pt.isInstanceOf[ApplyingProto]
735-
&& !tree.tpe.match
756+
&& !tree1.tpe.match
736757
case tp: NamedType => tp.denot.hasAltWith(_.symbol.isStableMember && tp.prefix.isStable || tp.info.isStable)
737758
case tp => tp.isStable
738-
&& !isWildcardArg(tree)
759+
&& !isWildcardArg(tree1)
739760
then
740-
report.error(StableIdentPattern(tree, pt), tree.srcPos)
761+
report.error(StableIdentPattern(tree1, pt), tree1.srcPos)
762+
tree1
741763

742764
def typedSelectWithAdapt(tree0: untpd.Select, pt: Type, qual: Tree)(using Context): Tree =
743765
val selName = tree0.name
@@ -751,8 +773,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
751773
if checkedType.exists then
752774
val select = toNotNullTermRef(assignType(tree, checkedType), pt)
753775
if selName.isTypeName then checkStable(qual.tpe, qual.srcPos, "type prefix")
754-
checkLegalValue(select, pt)
755-
ConstFold(select)
776+
ConstFold(checkLegalValue(select, pt))
756777
else EmptyTree
757778

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

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

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

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

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)