diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4074bd8ab298..bbde24de7f90 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,6 +15,9 @@ jobs: container: lampepfl/dotty:2020-04-24 steps: + - name: Set JDK 11 as default + run: echo "::add-path::/usr/lib/jvm/java-11-openjdk-amd64/bin" + - name: Checkout cleanup script uses: actions/checkout@v2 @@ -55,6 +58,9 @@ jobs: container: lampepfl/dotty:2020-04-24 steps: + - name: Set JDK 11 as default + run: echo "::add-path::/usr/lib/jvm/java-11-openjdk-amd64/bin" + - name: Checkout cleanup script uses: actions/checkout@v2 @@ -174,7 +180,7 @@ jobs: - name: Test run: ./project/scripts/sbt sbt-dotty/scripted - test_java11: + test_java8: runs-on: self-hosted container: lampepfl/dotty:2020-04-24 if: ( @@ -184,6 +190,9 @@ jobs: github.event_name == 'schedule' steps: + - name: Set JDK 8 as default + run: echo "::add-path::/usr/lib/jvm/java-1.8.0-openjdk-amd64/bin" + - name: Checkout cleanup script uses: actions/checkout@v2 @@ -215,14 +224,12 @@ jobs: restore-keys: ${{ runner.os }}-general- - name: Test - run: | - export PATH="/usr/lib/jvm/java-11-openjdk-amd64/bin:$PATH" - ./project/scripts/sbt ";compile ;test" + run: ./project/scripts/sbt ";compile ;test" publish_nightly: runs-on: self-hosted container: lampepfl/dotty:2020-04-24 - needs: [test, test_bootstrapped, community_build, test_sbt, test_java11] + needs: [test, test_bootstrapped, community_build, test_sbt, test_java8] if: github.event_name == 'schedule' env: NIGHTLYBUILD: yes @@ -323,7 +330,7 @@ jobs: publish_release: runs-on: self-hosted container: lampepfl/dotty:2020-04-24 - needs: [test, test_bootstrapped, community_build, test_sbt, test_java11] + needs: [test, test_bootstrapped, community_build, test_sbt, test_java8] if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') && !startsWith(github.event.ref, 'refs/tags/sbt-dotty-') @@ -475,7 +482,7 @@ jobs: publish_sbt_release: runs-on: self-hosted container: lampepfl/dotty:2020-04-24 - needs: [test, test_bootstrapped, community_build, test_sbt, test_java11] + needs: [test, test_bootstrapped, community_build, test_sbt, test_java8] if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/sbt-dotty-') diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index 3007ea2b05cc..593645f0113e 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -364,9 +364,13 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { } else { mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) - generatedType = - if (tree.symbol == defn.ArrayClass) ObjectReference - else classBTypeFromSymbol(claszSymbol) + // When compiling Array.scala, the constructor invokes `Array.this.super.`. The expectedType + // is `[Object` (computed by typeToBType, the type of This(Array) is `Array[T]`). If we would set + // the generatedType to `Array` below, the call to adapt at the end would fail. The situation is + // similar for primitives (`I` vs `Int`). + if (tree.symbol != defn.ArrayClass && !tree.symbol.isPrimitiveValueClass) { + generatedType = classBTypeFromSymbol(claszSymbol) + } } case DesugaredSelect(Ident(nme.EMPTY_PACKAGE), module) => @@ -710,33 +714,18 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { if (t.symbol ne defn.Object_synchronized) genTypeApply(t) else genSynchronized(app, expectedType) - case Apply(fun @ DesugaredSelect(Super(_, _), _), args) => - def initModule(): Unit = { - // we initialize the MODULE$ field immediately after the super ctor - if (!isModuleInitialized && - jMethodName == INSTANCE_CONSTRUCTOR_NAME && - fun.symbol.javaSimpleName == INSTANCE_CONSTRUCTOR_NAME && - claszSymbol.isStaticModuleClass) { - isModuleInitialized = true - mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) - mnode.visitFieldInsn( - asm.Opcodes.PUTSTATIC, - thisName, - str.MODULE_INSTANCE_FIELD, - "L" + thisName + ";" - ) - } - } + case Apply(fun @ DesugaredSelect(Super(superQual, _), _), args) => // 'super' call: Note: since constructors are supposed to // return an instance of what they construct, we have to take // special care. On JVM they are 'void', and Scala forbids (syntactically) // to call super constructors explicitly and/or use their 'returned' value. // therefore, we can ignore this fact, and generate code that leaves nothing // on the stack (contrary to what the type in the AST says). - mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) + + // scala/bug#10290: qual can be `this.$outer()` (not just `this`), so we call genLoad (not just ALOAD_0) + genLoad(superQual) genLoadArguments(args, paramTKs(app)) generatedType = genCallMethod(fun.symbol, InvokeStyle.Super, app.span) - initModule() // 'new' constructor call: Note: since constructors are // thought to return an instance of what they construct, diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala index 6e2646c9e051..708a6fee9fff 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala @@ -29,6 +29,7 @@ trait BCodeIdiomatic { case "jvm-1.6" => asm.Opcodes.V1_6 case "jvm-1.7" => asm.Opcodes.V1_7 case "jvm-1.8" => asm.Opcodes.V1_8 + case "jvm-9" => asm.Opcodes.V9 } lazy val majorVersion: Int = (classfileVersion & 0xFF) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index acc80157238a..d9a6346168e9 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -10,15 +10,19 @@ import scala.tools.asm.util.{TraceMethodVisitor, ASMifier} import java.io.PrintWriter import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.ast.TreeTypeMap import dotty.tools.dotc.CompilationUnit import dotty.tools.dotc.core.Annotations.Annotation import dotty.tools.dotc.core.Decorators._ import dotty.tools.dotc.core.Flags._ -import dotty.tools.dotc.core.StdNames.{nme, str} +import dotty.tools.dotc.core.StdNames._ +import dotty.tools.dotc.core.NameKinds._ import dotty.tools.dotc.core.Symbols._ -import dotty.tools.dotc.core.Types.{MethodType, Type} +import dotty.tools.dotc.core.Types._ +import dotty.tools.dotc.core.Contexts._ import dotty.tools.dotc.util.Spans._ import dotty.tools.dotc.report +import dotty.tools.dotc.transform.SymUtils._ /* * @@ -93,12 +97,12 @@ trait BCodeSkelBuilder extends BCodeHelpers { /* ---------------- helper utils for generating classes and fields ---------------- */ - def genPlainClass(cd: TypeDef) = cd match { - case TypeDef(_, impl) => + def genPlainClass(cd0: TypeDef) = cd0 match { + case TypeDef(_, impl: Template) => assert(cnode == null, "GenBCode detected nested methods.") innerClassBufferASM.clear() - claszSymbol = cd.symbol + claszSymbol = cd0.symbol isCZParcelable = isAndroidParcelableClass(claszSymbol) isCZStaticModule = claszSymbol.isStaticModuleClass thisName = internalName(claszSymbol) @@ -107,14 +111,96 @@ trait BCodeSkelBuilder extends BCodeHelpers { initJClass(cnode) - val methodSymbols = for (f <- cd.symbol.info.decls.toList if f.is(Method) && f.isTerm && !f.is(Module)) yield f - val hasStaticCtor = methodSymbols exists (_.isStaticConstructor) - if (!hasStaticCtor) { - // but needs one ... - if (isCZStaticModule || isCZParcelable) { - fabricateStaticInit() + val cd = if (isCZStaticModule) { + // Move statements from the primary constructor following the superclass constructor call to + // a newly synthesised tree representing the "", which also assigns the MODULE$ field. + // Because the assigments to both the module instance fields, and the fields of the module itself + // are in the , these fields can be static + final. + + // Should we do this transformation earlier, say in Constructors? Or would that just cause + // pain for scala-{js, native}? + // + // @sjrd (https://github.com/lampepfl/dotty/pull/9181#discussion_r457458205): + // moving that before the back-end would make things significantly more complicated for + // Scala.js and Native. Both have a first-class concept of ModuleClass, and encode the + // singleton pattern of MODULE$ in a completely different way. In the Scala.js IR, there + // even isn't anything that corresponds to MODULE$ per se. + // + // So if you move this before the back-end, then Scala.js and Scala Native will have to + // reverse all the effects of this transformation, which would be counter-productive. + + + // TODO: remove `!f.name.is(LazyBitMapName)` once we change lazy val encoding + // https://github.com/lampepfl/dotty/issues/7140 + // + // Lazy val encoding assumes bitmap fields are non-static + // + // See `tests/run/given-var.scala` + // + + claszSymbol.info.decls.foreach { f => + if f.isField && !f.name.is(LazyBitMapName) then + f.setFlag(JavaStatic) } - } + + val (clinits, body) = impl.body.partition(stat => stat.isInstanceOf[DefDef] && stat.symbol.isStaticConstructor) + + val (uptoSuperStats, remainingConstrStats) = splitAtSuper(impl.constr.rhs.asInstanceOf[Block].stats) + val clInitSymbol: TermSymbol = + if (clinits.nonEmpty) clinits.head.symbol.asTerm + else newSymbol( + claszSymbol, + nme.STATIC_CONSTRUCTOR, + JavaStatic | Method, + MethodType(Nil)(_ => Nil, _ => defn.UnitType), + privateWithin = NoSymbol, + coord = claszSymbol.coord + ) + + val moduleField = newSymbol( + claszSymbol, + str.MODULE_INSTANCE_FIELD.toTermName, + JavaStatic | Final, + claszSymbol.typeRef, + privateWithin = NoSymbol, + coord = claszSymbol.coord + ).entered + + val thisMap = new TreeMap { + override def transform(tree: Tree)(using Context) = { + val tp = tree.tpe.substThis(claszSymbol.asClass, claszSymbol.sourceModule.termRef) + tree.withType(tp) match { + case tree: This if tree.symbol == claszSymbol => + ref(claszSymbol.sourceModule) + case tree => + super.transform(tree) + } + } + } + + def rewire(stat: Tree) = thisMap.transform(stat).changeOwner(claszSymbol.primaryConstructor, clInitSymbol) + + val callConstructor = New(claszSymbol.typeRef).select(claszSymbol.primaryConstructor).appliedToArgs(Nil) + val assignModuleField = Assign(ref(moduleField), callConstructor) + val remainingConstrStatsSubst = remainingConstrStats.map(rewire) + val clinit = clinits match { + case (ddef: DefDef) :: _ => + cpy.DefDef(ddef)(rhs = Block(ddef.rhs :: assignModuleField :: remainingConstrStatsSubst, unitLiteral)) + case _ => + DefDef(clInitSymbol, Block(assignModuleField :: remainingConstrStatsSubst, unitLiteral)) + } + + val constr2 = { + val rhs = Block(uptoSuperStats, impl.constr.rhs.asInstanceOf[Block].expr) + cpy.DefDef(impl.constr)(rhs = rhs) + } + + val impl2 = cpy.Template(impl)(constr = constr2, body = clinit :: body) + cpy.TypeDef(cd0)(rhs = impl2) + } else cd0 + + val hasStaticCtor = isCZStaticModule || cd.symbol.info.decls.exists(_.isStaticConstructor) + if (!hasStaticCtor && isCZParcelable) fabricateStaticInitAndroid() val optSerial: Option[Long] = claszSymbol.getAnnotation(defn.SerialVersionUIDAnnot).flatMap { annot => @@ -134,7 +220,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { addClassFields() innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.memberClasses - gen(impl) + gen(cd.rhs) addInnerClassesASM(cnode, innerClassBufferASM.toList) if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern)) @@ -179,12 +265,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { cnode.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign) emitAnnotations(cnode, claszSymbol.annotations ++ ssa) - if (isCZStaticModule || isCZParcelable) { - - if (isCZStaticModule) { addModuleInstanceField() } - - } else { - + if (!isCZStaticModule && !isCZParcelable) { val skipStaticForwarders = (claszSymbol.isInterface || claszSymbol.is(Module) || ctx.settings.XnoForwarders.value) if (!skipStaticForwarders) { val lmoc = claszSymbol.companionModule @@ -205,25 +286,10 @@ trait BCodeSkelBuilder extends BCodeHelpers { } // end of method initJClass - /* - * can-multi-thread - */ - private def addModuleInstanceField(): Unit = { - val fv = - cnode.visitField(GenBCodeOps.PublicStaticFinal, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED - str.MODULE_INSTANCE_FIELD, - "L" + thisName + ";", - null, // no java-generic-signature - null // no initial value - ) - - fv.visitEnd() - } - /* * must-single-thread */ - private def fabricateStaticInit(): Unit = { + private def fabricateStaticInitAndroid(): Unit = { val clinit: asm.MethodVisitor = cnode.visitMethod( GenBCodeOps.PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED @@ -234,15 +300,9 @@ trait BCodeSkelBuilder extends BCodeHelpers { ) clinit.visitCode() - /* "legacy static initialization" */ - if (isCZStaticModule) { - clinit.visitTypeInsn(asm.Opcodes.NEW, thisName) - clinit.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, - thisName, INSTANCE_CONSTRUCTOR_NAME, "()V", false) - } - if (isCZParcelable) { legacyAddCreatorCode(clinit, cnode, thisName) } - clinit.visitInsn(asm.Opcodes.RETURN) + legacyAddCreatorCode(clinit, cnode, thisName) + clinit.visitInsn(asm.Opcodes.RETURN) clinit.visitMaxs(0, 0) // just to follow protocol, dummy arguments clinit.visitEnd() } @@ -281,8 +341,6 @@ trait BCodeSkelBuilder extends BCodeHelpers { var isMethSymStaticCtor = false var returnType: BType = null var methSymbol: Symbol = null - // in GenASM this is local to genCode(), ie should get false whenever a new method is emitted (including fabricated ones eg addStaticInit()) - var isModuleInitialized = false // used by genLoadTry() and genSynchronized() var earlyReturnVar: Symbol = null var shouldEmitCleanup = false @@ -481,7 +539,6 @@ trait BCodeSkelBuilder extends BCodeHelpers { assert(varsInScope == null, "Unbalanced entering/exiting of GenBCode's genBlock().") // check previous invocation of genDefDef unregistered as many cleanups as it registered. assert(cleanups == Nil, "Previous invocation of genDefDef didn't unregister as many cleanups as it registered.") - isModuleInitialized = false earlyReturnVar = null shouldEmitCleanup = false @@ -679,7 +736,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { /* * must-single-thread * - * TODO document, explain interplay with `fabricateStaticInit()` + * TODO document, explain interplay with `fabricateStaticInitAndroid()` */ private def appendToStaticCtor(dd: DefDef): Unit = { @@ -698,21 +755,6 @@ trait BCodeSkelBuilder extends BCodeHelpers { mnode foreachInsn { i => if (i.getOpcode() == asm.Opcodes.RETURN) { rets ::= i } } if (rets.isEmpty) { return } - var insnModA: asm.tree.AbstractInsnNode = null - var insnModB: asm.tree.AbstractInsnNode = null - // call object's private ctor from static ctor - if (isCZStaticModule) { - // NEW `moduleName` - val className = internalName(methSymbol.enclosingClass) - insnModA = new asm.tree.TypeInsnNode(asm.Opcodes.NEW, className) - // INVOKESPECIAL - val callee = methSymbol.enclosingClass.primaryConstructor - val jname = callee.javaSimpleName - val jowner = internalName(callee.owner) - val jtype = asmMethodType(callee).descriptor - insnModB = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESPECIAL, jowner, jname, jtype, false) - } - var insnParcA: asm.tree.AbstractInsnNode = null var insnParcB: asm.tree.AbstractInsnNode = null // android creator code @@ -738,7 +780,6 @@ trait BCodeSkelBuilder extends BCodeHelpers { // insert a few instructions for initialization before each return instruction for(r <- rets) { - insertBefore(r, insnModA, insnModB) insertBefore(r, insnParcA, insnParcB) } diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 2458de77ba9c..f983743281cc 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -31,7 +31,7 @@ import tpd._ import scala.tools.asm import StdNames.{nme, str} -import NameKinds.{DefaultGetterName, ExpandedName} +import NameKinds.{DefaultGetterName, ExpandedName, LazyBitMapName} import Names.TermName import Annotations.Annotation import Names.Name @@ -131,8 +131,19 @@ object DottyBackendInterface { def isStaticConstructor(using Context): Boolean = (sym.isStaticMember && sym.isClassConstructor) || (sym.name eq nme.STATIC_CONSTRUCTOR) + /** Fields of static modules will be static at backend + * + * Note that lazy val encoding assumes bitmap fields are non-static. + * See also `genPlainClass` in `BCodeSkelBuilder.scala`. + * + * TODO: remove the special handing of `LazyBitMapName` once we swtich to + * the new lazy val encoding: https://github.com/lampepfl/dotty/issues/7140 + */ + def isStaticModuleField(using Context): Boolean = + sym.owner.isStaticModuleClass && sym.isField && !sym.name.is(LazyBitMapName) + def isStaticMember(using Context): Boolean = (sym ne NoSymbol) && - (sym.is(JavaStatic) || sym.isScalaStatic) + (sym.is(JavaStatic) || sym.isScalaStatic || sym.isStaticModuleField) // guard against no sumbol cause this code is executed to select which call type(static\dynamic) to use to call array.clone /** diff --git a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala index d47042ca7480..75f2451374f3 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala @@ -1410,7 +1410,7 @@ class JSCodeGen()(using genCtx: Context) { */ private def genSuperCall(tree: Apply, isStat: Boolean): js.Tree = { implicit val pos = tree.span - val Apply(fun @ Select(sup @ Super(_, mix), _), args) = tree + val Apply(fun @ Select(sup @ Super(qual, _), _), args) = tree val sym = fun.symbol if (sym == defn.Any_getClass) { @@ -1419,8 +1419,11 @@ class JSCodeGen()(using genCtx: Context) { } else /*if (isScalaJSDefinedJSClass(currentClassSym)) { genJSSuperCall(tree, isStat) } else*/ { + /* #3013 `qual` can be `this.$outer()` in some cases since Scala 2.12, + * so we call `genExpr(qual)`, not just `genThis()`. + */ val superCall = genApplyMethodStatically( - genThis()(sup.span), sym, genActualArgs(sym, args)) + genExpr(qual), sym, genActualArgs(sym, args)) // Initialize the module instance just after the super constructor call. if (isStaticModule(currentClassSym) && !isModuleInitialized && diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index 7af09bba98f1..0aa3b65437fa 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -828,6 +828,19 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => loop(tree) } + /** Return a pair consisting of (supercall, rest) + * + * - supercall: the superclass call, excluding trait constr calls + * + * The supercall is always the first statement (if it exists) + */ + final def splitAtSuper(constrStats: List[Tree])(implicit ctx: Context): (List[Tree], List[Tree]) = + constrStats.toList match { + case (sc: Apply) :: rest if sc.symbol.isConstructor => (sc :: Nil, rest) + case (block @ Block(_, sc: Apply)) :: rest if sc.symbol.isConstructor => (block :: Nil, rest) + case stats => (Nil, stats) + } + /** Structural tree comparison (since == on trees is reference equality). * For the moment, only Ident, Select, Literal, Apply and TypeApply are supported */ diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index f875452c7252..7a81a33ace66 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -35,8 +35,7 @@ class ScalaSettings extends Settings.SettingGroup { val help: Setting[Boolean] = BooleanSetting("-help", "Print a synopsis of standard options.") withAbbreviation "--help" val color: Setting[String] = ChoiceSetting("-color", "mode", "Colored output", List("always", "never"/*, "auto"*/), "always"/* "auto"*/) withAbbreviation "--color" val source: Setting[String] = ChoiceSetting("-source", "source version", "source version", List("3.0", "3.1", "3.0-migration", "3.1-migration"), "3.0").withAbbreviation("--source") - val target: Setting[String] = ChoiceSetting("-target", "target", "Target platform for object files. All JVM 1.5 targets are deprecated.", - List("jvm-1.5", "jvm-1.5-fjbg", "jvm-1.5-asm", "jvm-1.6", "jvm-1.7", "jvm-1.8", "msil"), "jvm-1.8") withAbbreviation "--target" + val target: Setting[String] = ChoiceSetting("-target", "target", "Target platform for object files.", List("jvm-1.8", "jvm-9"), "jvm-1.8") withAbbreviation "--target" val scalajs: Setting[Boolean] = BooleanSetting("-scalajs", "Compile in Scala.js mode (requires scalajs-library.jar on the classpath).") withAbbreviation "--scalajs" val unchecked: Setting[Boolean] = BooleanSetting("-unchecked", "Enable additional warnings where generated code depends on assumptions.") withAbbreviation "--unchecked" val uniqid: Setting[Boolean] = BooleanSetting("-uniqid", "Uniquely tag all identifiers in debugging output.") withAbbreviation "--unique-id" diff --git a/compiler/src/dotty/tools/dotc/transform/Constructors.scala b/compiler/src/dotty/tools/dotc/transform/Constructors.scala index e7f04570e029..a462cba7a000 100644 --- a/compiler/src/dotty/tools/dotc/transform/Constructors.scala +++ b/compiler/src/dotty/tools/dotc/transform/Constructors.scala @@ -284,10 +284,7 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase = // TODO: this happens to work only because Constructors is the last phase in group } - val (superCalls, followConstrStats) = constrStats.toList match { - case (sc: Apply) :: rest if sc.symbol.isConstructor => (sc :: Nil, rest) - case stats => (Nil, stats) - } + val (superCalls, followConstrStats) = splitAtSuper(constrStats.toList) val mappedSuperCalls = vparams match { case (outerParam @ ValDef(nme.OUTER, _, _)) :: _ => diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index 8ac80b3310d3..719e298fe067 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -401,7 +401,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { else { // need to create a new flag offsetSymbol = newSymbol(claz, offsetById, Synthetic, defn.LongType).enteredAfter(this) offsetSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot)) - val flagName = s"${StdNames.nme.BITMAP_PREFIX}$id".toTermName + val flagName = LazyBitMapName.fresh(id.toString.toTermName) val flagSymbol = newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this) flag = ValDef(flagSymbol, Literal(Constant(0L))) val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(thizClass, Literal(Constant(flagName.toString)))) @@ -411,7 +411,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { case None => offsetSymbol = newSymbol(claz, offsetName(0), Synthetic, defn.LongType).enteredAfter(this) offsetSymbol.addAnnotation(Annotation(defn.ScalaStaticAnnot)) - val flagName = s"${StdNames.nme.BITMAP_PREFIX}0".toTermName + val flagName = LazyBitMapName.fresh("0".toTermName) val flagSymbol = newSymbol(claz, flagName, containerFlags, defn.LongType).enteredAfter(this) flag = ValDef(flagSymbol, Literal(Constant(0L))) val offsetTree = ValDef(offsetSymbol, getOffset.appliedTo(thizClass, Literal(Constant(flagName.toString)))) diff --git a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala index 5a73402b088d..7657ad21e31d 100644 --- a/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala +++ b/compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala @@ -9,6 +9,8 @@ import SymUtils._ import Symbols._ import Decorators._ import DenotTransformers._ +import Names._ +import StdNames._ import NameOps._ import NameKinds._ import ResolveSuper._ @@ -79,10 +81,20 @@ object ResolveSuper { def rebindSuper(base: Symbol, acc: Symbol)(using Context): Symbol = { var bcs = base.info.baseClasses.dropWhile(acc.owner != _).tail var sym: Symbol = NoSymbol - val SuperAccessorName(memberName) = acc.name.unexpandedName + + var mix: Name = nme.EMPTY + val memberName = acc.name.unexpandedName match + case SuperAccessorName(ExpandPrefixName(name, mixName)) => + mix = mixName.toTypeName + name + case SuperAccessorName(name) => + name + report.debuglog(i"starting rebindsuper from $base of ${acc.showLocated}: ${acc.info} in $bcs, name = $memberName") + while (bcs.nonEmpty && sym == NoSymbol) { val other = bcs.head.info.nonPrivateDecl(memberName) + .filterWithPredicate(denot => mix.isEmpty || denot.symbol.owner.name == mix) .matchingDenotation(base.thisType, base.thisType.memberInfo(acc)) report.debuglog(i"rebindsuper ${bcs.head} $other deferred = ${other.symbol.is(Deferred)}") if other.exists && !other.symbol.is(Deferred) then diff --git a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala index ef0b41bc8861..03b87c1cf357 100644 --- a/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala +++ b/compiler/src/dotty/tools/dotc/transform/SuperAccessors.scala @@ -5,13 +5,13 @@ import dotty.tools.dotc.ast.{Trees, tpd} import scala.collection.mutable import ValueClasses.isMethodWithExtension import core._ -import Contexts._, Flags._, Symbols._, NameOps._, Trees._ +import Contexts._, Flags._, Symbols._, Names._, StdNames._, NameOps._, Trees._ import TypeUtils._, SymUtils._ import DenotTransformers.DenotTransformer import Symbols._ import util.Spans._ import Decorators._ -import NameKinds.SuperAccessorName +import NameKinds.{ SuperAccessorName, ExpandPrefixName } /** This class adds super accessors for all super calls that either * appear in a trait or have as a target a member of some outer class. @@ -62,11 +62,12 @@ class SuperAccessors(thisPhase: DenotTransformer) { private val accDefs = newMutableSymbolMap[mutable.ListBuffer[Tree]] /** A super accessor call corresponding to `sel` */ - private def superAccessorCall(sel: Select)(using Context) = { + private def superAccessorCall(sel: Select, mixName: Name = nme.EMPTY)(using Context) = { val Select(qual, name) = sel val sym = sel.symbol val clazz = qual.symbol.asClass - var superName = SuperAccessorName(name.asTermName) + val preName = if (mixName.isEmpty) name.toTermName else ExpandPrefixName(name.toTermName, mixName.toTermName) + var superName = SuperAccessorName(preName) if (clazz.is(Trait)) superName = superName.expandedName(clazz) val superInfo = sel.tpe.widenSingleton.ensureMethodic @@ -99,6 +100,7 @@ class SuperAccessors(thisPhase: DenotTransformer) { val sym = sel.symbol assert(sup.symbol.exists, s"missing symbol in $sel: ${sup.tpe}") val clazz = sup.symbol + val currentClass = ctx.owner.enclosingClass if (sym.isTerm && !sym.is(Method, butNot = Accessor) && !ctx.owner.isAllOf(ParamForwarder)) // ParamForwaders as installed ParamForwarding.scala do use super calls to vals @@ -145,9 +147,15 @@ class SuperAccessors(thisPhase: DenotTransformer) { sel.sourcePos) } } - if (name.isTermName && mix.name.isEmpty && - (clazz.is(Trait) || clazz != ctx.owner.enclosingClass || !validCurrentClass)) - atPhase(thisPhase.next)(superAccessorCall(sel)) + + val needAccessor = name.isTermName && { + mix.name.isEmpty && (clazz.is(Trait) || clazz != currentClass || !validCurrentClass) || + // SI-8803. If we access super[A] from an inner class (!= currentClass) or closure (validCurrentClass), + // where A is the superclass we need an accessor. + !mix.name.isEmpty && (clazz != currentClass || !validCurrentClass) + } + + if (needAccessor) atPhase(thisPhase.next)(superAccessorCall(sel, mix.name)) else sel } diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index df1ec46bb4f6..10f48ad182ea 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -413,6 +413,10 @@ class TreeChecker extends Phase with SymTransformer { res } + override def typedSuper(tree: untpd.Super, pt: Type)(using Context): Tree = + assert(tree.qual.tpe.isInstanceOf[ThisType], i"expect prefix of Super to be This, actual = ${tree.qual}") + super.typedSuper(tree, pt) + private def checkOwner(tree: untpd.Tree)(using Context): Unit = { def ownerMatches(symOwner: Symbol, ctxOwner: Symbol): Boolean = symOwner == ctxOwner || @@ -433,7 +437,8 @@ class TreeChecker extends Phase with SymTransformer { def isNonMagicalMember(x: Symbol) = !x.isValueClassConvertMethod && - !x.name.is(DocArtifactName) + !x.name.is(DocArtifactName) && + !(ctx.phase.id >= genBCodePhase.id && x.name == str.MODULE_INSTANCE_FIELD.toTermName) val decls = cls.classInfo.decls.toList.toSet.filter(isNonMagicalMember) val defined = impl.body.map(_.symbol) diff --git a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala index 01775432ca12..c067fdd8399b 100644 --- a/compiler/test/dotty/tools/vulpix/TestConfiguration.scala +++ b/compiler/test/dotty/tools/vulpix/TestConfiguration.scala @@ -8,7 +8,8 @@ object TestConfiguration { val noCheckOptions = Array( "-pagewidth", "120", - "-color:never" + "-color:never", + "-target", defaultTarget ) val checkOptions = Array( @@ -76,4 +77,11 @@ object TestConfiguration { /** Enables explicit nulls */ val explicitNullsOptions = defaultOptions and "-Yexplicit-nulls" + + /** Default target of the generated class files */ + private def defaultTarget: String = { + import scala.util.Properties.isJavaAtLeast + + if isJavaAtLeast("9") then "jvm-9" else "jvm-1.8" + } } diff --git a/tests/run/i1441.scala b/tests/run/i1441.scala new file mode 100644 index 000000000000..01a0e611f6bc --- /dev/null +++ b/tests/run/i1441.scala @@ -0,0 +1,10 @@ +class FrameworkTest(val cls: Class[_], val format: Int = 1, val expected: String) + +class FixtureFrameworkSuite +object FixtureFrameworkSuite extends FrameworkTest( + classOf[FixtureFrameworkSuite], + expected = "test" +) + +@main +def Test = FixtureFrameworkSuite diff --git a/tests/run/i9341.scala b/tests/run/i9341.scala new file mode 100644 index 000000000000..cf589ad88adb --- /dev/null +++ b/tests/run/i9341.scala @@ -0,0 +1,10 @@ +class T { def f: Int = 10 } +class B extends T { + class C { B.super[T].f } + class D { B.super.f } + new C + new D +} + +@main +def Test = new B diff --git a/tests/run/t10290.scala b/tests/run/t10290.scala new file mode 100644 index 000000000000..eb345f5d6114 --- /dev/null +++ b/tests/run/t10290.scala @@ -0,0 +1,28 @@ +trait A1 { + private val s = "A1" + def f = s +} + +trait A2 { + private val s = "A2" + def f = s +} + +class B extends A1 with A2 { + override def f = "B" + class C { + def t1 = B.super[A1].f + def t2 = B.super[A2].f + def t3 = B.this.f + } +} + +object Test { + def main(args : Array[String]) : Unit = { + val b = new B + val c = new b.C + assert(c.t1 == "A1", c.t1) + assert(c.t2 == "A2", c.t2) + assert(c.t3 == "B", c.t3) + } +} diff --git a/tests/run/t8803.check b/tests/run/t8803.check new file mode 100644 index 000000000000..bd26a0fb14aa --- /dev/null +++ b/tests/run/t8803.check @@ -0,0 +1,16 @@ +a +b +b +c +a +b +b +c +a +b +b +c +a +b +b +c diff --git a/tests/run/t8803.scala b/tests/run/t8803.scala new file mode 100644 index 000000000000..2e56180502b6 --- /dev/null +++ b/tests/run/t8803.scala @@ -0,0 +1,57 @@ +class A { + def m = "a" + protected def n = "a" +} + +trait B { + def m = "b" + protected def n = "b" +} + +class C extends A with B { + override def m = "c" + override protected def n = "c" + + val f1 = () => super[A].m + val f2 = () => super[B].m + val f3 = () => super.m + val f4 = () => this.m + + val g1 = new runtime.AbstractFunction0[String] { def apply() = C.super[A].m } + val g2 = new runtime.AbstractFunction0[String] { def apply() = C.super[B].m } + val g3 = new runtime.AbstractFunction0[String] { def apply() = C.super.m } + val g4 = new runtime.AbstractFunction0[String] { def apply() = C.this.m } + + val h1 = () => super[A].n + val h2 = () => super[B].n + val h3 = () => super.n + val h4 = () => this.n + + val i1 = new runtime.AbstractFunction0[String] { def apply() = C.super[A].n } + val i2 = new runtime.AbstractFunction0[String] { def apply() = C.super[B].n } + val i3 = new runtime.AbstractFunction0[String] { def apply() = C.super.n } + val i4 = new runtime.AbstractFunction0[String] { def apply() = C.this.n } +} + +object Test extends App { + val c = new C + println(c.f1()) + println(c.f2()) + println(c.f3()) + println(c.f4()) + + println(c.g1()) + println(c.g2()) + println(c.g3()) + println(c.g4()) + + println(c.h1()) + println(c.h2()) + println(c.h3()) + println(c.h4()) + + println(c.i1()) + println(c.i2()) + println(c.i3()) + println(c.i4()) +}