Skip to content

Commit

Permalink
Emit mixin forwarders as bridges
Browse files Browse the repository at this point in the history
This is the same scheme I proposed in
scala/scala#7843 which sidesteps all the issues
regarding mixin forwarders and generic signatures, see the discussion in
that PR for more information.

Tests imported from scalac, some of the comments in them might not be
correct for Dotty anymore.
  • Loading branch information
smarter committed Mar 21, 2019
1 parent 6ad49e9 commit 6d0f9ca
Show file tree
Hide file tree
Showing 20 changed files with 284 additions and 78 deletions.
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/Mixin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ class Mixin extends MiniPhase with SymTransformer { thisPhase =>
for (meth <- mixin.info.decls.toList if needsMixinForwarder(meth))
yield {
util.Stats.record("mixin forwarders")
transformFollowing(polyDefDef(mkForwarderSym(meth.asTerm), forwarderRhsFn(meth)))
transformFollowing(polyDefDef(mkForwarderSym(meth.asTerm, Bridge), forwarderRhsFn(meth)))
}


Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/MixinOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ class MixinOps(cls: ClassSymbol, thisPhase: DenotTransformer)(implicit ctx: Cont
map(n => ctx.getClassIfDefined("org.junit." + n)).
filter(_.exists)

def mkForwarderSym(member: TermSymbol): TermSymbol = {
def mkForwarderSym(member: TermSymbol, extraFlags: FlagSet = EmptyFlags): TermSymbol = {
val res = member.copy(
owner = cls,
name = member.name.stripScala2LocalSuffix,
flags = member.flags &~ Deferred | Synthetic | Artifact,
flags = member.flags &~ Deferred | Synthetic | Artifact | extraFlags,
info = cls.thisType.memberInfo(member)).enteredAfter(thisPhase).asTerm
res.addAnnotations(member.annotations.filter(_.symbol != defn.TailrecAnnot))
res
Expand Down
2 changes: 2 additions & 0 deletions compiler/test/dotc/run-test-pickling.blacklist
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ derive-generic.scala
deriving-interesting-prefixes.scala
instances.scala
instances-anonymous.scala
mixin-forwarder-overload
t8905
14 changes: 0 additions & 14 deletions tests/run/mixin-bridge-methods.scala

This file was deleted.

9 changes: 9 additions & 0 deletions tests/run/mixin-forwarder-overload/A.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
case class Foo(x: Int)

trait A[X] {
def concat[Dummy](suffix: Int): Dummy = ???
}

class Bar extends A[Foo] {
def concat(suffix: Int): Foo = Foo(0)
}
7 changes: 7 additions & 0 deletions tests/run/mixin-forwarder-overload/Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
public class Test {
public static void main(String[] args) {
Bar bar = new Bar();
Foo x = bar.concat(0);
System.out.println(x);
}
}
77 changes: 77 additions & 0 deletions tests/run/mixin-signatures.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
class Test$bar1$ {
public java.lang.Object Test$bar1$.f(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar1$.f(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar1$.g(java.lang.String)
public java.lang.Object Test$bar1$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar1$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.Object Test$bar1$.h(java.lang.Object) <bridge> <synthetic>
}

class Test$bar2$ {
public java.lang.Object Test$bar2$.f(java.lang.Object) <bridge> <synthetic>
public java.lang.Object Test$bar2$.f(java.lang.String) <bridge> <synthetic>
public java.lang.String Test$bar2$.g(java.lang.String)
public java.lang.Object Test$bar2$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.Object Test$bar2$.g(java.lang.String) <bridge> <synthetic>
public java.lang.Object Test$bar2$.h(java.lang.Object) <bridge> <synthetic>
}

class Test$bar3$ {
public java.lang.String Foo3.f(java.lang.Object)
generic: public java.lang.String Foo3.f(T)
public java.lang.Object Foo3.f(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar3$.g(java.lang.String)
public java.lang.Object Test$bar3$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar3$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.Object Foo3.h(java.lang.Object) <bridge> <synthetic>
}

class Test$bar4$ {
public java.lang.Object Foo4.f(java.lang.String)
generic: public R Foo4.f(java.lang.String)
public java.lang.Object Foo4.f(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar4$.g(java.lang.String)
public java.lang.Object Test$bar4$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.Object Test$bar4$.g(java.lang.String) <bridge> <synthetic>
public java.lang.Object Foo4.h(java.lang.Object) <bridge> <synthetic>
}

class Test$bar5$ {
public java.lang.String Test$bar5$.f(java.lang.String)
public java.lang.Object Test$bar5$.f(java.lang.Object) <bridge> <synthetic>
public java.lang.Object Test$bar5$.f(java.lang.String) <bridge> <synthetic>
public java.lang.String Test$bar5$.f(java.lang.Object) <bridge> <synthetic>
public java.lang.String Test$bar5$.g(java.lang.String)
public java.lang.Object Test$bar5$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.Object Test$bar5$.g(java.lang.String) <bridge> <synthetic>
public java.lang.String Test$bar5$.g(java.lang.Object) <bridge> <synthetic>
public java.lang.Object Test$bar5$.h(java.lang.Object) <bridge> <synthetic>
}

interface Foo1 {
public abstract java.lang.Object Base.f(java.lang.Object)
generic: public abstract R Base.f(T)
public default java.lang.String Foo1.f(java.lang.Object)
generic: public default java.lang.String Foo1.f(T)
public abstract java.lang.Object Base.g(java.lang.Object)
generic: public abstract R Base.g(T)
public abstract java.lang.String Foo1.g(java.lang.Object)
generic: public abstract java.lang.String Foo1.g(T)
public default java.lang.Object Base.h(java.lang.Object)
generic: public default R Base.h(T)
}

interface Foo2 {
public abstract java.lang.Object Base.f(java.lang.Object)
generic: public abstract R Base.f(T)
public default java.lang.Object Foo2.f(java.lang.String)
generic: public default R Foo2.f(java.lang.String)
public abstract java.lang.Object Base.g(java.lang.Object)
generic: public abstract R Base.g(T)
public abstract java.lang.Object Foo2.g(java.lang.String)
generic: public abstract R Foo2.g(java.lang.String)
public default java.lang.Object Base.h(java.lang.Object)
generic: public default R Base.h(T)
}

000000000000000000000000000000000000
105 changes: 105 additions & 0 deletions tests/run/mixin-signatures.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
trait Base[T, R] {
def f(x: T): R
def g(x: T): R
def h(x: T): R = null.asInstanceOf[R]
}

trait Foo1[T] extends Base[T, String] {
def f(x: T): String = null
def g(x: T): String
}
trait Foo2[R] extends Base[String, R] {
def f(x: String): R = { print(x.length) ; null.asInstanceOf[R] }
def g(x: String): R
}
abstract class Foo3[T] extends Base[T, String] {
def f(x: T): String = ""
def g(x: T): String
}
abstract class Foo4[R] extends Base[String, R] {
def f(x: String): R = { print(x.length) ; null.asInstanceOf[R] }
def g(x: String): R
}

object Test {
object bar1 extends Foo1[String] { def g(x: String): String = { print(x.length) ; "" } }
object bar2 extends Foo2[String] { def g(x: String): String = { print(x.length) ; "" } }
object bar3 extends Foo3[String] { def g(x: String): String = { print(x.length) ; "" } }
object bar4 extends Foo4[String] { def g(x: String): String = { print(x.length) ; "" } }

// Notice that in bar5, f and g require THREE bridges, because the final
// implementation is (String)String, but:
//
// inherited abstract signatures: T(R), (T)String, and (String)R
// which erase to: (Object)Object, (Object)String, and (String)Object
//
// each of which must be bridged to the actual (String)String implementation.
//
// public java.lang.String Test$bar5$.g(java.lang.String)
// public java.lang.Object Test$bar5$.g(java.lang.String) <bridge> <synthetic>
// public java.lang.Object Test$bar5$.g(java.lang.Object) <bridge> <synthetic>
// public java.lang.String Test$bar5$.g(java.lang.Object) <bridge> <synthetic>
object bar5 extends Foo1[String] with Foo2[String] {
override def f(x: String): String = { print(x.length) ; x }
def g(x: String): String = { print(x.length) ; x }
}

final def m1[T, R](x: Base[T, R], y: T) = { x.f(y) ; x.g(y) ; x.h(y) }
final def m2[T](x: Base[T, String], y: T) = { x.f(y) ; x.g(y) ; x.h(y) }
final def m3[R](x: Base[String, R]) = { x.f("") ; x.g("") ; x.h("") }
final def m4(x: Base[String, String]) = { x.f("") ; x.g("") ; x.h("") }

final def m11[T](x: Foo1[T], y: T) = { x.f(y) ; x.g(y) ; x.h(y) }
final def m12(x: Foo1[String]) = { x.f("") ; x.g("") ; x.h("") }
final def m21[T](x: Foo2[T], y: T) = { x.f("") ; x.g("") ; x.h("") }
final def m22(x: Foo2[String]) = { x.f("") ; x.g("") ; x.h("") }
final def m31[T](x: Foo3[T], y: T) = { x.f(y) ; x.g(y) ; x.h(y) }
final def m32(x: Foo3[String]) = { x.f("") ; x.g("") ; x.h("") }
final def m41[T](x: Foo4[T], y: T) = { x.f("") ; x.g("") ; x.h("") }
final def m42(x: Foo4[String]) = { x.f("") ; x.g("") ; x.h("") }

def go = {
m1(bar1, "") ; m2(bar1, "") ; m3(bar1) ; m4(bar1)
m1(bar2, "") ; m2(bar2, "") ; m3(bar2) ; m4(bar2)
m1(bar3, "") ; m2(bar3, "") ; m3(bar3) ; m4(bar3)
m1(bar4, "") ; m2(bar4, "") ; m3(bar4) ; m4(bar4)

m11(bar1, "") ; m12(bar1)
m21(bar2, "") ; m22(bar2)
m31(bar3, "") ; m32(bar3)
m41(bar4, "") ; m42(bar4)
""
}

def flagsString(m: java.lang.reflect.Method) = {
val str = List(
if (m.isBridge) "<bridge>" else "",
if (m.isSynthetic) "<synthetic>" else ""
) filterNot (_ == "") mkString " "

if (str == "") "" else " " + str
//
// val flags = scala.reflect.internal.ClassfileConstants.toScalaMethodFlags(m.getModifiers())
// scala.tools.nsc.symtab.Flags.flagsToString(flags)
}

def show(clazz: Class[_]): Unit = {
print(clazz.toString + " {")
clazz.getMethods.sortBy(x => (x.getName, x.isBridge, x.toString)) filter (_.getName.length == 1) foreach { m =>
print("\n " + m + flagsString(m))
if ("" + m != "" + m.toGenericString) {
print("\n generic: " + m.toGenericString)
}
}
println("\n}")
println("")
}
def show(x: AnyRef): Unit = { show(x.getClass) }
def show(x: String): Unit = { show(Class.forName(x)) }

def main(args: Array[String]): Unit = {
List(bar1, bar2, bar3, bar4, bar5) foreach show
List("Foo1", "Foo2") foreach show
println(go)
}
}
6 changes: 0 additions & 6 deletions tests/run/t3452b-bcode/J_2.java

This file was deleted.

17 changes: 0 additions & 17 deletions tests/run/t3452b-bcode/S_1.scala

This file was deleted.

5 changes: 0 additions & 5 deletions tests/run/t3452b-bcode/S_3.scala

This file was deleted.

2 changes: 1 addition & 1 deletion tests/run/t3452d/A.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ trait TraversableLike[A, Repr] {
def tail: Repr = null.asInstanceOf[Repr]
}

abstract class AbstractTrav[A] extends TraversableLike[A, Traversable[A]]
abstract class AbstractTrav[A] extends TraversableLike[A, Iterable[A]]

class C[A] extends AbstractTrav[A]
8 changes: 1 addition & 7 deletions tests/run/t3452d/Test.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import scala.collection.immutable.Nil;
import scala.collection.immutable.List;
import scala.collection.Traversable;

public class Test {
public static void main(String[] args) {
C<String> c = new C<String>();
// TODO add a bridge during mixin so we can expose
// sharper generic signature for `tail`.
/*Traversable<String>*/ Object ls = c.tail();
scala.collection.Iterable<String> ls = c.tail();
}
}
4 changes: 3 additions & 1 deletion tests/run/t3452g/A.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ trait TraversableLike[A, Repr] {

abstract class AbstractTrav[A] extends TraversableLike[A, AbstractTrav[A]]

class C1 extends AbstractTrav[String]

object O extends AbstractTrav[String]

class C[A] extends AbstractTrav[A]
class C2[A] extends AbstractTrav[A]
16 changes: 6 additions & 10 deletions tests/run/t3452g/Test.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@

public class Test {
public static void main(String[] args) {
// To get better types here, we would need to
// add bridge during mixin so we can expose
// a generic return type of Traversable<A>, because the erasure
// of this (Traversable) differs from the erasure of the mixed
// method (erasure(Repr) = Object)
public static void main(String[] args) {
AbstractTrav<String> lsSharp1 = new C1().tail();

Object lsSharp = O.tail();
// Object is the result type for the static forwarder (might be because of #11305)
Object lsSharp2 = O.tail();

Object lsSharp2 = new C<String>().tail();
}
AbstractTrav<String> lsSharp3 = new C2<String>().tail();
}
}
19 changes: 5 additions & 14 deletions tests/run/t3452h.scala
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
class Mix extends Foo with Bar { f; }
class Mix___eFoo_I_wBar__f extends Foo_I_ with Bar__f { f; }
trait T
abstract class Foo {
class I extends T
def f: I
f
}
trait Bar {
type I >: Null <: T
def f: I = null
f
def gobble: I = null
}
abstract class Foo_I_ { class I extends T ; def f: I ; f; }
trait Bar__f { type I>:Null<:T; def f: I = {null}; f; def gobble: I = {null}}

object Test extends dotty.runtime.LegacyApp {
new Mix
object Test extends App {
new Mix___eFoo_I_wBar__f
}
6 changes: 6 additions & 0 deletions tests/run/t7932.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public Category C.category()
public Category C.category1()
public default Category<F> M1.category()
public default Category<scala.Tuple2> M1.category1()
public default Category<F> M2.category()
public default Category<scala.Tuple2> M2.category1()
30 changes: 30 additions & 0 deletions tests/run/t7932.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import scala.language.higherKinds

class Category[M[_, _]]

trait M1[F] {
type X[a, b] = F
def category: Category[X] = null
def category1: Category[Tuple2] = null
}

// The second trait is needed to make sure there's a forwarder generated in C.
// otherwise the trait methods are just the inherited default methods from M1.
trait M2[F] { self: M1[F] =>
override def category: Category[X] = null
override def category1: Category[Tuple2] = null
}

abstract class C extends M1[Float] with M2[Float]

object Test {
def t(c: Class[_]) = {
val ms = c.getMethods.filter(_.getName.startsWith("category"))
println(ms.map(_.toGenericString).sorted.mkString("\n"))
}
def main(args: Array[String]): Unit = {
t(classOf[C])
t(classOf[M1[_]])
t(classOf[M2[_]])
}
}
Loading

0 comments on commit 6d0f9ca

Please sign in to comment.