Skip to content

Commit bd2e47c

Browse files
committed
Add creator apply methods
Use synthetic apply methods instead of a fallback in Typer to implement creator applications
1 parent fc8f8fb commit bd2e47c

32 files changed

+418
-234
lines changed

compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import dotty.tools.dotc.core.Symbols._
1414
import dotty.tools.dotc.core.Phases.Phase
1515
import dotty.tools.dotc.transform.SymUtils._
1616
import dotty.tools.dotc.util.WeakHashSet
17+
import dotty.tools.dotc.core.Decorators._
1718

1819
/**
1920
* This class mainly contains the method classBTypeFromSymbol, which extracts the necessary
@@ -101,7 +102,8 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes {
101102
else if (classSym.is(ModuleClass)) {
102103
// workaround #371
103104

104-
println(s"Warning: mocking up superclass for $classSym")
105+
println(i"Warning: mocking up superclass for $classSym ${classSym.id}")
106+
assert(false)
105107
defn.ObjectClass
106108
}
107109
else t

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

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

11+
val addCreatorCompanions = true
12+
val addCreatorApplies = true
13+
1114
/** If true, the `runWithOwner` operation uses a re-usable context,
1215
* similar to explore. This requires that the context does not escape
1316
* the call. If false, `runWithOwner` runs its operation argument

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

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -290,15 +290,10 @@ class Definitions {
290290
assert(!cls.isCompleted, "race for completing java.lang.Object")
291291
cls.info = ClassInfo(cls.owner.thisType, cls, AnyClass.typeRef :: Nil, newScope)
292292
cls.setFlag(NoInits | JavaDefined)
293-
294-
// The companion object doesn't really exist, so it needs to be marked as
295-
// absent. Here we need to set it before completing attempt to load Object's
296-
// classfile, which causes issue #1648.
297-
val companion = JavaLangPackageVal.info.decl(nme.Object).symbol
298-
companion.moduleClass.markAbsent()
299-
companion.markAbsent()
300-
301-
completeClass(cls)
293+
ensureConstructor(cls, cls.denot.asClass, EmptyScope)
294+
val companion = JavaLangPackageVal.info.decl(nme.Object).symbol.asTerm
295+
NamerOps.makeCreatorCompanion(companion, cls)
296+
cls
302297
}
303298
def ObjectType: TypeRef = ObjectClass.typeRef
304299

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

Lines changed: 11 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, "<creator/lifted>") // only used from lambda-lift
382382

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

423+
val (CreatorProxy @ _, _, _) = newFlags(62, "<creator proxy>")
424+
423425
// --------- Combined Flag Sets and Conjunctions ----------------------
424426

425427
/** All possible flags */
@@ -455,15 +457,16 @@ object Flags {
455457
Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic,
456458
OuterOrCovariant, LabelOrContravariant, CaseAccessor,
457459
Extension, NonMember, Implicit, Given, Permanent, Synthetic,
458-
SuperParamAliasOrScala2x, Inline, Macro)
460+
SuperParamAliasOrScala2x, Inline, Macro, CreatorProxy)
459461

460462
/** Flags that are not (re)set when completing the denotation, or, if symbol is
461463
* a top-level class or object, when completing the denotation once the class
462464
* file defining the symbol is loaded (which is generally before the denotation
463465
* is completed)
464466
*/
465467
val AfterLoadFlags: FlagSet = commonFlags(
466-
FromStartFlags, AccessFlags, Final, AccessorOrSealed, LazyOrTrait, SelfName, JavaDefined, Transparent)
468+
FromStartFlags, AccessFlags, Final, AccessorOrSealed,
469+
Abstract, LazyOrTrait, SelfName, JavaDefined, Transparent)
467470

468471
/** A value that's unstable unless complemented with a Stable flag */
469472
val UnstableValueFlags: FlagSet = Mutable | Method
@@ -508,7 +511,7 @@ object Flags {
508511
val RetainedModuleValAndClassFlags: FlagSet =
509512
AccessFlags | Package | Case |
510513
Synthetic | JavaDefined | JavaStatic | Artifact |
511-
Lifted | MixedIn | Specialized
514+
Lifted | MixedIn | Specialized | CreatorProxy
512515

513516
/** Flags that can apply to a module val */
514517
val RetainedModuleValFlags: FlagSet = RetainedModuleValAndClassFlags |
@@ -539,7 +542,10 @@ object Flags {
539542
val EnumCase: FlagSet = Case | Enum
540543
val CovariantLocal: FlagSet = Covariant | Local // A covariant type parameter
541544
val ContravariantLocal: FlagSet = Contravariant | Local // A contravariant type parameter
545+
val EffectivelyErased = CreatorProxy | Erased
546+
val CreatorProxyModule: FlagSet = CreatorProxy | Module
542547
val DefaultParameter: FlagSet = HasDefault | Param // A Scala 2x default parameter
548+
val DeferredInline: FlagSet = Deferred | Inline
543549
val DeferredOrLazy: FlagSet = Deferred | Lazy
544550
val DeferredOrLazyOrMethod: FlagSet = Deferred | Lazy | Method
545551
val DeferredOrTermParamOrAccessor: FlagSet = Deferred | ParamAccessor | TermParam // term symbols without right-hand sides
@@ -548,7 +554,7 @@ object Flags {
548554
val FinalOrInline: FlagSet = Final | Inline
549555
val FinalOrModuleClass: FlagSet = Final | ModuleClass // A module class or a final class
550556
val EffectivelyFinalFlags: FlagSet = Final | Private
551-
val ExcludedForwarder: Flags.FlagSet = Specialized | Lifted | Protected | JavaStatic | Private | Macro
557+
val ExcludedForwarder: Flags.FlagSet = Specialized | Lifted | Protected | JavaStatic | Private | Macro | CreatorProxy
552558
val FinalOrSealed: FlagSet = Final | Sealed
553559
val GivenOrImplicit: FlagSet = Given | Implicit
554560
val GivenOrImplicitVal: FlagSet = GivenOrImplicit.toTermFlags

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

Lines changed: 103 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}, Names.Name, 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,101 @@ object NamerOps:
5760
else NoSymbol.assertingErrorsReported(s"no companion $name in $scope")
5861
}
5962

63+
private val noAppliesNeededFlags = Abstract | Trait | Case | Synthetic | Module
64+
val CreatorCompanionFlags = Synthetic | CreatorProxy
65+
val applyProxyFlags = Synthetic | CreatorProxy | Inline | Method
66+
67+
def needsCreatorCompanion(cls: Symbol)(using Context): Boolean =
68+
Config.addCreatorCompanions
69+
&& cls.isClass
70+
&& !cls.flagsUNSAFE.isOneOf(noAppliesNeededFlags)
71+
&& !cls.isAnonymousClass
72+
73+
class ApplyProxyCompleter(constr: Symbol)(using Context) extends LazyType:
74+
def complete(denot: SymDenotation)(using Context): Unit =
75+
denot.info = constr.info
76+
77+
def addCreatorMethods(scope: MutableScope, cls: ClassSymbol, modcls: ClassSymbol)(using Context): scope.type =
78+
def creatorProxy(constr: Symbol): Symbol =
79+
newSymbol(
80+
modcls, nme.apply, applyProxyFlags | (constr.flagsUNSAFE & AccessFlags),
81+
ApplyProxyCompleter(constr), coord = constr.coord)
82+
//.showing(i"created apply ${result.showLocated}")
83+
if Config.addCreatorApplies then
84+
for dcl <- cls.info.decls do
85+
if dcl.isConstructor then scope.enter(creatorProxy(dcl))
86+
scope
87+
end addCreatorMethods
88+
89+
def creatorCompanionCompleter(cls: ClassSymbol)(
90+
modul: TermSymbol, modcls: ClassSymbol)(using Context): LazyType =
91+
new LazyType with SymbolLoaders.SecondCompleter {
92+
def complete(denot: SymDenotation)(using Context): Unit =
93+
val prefix = modcls.owner.thisType
94+
denot.info = ClassInfo(
95+
prefix, modcls, defn.AnyType :: Nil,
96+
addCreatorMethods(newScope, cls, modcls), TermRef(prefix, modul))
97+
}.withSourceModule(modul)
98+
99+
def creatorCompanion(cls: ClassSymbol)(using Context): TermSymbol =
100+
val companion = newModuleSymbol(
101+
cls.owner, cls.name.toTermName,
102+
CreatorCompanionFlags, CreatorCompanionFlags,
103+
creatorCompanionCompleter(cls), coord = cls.coord)
104+
companion.moduleClass.registerCompanion(cls)
105+
cls.registerCompanion(companion.moduleClass)
106+
companion
107+
//.showing(i"created creator companion $companion")
108+
/*
109+
if needsCreatorCompanion(cls) && !cls.linkedClass.exists then
110+
val companion = newModuleSymbol(
111+
cls.owner, cls.name.toTermName,
112+
CreatorCompanionFlags, CreatorCompanionFlags,
113+
creatorCompanionCompleter(cls), coord = cls.coord).entered
114+
companion.moduleClass.registerCompanion(cls)
115+
cls.registerCompanion(companion.moduleClass)
116+
println(i"created proxy companion $companion")
117+
118+
def ensureApplyProxyCompanion(cls: ClassSymbol)(using Context): Unit =
119+
println(i"ensure proxy companion $cls ${cls.flagsString}")
120+
if needsCreatorCompanion(cls) && !cls.linkedClass.exists then
121+
122+
val companion = newModuleSymbol(
123+
cls.owner, cls.name.toTermName,
124+
applyProxyCompanionFlags, applyProxyCompanionFlags,
125+
applyProxyCompanionCompleter(cls), coord = cls.coord).entered
126+
companion.moduleClass.registerCompanion(cls)
127+
cls.registerCompanion(companion.moduleClass)
128+
println(i"created proxy companion $companion")
129+
*/
130+
131+
/** For all nested classes without companion objects
132+
* add a companion object. For all constructors, add an apply method.
133+
*/
134+
def addCreatorDefs(cls: ClassSymbol)(using Context): Unit =
135+
//println(i"add creator defs $cls / ${}")
136+
def memberExists(cls: ClassSymbol, name: TermName): Boolean =
137+
cls.baseClasses.exists(_.info.decls.lookupEntry(name) != null)
138+
for mbr <- cls.info.decls do
139+
if needsCreatorCompanion(mbr)
140+
&& !mbr.asClass.unforcedRegisteredCompanion.exists
141+
&& !memberExists(cls, mbr.name.toTermName)
142+
then
143+
creatorCompanion(mbr.asClass).entered
144+
145+
if cls.is(Module)
146+
&& needsCreatorCompanion(cls.linkedClass)
147+
&& !memberExists(cls, nme.apply)
148+
then
149+
addCreatorMethods(cls.info.decls.openForMutations, cls.linkedClass.asClass, cls)
150+
end addCreatorDefs
151+
152+
def makeCreatorCompanion(modul: TermSymbol, cls: ClassSymbol)(using Context): Unit =
153+
val modcls = modul.moduleClass.asClass
154+
modul.setFlag(CreatorCompanionFlags)
155+
modcls.setFlag(CreatorCompanionFlags)
156+
modcls.info = creatorCompanionCompleter(cls)(modul, modcls)
157+
cls.registeredCompanion = modcls
158+
modcls.registeredCompanion = cls
159+
60160
end NamerOps

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

Lines changed: 43 additions & 30 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+
def unforcedRegisteredCompanion: Symbol = myCompanion
2170+
21682171
override def registeredCompanion(using Context) =
21692172
if !myCompanion.exists then
21702173
ensureCompleted()
@@ -2267,35 +2270,41 @@ object SymDenotations {
22672270
)
22682271
if compiledNow.exists then compiledNow
22692272
else
2270-
val assocFiles = multi.aggregate(d => Set(d.symbol.associatedFile), _ union _)
2271-
if assocFiles.size == 1 then
2272-
multi // they are all overloaded variants from the same file
2273+
val noCreators = multi.filterWithPredicate(!_.symbol.is(CreatorProxy))
2274+
if noCreators ne multi then
2275+
noCreators match
2276+
case noCreators: DenotUnion => dropStale(noCreators)
2277+
case _ => noCreators
22732278
else
2274-
// pick the variant(s) from the youngest class file
2275-
val lastModDate = assocFiles.map(_.lastModified).max
2276-
val youngest = assocFiles.filter(_.lastModified == lastModDate)
2277-
val chosen = youngest.head
2278-
def ambiguousFilesMsg(f: AbstractFile) =
2279-
em"""Toplevel definition $name is defined in
2280-
| $chosen
2281-
|and also in
2282-
| $f"""
2283-
if youngest.size > 1 then
2284-
throw TypeError(i"""${ambiguousFilesMsg(youngest.tail.head)}
2285-
|One of these files should be removed from the classpath.""")
2286-
2287-
// Warn if one of the older files comes from a different container.
2288-
// In that case picking the youngest file is not necessarily what we want,
2289-
// since the older file might have been loaded from a jar earlier in the
2290-
// classpath.
2291-
def sameContainer(f: AbstractFile): Boolean =
2292-
try f.container == chosen.container catch case NonFatal(ex) => true
2293-
if !ambiguityWarningIssued then
2294-
for conflicting <- assocFiles.find(!sameContainer(_)) do
2295-
report.warning(i"""${ambiguousFilesMsg(conflicting)}
2296-
|Keeping only the definition in $chosen""")
2297-
ambiguityWarningIssued = true
2298-
multi.filterWithPredicate(_.symbol.associatedFile == chosen)
2279+
val assocFiles = multi.aggregate(d => Set(d.symbol.associatedFile), _ union _)
2280+
if assocFiles.size == 1 then
2281+
multi // they are all overloaded variants from the same file
2282+
else
2283+
// pick the variant(s) from the youngest class file
2284+
val lastModDate = assocFiles.map(_.lastModified).max
2285+
val youngest = assocFiles.filter(_.lastModified == lastModDate)
2286+
val chosen = youngest.head
2287+
def ambiguousFilesMsg(f: AbstractFile) =
2288+
em"""Toplevel definition $name is defined in
2289+
| $chosen
2290+
|and also in
2291+
| $f"""
2292+
if youngest.size > 1 then
2293+
throw TypeError(i"""${ambiguousFilesMsg(youngest.tail.head)}
2294+
|One of these files should be removed from the classpath.""")
2295+
2296+
// Warn if one of the older files comes from a different container.
2297+
// In that case picking the youngest file is not necessarily what we want,
2298+
// since the older file might have been loaded from a jar earlier in the
2299+
// classpath.
2300+
def sameContainer(f: AbstractFile): Boolean =
2301+
try f.container == chosen.container catch case NonFatal(ex) => true
2302+
if !ambiguityWarningIssued then
2303+
for conflicting <- assocFiles.find(!sameContainer(_)) do
2304+
report.warning(i"""${ambiguousFilesMsg(conflicting)}
2305+
|Keeping only the definition in $chosen""")
2306+
ambiguityWarningIssued = true
2307+
multi.filterWithPredicate(_.symbol.associatedFile == chosen)
22992308
end dropStale
23002309

23012310
if symbol eq defn.ScalaPackageClass then
@@ -2392,7 +2401,11 @@ object SymDenotations {
23922401
}
23932402

23942403
def stillValid(denot: SymDenotation)(using Context): Boolean =
2395-
if (denot.isOneOf(ValidForeverFlags) || denot.isRefinementClass || denot.isImport) true
2404+
if denot.isOneOf(ValidForeverFlags)
2405+
|| denot.isRefinementClass
2406+
|| denot.isImport
2407+
|| denot.isAllOf(CreatorProxyModule)
2408+
then true
23962409
else {
23972410
val initial = denot.initial
23982411
val firstPhaseId =

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -359,21 +359,28 @@ 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.needsCreatorCompanion(other) then
366+
NamerOps.makeCreatorCompanion(
367+
denot.sourceModule.asTerm, other.asClass)
368+
denot.resetFlag(Touched)
369+
else
370+
denot.markAbsent()
371+
372+
val other = if root.isRoot then NoSymbol else root.scalacLinkedClass
373+
postProcess(root, other)
367374
if (!root.isRoot)
368-
postProcess(root.scalacLinkedClass.denot)
375+
postProcess(other, root.symbol)
369376
}
370377
}
371378

372379
protected def rootDenots(rootDenot: ClassDenotation)(using Context): (ClassDenotation, ClassDenotation) = {
373380
val linkedDenot = rootDenot.scalacLinkedClass.denot match {
374381
case d: ClassDenotation => d
375382
case d =>
376-
// this can happen if the companion if shadowed by a val or type
383+
// this can happen if the com*.scaapanion if shadowed by a val or type
377384
// in a package object; in this case, we make up some dummy denotation
378385
// as a stand in for loading.
379386
// An example for this situation is scala.reflect.Manifest, which exists

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,10 @@ object Symbols {
5454
//assert(id != 723)
5555

5656
def coord: Coord = myCoord
57-
/** Set the coordinate of this class, this is only useful when the coordinate is
58-
* not known at symbol creation. This is the case for root symbols
59-
* unpickled from TASTY.
60-
*
61-
* @pre coord == NoCoord
62-
*/
57+
6358
private[core] def coord_=(c: Coord): Unit = {
64-
assert(myCoord == NoCoord)
59+
// This assertion fails for CommentPickling test
60+
// assert(myCoord == NoCoord)
6561
myCoord = c
6662
}
6763

compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ class ClassfileParser(
196196
addAnnotationConstructor(classInfo.asInstanceOf[TempClassInfoType])
197197

198198
setClassInfo(classRoot, classInfo, fromScala2 = false)
199+
if NamerOps.needsCreatorCompanion(classRoot.classSymbol) then
200+
NamerOps.addCreatorDefs(moduleRoot.classSymbol)
199201
}
200202
else if (result == Some(NoEmbedded))
201203
for (sym <- List(moduleRoot.sourceModule, moduleRoot.symbol, classRoot.symbol)) {

0 commit comments

Comments
 (0)