Skip to content

Commit

Permalink
Export constructor proxies
Browse files Browse the repository at this point in the history
When creating an exported type alias of a class that has a constructor proxy companion,
export an alias for that companion with it. The alias is again a constructor proxy.

Fixes #12299
  • Loading branch information
odersky committed May 3, 2021
1 parent be5043a commit 56c40f8
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 15 deletions.
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Flags.scala
Expand Up @@ -465,7 +465,7 @@ object Flags {
Module, Package, Deferred, Method, Case, Enum, Param, ParamAccessor,
Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic,
OuterOrCovariant, LabelOrContravariant, CaseAccessor,
Extension, NonMember, Implicit, Given, Permanent, Synthetic,
Extension, NonMember, Implicit, Given, Permanent, Synthetic, Exported,
SuperParamAliasOrScala2x, Inline, Macro, ConstructorProxy, Invisible)

/** Flags that are not (re)set when completing the denotation, or, if symbol is
Expand Down
38 changes: 28 additions & 10 deletions compiler/src/dotty/tools/dotc/core/NamerOps.scala
Expand Up @@ -77,10 +77,15 @@ object NamerOps:
val ApplyProxyFlags = Synthetic | ConstructorProxy | Inline | Method

/** Does symbol `cls` need constructor proxies to be generated? */
def needsConstructorProxies(cls: Symbol)(using Context): Boolean =
cls.isClass
&& !cls.flagsUNSAFE.isOneOf(NoConstructorProxyNeededFlags)
&& !cls.isAnonymousClass
def needsConstructorProxies(sym: Symbol)(using Context): Boolean =
sym.isClass
&& !sym.flagsUNSAFE.isOneOf(NoConstructorProxyNeededFlags)
&& !sym.isAnonymousClass
||
sym.isType && sym.is(Exported)
&& sym.info.loBound.underlyingClassRef(refinementOK = false).match
case tref: TypeRef => tref.prefix.isStable
case _ => false

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

/** A new symbol that is the constructor companion for class `cls` */
def constructorCompanion(cls: ClassSymbol)(using Context): TermSymbol =
def classConstructorCompanion(cls: ClassSymbol)(using Context): TermSymbol =
val companion = newModuleSymbol(
cls.owner, cls.name.toTermName,
ConstructorCompanionFlags, ConstructorCompanionFlags,
Expand All @@ -125,6 +130,10 @@ object NamerOps:
cls.registerCompanion(companion.moduleClass)
companion

def typeConstructorCompanion(tsym: Symbol, prefix: Type, proxy: Symbol)(using Context): TermSymbol =
newSymbol(tsym.owner, tsym.name.toTermName,
ConstructorCompanionFlags | StableRealizable | Method, ExprType(prefix.select(proxy)), coord = tsym.coord)

/** Add all necesssary constructor proxy symbols for members of class `cls`. This means:
*
* - if a member is a class that needs a constructor companion, add one,
Expand All @@ -137,12 +146,21 @@ object NamerOps:

def memberExists(cls: ClassSymbol, name: TermName): Boolean =
cls.baseClasses.exists(_.info.decls.lookupEntry(name) != null)

for mbr <- cls.info.decls do
if needsConstructorProxies(mbr)
&& !mbr.asClass.unforcedRegisteredCompanion.exists
&& !memberExists(cls, mbr.name.toTermName)
then
constructorCompanion(mbr.asClass).entered
if needsConstructorProxies(mbr) then
mbr match
case mbr: ClassSymbol =>
if !mbr.unforcedRegisteredCompanion.exists
&& !memberExists(cls, mbr.name.toTermName)
then
classConstructorCompanion(mbr).entered
case _ =>
mbr.info.loBound.underlyingClassRef(refinementOK = false) match
case ref: TypeRef =>
val proxy = ref.symbol.registeredCompanion
if proxy.is(ConstructorProxy) && !memberExists(cls, mbr.name.toTermName) then
typeConstructorCompanion(mbr, ref.prefix, proxy).entered

if cls.is(Module)
&& needsConstructorProxies(cls.linkedClass)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Expand Up @@ -731,6 +731,7 @@ class TreePickler(pickler: TastyPickler) {
if flags.is(Infix) then writeModTag(INFIX)
if flags.is(Invisible) then writeModTag(INVISIBLE)
if (flags.is(Erased)) writeModTag(ERASED)
if (flags.is(Exported)) writeModTag(EXPORTED)
if (isTerm) {
if (flags.is(Implicit)) writeModTag(IMPLICIT)
if (flags.is(Given)) writeModTag(GIVEN)
Expand All @@ -744,7 +745,6 @@ class TreePickler(pickler: TastyPickler) {
if (flags.is(Extension)) writeModTag(EXTENSION)
if (flags.is(ParamAccessor)) writeModTag(PARAMsetter)
if (flags.is(SuperParamAlias)) writeModTag(PARAMalias)
if (flags.is(Exported)) writeModTag(EXPORTED)
assert(!(flags.is(Label)))
}
else {
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Expand Up @@ -617,7 +617,7 @@ class Namer { typer: Typer =>
val classSym = ctx.effectiveScope.lookup(className)
val moduleName = className.toTermName
if needsConstructorProxies(classSym) && ctx.effectiveScope.lookupEntry(moduleName) == null then
enterSymbol(constructorCompanion(classSym.asClass))
enterSymbol(classConstructorCompanion(classSym.asClass))
else if ctx.owner.is(PackageClass) then
for case cdef @ TypeDef(moduleName, _) <- moduleDef.values do
val moduleSym = ctx.effectiveScope.lookup(moduleName)
Expand All @@ -634,12 +634,12 @@ class Namer { typer: Typer =>
val moduleName = className.toTermName
val companionVals = ctx.effectiveScope.lookupAll(moduleName.encode)
if companionVals.isEmpty && needsConstructorProxies(classSym) then
enterSymbol(constructorCompanion(classSym.asClass))
enterSymbol(classConstructorCompanion(classSym.asClass))
else
for moduleSym <- companionVals do
if moduleSym.is(Module) && !moduleSym.isDefinedInCurrentRun then
val companion =
if needsConstructorProxies(classSym) then constructorCompanion(classSym.asClass)
if needsConstructorProxies(classSym) then classConstructorCompanion(classSym.asClass)
else newModuleSymbol(
ctx.owner, moduleName, EmptyFlags, EmptyFlags, (_, _) => NoType)
enterSymbol(companion)
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotc/pos-test-pickling.blacklist
Expand Up @@ -16,6 +16,7 @@ tuple-filter.scala
i7740a.scala
i7740b.scala
i6507b.scala
i12299a.scala

# Stale symbol: package object scala
seqtype-cycle
Expand Down
24 changes: 24 additions & 0 deletions tests/neg/i12299.scala
@@ -0,0 +1,24 @@
object Outer {

object Inner {
class Bar(x: Int)
object Bar
}

export Inner.Bar._

val _ = apply(2) // error (constructor proxies are not exported)

}
object Outer2 {

object Inner {
class Bar(x: Int)
object Bar
}

export Inner.Bar.apply // error: no eligible member

val _ = apply(2) // error (constructor proxies are not exported)

}
54 changes: 54 additions & 0 deletions tests/pos/i12299.scala
@@ -0,0 +1,54 @@
object Outer0 {

object Inner {
class Bar(x: Int):
def this() = this(0)
}

export Inner.Bar

val _ = Bar()
val _ = Bar(2)

}

object Outer2 {

object Inner {
class Bar(x: Int):
def this() = this(0)
}

object test2:
export Inner._

val x = Bar()
val y = Bar(2)

object test3:
export Inner.Bar
def Bar: () => String = () => ""
val x = Bar()
}

object Outer3 {
export Outer0._

private val x = Bar()
private val y = Bar(2)
}

object Outer4 {

object Inner {
class Bar(x: Int):
def this() = this(0)
object Bar
}

export Inner._

val _ = Bar()
val _ = Bar(2)

}
9 changes: 9 additions & 0 deletions tests/pos/i12299/Outer_1.scala
@@ -0,0 +1,9 @@
object Outer {

object Inner {
class Bar(x: Int):
def this() = this(0)
}

export Inner.Bar
}
4 changes: 4 additions & 0 deletions tests/pos/i12299/Test_2.scala
@@ -0,0 +1,4 @@
import Outer._

val x = Bar()
val y = Bar(2)
11 changes: 11 additions & 0 deletions tests/pos/i12299a.scala
@@ -0,0 +1,11 @@
object Outer {

object Wrap {
export Outer.Bar
}

class Bar

val wrapBar = Wrap.Bar()
}

0 comments on commit 56c40f8

Please sign in to comment.