Skip to content

Commit 3f2775f

Browse files
committed
Add creator proxies WIP
1 parent 40d5a36 commit 3f2775f

33 files changed

+439
-237
lines changed

compiler/src/dotty/tools/dotc/config/Config.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ object Config {
88
inline val cacheImplicitScopes = true
99
inline val cacheMatchReduced = true
1010

11+
/** If true, we implement creator expressions by adding constructor proxies.
12+
* If false, we rely on fallbacks in Typer to try a constructor if everything else failed.
13+
*/
14+
inline val addConstructorProxies = true
15+
1116
/** If true, the `runWithOwner` operation uses a re-usable context,
1217
* similar to explore. This requires that the context does not escape
1318
* the call. If false, `runWithOwner` runs its operation argument

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -300,14 +300,19 @@ class Definitions {
300300
cls.info = ClassInfo(cls.owner.thisType, cls, List(AnyType, MatchableType), newScope)
301301
cls.setFlag(NoInits | JavaDefined)
302302

303-
// The companion object doesn't really exist, so it needs to be marked as
304-
// absent. Here we need to set it before completing attempt to load Object's
305-
// classfile, which causes issue #1648.
306-
val companion = JavaLangPackageVal.info.decl(nme.Object).symbol
307-
companion.moduleClass.markAbsent()
308-
companion.markAbsent()
309-
310-
completeClass(cls)
303+
if config.Config.addConstructorProxies then
304+
ensureConstructor(cls, cls.denot.asClass, EmptyScope)
305+
val companion = JavaLangPackageVal.info.decl(nme.Object).symbol.asTerm
306+
NamerOps.makeConstructorCompanion(companion, cls)
307+
cls
308+
else
309+
// The companion object doesn't really exist, so it needs to be marked as
310+
// absent. Here we need to set it before completing attempt to load Object's
311+
// classfile, which causes issue #1648.
312+
val companion = JavaLangPackageVal.info.decl(nme.Object).symbol
313+
companion.moduleClass.markAbsent()
314+
companion.markAbsent()
315+
completeClass(cls)
311316
}
312317
def ObjectType: TypeRef = ObjectClass.typeRef
313318

compiler/src/dotty/tools/dotc/core/Flags.scala

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ object Flags {
378378
val (Touched @ _, _, _) = newFlags(50, "<touched>")
379379

380380
/** Class has been lifted out to package level, local value has been lifted out to class level */
381-
val (Lifted @ _, _, _) = newFlags(51, "<lifted>")
381+
val (Lifted @ _, _, _) = newFlags(51, "<lifted>") // only used from lambda-lift (could be merged with ConstructorProxy)
382382

383383
/** Term member has been mixed in */
384384
val (MixedIn @ _, _, _) = newFlags(52, "<mixedin>")
@@ -420,6 +420,9 @@ object Flags {
420420
/** A denotation that is valid in all run-ids */
421421
val (Permanent @ _, _, _) = newFlags(61, "<permanent>")
422422

423+
/** Symbol is a constructor proxy (either companion, or apply method) */
424+
val (ConstructorProxy @ _, _, _) = newFlags(62, "<constructor proxy>") // (could be merged with Lifted)
425+
423426
// --------- Combined Flag Sets and Conjunctions ----------------------
424427

425428
/** All possible flags */
@@ -455,15 +458,16 @@ object Flags {
455458
Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic,
456459
OuterOrCovariant, LabelOrContravariant, CaseAccessor,
457460
Extension, NonMember, Implicit, Given, Permanent, Synthetic,
458-
SuperParamAliasOrScala2x, Inline, Macro)
461+
SuperParamAliasOrScala2x, Inline, Macro, ConstructorProxy)
459462

460463
/** Flags that are not (re)set when completing the denotation, or, if symbol is
461464
* a top-level class or object, when completing the denotation once the class
462465
* file defining the symbol is loaded (which is generally before the denotation
463466
* is completed)
464467
*/
465468
val AfterLoadFlags: FlagSet = commonFlags(
466-
FromStartFlags, AccessFlags, Final, AccessorOrSealed, LazyOrTrait, SelfName, JavaDefined, Transparent)
469+
FromStartFlags, AccessFlags, Final, AccessorOrSealed,
470+
Abstract, LazyOrTrait, SelfName, JavaDefined, Transparent)
467471

468472
/** A value that's unstable unless complemented with a Stable flag */
469473
val UnstableValueFlags: FlagSet = Mutable | Method
@@ -508,7 +512,7 @@ object Flags {
508512
val RetainedModuleValAndClassFlags: FlagSet =
509513
AccessFlags | Package | Case |
510514
Synthetic | JavaDefined | JavaStatic | Artifact |
511-
Lifted | MixedIn | Specialized
515+
Lifted | MixedIn | Specialized | ConstructorProxy
512516

513517
/** Flags that can apply to a module val */
514518
val RetainedModuleValFlags: FlagSet = RetainedModuleValAndClassFlags |
@@ -539,7 +543,10 @@ object Flags {
539543
val EnumCase: FlagSet = Case | Enum
540544
val CovariantLocal: FlagSet = Covariant | Local // A covariant type parameter
541545
val ContravariantLocal: FlagSet = Contravariant | Local // A contravariant type parameter
546+
val EffectivelyErased = ConstructorProxy | Erased
547+
val ConstructorProxyModule: FlagSet = ConstructorProxy | Module
542548
val DefaultParameter: FlagSet = HasDefault | Param // A Scala 2x default parameter
549+
val DeferredInline: FlagSet = Deferred | Inline
543550
val DeferredOrLazy: FlagSet = Deferred | Lazy
544551
val DeferredOrLazyOrMethod: FlagSet = Deferred | Lazy | Method
545552
val DeferredOrTermParamOrAccessor: FlagSet = Deferred | ParamAccessor | TermParam // term symbols without right-hand sides
@@ -548,7 +555,7 @@ object Flags {
548555
val FinalOrInline: FlagSet = Final | Inline
549556
val FinalOrModuleClass: FlagSet = Final | ModuleClass // A module class or a final class
550557
val EffectivelyFinalFlags: FlagSet = Final | Private
551-
val ExcludedForwarder: Flags.FlagSet = Specialized | Lifted | Protected | JavaStatic | Private | Macro
558+
val ExcludedForwarder: Flags.FlagSet = Specialized | Lifted | Protected | JavaStatic | Private | Macro | ConstructorProxy
552559
val FinalOrSealed: FlagSet = Final | Sealed
553560
val GivenOrImplicit: FlagSet = Given | Implicit
554561
val GivenOrImplicitVal: FlagSet = GivenOrImplicit.toTermFlags

compiler/src/dotty/tools/dotc/core/NamerOps.scala

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
package dotty.tools.dotc
1+
package dotty.tools
2+
package dotc
23
package core
34

4-
import Contexts._, Symbols._, Types._, Flags._, Scopes._, Decorators._, NameOps._
5+
import Contexts._, Symbols._, Types._, Flags._, Scopes._, Decorators._, Names._, NameOps._
56
import Denotations._
6-
import SymDenotations.LazyType, Names.Name, StdNames.nme
7+
import SymDenotations.{LazyType, SymDenotation}, StdNames.nme
8+
import config.Config
9+
import ast.untpd
710

811
/** Operations that are shared between Namer and TreeUnpickler */
912
object NamerOps:
@@ -57,4 +60,99 @@ object NamerOps:
5760
else NoSymbol.assertingErrorsReported(s"no companion $name in $scope")
5861
}
5962

63+
/** If a class has one of these flags, it does not get a constructor companion */
64+
private val NoConstructorProxyNeededFlags = Abstract | Trait | Case | Synthetic | Module
65+
66+
/** The flags of a constructor companion */
67+
private val ConstructorCompanionFlags = Synthetic | ConstructorProxy
68+
69+
/** The flags of an `apply` method that serves as a constructor proxy */
70+
val ApplyProxyFlags = Synthetic | ConstructorProxy | Inline | Method
71+
72+
/** Does symbol `cls` need constructor proxies to be generated? */
73+
def needsConstructorProxies(cls: Symbol)(using Context): Boolean =
74+
Config.addConstructorProxies
75+
&& cls.isClass
76+
&& !cls.flagsUNSAFE.isOneOf(NoConstructorProxyNeededFlags)
77+
&& !cls.isAnonymousClass
78+
79+
/** The completer of a constructor proxy apply method */
80+
class ApplyProxyCompleter(constr: Symbol)(using Context) extends LazyType:
81+
def complete(denot: SymDenotation)(using Context): Unit =
82+
denot.info = constr.info
83+
84+
/** Add constructor proxy apply methods to `scope`. Proxies are for constructors
85+
* in `cls` and they reside in `modcls`.
86+
*/
87+
def addConstructorApplies(scope: MutableScope, cls: ClassSymbol, modcls: ClassSymbol)(using Context): scope.type =
88+
def proxy(constr: Symbol): Symbol =
89+
newSymbol(
90+
modcls, nme.apply, ApplyProxyFlags | (constr.flagsUNSAFE & AccessFlags),
91+
ApplyProxyCompleter(constr), coord = constr.coord)
92+
if Config.addConstructorProxies then
93+
for dcl <- cls.info.decls do
94+
if dcl.isConstructor then scope.enter(proxy(dcl))
95+
scope
96+
end addConstructorApplies
97+
98+
/** The completer of a constructor companion for class `cls`, where
99+
* `modul` is the companion symbol and `modcls` is its class.
100+
*/
101+
def constructorCompanionCompleter(cls: ClassSymbol)(
102+
modul: TermSymbol, modcls: ClassSymbol)(using Context): LazyType =
103+
new LazyType with SymbolLoaders.SecondCompleter {
104+
def complete(denot: SymDenotation)(using Context): Unit =
105+
val prefix = modcls.owner.thisType
106+
denot.info = ClassInfo(
107+
prefix, modcls, defn.AnyType :: Nil,
108+
addConstructorApplies(newScope, cls, modcls), TermRef(prefix, modul))
109+
}.withSourceModule(modul)
110+
111+
/** A new symbol that is the constructor companion for class `cls` */
112+
def constructorCompanion(cls: ClassSymbol)(using Context): TermSymbol =
113+
val companion = newModuleSymbol(
114+
cls.owner, cls.name.toTermName,
115+
ConstructorCompanionFlags, ConstructorCompanionFlags,
116+
constructorCompanionCompleter(cls),
117+
coord = cls.coord,
118+
assocFile = cls.assocFile)
119+
companion.moduleClass.registerCompanion(cls)
120+
cls.registerCompanion(companion.moduleClass)
121+
companion
122+
123+
/** Add all necesssary constructor proxy symbols for members of class `cls`. This means:
124+
*
125+
* - if a member is a class that needs a constructor companion, add one,
126+
* provided no member with the same name exists.
127+
* - if `cls` is a companion object of a class that needs a constructor companion,
128+
* and `cls` does not already define or inherit an `apply` method,
129+
* add `apply` methods for all constructors of the companion class.
130+
*/
131+
def addConstructorProxies(cls: ClassSymbol)(using Context): Unit =
132+
133+
def memberExists(cls: ClassSymbol, name: TermName): Boolean =
134+
cls.baseClasses.exists(_.info.decls.lookupEntry(name) != null)
135+
for mbr <- cls.info.decls do
136+
if needsConstructorProxies(mbr)
137+
&& !mbr.asClass.unforcedRegisteredCompanion.exists
138+
&& !memberExists(cls, mbr.name.toTermName)
139+
then
140+
constructorCompanion(mbr.asClass).entered
141+
142+
if cls.is(Module)
143+
&& needsConstructorProxies(cls.linkedClass)
144+
&& !memberExists(cls, nme.apply)
145+
then
146+
addConstructorApplies(cls.info.decls.openForMutations, cls.linkedClass.asClass, cls)
147+
end addConstructorProxies
148+
149+
/** Turn `modul` into a constructor companion for class `cls` */
150+
def makeConstructorCompanion(modul: TermSymbol, cls: ClassSymbol)(using Context): Unit =
151+
val modcls = modul.moduleClass.asClass
152+
modul.setFlag(ConstructorCompanionFlags)
153+
modcls.setFlag(ConstructorCompanionFlags)
154+
modcls.info = constructorCompanionCompleter(cls)(modul, modcls)
155+
cls.registeredCompanion = modcls
156+
modcls.registeredCompanion = cls
157+
60158
end NamerOps

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 56 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,8 @@ object SymDenotations {
951951

952952
/** An erased value or an erased inline method or field */
953953
def isEffectivelyErased(using Context): Boolean =
954-
is(Erased) || is(Inline) && !isRetainedInline && !hasAnnotation(defn.ScalaStaticAnnot)
954+
isOneOf(EffectivelyErased)
955+
|| is(Inline) && !isRetainedInline && !hasAnnotation(defn.ScalaStaticAnnot)
955956

956957
/** ()T and => T types should be treated as equivalent for this symbol.
957958
* Note: For the moment, we treat Scala-2 compiled symbols as loose matching,
@@ -2165,6 +2166,8 @@ object SymDenotations {
21652166
if (companion.isClass && !isAbsent(canForce = false) && !companion.isAbsent(canForce = false))
21662167
myCompanion = companion
21672168

2169+
private[core] def unforcedRegisteredCompanion: Symbol = myCompanion
2170+
21682171
override def registeredCompanion(using Context) =
21692172
if !myCompanion.exists then
21702173
ensureCompleted()
@@ -2252,11 +2255,22 @@ object SymDenotations {
22522255
case nil =>
22532256
val directMembers = super.computeMembersNamed(name)
22542257
if !acc.exists then directMembers
2255-
else acc.union(directMembers.filterWithPredicate(!_.symbol.isAbsent())) match
2256-
case d: DenotUnion => dropStale(d)
2257-
case d => d
2258-
2259-
def dropStale(multi: DenotUnion): PreDenotation =
2258+
else dropStale(acc.union(directMembers.filterWithPredicate(!_.symbol.isAbsent())))
2259+
2260+
def dropStale(d: PreDenotation): PreDenotation = d match
2261+
case multi: DenotUnion => dropStaleFromUnion(multi)
2262+
case _ => d
2263+
2264+
/** Filter symbols making up a DenotUnion to remove alternatives from stale classfiles.
2265+
* This proceeds as follow:
2266+
*
2267+
* - prefer alternatives that are currently compiled over ones that have been compiled before.
2268+
* - if no alternative is compiled now, and they all come from the same file, keep all of them
2269+
* - if no alternative is compiled now, and they come from different files, keep the
2270+
* ones from the youngest file, but issue a warning that one of the class files
2271+
* should be removed from the classpath.
2272+
*/
2273+
def dropStaleFromUnion(multi: PreDenotation): PreDenotation =
22602274
val compiledNow = multi.filterWithPredicate(d =>
22612275
d.symbol.isDefinedInCurrentRun || d.symbol.associatedFile == null
22622276
// if a symbol does not have an associated file, assume it is defined
@@ -2265,36 +2279,41 @@ object SymDenotations {
22652279
)
22662280
if compiledNow.exists then compiledNow
22672281
else
2282+
// leave
22682283
val assocFiles = multi.aggregate(d => Set(d.symbol.associatedFile), _ union _)
22692284
if assocFiles.size == 1 then
22702285
multi // they are all overloaded variants from the same file
22712286
else
2272-
// pick the variant(s) from the youngest class file
2273-
val lastModDate = assocFiles.map(_.lastModified).max
2274-
val youngest = assocFiles.filter(_.lastModified == lastModDate)
2275-
val chosen = youngest.head
2276-
def ambiguousFilesMsg(f: AbstractFile) =
2277-
em"""Toplevel definition $name is defined in
2278-
| $chosen
2279-
|and also in
2280-
| $f"""
2281-
if youngest.size > 1 then
2282-
throw TypeError(i"""${ambiguousFilesMsg(youngest.tail.head)}
2283-
|One of these files should be removed from the classpath.""")
2284-
2285-
// Warn if one of the older files comes from a different container.
2286-
// In that case picking the youngest file is not necessarily what we want,
2287-
// since the older file might have been loaded from a jar earlier in the
2288-
// classpath.
2289-
def sameContainer(f: AbstractFile): Boolean =
2290-
try f.container == chosen.container catch case NonFatal(ex) => true
2291-
if !ambiguityWarningIssued then
2292-
for conflicting <- assocFiles.find(!sameContainer(_)) do
2293-
report.warning(i"""${ambiguousFilesMsg(conflicting)}
2294-
|Keeping only the definition in $chosen""")
2295-
ambiguityWarningIssued = true
2296-
multi.filterWithPredicate(_.symbol.associatedFile == chosen)
2297-
end dropStale
2287+
// prefer normal symbols over creator proxies
2288+
val noCreators = multi.filterWithPredicate(!_.symbol.is(ConstructorProxy))
2289+
if false && noCreators.exists && (noCreators ne multi) then dropStale(noCreators)
2290+
else
2291+
// pick the variant(s) from the youngest class file
2292+
val lastModDate = assocFiles.map(_.lastModified).max
2293+
val youngest = assocFiles.filter(_.lastModified == lastModDate)
2294+
val chosen = youngest.head
2295+
def ambiguousFilesMsg(f: AbstractFile) =
2296+
em"""Toplevel definition $name is defined in
2297+
| $chosen
2298+
|and also in
2299+
| $f"""
2300+
if youngest.size > 1 then
2301+
throw TypeError(i"""${ambiguousFilesMsg(youngest.tail.head)}
2302+
|One of these files should be removed from the classpath.""")
2303+
2304+
// Warn if one of the older files comes from a different container.
2305+
// In that case picking the youngest file is not necessarily what we want,
2306+
// since the older file might have been loaded from a jar earlier in the
2307+
// classpath.
2308+
def sameContainer(f: AbstractFile): Boolean =
2309+
try f.container == chosen.container catch case NonFatal(ex) => true
2310+
if !ambiguityWarningIssued then
2311+
for conflicting <- assocFiles.find(!sameContainer(_)) do
2312+
report.warning(i"""${ambiguousFilesMsg(conflicting)}
2313+
|Keeping only the definition in $chosen""")
2314+
ambiguityWarningIssued = true
2315+
multi.filterWithPredicate(_.symbol.associatedFile == chosen)
2316+
end dropStaleFromUnion
22982317

22992318
if symbol eq defn.ScalaPackageClass then
23002319
val denots = super.computeMembersNamed(name)
@@ -2390,7 +2409,11 @@ object SymDenotations {
23902409
}
23912410

23922411
def stillValid(denot: SymDenotation)(using Context): Boolean =
2393-
if (denot.isOneOf(ValidForeverFlags) || denot.isRefinementClass || denot.isImport) true
2412+
if denot.isOneOf(ValidForeverFlags)
2413+
|| denot.isRefinementClass
2414+
|| denot.isImport
2415+
|| denot.isAllOf(ConstructorProxyModule) // *** && stillValid(linkedClass)?
2416+
then true
23942417
else {
23952418
val initial = denot.initial
23962419
val firstPhaseId =

compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -359,21 +359,27 @@ abstract class SymbolLoader extends LazyType { self =>
359359
throw ex
360360
}
361361
finally {
362-
def postProcess(denot: SymDenotation) =
363-
if (!denot.isCompleted &&
364-
!denot.completer.isInstanceOf[SymbolLoaders.SecondCompleter])
365-
denot.markAbsent()
366-
postProcess(root)
362+
def postProcess(denot: SymDenotation, other: Symbol) =
363+
if !denot.isCompleted &&
364+
!denot.completer.isInstanceOf[SymbolLoaders.SecondCompleter] then
365+
if denot.is(ModuleClass) && NamerOps.needsConstructorProxies(other) then
366+
NamerOps.makeConstructorCompanion(denot.sourceModule.asTerm, other.asClass)
367+
denot.resetFlag(Touched)
368+
else
369+
denot.markAbsent()
370+
371+
val other = if root.isRoot then NoSymbol else root.scalacLinkedClass
372+
postProcess(root, other)
367373
if (!root.isRoot)
368-
postProcess(root.scalacLinkedClass.denot)
374+
postProcess(other, root.symbol)
369375
}
370376
}
371377

372378
protected def rootDenots(rootDenot: ClassDenotation)(using Context): (ClassDenotation, ClassDenotation) = {
373379
val linkedDenot = rootDenot.scalacLinkedClass.denot match {
374380
case d: ClassDenotation => d
375381
case d =>
376-
// this can happen if the companion if shadowed by a val or type
382+
// this can happen if the com*.scaapanion if shadowed by a val or type
377383
// in a package object; in this case, we make up some dummy denotation
378384
// as a stand in for loading.
379385
// An example for this situation is scala.reflect.Manifest, which exists

0 commit comments

Comments
 (0)