diff --git a/community-build/community-projects/stdLib213 b/community-build/community-projects/stdLib213 index d175ff3e32cc..eaa470c7a620 160000 --- a/community-build/community-projects/stdLib213 +++ b/community-build/community-projects/stdLib213 @@ -1 +1 @@ -Subproject commit d175ff3e32ccf033020925cb427d0f5dd7a8a55d +Subproject commit eaa470c7a62072785ee643f38bd30c36e9352d51 diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 5994fd325325..e2e5229bb3fc 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -90,6 +90,7 @@ class Compiler { new AugmentScala2Traits, // Augments Scala2 traits with additional members needed for mixin composition. new ResolveSuper, // Implement super accessors new FunctionXXLForwarders, // Add forwarders for FunctionXXL apply method + new ParamForwarding, // Add forwarders for aliases of superclass parameters new TupleOptimizations, // Optimize generic operations on tuples new ArrayConstructors) :: // Intercept creation of (non-generic) arrays and intrinsify. List(new Erasure) :: // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements. diff --git a/compiler/src/dotty/tools/dotc/core/Annotations.scala b/compiler/src/dotty/tools/dotc/core/Annotations.scala index 6ed5640bfcc4..ef125852cf35 100644 --- a/compiler/src/dotty/tools/dotc/core/Annotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Annotations.scala @@ -145,10 +145,6 @@ object Annotations { def deferredResolve(atp: Type, args: List[Tree])(implicit ctx: Context): Annotation = deferred(atp.classSymbol)(resolveConstructor(atp, args)) - def makeAlias(sym: TermSymbol)(implicit ctx: Context): Annotation = - apply(defn.AliasAnnot, List( - ref(TermRef(sym.owner.thisType, sym.name, sym)))) - /** Extractor for child annotations */ object Child { diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 3873ee2e6249..9743557cc8a7 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -780,7 +780,6 @@ class Definitions { @tu lazy val RefiningAnnotationClass: ClassSymbol = ctx.requiredClass("scala.annotation.RefiningAnnotation") // Annotation classes - @tu lazy val AliasAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.Alias") @tu lazy val AnnotationDefaultAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.AnnotationDefault") @tu lazy val BodyAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.Body") @tu lazy val ChildAnnot: ClassSymbol = ctx.requiredClass("scala.annotation.internal.Child") diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index c5b033342460..48a5dfbf8839 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -296,7 +296,7 @@ object Flags { val (_, CaseAccessor @ _, _) = newFlags(25, "") /** A Scala 2x super accessor / an unpickled Scala 2.x class */ - val (SuperAccessorOrScala2x @ _, Scala2SuperAccessor @ _, Scala2x @ _) = newFlags(26, "", "") + val (SuperParamAliasOrScala2x @ _, SuperParamAlias @ _, Scala2x @ _) = newFlags(26, "", "") /** A method that has default params */ val (_, DefaultParameterized @ _, _) = newFlags(27, "") @@ -371,8 +371,9 @@ object Flags { /** Symbol is a self name */ val (_, SelfName @ _, _) = newFlags(54, "") - /** An existentially bound symbol (Scala 2.x only) */ - val (Scala2ExistentialCommon @ _, _, Scala2Existential @ _) = newFlags(55, "") + /** A Scala 2 superaccessor (only needed during Scala2Unpickling) / + * an existentially bound symbol (Scala 2.x only) */ + val (Scala2SpecialFlags @ _, Scala2SuperAccessor @ _, Scala2Existential @ _) = newFlags(55, "") /** Children were queried on this class */ val (_, _, ChildrenQueried @ _) = newFlags(56, "") @@ -439,10 +440,10 @@ object Flags { val FromStartFlags: FlagSet = commonFlags( Module, Package, Deferred, Method, Case, HigherKinded, Param, ParamAccessor, - Scala2ExistentialCommon, MutableOrOpen, Opaque, Touched, JavaStatic, + Scala2SpecialFlags, MutableOrOpen, Opaque, Touched, JavaStatic, OuterOrCovariant, LabelOrContravariant, CaseAccessor, Extension, NonMember, Implicit, Given, Permanent, Synthetic, - SuperAccessorOrScala2x, Inline, Macro) + SuperParamAliasOrScala2x, Inline, Macro) /** Flags that are not (re)set when completing the denotation, or, if symbol is * a top-level class or object, when completing the denotation once the class @@ -551,6 +552,7 @@ object Flags { val JavaProtected: FlagSet = JavaDefined | Protected val MethodOrLazy: FlagSet = Lazy | Method val MutableOrLazy: FlagSet = Lazy | Mutable + val MethodOrLazyOrMutable: FlagSet = Lazy | Method | Mutable val LiftedMethod: FlagSet = Lifted | Method val LocalParam: FlagSet = Local | Param val LocalParamAccessor: FlagSet = Local | ParamAccessor | Private @@ -561,7 +563,7 @@ object Flags { val PrivateMethod: FlagSet = Method | Private val NoInitsInterface: FlagSet = NoInits | PureInterface val NoInitsTrait: FlagSet = NoInits | Trait // A trait that does not need to be initialized - val ValidForeverFlags: FlagSet = Package | Permanent | Scala2ExistentialCommon + val ValidForeverFlags: FlagSet = Package | Permanent | Scala2SpecialFlags val TermParamOrAccessor: FlagSet = Param | ParamAccessor val PrivateParamAccessor: FlagSet = ParamAccessor | Private val PrivateOrSynthetic: FlagSet = Private | Synthetic diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index bfe4467e8dd5..0d6879229f3c 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -354,12 +354,12 @@ object NameKinds { val InlineAccessorName: PrefixNameKind = new PrefixNameKind(INLINEACCESSOR, "inline$") val AvoidClashName: SuffixNameKind = new SuffixNameKind(AVOIDCLASH, "$_avoid_name_clash_$") - val CacheName = new SuffixNameKind(CACHE, "$_cache") val DirectMethodName: SuffixNameKind = new SuffixNameKind(DIRECT, "$direct") { override def definesNewName = true } val FieldName: SuffixNameKind = new SuffixNameKind(FIELD, "$$local") { override def mkString(underlying: TermName, info: ThisInfo) = underlying.toString } val ExtMethName: SuffixNameKind = new SuffixNameKind(EXTMETH, "$extension") + val ParamAccessorName: SuffixNameKind = new SuffixNameKind(PARAMACC, "$accessor") val ModuleClassName: SuffixNameKind = new SuffixNameKind(OBJECTCLASS, "$", optInfoString = "ModuleClass") val ImplMethName: SuffixNameKind = new SuffixNameKind(IMPLMETH, "$") val AdaptedClosureName: SuffixNameKind = new SuffixNameKind(ADAPTEDCLOSURE, "$adapted") { override def definesNewName = true } diff --git a/compiler/src/dotty/tools/dotc/core/NameTags.scala b/compiler/src/dotty/tools/dotc/core/NameTags.scala index 8b7a581104a9..d58ec050cab2 100644 --- a/compiler/src/dotty/tools/dotc/core/NameTags.scala +++ b/compiler/src/dotty/tools/dotc/core/NameTags.scala @@ -34,7 +34,7 @@ object NameTags extends TastyFormat.NameTags { final val IMPLMETH = 32 // Used to define methods in implementation classes // (can probably be removed). - final val CACHE = 33 // Used as a cache for the rhs of an alias implicit. + final val PARAMACC = 33 // Used for a private parameter alias def nameTagToString(tag: Int): String = tag match { case UTF8 => "UTF8" @@ -55,6 +55,8 @@ object NameTags extends TastyFormat.NameTags { case DIRECT => "DIRECT" case FIELD => "FIELD" case EXTMETH => "EXTMETH" + case IMPLMETH => "IMPLMETH" + case PARAMACC => "PARAMACC" case ADAPTEDCLOSURE => "ADAPTEDCLOSURE" case OBJECTCLASS => "OBJECTCLASS" diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index fbb02378df87..2073282b0d01 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1485,6 +1485,11 @@ object SymDenotations { override def transformAfter(phase: DenotTransformer, f: SymDenotation => SymDenotation)(implicit ctx: Context): Unit = super.transformAfter(phase, f) + /** Set flag `flags` in current phase and in all phases that follow */ + def setFlagFrom(phase: DenotTransformer, flags: FlagSet)(using Context): Unit = + setFlag(flags) + transformAfter(phase, sd => { sd.setFlag(flags); sd }) + /** If denotation is private, remove the Private flag and expand the name if necessary */ def ensureNotPrivate(implicit ctx: Context): SymDenotation = if (is(Private)) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala index 47df2955b211..7703108f5d0b 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala @@ -682,6 +682,7 @@ class TreePickler(pickler: TastyPickler) { if (flags.is(StableRealizable)) writeModTag(STABLE) 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))) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index 96a8cfe5e8a2..e4f2772ed960 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -644,6 +644,7 @@ class TreeUnpickler(reader: TastyReader, case EXTENSION => addFlag(Extension) case GIVEN => addFlag(Given) case PARAMsetter => addFlag(ParamAccessor) + case PARAMalias => addFlag(SuperParamAlias) case EXPORTED => addFlag(Exported) case OPEN => addFlag(Open) case PRIVATEqualified => diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 10def4aa7693..13524342e6ad 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -598,7 +598,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas denot.info matches denot.owner.thisType.memberInfo(alt) } val alias = readDisambiguatedSymbolRef(disambiguate).asTerm - denot.addAnnotation(Annotation.makeAlias(alias)) + if alias.name == denot.name then denot.setFlag(SuperParamAlias) } } // println(s"unpickled ${denot.debugString}, info = ${denot.info}") !!! DEBUG diff --git a/compiler/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala b/compiler/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala index d7280624757e..803c91862876 100644 --- a/compiler/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala +++ b/compiler/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala @@ -64,7 +64,6 @@ class AugmentScala2Traits extends MiniPhase with IdentityDenotTransformer { this || sym.isSuperAccessor) // scala2 superaccessors are pickled as private, but are compiled as public expanded sym.ensureNotPrivate.installAfter(thisPhase) } - mixin.setFlag(Scala2xPartiallyAugmented) - mixin.transformAfter(thisPhase, d => { d.setFlag(Scala2xPartiallyAugmented); d }) + mixin.setFlagFrom(thisPhase, Scala2xPartiallyAugmented) } } diff --git a/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala b/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala index 2b1ed3efd5cc..fcc5327e5080 100644 --- a/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala +++ b/compiler/src/dotty/tools/dotc/transform/CacheAliasImplicits.scala @@ -8,7 +8,6 @@ import core.Contexts._ import core.Types._ import core.Flags._ import core.StdNames.nme -import core.NameKinds.CacheName import core.Constants.Constant import core.Decorators._ import core.TypeErasure.erasure diff --git a/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala b/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala index 794539d7fa1f..fbbb233a89d7 100644 --- a/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala +++ b/compiler/src/dotty/tools/dotc/transform/ParamForwarding.scala @@ -1,88 +1,82 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package transform import core._ import ast.Trees._ import Contexts._, Types._, Symbols._, Flags._, TypeUtils._, DenotTransformers._, StdNames._ import Decorators._ +import MegaPhase._ +import NameKinds.ParamAccessorName import config.Printers.typr -/** For all parameter accessors +/** For all private parameter accessors * - * val x: T = ... + * private val x: T = ... * - * if - * (1) x is forwarded in the supercall to a parameter that's also named `x` - * (2) the superclass parameter accessor for `x` is accessible from the current class - * change the accessor to + * If there is a chain of parameter accessors starting with `x` such that + * (1) The last parameter accessor in the chain is a field that's accessible + * from the current class, and + * (2) each preceding parameter is forwarded in the supercall of its class + * to a parameter that's also named `x` + * then change the accessor to * - * def x: T = super.x.asInstanceOf[T] + * private def x$accessor: T = super.x'.asInstanceOf[T] + * + * where x' is a reference to the final parameter in the chain. + * Property (1) is established by the @see forwardParamAccessors method in PostTyper. + * + * The reason for renaming `x` to `x$accessor` is that private methods in the JVM + * cannot override public ones. * - * Do the same also if there are intermediate inaccessible parameter accessor forwarders. * The aim of this transformation is to avoid redundant parameter accessor fields. */ -class ParamForwarding(thisPhase: DenotTransformer) { +class ParamForwarding extends MiniPhase with IdentityDenotTransformer: import ast.tpd._ - def forwardParamAccessors(impl: Template)(implicit ctx: Context): Template = { - def fwd(stats: List[Tree])(implicit ctx: Context): List[Tree] = { - val (superArgs, superParamNames) = impl.parents match { - case superCall @ Apply(fn, args) :: _ => - fn.tpe.widen match { - case MethodType(paramNames) => (args, paramNames) - case _ => (Nil, Nil) - } - case _ => (Nil, Nil) - } - def inheritedAccessor(sym: Symbol): Symbol = { - /** - * Dmitry: having it have the same name is needed to maintain correctness in presence of subclassing - * if you would use parent param-name `a` to implement param-field `b` - * overriding field `b` will actually override field `a`, that is wrong! - * - * class A(val s: Int); - * class B(val b: Int) extends A(b) - * class C extends A(2) { - * def s = 3 - * assert(this.b == 2) - * } - */ - val candidate = sym.owner.asClass.superClass - .info.decl(sym.name).suchThat(_.is(ParamAccessor, butNot = Mutable)).symbol - if (candidate.isAccessibleFrom(currentClass.thisType, superAccess = true)) candidate - else if (candidate.exists) inheritedAccessor(candidate) - else NoSymbol - } - def forwardParamAccessor(stat: Tree): Tree = { - stat match { - case stat: ValDef => - val sym = stat.symbol.asTerm - if (sym.is(ParamAccessor, butNot = Mutable) && !sym.info.isInstanceOf[ExprType]) { - // ElimByName gets confused with methods returning an ExprType, - // so avoid param forwarding if parameter is by name. See i1766.scala - val idx = superArgs.indexWhere(_.symbol == sym) - if (idx >= 0 && superParamNames(idx) == stat.name) { // supercall to like-named parameter - val alias = inheritedAccessor(sym) - if (alias.exists) { - def forwarder(implicit ctx: Context) = { - sym.copySymDenotation(initFlags = sym.flags | Method | StableRealizable, info = sym.info.ensureMethodic) - .installAfter(thisPhase) - val superAcc = - Super(This(currentClass), tpnme.EMPTY, inConstrCall = false).select(alias) - typr.println(i"adding param forwarder $superAcc") - DefDef(sym, superAcc.ensureConforms(sym.info.widen)).withSpan(stat.span) - } - return forwarder(ctx.withPhase(thisPhase.next)) - } - } - } - case _ => - } - stat - } - stats map forwardParamAccessor - } + private def thisPhase: ParamForwarding = this + + val phaseName: String = "paramForwarding" + + def transformIfParamAlias(mdef: ValOrDefDef)(using ctx: Context): Tree = + + def inheritedAccessor(sym: Symbol)(using Context): Symbol = + val candidate = sym.owner.asClass.superClass + .info.decl(sym.name).suchThat(_.is(ParamAccessor, butNot = Mutable)) + .symbol + if !candidate.is(Private) // candidate might be private and accessible if it is in an outer class + && candidate.isAccessibleFrom(currentClass.thisType, superAccess = true) + then + candidate + else if candidate.is(SuperParamAlias) then + inheritedAccessor(candidate) + else + NoSymbol + + val sym = mdef.symbol.asTerm + if sym.is(SuperParamAlias) then + assert(sym.is(ParamAccessor, butNot = Mutable)) + val alias = inheritedAccessor(sym)(using ctx.withPhase(thisPhase)) + if alias.exists then + sym.copySymDenotation( + name = ParamAccessorName(sym.name), + initFlags = sym.flags | Method | StableRealizable, + info = sym.info.ensureMethodic + ).installAfter(thisPhase) + val superAcc = + Super(This(currentClass), tpnme.EMPTY, inConstrCall = false) + .withSpan(mdef.span) + .select(alias) + .ensureApplied + .ensureConforms(sym.info.finalResultType) + ctx.log(i"adding param forwarder $superAcc") + DefDef(sym, superAcc) + else mdef + else mdef + end transformIfParamAlias + + override def transformValDef(mdef: ValDef)(using Context): Tree = + transformIfParamAlias(mdef) - cpy.Template(impl)(body = fwd(impl.body)(ctx.withPhase(thisPhase))) - } -} + override def transformDefDef(mdef: DefDef)(using Context): Tree = + transformIfParamAlias(mdef) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 70095a2812d5..7d5b9f4c741a 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -11,6 +11,7 @@ import Types._, Contexts._, Names._, Flags._, DenotTransformers._, Phases._ import SymDenotations._, StdNames._, Annotations._, Trees._, Scopes._ import Decorators._ import Symbols._, SymUtils._ +import config.Printers.typr import reporting.diagnostic.messages._ object PostTyper { @@ -24,7 +25,7 @@ object PostTyper { * (2) Convert parameter fields that have the same name as a corresponding * public parameter field in a superclass to a forwarder to the superclass * field (corresponding = super class field is initialized with subclass field) - * (@see ForwardParamAccessors) + * @see forwardParamAccessors. * * (3) Add synthetic members (@see SyntheticMembers) * @@ -72,7 +73,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase new PostTyperTransformer val superAcc: SuperAccessors = new SuperAccessors(thisPhase) - val paramFwd: ParamForwarding = new ParamForwarding(thisPhase) val synthMbr: SyntheticMembers = new SyntheticMembers(thisPhase) private def newPart(tree: Tree): Option[New] = methPart(tree) match { @@ -98,6 +98,30 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase def isCheckable(t: New): Boolean = !inJavaAnnot && !noCheckNews.contains(t) + /** Mark parameter accessors that are aliases of like-named parameters + * in their superclass with SuperParamAlias. + * This info is used in phase ParamForwarding + */ + private def forwardParamAccessors(impl: Template)(using Context): Unit = impl.parents match + case superCall @ Apply(fn, superArgs) :: _ if superArgs.nonEmpty => + fn.tpe.widen match + case MethodType(superParamNames) => + for case stat: ValDef <- impl.body do + val sym = stat.symbol + if sym.isAllOf(PrivateParamAccessor, butNot = Mutable) + && !sym.info.isInstanceOf[ExprType] // val-parameters cannot be call-by name, so no need to try to forward to them + then + val idx = superArgs.indexWhere(_.symbol == sym) + if idx >= 0 && superParamNames(idx) == stat.name then + // Supercall to like-named parameter. + // Having it have the same name is needed to maintain correctness in presence of subclassing + // if you would use parent param-name `a` to implement param-field `b` + // overriding field `b` will actually override field `a`, that is wrong! + typr.println(i"super alias: ${sym.showLocated}") + sym.setFlagFrom(thisPhase, SuperParamAlias) + case _ => + case _ => + private def transformAnnot(annot: Tree)(implicit ctx: Context): Tree = { val saved = inJavaAnnot inJavaAnnot = annot.symbol.is(JavaDefined) @@ -227,11 +251,11 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase val pos = call.sourcePos val callTrace = Inliner.inlineCallTrace(call.symbol, pos)(ctx.withSource(pos.source)) cpy.Inlined(tree)(callTrace, transformSub(bindings), transform(expansion)(inlineContext(call))) - case tree: Template => - withNoCheckNews(tree.parents.flatMap(newPart)) { - val templ1 = paramFwd.forwardParamAccessors(tree) + case templ: Template => + withNoCheckNews(templ.parents.flatMap(newPart)) { + forwardParamAccessors(templ) synthMbr.addSyntheticMembers( - superAcc.wrapTemplate(templ1)( + superAcc.wrapTemplate(templ)( super.transform(_).asInstanceOf[Template])) } case tree: ValDef => diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 7eebc6aebfe4..8a1e2bd11390 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -866,6 +866,30 @@ object RefChecks { } } + /** Check that we do not "override" anything with a private method + * or something that becomes a private method. According to the Scala + * modeling this is non-sensical since private members don't override. + * But Java and the JVM disagree, if the private member is a method. + * A test case is neg/i7926b.scala. + * Note: The compiler could possibly silently rename the offending private + * instead of flagging it as an error. But that might mean we see some + * surprising names at runtime. E.g. in neg/i4564a.scala, a private + * case class `apply` method would have to be renamed to something else. + */ + def checkNoPrivateOverrides(tree: Tree)(using ctx: Context): Unit = + val sym = tree.symbol + if sym.owner.isClass + && sym.is(Private) + && (sym.isOneOf(MethodOrLazyOrMutable) || !sym.is(Local)) // in these cases we'll produce a getter later + && !sym.isConstructor + then + val cls = sym.owner.asClass + for bc <- cls.baseClasses.tail do + val other = sym.matchingDecl(bc, cls.thisType) + if other.exists then + ctx.error(i"private $sym cannot override ${other.showLocated}", sym.sourcePos) + end checkNoPrivateOverrides + type LevelAndIndex = immutable.Map[Symbol, (LevelInfo, Int)] class OptLevelInfo { @@ -957,6 +981,7 @@ class RefChecks extends MiniPhase { thisPhase => else ctx override def transformValDef(tree: ValDef)(implicit ctx: Context): ValDef = { + checkNoPrivateOverrides(tree) checkDeprecatedOvers(tree) val sym = tree.symbol if (sym.exists && sym.owner.isTerm) { @@ -976,6 +1001,7 @@ class RefChecks extends MiniPhase { thisPhase => } override def transformDefDef(tree: DefDef)(implicit ctx: Context): DefDef = { + checkNoPrivateOverrides(tree) checkDeprecatedOvers(tree) tree } diff --git a/library/src/scala/annotation/internal/Alias.scala b/library/src/scala/annotation/internal/Alias.scala index e3f56e70cb1f..5c8f7d4b9ffa 100644 --- a/library/src/scala/annotation/internal/Alias.scala +++ b/library/src/scala/annotation/internal/Alias.scala @@ -4,6 +4,7 @@ import scala.annotation.Annotation /** An annotation to record a Scala2 pickled alias. * @param aliased A TermRef pointing to the aliased field. + * TODO: Drop once the new param alias scheme is in the bootstrap compiler */ class Alias(aliased: Any) extends Annotation { diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index 439be1400cd0..379606d26588 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -207,6 +207,7 @@ Standard-Section: "ASTs" TopLevelStat* STABLE -- Method that is assumed to be stable, i.e. its applications are legal paths EXTENSION -- An extension method PARAMsetter -- The setter part `x_=` of a var parameter `x` which itself is pickled as a PARAM + PARAMalias -- Parameter is alias of a superclass parameter EXPORTED -- An export forwarder OPEN -- an open class Annotation @@ -355,6 +356,7 @@ object TastyFormat { final val EXPORTED = 39 final val OPEN = 40 final val PARAMEND = 41 + final val PARAMalias = 42 // Cat. 2: tag Nat @@ -481,7 +483,7 @@ object TastyFormat { /** Useful for debugging */ def isLegalTag(tag: Int): Boolean = - firstSimpleTreeTag <= tag && tag <= PARAMEND || + firstSimpleTreeTag <= tag && tag <= PARAMalias || firstNatTreeTag <= tag && tag <= RENAMED || firstASTTreeTag <= tag && tag <= BOUNDED || firstNatASTTreeTag <= tag && tag <= NAMEDARG || @@ -524,6 +526,7 @@ object TastyFormat { | STABLE | EXTENSION | PARAMsetter + | PARAMalias | EXPORTED | OPEN | ANNOTATION @@ -588,6 +591,7 @@ object TastyFormat { case EXPORTED => "EXPORTED" case OPEN => "OPEN" case PARAMEND => "PARAMEND" + case PARAMalias => "PARAMalias" case SHAREDterm => "SHAREDterm" case SHAREDtype => "SHAREDtype" diff --git a/tests/neg/i4564a.scala b/tests/neg/i4564a.scala new file mode 100644 index 000000000000..70f9a21b074b --- /dev/null +++ b/tests/neg/i4564a.scala @@ -0,0 +1,6 @@ + +class BaseCP[T] { + def apply(x: T): ClashPoly = if (???) ClashPoly(1) else ??? +} +object ClashPoly extends BaseCP[Int] +case class ClashPoly private(x: Int) // error: private method apply cannot override method apply in class BaseCP \ No newline at end of file diff --git a/tests/neg/i7926b.scala b/tests/neg/i7926b.scala new file mode 100644 index 000000000000..6bef9f840869 --- /dev/null +++ b/tests/neg/i7926b.scala @@ -0,0 +1,26 @@ +trait A { + def p: Int + def getP = p +} +class B extends A { + def p: Int = 22 +} +class C extends B { + private def p: Int = super.p // error +} +class D extends B { + private val p: Int = super.p // OK +} +class E extends B { + private var p: Int = 0 // error +} +class F extends B { + private lazy val p: Int = 0 // error +} +object Test { + def main(args: Array[String]): Unit = + println(new C().getP) // would give illegal access error at runtime if C compiled + println(new D().getP) // OK + println(new E().getP) // would give illegal access error at runtime if E compiled + println(new F().getP) // would give illegal access error at runtime if F compiled +} \ No newline at end of file diff --git a/tests/neg/i7926c.scala b/tests/neg/i7926c.scala new file mode 100644 index 000000000000..ada3661bd610 --- /dev/null +++ b/tests/neg/i7926c.scala @@ -0,0 +1,13 @@ + +trait A { + def p: Int + def getP = p +} +trait B extends A { + def p: Int = 22 +} +class C extends B { + private def p: Int = 23 // error +} +@main def Test = + C().getP // would crash with a duplicate method error if the private C#p was permitted diff --git a/tests/neg/i8005.scala b/tests/neg/i8005.scala index 1e11762509ea..8b723054c2bf 100644 --- a/tests/neg/i8005.scala +++ b/tests/neg/i8005.scala @@ -8,5 +8,5 @@ abstract class Buffer { class ByteBuffer extends Buffer { // error: ByteBufer needs to be abstract since `bar` is not defined private[nio] val isReadOnly: Boolean = false // error protected def foo(): Unit = () // error - private def bar(): Unit = () + private def bar(): Unit = () // error } diff --git a/tests/pos/i4564.scala b/tests/pos/i4564.scala index 3334d219e805..795d04223352 100644 --- a/tests/pos/i4564.scala +++ b/tests/pos/i4564.scala @@ -46,17 +46,9 @@ object NoClashOverload { case class NoClashOverload private (x: Int) class BaseNCP[T] { - // error: overloaded method apply needs result type def apply(x: T): NoClashPoly = if (???) NoClashPoly(1) else ??? } object NoClashPoly extends BaseNCP[Boolean] case class NoClashPoly (x: Int) - -class BaseCP[T] { - // error: overloaded method apply needs result type - def apply(x: T): ClashPoly = if (???) ClashPoly(1) else ??? -} -object ClashPoly extends BaseCP[Int] -case class ClashPoly private(x: Int) \ No newline at end of file diff --git a/tests/pos/paramAliases.scala b/tests/pos/paramAliases.scala deleted file mode 100644 index e7787864aee4..000000000000 --- a/tests/pos/paramAliases.scala +++ /dev/null @@ -1,11 +0,0 @@ -// This tests that the subclass parameter is not -// translated to a field, but is forwarded to the -// superclass parameter. Right now we need to verify -// this by inspecting the output with -Xprint:super -// TODO: Make a test that does this automatically. -class Base(val x: Int) - -class Sub(x: Int) extends Base(x) { - println(x) -} - diff --git a/tests/pos/typers.scala b/tests/pos/typers.scala index f335223aeae9..9bde682e9203 100644 --- a/tests/pos/typers.scala +++ b/tests/pos/typers.scala @@ -99,7 +99,7 @@ object typers { } class B extends A { - private def x: Int = 1 + private val x: Int = 1 } val b: B = new B diff --git a/tests/run/i7926.check b/tests/run/i7926.check new file mode 100644 index 000000000000..b0047fa49f09 --- /dev/null +++ b/tests/run/i7926.check @@ -0,0 +1 @@ +None diff --git a/tests/run/i7926.scala b/tests/run/i7926.scala new file mode 100644 index 000000000000..96dc90d8fa82 --- /dev/null +++ b/tests/run/i7926.scala @@ -0,0 +1,13 @@ +trait Foo { + def parent: Option[Foo] + def getParent: Option[Foo] = parent +} + +class Bar(val parent: Option[Foo]) extends Foo +class Qux(parent: Option[Qux]) extends Bar(parent) + +object Test { + val qux: Qux = new Qux(None) + + def main(args: Array[String]): Unit = println(qux.getParent) +} \ No newline at end of file diff --git a/tests/run/paramForwarding.check b/tests/run/paramForwarding.check index 1cadc5076a50..baf8486201de 100644 --- a/tests/run/paramForwarding.check +++ b/tests/run/paramForwarding.check @@ -5,7 +5,7 @@ Bz: private final int Bz.theValue private final int Bz.theValueInBz C: - +private final int C.theValue D: private final int D.other NonVal: @@ -13,4 +13,4 @@ NonVal: X: private final int X.theValue Y: - +private final int Y.theValue diff --git a/tests/run/paramForwarding.scala b/tests/run/paramForwarding.scala index 2fe1b9c4a06b..cef8a469d587 100644 --- a/tests/run/paramForwarding.scala +++ b/tests/run/paramForwarding.scala @@ -19,8 +19,7 @@ class Bz extends A(42) { val theValueInBz = theValue } -// C does not contain a field C.theValue$$local, it contains -// a getter C.theValue() which only calls super.theValue() +// C does contain a field C.theValue$$local class C(override val theValue: Int) extends A(theValue) // D contains a field D.other$$local and a corresponding getter. @@ -36,8 +35,7 @@ class NonVal(theValue: Int) extends A(theValue) { // X.theValue() which overrides A.theValue() class X(override val theValue: Int) extends NonVal(0) -// Y does not contain a field Y.theValue$$local, it contains -// a getter Y.theValue() which only calls super.theValue() +// Y does contain a field Y.theValue$$local class Y(override val theValue: Int) extends NonVal(theValue)