forked from scala/scala
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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<Ljava/lang/String;>; --- > signature f (Ljava/lang/Object;)Lscala/collection/immutable/List<Ljava/lang/Object;>; This takes the warning count for compiling collections under `-Ycheck:jvm` from 1521 to 26.
- Loading branch information
Showing
31 changed files
with
649 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
class Base[Coll] { | ||
trait Transformed[S] { | ||
lazy val underlying: Coll = ??? | ||
} | ||
} | ||
|
||
class Derived extends Base[String] { | ||
class C extends Transformed[Any] | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) <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) | ||
} | ||
|
||
class Test$bar2$ { | ||
public java.lang.Object Test$bar2$.f(java.lang.String) | ||
public java.lang.Object Test$bar2$.f(java.lang.Object) <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) | ||
} | ||
|
||
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) | ||
} | ||
|
||
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) | ||
} | ||
|
||
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) | ||
} | ||
|
||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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[_]) { | ||
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
BulkSearch.searchFor called. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
public class J_2 { | ||
public static void main(String[] args) { | ||
BulkSearchInstance.searchFor(new UpRelation()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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[_]] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
object Test { | ||
def main(args: Array[String]): Unit = { | ||
J_2.main(args) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Search received: test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
public class J_2 { | ||
public static void j() { | ||
StringSearch.search("test"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
object Test { | ||
def main(args: Array[String]): Unit = { | ||
J_2.j() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
3 | ||
3 | ||
3 | ||
3 | ||
3 | ||
3 | ||
3 | ||
3 |
Oops, something went wrong.