Skip to content

Generic function creates anonymous class that uses function's type parameter, compiles but fails at runtime #2144

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

Closed
scabug opened this issue Jul 9, 2009 · 11 comments
Assignees
Milestone

Comments

@scabug
Copy link

scabug commented Jul 9, 2009

The method called approach3 in the attached example compiles but fails with NoSuchMethodException at runtime.

The problem concerns this construct:

def convert[A](a: A) = new { def frob(a2: A) = ... }
convert("foo").frob("bar")

Scala throws a NoSuchMethodException naming frob(String). Scala is trying to locate the frob method using the actual argument type, which is String. But the parameter type of frob is the erasure of A, which is Object.

Scala knows that frob is effectively a generic method, because it does correctly check the type of its argument at compile time. This line of code will not compile:

convert("foo").frob(1)

But it seems like the fact that it is a generic method gets lost somewhere within the call-by-reflection code.

I'm reporting this as a bug because approach1 and approach2, which are logically equivalent, work, probably because they don't require Scala to use reflection internally. If approach3 doesn't work by design, then I think that the Scala compiler should flag it as an error.

@scabug
Copy link
Author

scabug commented Jul 9, 2009

Imported From: https://issues.scala-lang.org/browse/SI-2144?orig=1
Reporter: Willis Blackburn (willisblackburn)
Attachments:

@scabug
Copy link
Author

scabug commented Jul 9, 2009

Willis Blackburn (willisblackburn) said:
Test case

@scabug
Copy link
Author

scabug commented Jul 9, 2009

@paulp said:
It looks like 2.8 takes a step backward, as the reported bug in approach3 still exists but in addition approach1 is now a crasher.

  def approach1 {
    class Wrapper[A](a: A) {
      def frob(a2: A) = a
    }
    implicit def convert[A](a: A) = new Wrapper(a)
    "foo".frob("bar")
  }
Exception in thread "main" java.lang.AssertionError: assertion failed
	at scala.Predef$$.assert(Predef.scala:92)
	at scala.tools.nsc.transform.LambdaLift$$$$anon$$1.apply(LambdaLift.scala:27)
	at scala.tools.nsc.transform.LambdaLift$$$$anon$$1.apply(LambdaLift.scala:23)
	at scala.tools.nsc.symtab.Types$$TypeMap.mapOver(Types.scala:2465)
	at scala.tools.nsc.transform.LambdaLift$$$$anon$$1.apply(LambdaLift.scala:35)
...

@scabug
Copy link
Author

scabug commented Jul 10, 2009

Willis Blackburn (willisblackburn) said:
There should be a line break before "convert" in the example:

def convert[A](a: A) = new { def frob(a2: A) = ... }[[BR]]convert("foo").frob("bar")

@scabug
Copy link
Author

scabug commented Nov 4, 2009

andrew cooke (andrew) said:
I am seeing what looks like the same issue when I use structural typing to match a generic class. When I call a method defined in the structural type the underlying generic implementation is called with the argument type in the structural type rather than Object (as above).

@scabug
Copy link
Author

scabug commented Nov 4, 2009

@paulp said:
Replying to [comment:5 andrew]:

I am seeing what looks like the same issue when I use structural typing to match a generic class. When I call a method defined in the structural type the underlying generic implementation is called with the argument type in the structural type rather than Object (as above).

This ticket might actually be zeroing in on closeable. Approaches 1 and 2 above work as they should, and on closer examination the problem with 3 is not that it doesn't work but that it compiles at all. As nearly as I can see it is a fine example of the prohibition "Parameter type in structural refinement may not refer to abstract type defined outside that same refinement", but not being detected.

See http://article.gmane.org/gmane.comp.lang.scala/7013 for an old explanation of why this is disallowed; old but I think still valid. It catches this case no problem:

scala> type foo[A] = { def bar(x: A): A }       
<console>:4: error: Parameter type in structural refinement may not refer to abstract type defined outside that same refinement
       type foo[A] = { def bar(x: A): A }
                                  ^

But it will let you create this:

scala> def foo[A] = new { def bar(x: A): A = x }
foo: [A]java.lang.Object{def bar(x: A): A}

...resulting in a bar method which cannot be called unless A is Object.

@scabug
Copy link
Author

scabug commented Nov 4, 2009

@paulp said:
In r19399 I have spiced up the logic such that case three no longer compiles. Although I can't be 100% sure of the validity of the fix, bugs like this which manifest as runtime failures are too dangerous to leave around. I am leaving the ticket open for gilles to review when he has the opportunity.

@scabug
Copy link
Author

scabug commented Nov 4, 2009

andrew cooke (andrew) said:
Thanks. I'll try the next nightly build.

@scabug
Copy link
Author

scabug commented Nov 6, 2009

andrew cooke (andrew) said:
OK, maybe this should be a separate ticket? I am not really sure if it's the same issue or not, but it still fails with 2.8.0-20091106.025327. Here's an example - the jgrapht references are what I am using in my application, but "MyGraph" is sufficient to show the problem:

package bug

//import org.jgrapht.graph.SimpleGraph
//import org.jgrapht.graph.DefaultWeightedEdge

object Main extends Application {

  class MyGraph[V <: Any] {
    def addVertex(v: V): Boolean = true
  }

  type DuckGraph = {
    def addVertex(vertex: Int): Boolean
  }

  def crash(graph: DuckGraph) {
    println("trying...")
    graph.addVertex(1)
    println("success")
  }

  crash(new MyGraph[Int])
//  crash(new SimpleGraph[Int, DefaultWeightedEdge](classOf[DefaultWeightedEdge]))
}

Cheers + thanks for what is (otehrwise) a pretty nice practical language that I hope one day to use at work...

@scabug
Copy link
Author

scabug commented Nov 6, 2009

andrew cooke (andrew) said:
Oh, and the error:

trying...
java.lang.ExceptionInInitializerError
        at bug.Main.main(Main.scala)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at scala.util.ScalaClassLoader$$$$anonfun$$run$$1.apply(ScalaClassLoader.scala:54)
        at scala.util.ScalaClassLoader$$class.asContext(ScalaClassLoader.scala:21)
        at scala.util.URLClassLoader.asContext(ScalaClassLoader.scala:58)
        at scala.util.ScalaClassLoader$$class.run(ScalaClassLoader.scala:54)
        at scala.util.URLClassLoader.run(ScalaClassLoader.scala:58)
        at scala.tools.nsc.ObjectRunner$$.run(ObjectRunner.scala:33)
        at xsbt.RunInterface.run(RunInterface.scala:17)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at xsbt.AnalyzingCompiler.call(AnalyzingCompiler.scala:61)
        at xsbt.AnalyzingCompiler.run(AnalyzingCompiler.scala:45)
        at sbt.Run.execute$$1(Run.scala:43)
        at sbt.Run$$$$anonfun$$run$$1.apply(Run.scala:44)
        at sbt.Run$$$$anonfun$$run$$1.apply(Run.scala:44)
        at sbt.TrapExit$$.executeMain$$1(TrapExit.scala:32)
        at sbt.TrapExit$$$$anon$$1.run(TrapExit.scala:34)
Caused by: java.lang.NoSuchMethodException: bug.Main$$MyGraph.addVertex(int)
        at java.lang.Class.getMethod(Class.java:1605)
        at bug.Main$$.reflMethod$$Method1(Main.scala:19)
        at bug.Main$$.crash(Main.scala:19)
        at bug.Main$$.<init>(Main.scala:23)
        at bug.Main$$.<clinit>(Main.scala)
        ... 23 more

@scabug
Copy link
Author

scabug commented Nov 19, 2009

@paulp said:
Replying to [comment:9 andrew]:

OK, maybe this should be a separate ticket?

You have indeed found a whole separate issue. As this one has been addressed with test case as of r19442 I'm closing this and have opened #2672 for the new one.

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

No branches or pull requests

2 participants