Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

name clash after erasure error can be bypassed by anonymous classes or private methods #9286

Closed
scabug opened this issue Apr 29, 2015 · 7 comments
Assignees
Milestone

Comments

@scabug
Copy link

scabug commented Apr 29, 2015

The following minimization illustrates the bug:

abstract class Piol extends java.util.PrimitiveIterator.OfLong {
  def hasNext = ???
  def nextLong() = ???
}

abstract class Foo[Whatever] { def pi: Whatever }

class Bar extends Foo[Piol] {
  def pi: Piol = new Piol {
    // This ends up private, but shadows the public one.
    // Buggy; gives IllegalAccessError when called.
    def forEachRemaining(c: java.util.function.Consumer[_ >: Long]) { ??? }
  }
}

class Baz extends Foo[Piol] {
  def pi: Piol = new Piol {
    // This is correctly public (and overrides the default method)
    def forEachRemaining(c: java.util.function.Consumer[_ >: java.lang.Long]) { ??? }
  }
}

class Quux {
  def pi: Piol = new Piol {
    // This is also correct (overrides default)
    def forEachRemaining(c: java.util.function.Consumer[_ >: java.lang.Long]) { ??? }
  }
}

class Bippy {
  def pi: Piol = new Piol {
    // Compile error if uncommented--incomprehensible unless you guess that
    // Long != Long (java.lang vs. scala) but at least it doesn't work.
    // def forEachRemaining(c: java.util.function.Consumer[_ >: Long]) { ??? }
  }
}

Note that only when the return type is a generic (even if the exact type is specified) does the compiler silently...do...I'm not even sure what it's doing. It's confused about what Long should be, and somehow that causes it to emit the method as private even though it's not marked private and it is identical after erasure to a public method.

The Bippy version (where the return type is not in any way generic) does not exhibit this problem, and Baz and Quux show that it works the way it ought to if you get the correct type in place.

@scabug
Copy link
Author

scabug commented Apr 29, 2015

Imported From: https://issues.scala-lang.org/browse/SI-9286?orig=1
Reporter: @Ichoran
Affected Versions: 2.11.6

@scabug
Copy link
Author

scabug commented Apr 30, 2015

@retronym said (edited on May 1, 2015 1:50:49 AM UTC):
Minimized a bit further:

class M[_]
trait T {
  def foo(m: M[_ >: String]) = 42
}

// class C extends T {
//   def foo(m: M[_ >: Any]) = 0 // gives "same type after erasure error"
// }

object Test {
  def t: T = new T {
    def foo(m: M[_ >: Any]) = 0
  }
  def main(args: Array[String]): Unit = {
    val m: M[String] = null
    t.foo(m) // VeriyError: Duplicate method name&signature
  }
}

This no doubt stems from the acrobatics performed in typechecking when deciding what declarations of anonymous classes ought to be hidden: https://github.com/scala/scala/blob/v2.11.6/src/compiler/scala/tools/nsc/typechecker/Typers.scala#L2327-L2384

Workaround is to use the override modifier, match the signatures exactly, or use named classes.

@scabug
Copy link
Author

scabug commented Apr 30, 2015

@retronym said:
Here's a pared down version of your test case that exhibits the IllegalAccessError rather than a VerifyError.

import java.util.PrimitiveIterator
import java.util.function.Consumer

class Bar {
  def pi: PrimitiveIterator.OfLong = new PrimitiveIterator.OfLong {
    // Adding override modifier offers: "method forEachRemaining overrides nothing"
    // The compiler should disallow this under the "double definition after erasure" rule.
    def forEachRemaining(c: Consumer[_ >: scala.Long]) { ??? }

    def hasNext: Boolean = false
    def nextLong: Long = ???
  }
}

object Test {
  def main(args: Array[String]): Unit = {
    val consumer: Consumer[java.lang.Long] = null
    new Bar().pi.forEachRemaining(consumer) // IllegalAccessError: Bar$$anon$1.forEachRemaining(Ljava/util/function/Consumer;)V
  }
}

@scabug
Copy link
Author

scabug commented May 1, 2015

@retronym said:
A variation that doesn't involve anonymous classes, just a private method that erases to public signatures in the parent type.

class T {
  def foo(o: Tuple1[String]) = ()
}

class U extends T {
  private def foo(o: Tuple1[Any]) = ()
}
 
object Test {
  def main(args: Array[String]): Unit = {
    new U().foo(null) // IllegalAccessError:  tried to access method U.foo(Lscala/Tuple1;)V from class Test$
  }
}

@scabug
Copy link
Author

scabug commented May 1, 2015

@retronym said:
And another:

scala> class C { def foo = 0 }; class D extends C { private def foo[A] = 0 }
defined class C
defined class D

scala> new D().foo
java.lang.IllegalAccessError: tried to access method D.foo()I from class
  ... 33 elided

scala> (new D() : C).foo
res1: Int = 0

@scabug
Copy link
Author

scabug commented May 1, 2015

@retronym said (edited on May 1, 2015 2:20:19 AM UTC):
Comparing with javac:

% cat sandbox/Test.java
public abstract class Test {
  class C { int foo() { return 0; } }
  class D extends C { private <A> int foo() { return 1; } }
}

% javac sandbox/Test.java
sandbox/Test.java:3: error: name clash: <A>foo() in Test.D and foo() in Test.C have the same erasure, yet neither overrides the other
  class D extends C { private <A> int foo() { return 1; } }
                                      ^
  where A is a type-variable:
    A extends Object declared in method <A>foo()
1 error

@scabug
Copy link
Author

scabug commented May 1, 2015

@retronym said:
scala/scala#4477

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants