Skip to content

Commit d6d374a

Browse files
authored
Merge pull request #8652 from sjrd/trait-init-like-scala-2
Revert to the Scala 2 encoding for trait initialization and super calls
2 parents 239f4e3 + 77e2925 commit d6d374a

22 files changed

+299
-317
lines changed

compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala

+11-3
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
780780
val invokeStyle =
781781
if (sym.isStaticMember) InvokeStyle.Static
782782
else if (sym.is(Private) || sym.isClassConstructor) InvokeStyle.Special
783+
else if (app.hasAttachment(BCodeHelpers.UseInvokeSpecial)) InvokeStyle.Special
783784
else InvokeStyle.Virtual
784785

785786
if (invokeStyle.hasInstance) genLoadQualifier(fun)
@@ -1165,9 +1166,16 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
11651166
val isInterface = isEmittedInterface(receiverClass)
11661167
import InvokeStyle._
11671168
if (style == Super) {
1168-
// DOTTY: this differ from how super-calls in traits are handled in the scalac backend,
1169-
// this is intentional but could change in the future, see https://github.com/lampepfl/dotty/issues/5928
1170-
bc.invokespecial(receiverName, jname, mdescr, isInterface)
1169+
if (isInterface && !method.is(JavaDefined)) {
1170+
val args = new Array[BType](bmType.argumentTypes.length + 1)
1171+
val ownerBType = toTypeKind(method.owner.info)
1172+
bmType.argumentTypes.copyToArray(args, 1)
1173+
val staticDesc = MethodBType(ownerBType :: bmType.argumentTypes, bmType.returnType).descriptor
1174+
val staticName = traitSuperAccessorName(method)
1175+
bc.invokestatic(receiverName, staticName, staticDesc, isInterface)
1176+
} else {
1177+
bc.invokespecial(receiverName, jname, mdescr, isInterface)
1178+
}
11711179
} else {
11721180
val opc = style match {
11731181
case Static => Opcodes.INVOKESTATIC

compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala

+13
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
8282
}
8383
}
8484

85+
final def traitSuperAccessorName(sym: Symbol): String = {
86+
val nameString = sym.javaSimpleName.toString
87+
if (sym.name == nme.TRAIT_CONSTRUCTOR) nameString
88+
else nameString + "$"
89+
}
90+
8591
// -----------------------------------------------------------------------------------------
8692
// finding the least upper bound in agreement with the bytecode verifier (given two internal names handed by ASM)
8793
// Background:
@@ -950,4 +956,11 @@ object BCodeHelpers {
950956
val Super = new InvokeStyle(3) // InvokeSpecial (super calls)
951957
}
952958

959+
/** An attachment on Apply nodes indicating that it should be compiled with
960+
* `invokespecial` instead of `invokevirtual`. This is used for static
961+
* forwarders.
962+
* See BCodeSkelBuilder.makeStaticForwarder for more details.
963+
*/
964+
val UseInvokeSpecial = new dotc.util.Property.Key[Unit]
965+
953966
}

compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala

+50-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import dotty.tools.dotc.CompilationUnit
1414
import dotty.tools.dotc.core.Annotations.Annotation
1515
import dotty.tools.dotc.core.Decorators._
1616
import dotty.tools.dotc.core.Flags._
17-
import dotty.tools.dotc.core.StdNames.str
17+
import dotty.tools.dotc.core.StdNames.{nme, str}
1818
import dotty.tools.dotc.core.Symbols._
19-
import dotty.tools.dotc.core.Types.Type
19+
import dotty.tools.dotc.core.Types.{MethodType, Type}
2020
import dotty.tools.dotc.util.Spans._
2121
import dotty.tools.dotc.report
2222

@@ -496,7 +496,23 @@ trait BCodeSkelBuilder extends BCodeHelpers {
496496

497497
case ValDef(name, tpt, rhs) => () // fields are added in `genPlainClass()`, via `addClassFields()`
498498

499-
case dd: DefDef => genDefDef(dd)
499+
case dd: DefDef =>
500+
/* First generate a static forwarder if this is a non-private trait
501+
* trait method. This is required for super calls to this method, which
502+
* go through the static forwarder in order to work around limitations
503+
* of the JVM.
504+
* In theory, this would go in a separate MiniPhase, but it would have to
505+
* sit in a MegaPhase of its own between GenSJSIR and GenBCode, so the cost
506+
* is not worth it. We directly do it in this back-end instead, which also
507+
* kind of makes sense because it is JVM-specific.
508+
*/
509+
val sym = dd.symbol
510+
val needsStaticImplMethod =
511+
claszSymbol.isInterface && !dd.rhs.isEmpty && !sym.isPrivate && !sym.isStaticMember
512+
if needsStaticImplMethod then
513+
genStaticForwarderForDefDef(dd)
514+
515+
genDefDef(dd)
500516

501517
case tree: Template =>
502518
val body =
@@ -537,6 +553,37 @@ trait BCodeSkelBuilder extends BCodeHelpers {
537553

538554
} // end of method initJMethod
539555

556+
private def genStaticForwarderForDefDef(dd: DefDef): Unit =
557+
val forwarderDef = makeStaticForwarder(dd)
558+
genDefDef(forwarderDef)
559+
560+
/* Generates a synthetic static forwarder for a trait method.
561+
* For a method such as
562+
* def foo(...args: Ts): R
563+
* in trait X, we generate the following method:
564+
* static def foo$($this: X, ...args: Ts): R =
565+
* invokespecial $this.X::foo(...args)
566+
* We force an invokespecial with the attachment UseInvokeSpecial. It is
567+
* necessary to make sure that the call will not follow overrides of foo()
568+
* in subtraits and subclasses, since the whole point of this forward is to
569+
* encode super calls.
570+
*/
571+
private def makeStaticForwarder(dd: DefDef): DefDef =
572+
val origSym = dd.symbol.asTerm
573+
val name = traitSuperAccessorName(origSym)
574+
val info = origSym.info match
575+
case mt: MethodType =>
576+
MethodType(nme.SELF :: mt.paramNames, origSym.owner.typeRef :: mt.paramInfos, mt.resType)
577+
val sym = origSym.copy(
578+
name = name.toTermName,
579+
flags = Method | JavaStatic,
580+
info = info
581+
)
582+
tpd.DefDef(sym.asTerm, { paramss =>
583+
val params = paramss.head
584+
tpd.Apply(params.head.select(origSym), params.tail)
585+
.withAttachment(BCodeHelpers.UseInvokeSpecial, ())
586+
})
540587

541588
def genDefDef(dd: DefDef): Unit = {
542589
val rhs = dd.rhs

compiler/src/dotty/tools/dotc/Compiler.scala

+1-3
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ class Compiler {
8686
new LiftTry, // Put try expressions that might execute on non-empty stacks into their own methods
8787
new CollectNullableFields, // Collect fields that can be nulled out after use in lazy initialization
8888
new ElimOuterSelect, // Expand outer selections
89-
new AugmentScala2Traits, // Augments Scala2 traits with additional members needed for mixin composition.
9089
new ResolveSuper, // Implement super accessors
9190
new FunctionXXLForwarders, // Add forwarders for FunctionXXL apply method
9291
new ParamForwarding, // Add forwarders for aliases of superclass parameters
@@ -110,8 +109,7 @@ class Compiler {
110109
// Note: constructors changes decls in transformTemplate, no InfoTransformers should be added after it
111110
new FunctionalInterfaces, // Rewrites closures to implement @specialized types of Functions.
112111
new Instrumentation) :: // Count closure allocations under -Yinstrument-closures
113-
List(new LinkScala2Impls, // Redirect calls to trait methods defined by Scala 2.x, so that they now go to
114-
new LambdaLift, // Lifts out nested functions to class scope, storing free variables in environments
112+
List(new LambdaLift, // Lifts out nested functions to class scope, storing free variables in environments
115113
// Note: in this mini-phase block scopes are incorrect. No phases that rely on scopes should be here
116114
new ElimStaticThis, // Replace `this` references to static objects by global identifiers
117115
new CountOuterAccesses) :: // Identify outer accessors that can be dropped

compiler/src/dotty/tools/dotc/core/Flags.scala

+2-7
Original file line numberDiff line numberDiff line change
@@ -378,13 +378,8 @@ object Flags {
378378
/** Children were queried on this class */
379379
val (_, _, ChildrenQueried @ _) = newFlags(56, "<children-queried>")
380380

381-
/** A module variable (Scala 2.x only)
382-
* /
383-
* A Scala 2.x trait that has been partially augmented.
384-
* This is set in `AugmentScala2Trait` and reset in `LinkScala2Impls`
385-
* when the trait is fully augmented.
386-
*/
387-
val (_, Scala2ModuleVar @ _, Scala2xPartiallyAugmented @ _) = newFlags(57, "<modulevar>", "<scala-2.x-partially-augmented>")
381+
/** A module variable (Scala 2.x only) */
382+
val (_, Scala2ModuleVar @ _, _) = newFlags(57, "<modulevar>")
388383

389384
/** A macro */
390385
val (Macro @ _, _, _) = newFlags(58, "<macro>")

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -1153,7 +1153,7 @@ object SymDenotations {
11531153

11541154
final def scalacLinkedClass(using Context): Symbol =
11551155
if (this.is(ModuleClass)) companionNamed(effectiveName.toTypeName)
1156-
else if (this.isClass) companionNamed(effectiveName.moduleClassName).sourceModule.moduleClass
1156+
else if (this.isClass) companionNamed(effectiveName.moduleClassName)
11571157
else NoSymbol
11581158

11591159
/** Find companion class symbol with given name, or NoSymbol if none exists.

compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala

+6-3
Original file line numberDiff line numberDiff line change
@@ -454,8 +454,12 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
454454
flags = flags &~ Scala2ExpandedName
455455
}
456456
if (flags.is(Scala2SuperAccessor)) {
457-
name = name.asTermName.unmangle(SuperAccessorName)
458-
flags = flags &~ Scala2SuperAccessor
457+
/* Scala 2 super accessors are pickled as private, but are compiled as public expanded.
458+
* Dotty super accessors, however, are already pickled as public expanded.
459+
* We bridge the gap right now.
460+
*/
461+
name = name.asTermName.unmangle(SuperAccessorName).expandedName(owner)
462+
flags = flags &~ (Scala2SuperAccessor | Private)
459463
}
460464
name = name.mapLast(_.decode)
461465

@@ -1310,4 +1314,3 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
13101314
errorBadSignature("expected an TypeDef (" + other + ")")
13111315
}
13121316
}
1313-

compiler/src/dotty/tools/dotc/transform/AugmentScala2Traits.scala

-69
This file was deleted.

compiler/src/dotty/tools/dotc/transform/Constructors.scala

+28-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import dotty.tools.dotc.core.StdNames._
99
import ast._
1010
import Trees._
1111
import Flags._
12+
import NameOps._
1213
import SymUtils._
1314
import Symbols._
1415
import Decorators._
@@ -204,7 +205,33 @@ class Constructors extends MiniPhase with IdentityDenotTransformer { thisPhase =
204205
initFlags = sym.flags &~ Private,
205206
owner = constr.symbol).installAfter(thisPhase)
206207
constrStats += intoConstr(stat, sym)
207-
}
208+
} else
209+
dropped += sym
210+
case stat @ DefDef(name, _, _, tpt, _)
211+
if stat.symbol.isGetter && stat.symbol.owner.is(Trait) && !stat.symbol.is(Lazy) =>
212+
val sym = stat.symbol
213+
assert(isRetained(sym), sym)
214+
if (!stat.rhs.isEmpty && !isWildcardArg(stat.rhs))
215+
/* !!! Work around #9390
216+
* This should really just be `sym.setter`. However, if we do that, we'll miss
217+
* setters for mixed in `private var`s. Even though the scope clearly contains the
218+
* setter symbol with the correct Name structure (since the `find` finds it),
219+
* `.decl(setterName)` used by `.setter` through `.accessorNamed` will *not* find it.
220+
* Could it be that the hash table of the `Scope` is corrupted?
221+
* We still try `sym.setter` first as an optimization, since it will work for all
222+
* public vars in traits and all (public or private) vars in classes.
223+
*/
224+
val symSetter =
225+
if sym.setter.exists then
226+
sym.setter
227+
else
228+
val setterName = sym.asTerm.name.setterName
229+
sym.owner.info.decls.find(d => d.is(Accessor) && d.name == setterName)
230+
val setter =
231+
if (symSetter.exists) symSetter
232+
else sym.accessorNamed(Mixin.traitSetterName(sym.asTerm))
233+
constrStats += Apply(ref(setter), intoConstr(stat.rhs, sym).withSpan(stat.span) :: Nil)
234+
clsStats += cpy.DefDef(stat)(rhs = EmptyTree)
208235
case DefDef(nme.CONSTRUCTOR, _, ((outerParam @ ValDef(nme.OUTER, _, _)) :: _) :: Nil, _, _) =>
209236
clsStats += mapOuter(outerParam.symbol).transform(stat)
210237
case _: DefTree =>

compiler/src/dotty/tools/dotc/transform/ExplicitOuter.scala

+3-3
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,9 @@ object ExplicitOuter {
215215

216216
/** Class is always instantiated in the compilation unit where it is defined */
217217
private def hasLocalInstantiation(cls: ClassSymbol)(using Context): Boolean =
218-
// scala2x modules always take an outer pointer(as of 2.11)
219-
// dotty modules are always locally instantiated
220-
cls.owner.isTerm || cls.is(Private) || cls.is(Module, butNot = Scala2x)
218+
// Modules are normally locally instantiated, except if they are declared in a trait,
219+
// in which case they will be instantiated in the classes that mix in the trait.
220+
cls.owner.isTerm || cls.is(Private, butNot = Module) || (cls.is(Module) && !cls.owner.is(Trait))
221221

222222
/** The outer parameter accessor of cass `cls` */
223223
private def outerParamAccessor(cls: ClassSymbol)(using Context): TermSymbol =

0 commit comments

Comments
 (0)