Skip to content

Commit ba7e129

Browse files
authored
Merge pull request #1883 from dotty-staging/fix-1877
Fix #1877: Add forwarders for primitive/generic mixins.
2 parents 8b44b6c + cf57534 commit ba7e129

14 files changed

+195
-6
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class Compiler {
7272
new ElimByName, // Expand by-name parameters and arguments
7373
new AugmentScala2Traits, // Expand traits defined in Scala 2.11 to simulate old-style rewritings
7474
new ResolveSuper, // Implement super accessors and add forwarders to trait methods
75+
new PrimitiveForwarders, // Add forwarders to trait methods that have a mismatch between generic and primitives
7576
new ArrayConstructors), // Intercept creation of (non-generic) arrays and intrinsify.
7677
List(new Erasure), // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements.
7778
List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types

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

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,7 @@ class MixinOps(cls: ClassSymbol, thisTransform: DenotTransformer)(implicit ctx:
4848
* - there are multiple traits defining method with same signature
4949
*/
5050
def needsForwarder(meth: Symbol): Boolean = {
51-
lazy val competingMethods = cls.baseClasses.iterator
52-
.filter(_ ne meth.owner)
53-
.map(meth.overriddenSymbol)
54-
.filter(_.exists)
55-
.toList
51+
lazy val competingMethods = competingMethodsIterator(meth).toList
5652

5753
def needsDisambiguation = competingMethods.exists(x=> !(x is Deferred)) // multiple implementations are available
5854
def hasNonInterfaceDefinition = competingMethods.exists(!_.owner.is(Trait)) // there is a definition originating from class
@@ -61,8 +57,38 @@ class MixinOps(cls: ClassSymbol, thisTransform: DenotTransformer)(implicit ctx:
6157
(needsDisambiguation || hasNonInterfaceDefinition || meth.owner.is(Scala2x))
6258
}
6359

60+
/** Get `sym` of the method that needs a forwarder
61+
* Method needs a forwarder in those cases:
62+
* - there is a trait that defines a primitive version of implemented polymorphic method.
63+
* - there is a trait that defines a polymorphic version of implemented primitive method.
64+
*/
65+
def needsPrimitiveForwarderTo(meth: Symbol): Option[Symbol] = {
66+
def hasPrimitiveMissMatch(tp1: Type, tp2: Type): Boolean = (tp1, tp2) match {
67+
case (tp1: MethodicType, tp2: MethodicType) =>
68+
hasPrimitiveMissMatch(tp1.resultType, tp2.resultType) ||
69+
tp1.paramTypess.flatten.zip(tp1.paramTypess.flatten).exists(args => hasPrimitiveMissMatch(args._1, args._2))
70+
case _ =>
71+
def isPrimitiveOrValueClass(sym: Symbol): Boolean = sym.isPrimitiveValueClass || sym.isValueClass
72+
isPrimitiveOrValueClass(tp1.typeSymbol) ^ isPrimitiveOrValueClass(tp2.typeSymbol)
73+
}
74+
75+
def needsPrimitiveForwarder(m: Symbol): Boolean =
76+
m.owner != cls && !m.is(Deferred) && hasPrimitiveMissMatch(meth.info, m.info)
77+
78+
if (!meth.is(Method | Deferred, butNot = PrivateOrAccessor) || meth.overriddenSymbol(cls).exists || needsForwarder(meth)) None
79+
else competingMethodsIterator(meth).find(needsPrimitiveForwarder)
80+
}
81+
82+
final val PrivateOrAccessor = Private | Accessor
6483
final val PrivateOrAccessorOrDeferred = Private | Accessor | Deferred
6584

6685
def forwarder(target: Symbol) = (targs: List[Type]) => (vrefss: List[List[Tree]]) =>
6786
superRef(target).appliedToTypes(targs).appliedToArgss(vrefss)
87+
88+
private def competingMethodsIterator(meth: Symbol): Iterator[Symbol] = {
89+
cls.baseClasses.iterator
90+
.filter(_ ne meth.owner)
91+
.map(meth.overriddenSymbol)
92+
.filter(_.exists)
93+
}
6894
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core._
5+
import TreeTransforms._
6+
import Contexts.Context
7+
import Flags._
8+
import SymUtils._
9+
import Symbols._
10+
import SymDenotations._
11+
import Types._
12+
import Decorators._
13+
import DenotTransformers._
14+
import StdNames._
15+
import NameOps._
16+
import ast.Trees._
17+
import util.Positions._
18+
import Names._
19+
import collection.mutable
20+
import ResolveSuper._
21+
22+
/** This phase adds forwarder where mixedin generic and primitive typed methods have a missmatch.
23+
* In particular for every method that is declared both as generic with a primitive type and with a primitive type
24+
* `<mods> def f[Ts](ps1)...(psN): U` in trait M` and
25+
* `<mods> def f[Ts](ps1)...(psN): V = ...` in implemented in N`
26+
* where U is a primitive and V a polymorphic type (or vice versa) needs:
27+
*
28+
* <mods> def f[Ts](ps1)...(psN): U = super[N].f[Ts](ps1)...(psN)
29+
*
30+
* IMPORTANT: When\If Valhalla happens, we'll need to move mixin before erasure and than this code will need to be rewritten
31+
* as it will instead change super-class.
32+
*/
33+
class PrimitiveForwarders extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform =>
34+
import ast.tpd._
35+
36+
override def phaseName: String = "primitiveForwarders"
37+
38+
override def runsAfter = Set(classOf[ResolveSuper])
39+
40+
override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo) = {
41+
val cls = impl.symbol.owner.asClass
42+
val ops = new MixinOps(cls, thisTransform)
43+
import ops._
44+
45+
def methodPrimitiveForwarders: List[Tree] =
46+
for (meth <- mixins.flatMap(_.info.decls.flatMap(needsPrimitiveForwarderTo)).distinct)
47+
yield polyDefDef(implementation(meth.asTerm), forwarder(meth))
48+
49+
cpy.Template(impl)(body = methodPrimitiveForwarders ::: impl.body)
50+
}
51+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class ResolveSuper extends MiniPhaseTransform with IdentityDenotTransformer { th
8585
private val PrivateOrAccessorOrDeferred = Private | Accessor | Deferred
8686
}
8787

88-
object ResolveSuper{
88+
object ResolveSuper {
8989
/** Returns the symbol that is accessed by a super-accessor in a mixin composition.
9090
*
9191
* @param base The class in which everything is mixed together
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
true
2+
true
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
object Test {
3+
def main(args: Array[String]): Unit = {
4+
println((new Foo: Baz).value1)
5+
println((new Foo: Baz).value2)
6+
}
7+
}
8+
9+
class Foo extends Bar[Boolean](true) with Baz
10+
11+
class Bar[T](x: T) {
12+
def value1: T = x
13+
def value2(): T = x
14+
}
15+
16+
trait Baz {
17+
def value1: Boolean
18+
def value2(): Boolean
19+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
true
2+
true
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
object Test {
3+
def main(args: Array[String]): Unit = {
4+
println((new Foo: Bar[Boolean]).value1)
5+
println((new Foo: Bar[Boolean]).value2)
6+
}
7+
}
8+
9+
class Foo extends Baz with Bar[Boolean]
10+
11+
trait Bar[T] {
12+
def value1: T
13+
def value2(): T
14+
}
15+
16+
class Baz {
17+
def value1: Boolean = true
18+
def value2(): Boolean = true
19+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
true
2+
true
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
object Test {
3+
def main(args: Array[String]): Unit = {
4+
println((new Foo: Baz).value)
5+
println((new Foo: Qux).value)
6+
}
7+
}
8+
9+
class Foo extends Bar[Boolean](true) with Baz with Qux
10+
11+
class Bar[T](x: T) {
12+
def value: T = x
13+
}
14+
15+
trait Baz {
16+
def value: Boolean
17+
}
18+
19+
trait Qux {
20+
def value: Boolean
21+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
true
2+
true
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
object Test {
3+
def main(args: Array[String]): Unit = {
4+
println((new Foo: Baz).value1.v)
5+
println((new Foo: Baz).value2.v)
6+
}
7+
}
8+
9+
class Foo extends Bar[VBoolean](new VBoolean(true)) with Baz
10+
11+
class Bar[T](x: T) {
12+
def value1: T = x
13+
def value2(): T = x
14+
}
15+
16+
trait Baz {
17+
def value1: VBoolean
18+
def value2(): VBoolean
19+
}
20+
21+
class VBoolean(val v: Boolean) extends AnyVal
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
true
2+
true
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
object Test {
3+
def main(args: Array[String]): Unit = {
4+
println((new Foo: Bar[VBoolean]).value1.v)
5+
println((new Foo: Bar[VBoolean]).value2.v)
6+
}
7+
}
8+
9+
class Foo extends Baz with Bar[VBoolean]
10+
11+
trait Bar[T] {
12+
def value1: T
13+
def value2(): T
14+
}
15+
16+
class Baz {
17+
def value1: VBoolean = new VBoolean(true)
18+
def value2(): VBoolean = new VBoolean(true)
19+
}
20+
21+
class VBoolean(val v: Boolean) extends AnyVal

0 commit comments

Comments
 (0)