Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class Compiler {
new ElimByName, // Expand by-name parameters and arguments
new AugmentScala2Traits, // Expand traits defined in Scala 2.11 to simulate old-style rewritings
new ResolveSuper, // Implement super accessors and add forwarders to trait methods
new PrimitiveForwarders, // Add forwarders to trait methods that have a mismatch between generic and primitives
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.
List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types
Expand Down
36 changes: 31 additions & 5 deletions compiler/src/dotty/tools/dotc/transform/MixinOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,7 @@ class MixinOps(cls: ClassSymbol, thisTransform: DenotTransformer)(implicit ctx:
* - there are multiple traits defining method with same signature
*/
def needsForwarder(meth: Symbol): Boolean = {
lazy val competingMethods = cls.baseClasses.iterator
.filter(_ ne meth.owner)
.map(meth.overriddenSymbol)
.filter(_.exists)
.toList
lazy val competingMethods = competingMethodsIterator(meth).toList

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

/** Get `sym` of the method that needs a forwarder
* Method needs a forwarder in those cases:
* - there is a trait that defines a primitive version of implemented polymorphic method.
* - there is a trait that defines a polymorphic version of implemented primitive method.
*/
def needsPrimitiveForwarderTo(meth: Symbol): Option[Symbol] = {
def hasPrimitiveMissMatch(tp1: Type, tp2: Type): Boolean = (tp1, tp2) match {
case (tp1: MethodicType, tp2: MethodicType) =>
hasPrimitiveMissMatch(tp1.resultType, tp2.resultType) ||
tp1.paramTypess.flatten.zip(tp1.paramTypess.flatten).exists(args => hasPrimitiveMissMatch(args._1, args._2))
case _ =>
def isPrimitiveOrValueClass(sym: Symbol): Boolean = sym.isPrimitiveValueClass || sym.isValueClass
isPrimitiveOrValueClass(tp1.typeSymbol) ^ isPrimitiveOrValueClass(tp2.typeSymbol)
}

def needsPrimitiveForwarder(m: Symbol): Boolean =
m.owner != cls && !m.is(Deferred) && hasPrimitiveMissMatch(meth.info, m.info)

if (!meth.is(Method | Deferred, butNot = PrivateOrAccessor) || meth.overriddenSymbol(cls).exists || needsForwarder(meth)) None
else competingMethodsIterator(meth).find(needsPrimitiveForwarder)
}

final val PrivateOrAccessor = Private | Accessor
final val PrivateOrAccessorOrDeferred = Private | Accessor | Deferred

def forwarder(target: Symbol) = (targs: List[Type]) => (vrefss: List[List[Tree]]) =>
superRef(target).appliedToTypes(targs).appliedToArgss(vrefss)

private def competingMethodsIterator(meth: Symbol): Iterator[Symbol] = {
cls.baseClasses.iterator
.filter(_ ne meth.owner)
.map(meth.overriddenSymbol)
.filter(_.exists)
}
}
51 changes: 51 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/PrimitiveForwarders.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package dotty.tools.dotc
package transform

import core._
import TreeTransforms._
import Contexts.Context
import Flags._
import SymUtils._
import Symbols._
import SymDenotations._
import Types._
import Decorators._
import DenotTransformers._
import StdNames._
import NameOps._
import ast.Trees._
import util.Positions._
import Names._
import collection.mutable
import ResolveSuper._

/** This phase adds forwarder where mixedin generic and primitive typed methods have a missmatch.
* In particular for every method that is declared both as generic with a primitive type and with a primitive type
* `<mods> def f[Ts](ps1)...(psN): U` in trait M` and
* `<mods> def f[Ts](ps1)...(psN): V = ...` in implemented in N`
* where U is a primitive and V a polymorphic type (or vice versa) needs:
*
* <mods> def f[Ts](ps1)...(psN): U = super[N].f[Ts](ps1)...(psN)
*
* IMPORTANT: When\If Valhalla happens, we'll need to move mixin before erasure and than this code will need to be rewritten
* as it will instead change super-class.
*/
class PrimitiveForwarders extends MiniPhaseTransform with IdentityDenotTransformer { thisTransform =>
import ast.tpd._

override def phaseName: String = "primitiveForwarders"

override def runsAfter = Set(classOf[ResolveSuper])

override def transformTemplate(impl: Template)(implicit ctx: Context, info: TransformerInfo) = {
val cls = impl.symbol.owner.asClass
val ops = new MixinOps(cls, thisTransform)
import ops._

def methodPrimitiveForwarders: List[Tree] =
for (meth <- mixins.flatMap(_.info.decls.flatMap(needsPrimitiveForwarderTo)).distinct)
yield polyDefDef(implementation(meth.asTerm), forwarder(meth))

cpy.Template(impl)(body = methodPrimitiveForwarders ::: impl.body)
}
}
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/ResolveSuper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class ResolveSuper extends MiniPhaseTransform with IdentityDenotTransformer { th
private val PrivateOrAccessorOrDeferred = Private | Accessor | Deferred
}

object ResolveSuper{
object ResolveSuper {
/** Returns the symbol that is accessed by a super-accessor in a mixin composition.
*
* @param base The class in which everything is mixed together
Expand Down
2 changes: 2 additions & 0 deletions tests/run/mixin-primitive-on-generic-1.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
true
true
19 changes: 19 additions & 0 deletions tests/run/mixin-primitive-on-generic-1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

object Test {
def main(args: Array[String]): Unit = {
println((new Foo: Baz).value1)
println((new Foo: Baz).value2)
}
}

class Foo extends Bar[Boolean](true) with Baz
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need a test with a value class, also it would be nice to check that the method called would be the unboxed one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in mixin-primitive-on-generic-4.scalaand mixin-primitive-on-generic-5.scala and I checked that the method called is the unboxed one.


class Bar[T](x: T) {
def value1: T = x
def value2(): T = x
}

trait Baz {
def value1: Boolean
def value2(): Boolean
}
2 changes: 2 additions & 0 deletions tests/run/mixin-primitive-on-generic-2.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
true
true
19 changes: 19 additions & 0 deletions tests/run/mixin-primitive-on-generic-2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

object Test {
def main(args: Array[String]): Unit = {
println((new Foo: Bar[Boolean]).value1)
println((new Foo: Bar[Boolean]).value2)
}
}

class Foo extends Baz with Bar[Boolean]

trait Bar[T] {
def value1: T
def value2(): T
}

class Baz {
def value1: Boolean = true
def value2(): Boolean = true
}
2 changes: 2 additions & 0 deletions tests/run/mixin-primitive-on-generic-3.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
true
true
21 changes: 21 additions & 0 deletions tests/run/mixin-primitive-on-generic-3.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

object Test {
def main(args: Array[String]): Unit = {
println((new Foo: Baz).value)
println((new Foo: Qux).value)
}
}

class Foo extends Bar[Boolean](true) with Baz with Qux

class Bar[T](x: T) {
def value: T = x
}

trait Baz {
def value: Boolean
}

trait Qux {
def value: Boolean
}
2 changes: 2 additions & 0 deletions tests/run/mixin-primitive-on-generic-4.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
true
true
21 changes: 21 additions & 0 deletions tests/run/mixin-primitive-on-generic-4.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

object Test {
def main(args: Array[String]): Unit = {
println((new Foo: Baz).value1.v)
println((new Foo: Baz).value2.v)
}
}

class Foo extends Bar[VBoolean](new VBoolean(true)) with Baz

class Bar[T](x: T) {
def value1: T = x
def value2(): T = x
}

trait Baz {
def value1: VBoolean
def value2(): VBoolean
}

class VBoolean(val v: Boolean) extends AnyVal
2 changes: 2 additions & 0 deletions tests/run/mixin-primitive-on-generic-5.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
true
true
21 changes: 21 additions & 0 deletions tests/run/mixin-primitive-on-generic-5.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

object Test {
def main(args: Array[String]): Unit = {
println((new Foo: Bar[VBoolean]).value1.v)
println((new Foo: Bar[VBoolean]).value2.v)
}
}

class Foo extends Baz with Bar[VBoolean]

trait Bar[T] {
def value1: T
def value2(): T
}

class Baz {
def value1: VBoolean = new VBoolean(true)
def value2(): VBoolean = new VBoolean(true)
}

class VBoolean(val v: Boolean) extends AnyVal