From f3a4263a6ff701f64c569febf6fe3c4897713ac7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 2 May 2021 17:17:13 +0200 Subject: [PATCH 1/4] Make forwarder logic more explicit --- .../src/dotty/tools/dotc/typer/Namer.scala | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 10872e39b516..f7e4d14c0ad7 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -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 @@ -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`, @@ -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 @@ -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 @@ -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) @@ -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) @@ -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 = From be5043a9890db5c3f717512bd71a1e5bf7884526 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 2 May 2021 19:42:04 +0200 Subject: [PATCH 2/4] Keep constructor proxies after erasure It's awkward to delete all references to them. We should treat them like other symbols that are effectively erased. --- .../tools/backend/jvm/BTypesFromSymbols.scala | 2 +- .../dotty/tools/dotc/core/SymDenotations.scala | 2 ++ .../src/dotty/tools/dotc/core/TypeErasure.scala | 1 + .../dotty/tools/dotc/transform/Erasure.scala | 17 +---------------- .../tools/dotc/transform/TreeChecker.scala | 3 +-- 5 files changed, 6 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala index d8cb5534e5e3..cff63a06ff20 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 2d71bd350b3b..a2757740885f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -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, diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index 9b82aa7cba25..8fd5f5043ffd 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -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 { diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 915aaf371219..a9568a9d23cb 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index e3a0489cf0fa..2bfc77fc1bb7 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -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) From 8d331eee3b23ead37a83ae3834894ebe42286a99 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 3 May 2021 13:50:16 +0200 Subject: [PATCH 3/4] Export constructor proxies 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 #11229 --- .../src/dotty/tools/dotc/core/Flags.scala | 2 +- .../src/dotty/tools/dotc/core/NamerOps.scala | 38 +++++++++---- .../tools/dotc/core/tasty/TreePickler.scala | 2 +- .../src/dotty/tools/dotc/typer/Namer.scala | 6 +-- .../test/dotc/pos-test-pickling.blacklist | 1 + tests/neg/i12299.scala | 24 +++++++++ tests/pos/i12299.scala | 54 +++++++++++++++++++ tests/pos/i12299/Outer_1.scala | 9 ++++ tests/pos/i12299/Test_2.scala | 4 ++ tests/pos/i12299a.scala | 11 ++++ 10 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 tests/neg/i12299.scala create mode 100644 tests/pos/i12299.scala create mode 100644 tests/pos/i12299/Outer_1.scala create mode 100644 tests/pos/i12299/Test_2.scala create mode 100644 tests/pos/i12299a.scala diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 76cb45c1b28c..10d0bfb3c2e0 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index ce525fc5d827..26dd19ebf56f 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -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: @@ -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, @@ -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, @@ -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) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 06ba83ae15bf..af66ccd3ecc4 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -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) @@ -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 { diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index f7e4d14c0ad7..59f2748982ad 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -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) @@ -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) diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 423c429d19e0..4e4609cf34f4 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -16,6 +16,7 @@ tuple-filter.scala i7740a.scala i7740b.scala i6507b.scala +i12299a.scala # Stale symbol: package object scala seqtype-cycle diff --git a/tests/neg/i12299.scala b/tests/neg/i12299.scala new file mode 100644 index 000000000000..424be6cc4fd2 --- /dev/null +++ b/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) + +} diff --git a/tests/pos/i12299.scala b/tests/pos/i12299.scala new file mode 100644 index 000000000000..cabd12ed3b05 --- /dev/null +++ b/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) + +} diff --git a/tests/pos/i12299/Outer_1.scala b/tests/pos/i12299/Outer_1.scala new file mode 100644 index 000000000000..200c21beecb8 --- /dev/null +++ b/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 +} diff --git a/tests/pos/i12299/Test_2.scala b/tests/pos/i12299/Test_2.scala new file mode 100644 index 000000000000..ce4c9390744d --- /dev/null +++ b/tests/pos/i12299/Test_2.scala @@ -0,0 +1,4 @@ +import Outer._ + +val x = Bar() +val y = Bar(2) diff --git a/tests/pos/i12299a.scala b/tests/pos/i12299a.scala new file mode 100644 index 000000000000..90819f0a0bca --- /dev/null +++ b/tests/pos/i12299a.scala @@ -0,0 +1,11 @@ +object Outer { + + object Wrap { + export Outer.Bar + } + + class Bar + + val wrapBar = Wrap.Bar() +} + From 7175bfb7359414b90ec76ef3e87c31ac6eebde25 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Mon, 10 May 2021 20:17:21 +0200 Subject: [PATCH 4/4] update docs --- compiler/src/dotty/tools/dotc/core/NamerOps.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/NamerOps.scala b/compiler/src/dotty/tools/dotc/core/NamerOps.scala index 26dd19ebf56f..e8199b6e7040 100644 --- a/compiler/src/dotty/tools/dotc/core/NamerOps.scala +++ b/compiler/src/dotty/tools/dotc/core/NamerOps.scala @@ -76,7 +76,7 @@ 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? */ + /** Does symbol `sym` need constructor proxies to be generated? */ def needsConstructorProxies(sym: Symbol)(using Context): Boolean = sym.isClass && !sym.flagsUNSAFE.isOneOf(NoConstructorProxyNeededFlags) @@ -136,7 +136,7 @@ object NamerOps: /** 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,