Skip to content

Commit 18de547

Browse files
committed
Move mixin forwarders generation after erasure
* Pros - When forwarding before erasure, we might end up needing a bridge, the non-bridged forwarder therefore serves no practical purpose and can cause clashes. These are problematic for two reasons: 1. Valid Scala 2 code might break, and can only be fixed in binary incompatible way, like with scala/scala@4366332 and scala/scala@e3ef657 2. Clashes might not be detected under separate compilation, as demonstrated by the previous commit. Forwarding after erasure solves both of these problems. - Scala 2 has always done it this way, so we're less likely to run into surprising problems. * Cons - When forwarding after erasure, generic signatures will be missing, Scala 2 tries to restore this information but that doesn't always work (scala/bug#8905). We'll either have to do something similar, or investigate a different solution like scala/scala#7843.
1 parent a149aed commit 18de547

File tree

11 files changed

+149
-55
lines changed

11 files changed

+149
-55
lines changed

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

+19-3
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ object Mixin {
8686
*
8787
* <mods> def x_=(y: T) = ()
8888
*
89+
* 3.5 (done in `mixinForwarders`) For every method
90+
* `<mods> def f[Ts](ps1)...(psN): U` imn M` that needs to be disambiguated:
91+
*
92+
* <mods> def f[Ts](ps1)...(psN): U = super[M].f[Ts](ps1)...(psN)
93+
*
94+
* A method in M needs to be disambiguated if it is concrete, not overridden in C,
95+
* and if it overrides another concrete method.
96+
*
8997
* 4. (done in `transformTemplate` and `transformSym`) Drop all parameters from trait
9098
* constructors.
9199
*
@@ -239,7 +247,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase =>
239247
else
240248
initial
241249
// transformFollowing call is needed to make memoize & lazy vals run
242-
transformFollowing(DefDef(implementation(getter.asTerm), rhs))
250+
transformFollowing(DefDef(mkForwarder(getter.asTerm), rhs))
243251
}
244252
else if (isScala2x || was(getter, ParamAccessor | Lazy)) EmptyTree
245253
else initial
@@ -248,7 +256,15 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase =>
248256

249257
def setters(mixin: ClassSymbol): List[Tree] =
250258
for (setter <- mixin.info.decls.filter(setr => setr.isSetter && !was(setr, Deferred)))
251-
yield transformFollowing(DefDef(implementation(setter.asTerm), unitLiteral.withSpan(cls.span)))
259+
yield transformFollowing(DefDef(mkForwarder(setter.asTerm), unitLiteral.withSpan(cls.span)))
260+
261+
def mixinForwarders(mixin: ClassSymbol): List[Tree] =
262+
for (meth <- mixin.info.decls.toList if needsForwarder(meth))
263+
yield {
264+
util.Stats.record("mixin forwarders")
265+
transformFollowing(polyDefDef(mkForwarder(meth.asTerm), forwarder(meth)))
266+
}
267+
252268

253269
cpy.Template(impl)(
254270
constr =
@@ -259,7 +275,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase =>
259275
if (cls is Trait) traitDefs(impl.body)
260276
else {
261277
val mixInits = mixins.flatMap { mixin =>
262-
flatten(traitInits(mixin)) ::: superCallOpt(mixin) ::: setters(mixin)
278+
flatten(traitInits(mixin)) ::: superCallOpt(mixin) ::: setters(mixin) ::: mixinForwarders(mixin)
263279
}
264280
superCallOpt(superCls) ::: mixInits ::: impl.body
265281
})

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class MixinOps(cls: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont
1818
map(n => ctx.getClassIfDefined("org.junit." + n)).
1919
filter(_.exists)
2020

21-
def implementation(member: TermSymbol): TermSymbol = {
21+
def mkForwarder(member: TermSymbol): TermSymbol = {
2222
val res = member.copy(
2323
owner = cls,
2424
name = member.name.stripScala2LocalSuffix,

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

+8-26
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,16 @@ import NameKinds._
1414
import ResolveSuper._
1515
import reporting.diagnostic.messages.IllegalSuperAccessor
1616

17-
/** This phase adds super accessors and method overrides where
18-
* linearization differs from Java's rule for default methods in interfaces.
19-
* In particular:
17+
/** This phase adds super accessors.
2018
*
21-
* For every trait M directly implemented by the class (see SymUtils.mixin), in
22-
* reverse linearization order, add the following definitions to C:
19+
* For every trait M directly implemented by the class (see SymUtils.mixin), in
20+
* reverse linearization order, add the following definitions to C:
2321
*
24-
* 3.1 (done in `superAccessors`) For every superAccessor
25-
* `<mods> def super$f[Ts](ps1)...(psN): U` in M:
22+
* For every superAccessor `<mods> def super$f[Ts](ps1)...(psN): U` in M:
2623
*
27-
* <mods> def super$f[Ts](ps1)...(psN): U = super[S].f[Ts](ps1)...(psN)
24+
* <mods> def super$f[Ts](ps1)...(psN): U = super[S].f[Ts](ps1)...(psN)
2825
*
29-
* where `S` is the superclass of `M` in the linearization of `C`.
30-
*
31-
* 3.2 (done in `methodOverrides`) For every method
32-
* `<mods> def f[Ts](ps1)...(psN): U` in M` that needs to be disambiguated:
33-
*
34-
* <mods> def f[Ts](ps1)...(psN): U = super[M].f[Ts](ps1)...(psN)
35-
*
36-
* A method in M needs to be disambiguated if it is concrete, not overridden in C,
37-
* and if it overrides another concrete method.
26+
* where `S` is the superclass of `M` in the linearization of `C`.
3827
*
3928
* This is the first part of what was the mixin phase. It is complemented by
4029
* Mixin, which runs after erasure.
@@ -59,17 +48,10 @@ class ResolveSuper extends MiniPhase with IdentityDenotTransformer { thisPhase =
5948
for (superAcc <- mixin.info.decls.filter(_.isSuperAccessor))
6049
yield {
6150
util.Stats.record("super accessors")
62-
polyDefDef(implementation(superAcc.asTerm), forwarder(rebindSuper(cls, superAcc)))
63-
}
64-
65-
def methodOverrides(mixin: ClassSymbol): List[Tree] =
66-
for (meth <- mixin.info.decls.toList if needsForwarder(meth))
67-
yield {
68-
util.Stats.record("method forwarders")
69-
polyDefDef(implementation(meth.asTerm), forwarder(meth))
51+
polyDefDef(mkForwarder(superAcc.asTerm), forwarder(rebindSuper(cls, superAcc)))
7052
}
7153

72-
val overrides = mixins.flatMap(mixin => superAccessors(mixin) ::: methodOverrides(mixin))
54+
val overrides = mixins.flatMap(superAccessors)
7355

7456
cpy.Template(impl)(body = overrides ::: impl.body)
7557
}
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<527..527> in mixin-forwarder-clash1.scala
2+
Name clash between inherited members:
3+
override def concat(suffix: Int): X in trait OneB at line 10 and
4+
override def concat: [Dummy](suffix: Int): Y in trait TwoB at line 18
5+
have the same type after erasure.

tests/neg/mixin-forwarder-clash1.scala

+11-14
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,29 @@ trait OneB[X] { self: OneA[X] =>
1010
override def concat(suffix: Int): X = ???
1111
}
1212

13-
trait TwoA[Y <: Foo] {
13+
trait TwoA[Y/* <: Foo*/] {
1414
def concat[Dummy](suffix: Int): Y = ???
1515
}
1616

17-
trait TwoB[Y <: Foo] { self: TwoA[Y] =>
17+
trait TwoB[Y/* <: Foo*/] { self: TwoA[Y] =>
1818
override def concat[Dummy](suffix: Int): Y = ???
1919
}
2020

2121
class Bar1 extends OneA[Foo] with OneB[Foo]
22-
// Because mixin forwarders are generated before erasure, we get:
23-
// override def concat(suffix: Int): Foo
22+
// Because mixin forwarders are generated after erasure, we get:
23+
// override def concat(suffix: Int): Object
2424

2525
class Bar2 extends Bar1 with TwoA[Foo] with TwoB[Foo] // error
2626
// We get a mixin forwarder for TwoB:
27-
// override def concat[Dummy](suffix: Int): Foo
28-
// which gets erased to:
29-
// override def concat(suffix: Int): Foo
27+
// override def concat(suffix: Int): Object
3028
// This clashes with the forwarder generated in Bar1, and the compiler detects that:
3129
//
3230
// |class Bar2 extends Bar1 with TwoA[Foo] with TwoB[Foo]
3331
// | ^
34-
// | Name clash between defined and inherited member:
35-
// | override def concat(suffix: Int): Foo in class Bar1 and
36-
// | override def concat: [Dummy](suffix: Int): Foo in class Bar2
37-
// | have the same type after erasure.
32+
// | Name clash between inherited members:
33+
// | override def concat(suffix: Int): Object in trait OneB at line 10 and
34+
// | override def concat: [Dummy](suffix: Int): Y in trait TwoB at line 18
35+
// | have the same type after erasure.
3836
//
39-
// But note that the compiler is able to see the mixin forwarder in Bar1
40-
// only because of joint compilation, this doesn't work with separate
41-
// compilation as in mixin-forwarder-clash2.
37+
// This also works with separate compilation as demonstrated by
38+
// mixin-forwarder-clash2.
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<6..6> in B_2.scala
2+
Name clash between inherited members:
3+
override def concat(suffix: Int): X in trait OneB and
4+
override def concat: [Dummy](suffix: Int): Y in trait TwoB
5+
have the same type after erasure.

tests/neg/mixin-forwarder-clash2/A_1.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ trait OneB[X] { self: OneA[X] =>
1010
override def concat(suffix: Int): X = ???
1111
}
1212

13-
trait TwoA[Y <: Foo] {
13+
trait TwoA[Y/* <: Foo*/] {
1414
def concat[Dummy](suffix: Int): Y = ???
1515
}
1616

17-
trait TwoB[Y <: Foo] { self: TwoA[Y] =>
17+
trait TwoB[Y/* <: Foo*/] { self: TwoA[Y] =>
1818
override def concat[Dummy](suffix: Int): Y = ???
1919
}
2020

2121
class Bar1 extends OneA[Foo] with OneB[Foo]
22-
// Because mixin forwarders are generated before erasure, we get:
23-
// override def concat(suffix: Int): Foo
22+
// Because mixin forwarders are generated after erasure, we get:
23+
// override def concat(suffix: Int): Object
+14-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
class Bar2 extends Bar1 with TwoA[Foo] with TwoB[Foo] // error
22
// We get a mixin forwarder for TwoB:
3-
// override def concat[Dummy](suffix: Int): Foo
4-
// which gets erased to:
5-
// override def concat(suffix: Int): Foo
6-
// This clashes with the forwarder generated in Bar1, but
7-
// unlike with mixin-forwarder-clash1, the compiler
8-
// doesn't detect that due to separate compilation,
9-
// so this test case fails.
3+
// override def concat(suffix: Int): Object
4+
// This clashes with the forwarder generated in Bar1, and
5+
// we can detect that even with separate compilation,
6+
// because forwarders are always generated after erasure
7+
// so their signature matches the erased signature of the
8+
// method they forward to, which double-defs check will
9+
// consider:
10+
//
11+
// |class Bar2 extends Bar1 with TwoA[Foo] with TwoB[Foo]
12+
// | ^
13+
// | Name clash between inherited members:
14+
// | override def concat(suffix: Int): X in trait OneB and
15+
// | override def concat: [Dummy](suffix: Int): Y in trait TwoB
16+
// | have the same type after erasure.
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// This test case used to fail when mixin forwarders were generated before erasure,
2+
// it doesn't anymore since the forwarders generated after erasure do not clash,
3+
// the comments are preserved for posterity.
4+
5+
class Foo
6+
7+
// Using self-types to force mixin forwarders
8+
9+
trait OneA[X] {
10+
def concat(suffix: Int): X = ???
11+
}
12+
13+
trait OneB[X] { self: OneA[X] =>
14+
override def concat(suffix: Int): X = ???
15+
}
16+
17+
trait TwoA[Y <: Foo] {
18+
def concat[Dummy](suffix: Int): Y = ???
19+
}
20+
21+
trait TwoB[Y <: Foo] { self: TwoA[Y] =>
22+
override def concat[Dummy](suffix: Int): Y = ???
23+
}
24+
25+
class Bar1 extends OneA[Foo] with OneB[Foo]
26+
// Because mixin forwarders are generated before erasure, we get:
27+
// override def concat(suffix: Int): Foo
28+
29+
class Bar2 extends Bar1 with TwoA[Foo] with TwoB[Foo] // error
30+
// We get a mixin forwarder for TwoB:
31+
// override def concat[Dummy](suffix: Int): Foo
32+
// which gets erased to:
33+
// override def concat(suffix: Int): Foo
34+
// This clashes with the forwarder generated in Bar1, and the compiler detects that:
35+
//
36+
// |class Bar2 extends Bar1 with TwoA[Foo] with TwoB[Foo]
37+
// | ^
38+
// | Name clash between defined and inherited member:
39+
// | override def concat(suffix: Int): Foo in class Bar1 and
40+
// | override def concat: [Dummy](suffix: Int): Foo in class Bar2
41+
// | have the same type after erasure.
42+
//
43+
// But note that the compiler is able to see the mixin forwarder in Bar1
44+
// only because of joint compilation, this doesn't work with separate
45+
// compilation as in mixin-forwarder-clash2.
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class Foo
2+
3+
// Using self-types to force mixin forwarders
4+
5+
trait OneA[X] {
6+
def concat(suffix: Int): X = ???
7+
}
8+
9+
trait OneB[X] { self: OneA[X] =>
10+
override def concat(suffix: Int): X = ???
11+
}
12+
13+
trait TwoA[Y <: Foo] {
14+
def concat[Dummy](suffix: Int): Y = ???
15+
}
16+
17+
trait TwoB[Y <: Foo] { self: TwoA[Y] =>
18+
override def concat[Dummy](suffix: Int): Y = ???
19+
}
20+
21+
class Bar1 extends OneA[Foo] with OneB[Foo]
22+
// Because mixin forwarders are generated before erasure, we get:
23+
// override def concat(suffix: Int): Foo
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// This test case was supposed to fail when mixin forwarders were generated before erasure,
2+
// but didn't due to separate compilation unlike mixin-forwarder-clash1,
3+
// it's not supposed to fail anymore since the forwarders generated after erasure do not clash,
4+
// the comments are preserved for posterity.
5+
6+
class Bar2 extends Bar1 with TwoA[Foo] with TwoB[Foo] // error
7+
// We get a mixin forwarder for TwoB:
8+
// override def concat[Dummy](suffix: Int): Foo
9+
// which gets erased to:
10+
// override def concat(suffix: Int): Foo
11+
// This clashes with the forwarder generated in Bar1, but
12+
// unlike with mixin-forwarder-clash1, the compiler
13+
// doesn't detect that due to separate compilation,
14+
// so this test case fails.

0 commit comments

Comments
 (0)