Skip to content

Export constructor proxies #12311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes {
private def definedClasses(sym: Symbol, phase: Phase) =
if (sym.isDefinedInCurrentRun)
atPhase(phase) {
toDenot(sym).info.decls.filter(_.isClass)
toDenot(sym).info.decls.filter(sym => sym.isClass && !sym.isEffectivelyErased)
}
else Nil

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
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
42 changes: 30 additions & 12 deletions compiler/src/dotty/tools/dotc/core/NamerOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,16 @@ object NamerOps:
/** The flags of an `apply` method that serves as a constructor proxy */
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
/** Does symbol `sym` need constructor proxies to be generated? */
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,9 +130,13 @@ 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,
* - if a member is a class, or type alias, that needs a constructor companion, add one,
* provided no member with the same name exists.
* - if `cls` is a companion object of a class that needs a constructor companion,
* and `cls` does not already define or inherit an `apply` method,
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: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1180,11 +1180,13 @@ object SymDenotations {
*/
final def companionModule(using Context): Symbol =
if (is(Module)) sourceModule
else if registeredCompanion.isAbsent() then NoSymbol
else registeredCompanion.sourceModule

private def companionType(using Context): Symbol =
if (is(Package)) NoSymbol
else if (is(ModuleVal)) moduleClass.denot.companionType
else if registeredCompanion.isAbsent() then NoSymbol
else registeredCompanion

/** The class with the same (type-) name as this module or module class,
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ object TypeErasure {

if (defn.isPolymorphicAfterErasure(sym)) eraseParamBounds(sym.info.asInstanceOf[PolyType])
else if (sym.isAbstractType) TypeAlias(WildcardType)
else if sym.is(ConstructorProxy) then NoType
else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(using preErasureCtx))
else if (sym.is(Label)) erase.eraseResult(sym.info)(using preErasureCtx)
else erase.eraseInfo(tp, sym)(using preErasureCtx) match {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
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
17 changes: 1 addition & 16 deletions compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1038,24 +1038,9 @@ object Erasure {
override def typedTypeDef(tdef: untpd.TypeDef, sym: Symbol)(using Context): Tree =
EmptyTree

/** Drop all constructor proxies of members of class `cls`.
* If `cls` is itself a constructor proxy, mark it as absent after erasure.
*/
private def dropConstructorProxies(cls: ClassSymbol)(using Context) =
import Flags._
if cls.linkedClass.is(ConstructorProxy) then
if cls.owner.is(PackageClass) && cls.isDefinedInCurrentRun then
cls.linkedClass.copySymDenotation(initFlags = EmptyFlags, info = NoType)
.installAfter(erasurePhase)
cls.registeredCompanion = NoSymbol
for mbr <- cls.info.decls do
if mbr.is(ConstructorProxy) then mbr.dropAfter(erasurePhase)

override def typedClassDef(cdef: untpd.TypeDef, cls: ClassSymbol)(using Context): Tree =
if cls.is(Flags.Erased) then erasedDef(cls)
else
try super.typedClassDef(cdef, cls)
finally dropConstructorProxies(cls)
else super.typedClassDef(cdef, cls)

override def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree =
typed(tree.arg, pt)
Expand Down
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/dotc/transform/TreeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -446,8 +446,7 @@ class TreeChecker extends Phase with SymTransformer {
val decls = cls.classInfo.decls.toList.toSet.filter(isNonMagicalMember)
val defined = impl.body.map(_.symbol)

def isAllowed(sym: Symbol): Boolean =
sym.is(ConstructorProxy) && !ctx.phase.erasedTypes
def isAllowed(sym: Symbol): Boolean = sym.is(ConstructorProxy)

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

Expand Down
60 changes: 33 additions & 27 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
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 Expand Up @@ -974,7 +974,6 @@ class Namer { typer: Typer =>

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

def whyNoForwarder(mbr: SingleDenotation): String = {
def canForward(mbr: SingleDenotation): CanForward = {
import CanForward.*
val sym = mbr.symbol
if (!sym.isAccessibleFrom(path.tpe)) "is not accessible"
else if (sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(ConstructorProxy)) SKIP
else if (cls.derivesFrom(sym.owner) &&
(sym.owner == cls || !sym.is(Deferred))) i"is already a member of $cls"
else if (sym.is(Override))
if !sym.isAccessibleFrom(path.tpe) then
No("is not accessible")
else if sym.isConstructor || sym.is(ModuleClass) || sym.is(Bridge) || sym.is(ConstructorProxy) then
Skip
else if cls.derivesFrom(sym.owner) && (sym.owner == cls || !sym.is(Deferred)) then
No(i"is already a member of $cls")
else if sym.is(Override) then
sym.allOverriddenSymbols.find(
other => cls.derivesFrom(other.owner) && !other.is(Deferred)) match {
case Some(other) => i"overrides ${other.showLocated}, which is already a member of $cls"
case None => ""
}
else ""
other => cls.derivesFrom(other.owner) && !other.is(Deferred)
) match
case Some(other) => No(i"overrides ${other.showLocated}, which is already a member of $cls")
case None => Yes
else Yes
}

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

if whyNoForwarder(mbr) == "" then
if canForward(mbr) == CanForward.Yes then
val sym = mbr.symbol
val forwarder =
if mbr.isType then
Expand All @@ -1038,7 +1040,7 @@ class Namer { typer: Typer =>
// a parameterized class, say `C[X]` the alias will read `type C = d.C`. We currently do
// allow such type aliases. If we forbid them at some point (requiring the referred type to be
// fully applied), we'd have to change the scheme here as well.
else {
else
def refersToPrivate(tp: Type): Boolean = tp match
case tp: TermRef => tp.termSymbol.is(Private) || refersToPrivate(tp.prefix)
case _ => false
Expand All @@ -1051,16 +1053,17 @@ class Namer { typer: Typer =>
if sym.is(ExtensionMethod) then mbrFlags |= ExtensionMethod
val forwarderName = checkNoConflict(alias, isPrivate = false, span)
newSymbol(cls, forwarderName, mbrFlags, mbrInfo, coord = span)
}

forwarder.info = avoidPrivateLeaks(forwarder)
forwarder.addAnnotations(sym.annotations)

val forwarderDef =
if (forwarder.isType) tpd.TypeDef(forwarder.asType)
else {
import tpd._
val ref = path.select(sym.asTerm)
val ddef = tpd.DefDef(forwarder.asTerm, prefss =>
ref.appliedToArgss(adaptForwarderParams(Nil, sym.info, prefss))
ref.appliedToArgss(adaptForwarderParams(Nil, sym.info, prefss))
)
if forwarder.isInlineMethod then
PrepareInlineable.registerInlineInfo(forwarder, ddef.rhs)
Expand All @@ -1070,18 +1073,15 @@ class Namer { typer: Typer =>
buf += forwarderDef.withSpan(span)
end addForwarder

def addForwardersNamed(name: TermName, alias: TermName, span: Span): Unit = {
def addForwardersNamed(name: TermName, alias: TermName, span: Span): Unit =
val size = buf.size
val mbrs = List(name, name.toTypeName).flatMap(path.tpe.member(_).alternatives)
mbrs.foreach(addForwarder(alias, _, span))
if (buf.size == size) {
val reason = mbrs.map(whyNoForwarder).dropWhile(_ == SKIP) match {
case Nil => ""
case why :: _ => i"\n$path.$name cannot be exported because it $why"
}
if buf.size == size then
val reason = mbrs.map(canForward).collect {
case CanForward.No(whyNot) => i"\n$path.$name cannot be exported because it $whyNot"
}.headOption.getOrElse("")
report.error(i"""no eligible member $name at $path$reason""", ctx.source.atSpan(span))
}
}

def addWildcardForwardersNamed(name: TermName, span: Span): Unit =
List(name, name.toTypeName)
Expand Down Expand Up @@ -1357,6 +1357,12 @@ class Namer { typer: Typer =>
}
}

/** Possible actions to perform when deciding on a forwarder for a member */
private enum CanForward:
case Yes
case No(whyNot: String)
case Skip // for members that have never forwarders

class SuspendCompleter extends LazyType, SymbolLoaders.SecondCompleter {

final override def complete(denot: SymDenotation)(using Context): Unit =
Expand Down
1 change: 1 addition & 0 deletions compiler/test/dotc/pos-test-pickling.blacklist
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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)

}
Loading