From f8d80ea26a03a39fd1d0160a2fad69752712f574 Mon Sep 17 00:00:00 2001 From: Jason Zaugg Date: Fri, 31 May 2013 00:22:50 -0700 Subject: [PATCH] SI-3452 Correct Java generic signatures for mixins, static forwarders [Parts of this patch and some of the commentary are from @paulp] This took me so long to figure out I can't even tell you. Partly because there were two different bugs, one which only arose for trait forwarders and one for mirror class forwarders, and every time I'd make one set of tests work another set would start failing. The runtime failures associated with these bugs were fairly well hidden because you usually have to go through java to encounter them: scala doesn't pay that much attention to generic signatures, so they can be wrong and scala might still generate correct code. But java is not so lucky. Bug #1) During mixin composition, classes which extend traits receive forwarders to the implementations. An attempt was made to give these the correct info (in method "cloneBeforeErasure") but it was prone to giving the wrong answer, because: the key attribute which the forwarder must capture is what the underlying method will erase to *where the implementation is*, not how it appears to the class which contains it. That means the signature of the forwarder must be no more precise than the signature of the inherited implementation unless additional measures will be taken. This subtle difference will put on an unsubtle show for you in test run/t3452.scala. trait C[T] trait Search[M] { def search(input: M): C[Int] = null } object StringSearch extends Search[String] { } StringSearch.search("test"); // java // java.lang.NoSuchMethodError: StringSearch.search(Ljava/lang/String;)LC; The principled thing to do here would be to create a pair of methods in the host class: a mixin forwarder with the erased signature `(String)C[Int]`, and a bridge method with the same erased signature as the trait interface facet. But, this turns out to be pretty hard to retrofit onto the current setup of Mixin and Erasure, mostly due to the fact that mixin happens after erasure which has already taken care of bridging. For a future, release, we should try to move all bridging after mixin, and pursue this approach. But for now, what can we do about `LinkageError`s for Java clients? This commit simply checks if the pre-erasure method signature that we generate for the trait forward erases identically to that of the interface method. If so, we can be precise. If not, we emit the erased signature as the generic signature. Bug #2) The same principle is at work, at a different location. During genjvm, objects without declared companion classes are given static forwarders in the corresponding class, e.g. object Foo { def bar = 5 } which creates these classes (taking minor liberties): class Foo$ { static val MODULE$ = new Foo$ ; def bar = 5 } class Foo { static def bar = Foo$.MODULE$.bar } In generating these, genjvm circumvented the usual process whereby one creates a symbol and gives it an info, preferring to target the bytecode directly. However generic signatures are calculated from symbol info (in this case reusing the info from the module class.) Lacking even the attempt which was being made in mixin to "clone before erasure", we would have runtime failures of this kind: abstract class Foo { type T def f(x: T): List[T] = List() } object Bar extends Foo { type T = String } Bar.f(""); // java // java.lang.NoSuchMethodError: Bar.f(Ljava/lang/String;)Lscala/collection/immutable/List; Before/after this commit: < signature f (Ljava/lang/String;)Lscala/collection/immutable/List; --- > signature f (Ljava/lang/Object;)Lscala/collection/immutable/List; This takes the warning count for compiling collections under `-Ycheck:jvm` from 1521 to 26. --- .../scala/tools/nsc/backend/jvm/GenASM.scala | 13 +- .../scala/tools/nsc/transform/Mixin.scala | 27 +++-- test/files/neg/t4749.check | 6 +- test/files/pos/t3452f.scala | 10 ++ test/files/run/mixin-signatures.check | 59 +++++++++ test/files/run/mixin-signatures.scala | 105 ++++++++++++++++ test/files/run/t3452.check | 1 + test/files/run/t3452.scala | 21 ++++ test/files/run/t3452a.check | 1 + test/files/run/t3452a/J_2.java | 5 + test/files/run/t3452a/S_1.scala | 24 ++++ test/files/run/t3452a/S_3.scala | 5 + test/files/run/t3452b.check | 1 + test/files/run/t3452b/J_2.java | 5 + test/files/run/t3452b/S_1.scala | 10 ++ test/files/run/t3452b/S_3.scala | 5 + test/files/run/t3452c.check | 8 ++ test/files/run/t3452c.scala | 113 ++++++++++++++++++ test/files/run/t3452d/A.scala | 7 ++ test/files/run/t3452d/Test.java | 12 ++ test/files/run/t3452e/A.scala | 4 + test/files/run/t3452e/B.java | 2 + test/files/run/t3452e/Test.scala | 3 + test/files/run/t3452f.scala | 19 +++ test/files/run/t3452g/A.scala | 9 ++ test/files/run/t3452g/Test.java | 14 +++ test/files/run/t3452h.scala | 8 ++ test/files/run/t6554.check | 1 + test/files/run/t6554.scala | 11 ++ test/support/java-tests.txt | 97 +++++++++++++++ tools/compare-java-sigs | 56 +++++++++ 31 files changed, 649 insertions(+), 13 deletions(-) create mode 100644 test/files/pos/t3452f.scala create mode 100644 test/files/run/mixin-signatures.check create mode 100644 test/files/run/mixin-signatures.scala create mode 100644 test/files/run/t3452.check create mode 100644 test/files/run/t3452.scala create mode 100644 test/files/run/t3452a.check create mode 100644 test/files/run/t3452a/J_2.java create mode 100644 test/files/run/t3452a/S_1.scala create mode 100644 test/files/run/t3452a/S_3.scala create mode 100644 test/files/run/t3452b.check create mode 100644 test/files/run/t3452b/J_2.java create mode 100644 test/files/run/t3452b/S_1.scala create mode 100644 test/files/run/t3452b/S_3.scala create mode 100644 test/files/run/t3452c.check create mode 100644 test/files/run/t3452c.scala create mode 100644 test/files/run/t3452d/A.scala create mode 100644 test/files/run/t3452d/Test.java create mode 100644 test/files/run/t3452e/A.scala create mode 100644 test/files/run/t3452e/B.java create mode 100644 test/files/run/t3452e/Test.scala create mode 100644 test/files/run/t3452f.scala create mode 100644 test/files/run/t3452g/A.scala create mode 100644 test/files/run/t3452g/Test.java create mode 100644 test/files/run/t3452h.scala create mode 100644 test/files/run/t6554.check create mode 100644 test/files/run/t6554.scala create mode 100644 test/support/java-tests.txt create mode 100644 tools/compare-java-sigs diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index eb40e1dbdeb3..b276572f9bd5 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -1064,7 +1064,18 @@ abstract class GenASM extends SubComponent with BytecodeWriters with GenJVMASM { ) // TODO needed? for(ann <- m.annotations) { ann.symbol.initialize } - val jgensig = if (m.isDeferred) null else getGenericSignature(m, module); // only add generic signature if method concrete; bug #1745 + val jgensig = ( + // only add generic signature if method concrete; bug #1745 + if (m.isDeferred) null else { + val clazz = module.linkedClassOfClass + val m1 = ( + if ((clazz.info member m.name) eq NoSymbol) + enteringErasure(m.cloneSymbol(clazz, Flags.METHOD | Flags.STATIC)) + else m + ) + getGenericSignature(m1, clazz) + } + ) addRemoteExceptionAnnot(isRemoteClass, hasPublicBitSet(flags), m) val (throws, others) = m.annotations partition (_.symbol == ThrowsClass) val thrownExceptions: List[String] = getExceptions(throws) diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 89f9cb4b065d..673bc04bd947 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -172,18 +172,23 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { // info) as they are seen from the class. We can't use the member that we get from the // implementation class, as it's a clone that was made after erasure, and thus it does not // know its info at the beginning of erasure anymore. - // Optimize: no need if mixinClass has no typeparams. - mixinMember cloneSymbol clazz modifyInfo (info => - if (mixinClass.typeParams.isEmpty) info - else (clazz.thisType baseType mixinClass) memberInfo mixinMember - ) + val sym = mixinMember cloneSymbol clazz + + val erasureMap = erasure.erasure(mixinMember) + val erasedInterfaceInfo: Type = erasureMap(mixinMember.info) + val specificForwardInfo = (clazz.thisType baseType mixinClass) memberInfo mixinMember + val forwarderInfo = + if (erasureMap(specificForwardInfo) =:= erasedInterfaceInfo) + specificForwardInfo + else { + erasedInterfaceInfo + } + // Optimize: no need if mixinClass has no typeparams. + // !!! JZ Really? What about the effect of abstract types, prefix? + if (mixinClass.typeParams.isEmpty) sym + else sym modifyInfo (_ => forwarderInfo) } - // clone before erasure got rid of type info we'll need to generate a javaSig - // now we'll have the type info at (the beginning of) erasure in our history, - // and now newSym has the info that's been transformed to fit this period - // (no need for asSeenFrom as phase.erasedTypes) - // TODO: verify we need the updateInfo and document why - newSym updateInfo (mixinMember.info cloneInfo newSym) + newSym } /** Add getters and setters for all non-module fields of an implementation diff --git a/test/files/neg/t4749.check b/test/files/neg/t4749.check index 63d5c21532d9..3539140954cd 100644 --- a/test/files/neg/t4749.check +++ b/test/files/neg/t4749.check @@ -25,6 +25,10 @@ t4749.scala:26: warning: Fail6 has a main method with parameter type Array[Strin object Fail6 { ^ +t4749.scala:42: warning: Win3 has a main method with parameter type Array[String], but bippy.Win3 will not be a runnable program. + Reason: main method must have exact signature (Array[String])Unit + object Win3 extends WinBippy[Unit] { } + ^ error: No warnings can be incurred under -Xfatal-warnings. -6 warnings found +7 warnings found one error found diff --git a/test/files/pos/t3452f.scala b/test/files/pos/t3452f.scala new file mode 100644 index 000000000000..efe25a62fc0d --- /dev/null +++ b/test/files/pos/t3452f.scala @@ -0,0 +1,10 @@ +class Base[Coll] { + trait Transformed[S] { + lazy val underlying: Coll = ??? + } +} + +class Derived extends Base[String] { + class C extends Transformed[Any] +} + diff --git a/test/files/run/mixin-signatures.check b/test/files/run/mixin-signatures.check new file mode 100644 index 000000000000..3031fe75afe0 --- /dev/null +++ b/test/files/run/mixin-signatures.check @@ -0,0 +1,59 @@ +class Test$bar1$ { + public java.lang.String Test$bar1$.f(java.lang.Object) + public java.lang.Object Test$bar1$.f(java.lang.Object) + public java.lang.String Test$bar1$.g(java.lang.String) + public java.lang.Object Test$bar1$.g(java.lang.Object) + public java.lang.String Test$bar1$.g(java.lang.Object) + public java.lang.Object Test$bar1$.h(java.lang.Object) +} + +class Test$bar2$ { + public java.lang.Object Test$bar2$.f(java.lang.String) + public java.lang.Object Test$bar2$.f(java.lang.Object) + public java.lang.String Test$bar2$.g(java.lang.String) + public java.lang.Object Test$bar2$.g(java.lang.Object) + public java.lang.Object Test$bar2$.g(java.lang.String) + public java.lang.Object Test$bar2$.h(java.lang.Object) +} + +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) + public java.lang.String Test$bar3$.g(java.lang.String) + public java.lang.Object Test$bar3$.g(java.lang.Object) + public java.lang.String Test$bar3$.g(java.lang.Object) + public java.lang.Object Foo3.h(java.lang.Object) +} + +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) + public java.lang.String Test$bar4$.g(java.lang.String) + public java.lang.Object Test$bar4$.g(java.lang.Object) + public java.lang.Object Test$bar4$.g(java.lang.String) + public java.lang.Object Foo4.h(java.lang.Object) +} + +class Test$bar5$ { + public java.lang.String Test$bar5$.f(java.lang.String) + public java.lang.Object Test$bar5$.f(java.lang.Object) + public java.lang.Object Test$bar5$.f(java.lang.String) + public java.lang.String Test$bar5$.f(java.lang.Object) + public java.lang.String Test$bar5$.g(java.lang.String) + public java.lang.Object Test$bar5$.g(java.lang.Object) + public java.lang.Object Test$bar5$.g(java.lang.String) + public java.lang.String Test$bar5$.g(java.lang.Object) + public java.lang.Object Test$bar5$.h(java.lang.Object) +} + +class Foo1$class { + public static java.lang.String Foo1$class.f(Foo1,java.lang.Object) +} + +class Foo2$class { + public static java.lang.Object Foo2$class.f(Foo2,java.lang.String) +} + +000000000000000000000000000000000000 diff --git a/test/files/run/mixin-signatures.scala b/test/files/run/mixin-signatures.scala new file mode 100644 index 000000000000..afd3fad877d3 --- /dev/null +++ b/test/files/run/mixin-signatures.scala @@ -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) + // public java.lang.Object Test$bar5$.g(java.lang.Object) + // public java.lang.String Test$bar5$.g(java.lang.Object) + 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) "" else "", + if (m.isSynthetic) "" 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[_]) { + print(clazz + " {") + 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) { show(x.getClass) } + def show(x: String) { show(Class.forName(x)) } + + def main(args: Array[String]): Unit = { + List(bar1, bar2, bar3, bar4, bar5) foreach show + List("Foo1$class", "Foo2$class") foreach show + println(go) + } +} \ No newline at end of file diff --git a/test/files/run/t3452.check b/test/files/run/t3452.check new file mode 100644 index 000000000000..b8626c4cff28 --- /dev/null +++ b/test/files/run/t3452.check @@ -0,0 +1 @@ +4 diff --git a/test/files/run/t3452.scala b/test/files/run/t3452.scala new file mode 100644 index 000000000000..253fc93cfaf0 --- /dev/null +++ b/test/files/run/t3452.scala @@ -0,0 +1,21 @@ +trait IStringPair[T] { + def a : String + def b : String + def build(a : String, b : String) : T + def cat(that : IStringPair[T]) = build(this.a + that.a, this.b + that.b) + override def toString = a + b +} + +class StringPair(val a : String, val b : String) extends IStringPair[StringPair] { + def build(a : String, b : String) = new StringPair(a, b) + def len = a.length + b.length +} + +object Test { + def main(args: Array[String]): Unit = { + val a = new StringPair("A", "B") + val b = new StringPair("1", "2") + val c = a cat b + println(c.len) + } +} diff --git a/test/files/run/t3452a.check b/test/files/run/t3452a.check new file mode 100644 index 000000000000..9ff787eb8623 --- /dev/null +++ b/test/files/run/t3452a.check @@ -0,0 +1 @@ +BulkSearch.searchFor called. diff --git a/test/files/run/t3452a/J_2.java b/test/files/run/t3452a/J_2.java new file mode 100644 index 000000000000..62057ffe614d --- /dev/null +++ b/test/files/run/t3452a/J_2.java @@ -0,0 +1,5 @@ +public class J_2 { + public static void main(String[] args) { + BulkSearchInstance.searchFor(new UpRelation()); + } +} diff --git a/test/files/run/t3452a/S_1.scala b/test/files/run/t3452a/S_1.scala new file mode 100644 index 000000000000..791faf42fa40 --- /dev/null +++ b/test/files/run/t3452a/S_1.scala @@ -0,0 +1,24 @@ +abstract class BulkSearch { + type R <: Row + type Rel <: Relation [R] + type Corr <: Correspondence[R] + + def searchFor(input: Rel): Mapping[Corr] = { println("BulkSearch.searchFor called.") ; null } +} + +object BulkSearchInstance extends BulkSearch { + type R = UpRow + type Rel = UpRelation + type Corr = UpCorrespondence +} + +class Row +class UpRow extends Row + +class Relation [R <: Row] +class UpRelation extends Relation [UpRow] + +class Correspondence [R <: Row] +class UpCorrespondence extends Correspondence [UpRow] + +class Mapping[MC <: Correspondence[_]] diff --git a/test/files/run/t3452a/S_3.scala b/test/files/run/t3452a/S_3.scala new file mode 100644 index 000000000000..aaa898dcde94 --- /dev/null +++ b/test/files/run/t3452a/S_3.scala @@ -0,0 +1,5 @@ +object Test { + def main(args: Array[String]): Unit = { + J_2.main(args) + } +} diff --git a/test/files/run/t3452b.check b/test/files/run/t3452b.check new file mode 100644 index 000000000000..4f9ed634b4d2 --- /dev/null +++ b/test/files/run/t3452b.check @@ -0,0 +1 @@ +Search received: test diff --git a/test/files/run/t3452b/J_2.java b/test/files/run/t3452b/J_2.java new file mode 100644 index 000000000000..24c27b88bd53 --- /dev/null +++ b/test/files/run/t3452b/J_2.java @@ -0,0 +1,5 @@ +public class J_2 { + public static void j() { + StringSearch.search("test"); + } +} diff --git a/test/files/run/t3452b/S_1.scala b/test/files/run/t3452b/S_1.scala new file mode 100644 index 000000000000..3ab19ed031d5 --- /dev/null +++ b/test/files/run/t3452b/S_1.scala @@ -0,0 +1,10 @@ +trait Search[M] { + def search(input: M): C[Int] = { + println("Search received: " + input) + null + } +} + +object StringSearch extends Search[String] + +trait C[T] diff --git a/test/files/run/t3452b/S_3.scala b/test/files/run/t3452b/S_3.scala new file mode 100644 index 000000000000..102b433f478c --- /dev/null +++ b/test/files/run/t3452b/S_3.scala @@ -0,0 +1,5 @@ +object Test { + def main(args: Array[String]): Unit = { + J_2.j() + } +} diff --git a/test/files/run/t3452c.check b/test/files/run/t3452c.check new file mode 100644 index 000000000000..ab47181198de --- /dev/null +++ b/test/files/run/t3452c.check @@ -0,0 +1,8 @@ +3 +3 +3 +3 +3 +3 +3 +3 diff --git a/test/files/run/t3452c.scala b/test/files/run/t3452c.scala new file mode 100644 index 000000000000..2c55767abc52 --- /dev/null +++ b/test/files/run/t3452c.scala @@ -0,0 +1,113 @@ +trait Base[A, B, C] { + def f(x: A, y: B, z: C): Unit + def g(x: A, y: B, z: C) = f(x, y, z) + def h(x: A, y: B, z: C) = g(x, y, z) +} + +trait D1[B, C] extends Base[String, B, C] +trait D2[A, B] extends Base[A, B, String] +trait D3[A, C] extends Base[A, String, C] +trait D4[A] extends Base[A, String, String] +trait D5[B] extends Base[String, B, String] +trait D6[C] extends Base[String, String, C] +trait D7 extends Base[String, String, String] + +trait E1[B, C] extends Base[String, B, C] { def f(x: String, y: B, z: C): Unit ; override def h(x: String, y: B, z: C) = g(x, y, z) } +trait E2[A, B] extends Base[A, B, String] { def f(x: A, y: B, z: String): Unit ; override def h(x: A, y: B, z: String) = g(x, y, z) } +trait E3[A, C] extends Base[A, String, C] { def f(x: A, y: String, z: C): Unit ; override def h(x: A, y: String, z: C) = g(x, y, z) } +trait E4[A] extends Base[A, String, String] { def f(x: A, y: String, z: String): Unit ; override def h(x: A, y: String, z: String) = g(x, y, z) } +trait E5[B] extends Base[String, B, String] { def f(x: String, y: B, z: String): Unit ; override def h(x: String, y: B, z: String) = g(x, y, z) } +trait E6[C] extends Base[String, String, C] { def f(x: String, y: String, z: C): Unit ; override def h(x: String, y: String, z: C) = g(x, y, z) } +trait E7 extends Base[String, String, String] { def f(x: String, y: String, z: String): Unit ; override def h(x: String, y: String, z: String) = g(x, y, z) } + +trait F1[B, C] extends Base[String, B, C] { def f(x: String, y: B, z: C): Unit = println(x.length) } +trait F2[A, B] extends Base[A, B, String] { def f(x: A, y: B, z: String): Unit = println(z.length) } +trait F3[A, C] extends Base[A, String, C] { def f(x: A, y: String, z: C): Unit = println(y.length) } +trait F4[A] extends Base[A, String, String] { def f(x: A, y: String, z: String): Unit = println(y.length) } +trait F5[B] extends Base[String, B, String] { def f(x: String, y: B, z: String): Unit = println(x.length) } +trait F6[C] extends Base[String, String, C] { def f(x: String, y: String, z: C): Unit = println(x.length) } +trait F7 extends Base[String, String, String] { def f(x: String, y: String, z: String): Unit = println(x.length) } + +abstract class DBag extends D1[String, String] with D2[String, String] with D3[String, String] with D4[String] with D5[String] with D6[String] with D7 { + def f(x: String, y: String, z: String) = println(x.length + y.length + z.length) +} +abstract class EBag extends E1[String, String] with E2[String, String] with E3[String, String] with E4[String] with E5[String] with E6[String] with E7 { + def f(x: String, y: String, z: String) = println(x.length + y.length + z.length) +} +abstract class FBag extends F1[String, String] with F2[String, String] with F3[String, String] with F4[String] with F5[String] with F6[String] with F7 { + override def f(x: String, y: String, z: String) = println(x.length + y.length + z.length) +} + +abstract class GBag1[A, B] extends Base[A, B, String] with D2[A, B] { + def f(x: A, y: B, z: String) = println(z.length) +} +abstract class GBag2[A] extends GBag1[A, String] with D4[A] { + override def f(x: A, y: String, z: String) = println(z.length) +} +abstract class GBag3 extends GBag2[String] with D7 { + override def f(x: String, y: String, z: String) = println(z.length) +} +class GBag extends GBag3 with D2[String, String] with D3[String, String] with D4[String] with D5[String] with D6[String] with D7 { +} + +object Test { + def f0(x: Base[String, String, String]) = x.f("a", "b", "c") + def f1(x: D1[String, String]) = x.f("a", "b", "c") + def f2(x: D2[String, String]) = x.f("a", "b", "c") + def f3(x: D3[String, String]) = x.f("a", "b", "c") + def f4(x: D4[String]) = x.f("a", "b", "c") + def f5(x: D5[String]) = x.f("a", "b", "c") + def f6(x: D6[String]) = x.f("a", "b", "c") + def f7(x: D7) = x.f("a", "b", "c") + + def main(args: Array[String]): Unit = { + val x = new DBag { } + f0(x) + f1(x) + f2(x) + f3(x) + f4(x) + f5(x) + f6(x) + f7(x) + } +} + +object TestE { + def f0(x: Base[String, String, String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f1(x: E1[String, String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f2(x: E2[String, String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f3(x: E3[String, String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f4(x: E4[String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f5(x: E5[String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f6(x: E6[String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f7(x: E7) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + + def main(args: Array[String]): Unit = { + val x = new EBag { } + f0(x) + f1(x) + f2(x) + f3(x) + f4(x) + f5(x) + f6(x) + f7(x) + } +} + + +object TestG { + def f0(x: Base[String, String, String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f1(x: GBag1[String, String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f2(x: GBag2[String]) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + def f3(x: GBag3) = { x.f("a", "b", "c") ; x.g("a", "b", "c") ; x.h("a", "b", "c") } + + def main(args: Array[String]): Unit = { + val x = new GBag { } + f0(x) + f1(x) + f2(x) + f3(x) + } +} diff --git a/test/files/run/t3452d/A.scala b/test/files/run/t3452d/A.scala new file mode 100644 index 000000000000..67a2080d273b --- /dev/null +++ b/test/files/run/t3452d/A.scala @@ -0,0 +1,7 @@ +trait TraversableLike[A, Repr] { + def tail: Repr = null.asInstanceOf[Repr] +} + +abstract class AbstractTrav[A] extends TraversableLike[A, Traversable[A]] + +class C[A] extends AbstractTrav[A] diff --git a/test/files/run/t3452d/Test.java b/test/files/run/t3452d/Test.java new file mode 100644 index 000000000000..875be6176c5b --- /dev/null +++ b/test/files/run/t3452d/Test.java @@ -0,0 +1,12 @@ +import scala.collection.immutable.Nil; +import scala.collection.immutable.List; +import scala.collection.Traversable; + +public class Test { + public static void main(String[] args) { + C c = new C(); + // TODO add a bridge during mixin so we can expose + // sharper generic signature for `tail`. + /*Traversable*/ Object ls = c.tail(); + } +} diff --git a/test/files/run/t3452e/A.scala b/test/files/run/t3452e/A.scala new file mode 100644 index 000000000000..939172f4013c --- /dev/null +++ b/test/files/run/t3452e/A.scala @@ -0,0 +1,4 @@ +trait F1[T, R] { + def andThen[A](g: R => A): Int = 0 +} +class C1[TT, RR] extends F1[TT, RR] diff --git a/test/files/run/t3452e/B.java b/test/files/run/t3452e/B.java new file mode 100644 index 000000000000..0268af9987a4 --- /dev/null +++ b/test/files/run/t3452e/B.java @@ -0,0 +1,2 @@ +class B extends C1 { +} diff --git a/test/files/run/t3452e/Test.scala b/test/files/run/t3452e/Test.scala new file mode 100644 index 000000000000..fc175bf94a3a --- /dev/null +++ b/test/files/run/t3452e/Test.scala @@ -0,0 +1,3 @@ +object Test extends App { + new B +} diff --git a/test/files/run/t3452f.scala b/test/files/run/t3452f.scala new file mode 100644 index 000000000000..af64f5c04229 --- /dev/null +++ b/test/files/run/t3452f.scala @@ -0,0 +1,19 @@ +import language.higherKinds + +trait GenSet[A] + +trait GenSetTemplate[A, +CC[X] <: GenSet[X]] { + def empty: CC[A] = ??? +} + +trait SetLike[A, +This <: SetLike[A, This] with Set[A]] { + def empty: This +} + +abstract class Set[A] extends GenSet[A] with SetLike[A,Set[A]] with GenSetTemplate[A,Set] + +object Test { + def main(args: Array[String]): Unit = { + locally(classOf[Set[_]]) // trigger classloading to verify class + } +} diff --git a/test/files/run/t3452g/A.scala b/test/files/run/t3452g/A.scala new file mode 100644 index 000000000000..a3f74c1e1e4c --- /dev/null +++ b/test/files/run/t3452g/A.scala @@ -0,0 +1,9 @@ +trait TraversableLike[A, Repr] { + def tail: Repr = null.asInstanceOf[Repr] +} + +abstract class AbstractTrav[A] extends TraversableLike[A, AbstractTrav[A]] + +object O extends AbstractTrav[String] + +class C[A] extends AbstractTrav[A] diff --git a/test/files/run/t3452g/Test.java b/test/files/run/t3452g/Test.java new file mode 100644 index 000000000000..c3b4222d1686 --- /dev/null +++ b/test/files/run/t3452g/Test.java @@ -0,0 +1,14 @@ + +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, because the erasure + // of this (Traversable) differs from the erasure of the mixed + // method (erasure(Repr) = Object) + + Object lsSharp = O.tail(); + + Object lsSharp2 = new C().tail(); + } +} diff --git a/test/files/run/t3452h.scala b/test/files/run/t3452h.scala new file mode 100644 index 000000000000..6237d3ea641a --- /dev/null +++ b/test/files/run/t3452h.scala @@ -0,0 +1,8 @@ +class Mix___eFoo_I_wBar__f extends Foo_I_ with Bar__f { f; } +trait T +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 App { + new Mix___eFoo_I_wBar__f +} diff --git a/test/files/run/t6554.check b/test/files/run/t6554.check new file mode 100644 index 000000000000..6e0af7b47485 --- /dev/null +++ b/test/files/run/t6554.check @@ -0,0 +1 @@ +public java.lang.Object Bar.minBy(java.lang.Object) / public java.lang.Object Bar.minBy(java.lang.Object) diff --git a/test/files/run/t6554.scala b/test/files/run/t6554.scala new file mode 100644 index 000000000000..5d29d166665b --- /dev/null +++ b/test/files/run/t6554.scala @@ -0,0 +1,11 @@ +trait Foo[A] { + def minBy[B](b: B): A = ??? +} + +class Bar extends Foo[Int] + +object Test extends App { + val sigs = classOf[Bar].getDeclaredMethods.map(m => s"${m.toString} / ${m.toGenericString}").sorted + println(sigs.mkString("\n")) +} +// Was public java.lang.Object Bar.minBy(java.lang.Object) / public int Bar.minBy(B) diff --git a/test/support/java-tests.txt b/test/support/java-tests.txt new file mode 100644 index 000000000000..e0a3fddab33d --- /dev/null +++ b/test/support/java-tests.txt @@ -0,0 +1,97 @@ +test/files/buildmanager/t2280 +test/files/buildmanager/t3045 +test/files/buildmanager/t3133 +test/files/jvm/deprecation +test/files/jvm/t1143-2 +test/files/jvm/t1342 +test/files/jvm/t1464 +test/files/jvm/t2470 +test/files/jvm/t2570 +test/files/jvm/t2585 +test/files/jvm/t3003 +test/files/jvm/t3415 +test/files/jvm/ticket2163 +test/files/jvm/ticket4283 +test/files/jvm/varargs +test/files/neg/abstract-class-error +test/files/neg/java-access-neg +test/files/neg/primitive-sigs-1 +test/files/neg/protected-static-fail +test/files/neg/t0673 +test/files/neg/t1548 +test/files/neg/t3663 +test/files/neg/t3757 +test/files/neg/t4851 +test/files/pos/chang +test/files/pos/ilya +test/files/pos/ilya2 +test/files/pos/java-access-pos +test/files/pos/javaReadsSigs +test/files/pos/protected-static +test/files/pos/raw-map +test/files/pos/signatures +test/files/pos/super +test/files/pos/t0288 +test/files/pos/t0695 +test/files/pos/t1101 +test/files/pos/t1102 +test/files/pos/t1150 +test/files/pos/t1152 +test/files/pos/t1176 +test/files/pos/t1186 +test/files/pos/t1196 +test/files/pos/t1197 +test/files/pos/t1203 +test/files/pos/t1230 +test/files/pos/t1231 +test/files/pos/t1232 +test/files/pos/t1235 +test/files/pos/t1254 +test/files/pos/t1263 +test/files/pos/t1409 +test/files/pos/t1459 +test/files/pos/t1642 +test/files/pos/t1711 +test/files/pos/t1745 +test/files/pos/t1751 +test/files/pos/t1782 +test/files/pos/t1836 +test/files/pos/t1840 +test/files/pos/t1937 +test/files/pos/t2377 +test/files/pos/t2409 +test/files/pos/t2413 +test/files/pos/t2433 +test/files/pos/t2464 +test/files/pos/t2569 +test/files/pos/t2868 +test/files/pos/t294 +test/files/pos/t2940 +test/files/pos/t2956 +test/files/pos/t3249 +test/files/pos/t3349 +test/files/pos/t3404 +test/files/pos/t3429 +test/files/pos/t3486 +test/files/pos/t3521 +test/files/pos/t3567 +test/files/pos/t3622 +test/files/pos/t3642 +test/files/pos/t3938 +test/files/pos/t3946 +test/files/pos/t4402 +test/files/pos/t4603 +test/files/pos/t4737 +test/files/pos/t5644 +test/files/pos/t5703 +test/files/run/inner-parse +test/files/run/t1430 +test/files/run/t2296a +test/files/run/t2296b +test/files/run/t3452a +test/files/run/t3452b +test/files/run/t3897 +test/files/run/t4119 +test/files/run/t4238 +test/files/run/t4317 +test/files/run/t4891 diff --git a/tools/compare-java-sigs b/tools/compare-java-sigs new file mode 100644 index 000000000000..99ab775437e8 --- /dev/null +++ b/tools/compare-java-sigs @@ -0,0 +1,56 @@ +#!/bin/sh +# +# Compare javac -Xprint (i.e. see signatures from java point of view) +# for the given classes. +# +# Sample: +# +# % SCALA_HOME=/scala/inst/29 SCALA_BUILD=/scala/inst/3 tools/compare-java-sigs 'scala.Predef$' +# +# Comparing javac -Xprint for scala.Predef$ based on '/scala/inst/29' and '/scala/inst/3' +# 3c3 +# < public final class Predef$ extends scala.LowPriorityImplicits implements scala.ScalaObject { +# --- +# > public final class Predef$ extends scala.LowPriorityImplicits { +# 7d6 +# < private final scala.SpecializableCompanion AnyRef; +# 21,22d19 +# < public scala.SpecializableCompanion AnyRef(); +# < +# 68a66,67 +# > public scala.runtime.Nothing$ $qmark$qmark$qmark(); +# > +# 225c224,226 +# < public scala.collection.immutable.StringOps augmentString(java.lang.String x); +# --- +# > public scala.runtime.StringFormat any2stringfmt(java.lang.Object x); +# > +# > public java.lang.String augmentString(java.lang.String x); +# 227c228 +# < public java.lang.String unaugmentString(scala.collection.immutable.StringOps x); +# --- +# > public java.lang.String unaugmentString(java.lang.String x); +# + +set -e + +[[ $# -gt 0 ]] || { + echo "Usage: $(basename $0) ..." + echo "" + echo "# Example usage" + echo "SCALA_HOME=/scala/inst/29 SCALA_BUILD=/scala/inst/3 \\" + echo " $(basename $0) scala.Function1 scala.runtime.AbstractFunction1" + exit 0 +} + +home1=$(cd ${SCALA_HOME:-/scala/inst/3} && pwd) +home2=$(cd ${SCALA_BUILD:-$(dirname $BASH_SOURCE)/../build/pack} && pwd) + +echo "Comparing javac -Xprint for $@ based on '$home1' and '$home2'" +tmpdir=$(mktemp -dt $(basename $BASH_SOURCE)) + +cd $tmpdir +javac -Xprint -cp $home1:$home1/lib/'*' "$@" > before.txt +javac -Xprint -cp $home2:$home2/lib/'*' "$@" > after.txt + +diff before.txt after.txt && echo "No differences in javac -Xprint output."