Skip to content

Commit ba6cb92

Browse files
authored
Merge pull request #12311 from dotty-staging/fix-12299
Export constructor proxies
2 parents b981f56 + 7175bfb commit ba6cb92

File tree

15 files changed

+174
-60
lines changed

15 files changed

+174
-60
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes {
214214
private def definedClasses(sym: Symbol, phase: Phase) =
215215
if (sym.isDefinedInCurrentRun)
216216
atPhase(phase) {
217-
toDenot(sym).info.decls.filter(_.isClass)
217+
toDenot(sym).info.decls.filter(sym => sym.isClass && !sym.isEffectivelyErased)
218218
}
219219
else Nil
220220

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ object Flags {
465465
Module, Package, Deferred, Method, Case, Enum, Param, ParamAccessor,
466466
Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic,
467467
OuterOrCovariant, LabelOrContravariant, CaseAccessor,
468-
Extension, NonMember, Implicit, Given, Permanent, Synthetic,
468+
Extension, NonMember, Implicit, Given, Permanent, Synthetic, Exported,
469469
SuperParamAliasOrScala2x, Inline, Macro, ConstructorProxy, Invisible)
470470

471471
/** Flags that are not (re)set when completing the denotation, or, if symbol is

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

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,16 @@ object NamerOps:
7676
/** The flags of an `apply` method that serves as a constructor proxy */
7777
val ApplyProxyFlags = Synthetic | ConstructorProxy | Inline | Method
7878

79-
/** Does symbol `cls` need constructor proxies to be generated? */
80-
def needsConstructorProxies(cls: Symbol)(using Context): Boolean =
81-
cls.isClass
82-
&& !cls.flagsUNSAFE.isOneOf(NoConstructorProxyNeededFlags)
83-
&& !cls.isAnonymousClass
79+
/** Does symbol `sym` need constructor proxies to be generated? */
80+
def needsConstructorProxies(sym: Symbol)(using Context): Boolean =
81+
sym.isClass
82+
&& !sym.flagsUNSAFE.isOneOf(NoConstructorProxyNeededFlags)
83+
&& !sym.isAnonymousClass
84+
||
85+
sym.isType && sym.is(Exported)
86+
&& sym.info.loBound.underlyingClassRef(refinementOK = false).match
87+
case tref: TypeRef => tref.prefix.isStable
88+
case _ => false
8489

8590
/** The completer of a constructor proxy apply method */
8691
class ApplyProxyCompleter(constr: Symbol)(using Context) extends LazyType:
@@ -114,7 +119,7 @@ object NamerOps:
114119
}.withSourceModule(modul)
115120

116121
/** A new symbol that is the constructor companion for class `cls` */
117-
def constructorCompanion(cls: ClassSymbol)(using Context): TermSymbol =
122+
def classConstructorCompanion(cls: ClassSymbol)(using Context): TermSymbol =
118123
val companion = newModuleSymbol(
119124
cls.owner, cls.name.toTermName,
120125
ConstructorCompanionFlags, ConstructorCompanionFlags,
@@ -125,9 +130,13 @@ object NamerOps:
125130
cls.registerCompanion(companion.moduleClass)
126131
companion
127132

133+
def typeConstructorCompanion(tsym: Symbol, prefix: Type, proxy: Symbol)(using Context): TermSymbol =
134+
newSymbol(tsym.owner, tsym.name.toTermName,
135+
ConstructorCompanionFlags | StableRealizable | Method, ExprType(prefix.select(proxy)), coord = tsym.coord)
136+
128137
/** Add all necesssary constructor proxy symbols for members of class `cls`. This means:
129138
*
130-
* - if a member is a class that needs a constructor companion, add one,
139+
* - if a member is a class, or type alias, that needs a constructor companion, add one,
131140
* provided no member with the same name exists.
132141
* - if `cls` is a companion object of a class that needs a constructor companion,
133142
* and `cls` does not already define or inherit an `apply` method,
@@ -137,12 +146,21 @@ object NamerOps:
137146

138147
def memberExists(cls: ClassSymbol, name: TermName): Boolean =
139148
cls.baseClasses.exists(_.info.decls.lookupEntry(name) != null)
149+
140150
for mbr <- cls.info.decls do
141-
if needsConstructorProxies(mbr)
142-
&& !mbr.asClass.unforcedRegisteredCompanion.exists
143-
&& !memberExists(cls, mbr.name.toTermName)
144-
then
145-
constructorCompanion(mbr.asClass).entered
151+
if needsConstructorProxies(mbr) then
152+
mbr match
153+
case mbr: ClassSymbol =>
154+
if !mbr.unforcedRegisteredCompanion.exists
155+
&& !memberExists(cls, mbr.name.toTermName)
156+
then
157+
classConstructorCompanion(mbr).entered
158+
case _ =>
159+
mbr.info.loBound.underlyingClassRef(refinementOK = false) match
160+
case ref: TypeRef =>
161+
val proxy = ref.symbol.registeredCompanion
162+
if proxy.is(ConstructorProxy) && !memberExists(cls, mbr.name.toTermName) then
163+
typeConstructorCompanion(mbr, ref.prefix, proxy).entered
146164

147165
if cls.is(Module)
148166
&& needsConstructorProxies(cls.linkedClass)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,11 +1180,13 @@ object SymDenotations {
11801180
*/
11811181
final def companionModule(using Context): Symbol =
11821182
if (is(Module)) sourceModule
1183+
else if registeredCompanion.isAbsent() then NoSymbol
11831184
else registeredCompanion.sourceModule
11841185

11851186
private def companionType(using Context): Symbol =
11861187
if (is(Package)) NoSymbol
11871188
else if (is(ModuleVal)) moduleClass.denot.companionType
1189+
else if registeredCompanion.isAbsent() then NoSymbol
11881190
else registeredCompanion
11891191

11901192
/** The class with the same (type-) name as this module or module class,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ object TypeErasure {
238238

239239
if (defn.isPolymorphicAfterErasure(sym)) eraseParamBounds(sym.info.asInstanceOf[PolyType])
240240
else if (sym.isAbstractType) TypeAlias(WildcardType)
241+
else if sym.is(ConstructorProxy) then NoType
241242
else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(using preErasureCtx))
242243
else if (sym.is(Label)) erase.eraseResult(sym.info)(using preErasureCtx)
243244
else erase.eraseInfo(tp, sym)(using preErasureCtx) match {

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,7 @@ class TreePickler(pickler: TastyPickler) {
731731
if flags.is(Infix) then writeModTag(INFIX)
732732
if flags.is(Invisible) then writeModTag(INVISIBLE)
733733
if (flags.is(Erased)) writeModTag(ERASED)
734+
if (flags.is(Exported)) writeModTag(EXPORTED)
734735
if (isTerm) {
735736
if (flags.is(Implicit)) writeModTag(IMPLICIT)
736737
if (flags.is(Given)) writeModTag(GIVEN)
@@ -744,7 +745,6 @@ class TreePickler(pickler: TastyPickler) {
744745
if (flags.is(Extension)) writeModTag(EXTENSION)
745746
if (flags.is(ParamAccessor)) writeModTag(PARAMsetter)
746747
if (flags.is(SuperParamAlias)) writeModTag(PARAMalias)
747-
if (flags.is(Exported)) writeModTag(EXPORTED)
748748
assert(!(flags.is(Label)))
749749
}
750750
else {

compiler/src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,24 +1038,9 @@ object Erasure {
10381038
override def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(using Context): Tree =
10391039
EmptyTree
10401040

1041-
/** Drop all constructor proxies of members of class `cls`.
1042-
* If `cls` is itself a constructor proxy, mark it as absent after erasure.
1043-
*/
1044-
private def dropConstructorProxies(cls: ClassSymbol)(using Context) =
1045-
import Flags._
1046-
if cls.linkedClass.is(ConstructorProxy) then
1047-
if cls.owner.is(PackageClass) && cls.isDefinedInCurrentRun then
1048-
cls.linkedClass.copySymDenotation(initFlags = EmptyFlags, info = NoType)
1049-
.installAfter(erasurePhase)
1050-
cls.registeredCompanion = NoSymbol
1051-
for mbr <- cls.info.decls do
1052-
if mbr.is(ConstructorProxy) then mbr.dropAfter(erasurePhase)
1053-
10541041
override def typedClassDef(cdef: untpd.TypeDef, cls: ClassSymbol)(using Context): Tree =
10551042
if cls.is(Flags.Erased) then erasedDef(cls)
1056-
else
1057-
try super.typedClassDef(cdef, cls)
1058-
finally dropConstructorProxies(cls)
1043+
else super.typedClassDef(cdef, cls)
10591044

10601045
override def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree =
10611046
typed(tree.arg, pt)

compiler/src/dotty/tools/dotc/transform/TreeChecker.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,8 +448,7 @@ class TreeChecker extends Phase with SymTransformer {
448448
val decls = cls.classInfo.decls.toList.toSet.filter(isNonMagicalMember)
449449
val defined = impl.body.map(_.symbol)
450450

451-
def isAllowed(sym: Symbol): Boolean =
452-
sym.is(ConstructorProxy) && !ctx.phase.erasedTypes
451+
def isAllowed(sym: Symbol): Boolean = sym.is(ConstructorProxy)
453452

454453
val symbolsNotDefined = (decls -- defined - constr.symbol).filterNot(isAllowed)
455454

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ class Namer { typer: Typer =>
617617
val classSym = ctx.effectiveScope.lookup(className)
618618
val moduleName = className.toTermName
619619
if needsConstructorProxies(classSym) && ctx.effectiveScope.lookupEntry(moduleName) == null then
620-
enterSymbol(constructorCompanion(classSym.asClass))
620+
enterSymbol(classConstructorCompanion(classSym.asClass))
621621
else if ctx.owner.is(PackageClass) then
622622
for case cdef @ TypeDef(moduleName, _) <- moduleDef.values do
623623
val moduleSym = ctx.effectiveScope.lookup(moduleName)
@@ -634,12 +634,12 @@ class Namer { typer: Typer =>
634634
val moduleName = className.toTermName
635635
val companionVals = ctx.effectiveScope.lookupAll(moduleName.encode)
636636
if companionVals.isEmpty && needsConstructorProxies(classSym) then
637-
enterSymbol(constructorCompanion(classSym.asClass))
637+
enterSymbol(classConstructorCompanion(classSym.asClass))
638638
else
639639
for moduleSym <- companionVals do
640640
if moduleSym.is(Module) && !moduleSym.isDefinedInCurrentRun then
641641
val companion =
642-
if needsConstructorProxies(classSym) then constructorCompanion(classSym.asClass)
642+
if needsConstructorProxies(classSym) then classConstructorCompanion(classSym.asClass)
643643
else newModuleSymbol(
644644
ctx.owner, moduleName, EmptyFlags, EmptyFlags, (_, _) => NoType)
645645
enterSymbol(companion)
@@ -974,7 +974,6 @@ class Namer { typer: Typer =>
974974

975975
/** The forwarders defined by export `exp` */
976976
private def exportForwarders(exp: Export)(using Context): List[tpd.MemberDef] =
977-
val SKIP = "(skip)" // A string indicating that no forwarders for this kind of symbol are emitted
978977
val buf = new mutable.ListBuffer[tpd.MemberDef]
979978
val Export(expr, selectors) = exp
980979
if expr.isEmpty then
@@ -986,19 +985,22 @@ class Namer { typer: Typer =>
986985
lazy val wildcardBound = importBound(selectors, isGiven = false)
987986
lazy val givenBound = importBound(selectors, isGiven = true)
988987

989-
def whyNoForwarder(mbr: SingleDenotation): String = {
988+
def canForward(mbr: SingleDenotation): CanForward = {
989+
import CanForward.*
990990
val sym = mbr.symbol
991-
if (!sym.isAccessibleFrom(path.tpe)) "is not accessible"
992-
else if (sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(ConstructorProxy)) SKIP
993-
else if (cls.derivesFrom(sym.owner) &&
994-
(sym.owner == cls || !sym.is(Deferred))) i"is already a member of $cls"
995-
else if (sym.is(Override))
991+
if !sym.isAccessibleFrom(path.tpe) then
992+
No("is not accessible")
993+
else if sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(ConstructorProxy) then
994+
Skip
995+
else if cls.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred)) then
996+
No(i"is already a member of $cls")
997+
else if sym.is(Override) then
996998
sym.allOverriddenSymbols.find(
997-
other => cls.derivesFrom(other.owner) && !other.is(Deferred)) match {
998-
case Some(other) => i"overrides ${other.showLocated}, which is already a member of $cls"
999-
case None => ""
1000-
}
1001-
else ""
999+
other => cls.derivesFrom(other.owner) && !other.is(Deferred)
1000+
) match
1001+
case Some(other) => No(i"overrides ${other.showLocated}, which is already a member of $cls")
1002+
case None => Yes
1003+
else Yes
10021004
}
10031005

10041006
/** Add a forwarder with name `alias` or its type name equivalent to `mbr`,
@@ -1021,7 +1023,7 @@ class Namer { typer: Typer =>
10211023
case _ =>
10221024
acc.reverse ::: prefss
10231025

1024-
if whyNoForwarder(mbr) == "" then
1026+
if canForward(mbr) == CanForward.Yes then
10251027
val sym = mbr.symbol
10261028
val forwarder =
10271029
if mbr.isType then
@@ -1038,7 +1040,7 @@ class Namer { typer: Typer =>
10381040
// a parameterized class, say `C[X]` the alias will read `type C = d.C`. We currently do
10391041
// allow such type aliases. If we forbid them at some point (requiring the referred type to be
10401042
// fully applied), we'd have to change the scheme here as well.
1041-
else {
1043+
else
10421044
def refersToPrivate(tp: Type): Boolean = tp match
10431045
case tp: TermRef => tp.termSymbol.is(Private) || refersToPrivate(tp.prefix)
10441046
case _ => false
@@ -1051,16 +1053,17 @@ class Namer { typer: Typer =>
10511053
if sym.is(ExtensionMethod) then mbrFlags |= ExtensionMethod
10521054
val forwarderName = checkNoConflict(alias, isPrivate = false, span)
10531055
newSymbol(cls, forwarderName, mbrFlags, mbrInfo, coord = span)
1054-
}
1056+
10551057
forwarder.info = avoidPrivateLeaks(forwarder)
10561058
forwarder.addAnnotations(sym.annotations)
1059+
10571060
val forwarderDef =
10581061
if (forwarder.isType) tpd.TypeDef(forwarder.asType)
10591062
else {
10601063
import tpd._
10611064
val ref = path.select(sym.asTerm)
10621065
val ddef = tpd.DefDef(forwarder.asTerm, prefss =>
1063-
ref.appliedToArgss(adaptForwarderParams(Nil, sym.info, prefss))
1066+
ref.appliedToArgss(adaptForwarderParams(Nil, sym.info, prefss))
10641067
)
10651068
if forwarder.isInlineMethod then
10661069
PrepareInlineable.registerInlineInfo(forwarder, ddef.rhs)
@@ -1070,18 +1073,15 @@ class Namer { typer: Typer =>
10701073
buf += forwarderDef.withSpan(span)
10711074
end addForwarder
10721075

1073-
def addForwardersNamed(name: TermName, alias: TermName, span: Span): Unit = {
1076+
def addForwardersNamed(name: TermName, alias: TermName, span: Span): Unit =
10741077
val size = buf.size
10751078
val mbrs = List(name, name.toTypeName).flatMap(path.tpe.member(_).alternatives)
10761079
mbrs.foreach(addForwarder(alias, _, span))
1077-
if (buf.size == size) {
1078-
val reason = mbrs.map(whyNoForwarder).dropWhile(_ == SKIP) match {
1079-
case Nil => ""
1080-
case why :: _ => i"\n$path.$name cannot be exported because it $why"
1081-
}
1080+
if buf.size == size then
1081+
val reason = mbrs.map(canForward).collect {
1082+
case CanForward.No(whyNot) => i"\n$path.$name cannot be exported because it $whyNot"
1083+
}.headOption.getOrElse("")
10821084
report.error(i"""no eligible member $name at $path$reason""", ctx.source.atSpan(span))
1083-
}
1084-
}
10851085

10861086
def addWildcardForwardersNamed(name: TermName, span: Span): Unit =
10871087
List(name, name.toTypeName)
@@ -1357,6 +1357,12 @@ class Namer { typer: Typer =>
13571357
}
13581358
}
13591359

1360+
/** Possible actions to perform when deciding on a forwarder for a member */
1361+
private enum CanForward:
1362+
case Yes
1363+
case No(whyNot: String)
1364+
case Skip // for members that have never forwarders
1365+
13601366
class SuspendCompleter extends LazyType, SymbolLoaders.SecondCompleter {
13611367

13621368
final override def complete(denot: SymDenotation)(using Context): Unit =

compiler/test/dotc/pos-test-pickling.blacklist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ tuple-filter.scala
1616
i7740a.scala
1717
i7740b.scala
1818
i6507b.scala
19+
i12299a.scala
1920

2021
# Stale symbol: package object scala
2122
seqtype-cycle

tests/neg/i12299.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
object Outer {
2+
3+
object Inner {
4+
class Bar(x: Int)
5+
object Bar
6+
}
7+
8+
export Inner.Bar._
9+
10+
val _ = apply(2) // error (constructor proxies are not exported)
11+
12+
}
13+
object Outer2 {
14+
15+
object Inner {
16+
class Bar(x: Int)
17+
object Bar
18+
}
19+
20+
export Inner.Bar.apply // error: no eligible member
21+
22+
val _ = apply(2) // error (constructor proxies are not exported)
23+
24+
}

tests/pos/i12299.scala

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
object Outer0 {
2+
3+
object Inner {
4+
class Bar(x: Int):
5+
def this() = this(0)
6+
}
7+
8+
export Inner.Bar
9+
10+
val _ = Bar()
11+
val _ = Bar(2)
12+
13+
}
14+
15+
object Outer2 {
16+
17+
object Inner {
18+
class Bar(x: Int):
19+
def this() = this(0)
20+
}
21+
22+
object test2:
23+
export Inner._
24+
25+
val x = Bar()
26+
val y = Bar(2)
27+
28+
object test3:
29+
export Inner.Bar
30+
def Bar: () => String = () => ""
31+
val x = Bar()
32+
}
33+
34+
object Outer3 {
35+
export Outer0._
36+
37+
private val x = Bar()
38+
private val y = Bar(2)
39+
}
40+
41+
object Outer4 {
42+
43+
object Inner {
44+
class Bar(x: Int):
45+
def this() = this(0)
46+
object Bar
47+
}
48+
49+
export Inner._
50+
51+
val _ = Bar()
52+
val _ = Bar(2)
53+
54+
}

0 commit comments

Comments
 (0)