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

ClassCastException on by-name parameter in enrichment method #8035

Closed
travisbrown opened this issue Jan 19, 2020 · 1 comment
Closed

ClassCastException on by-name parameter in enrichment method #8035

travisbrown opened this issue Jan 19, 2020 · 1 comment
Assignees

Comments

@travisbrown
Copy link
Contributor

minimized code

implicit class Ops[A](val a: String) extends AnyVal {
  def foo(e: => String): Unit = ()
}

def bar(e: => String): Unit = (new Ops("")).foo(e)
def baz(e: => String): Unit = "".foo(e)
Compiles but fails at runtime
scala> bar("")

scala> baz("")
java.lang.ClassCastException: java.lang.String incompatible with scala.Function0
	at rs$line$1$.baz(rs$line$1:6)
	at rs$line$3$.<init>(rs$line$3:1)
	at rs$line$3$.<clinit>(rs$line$3)
	at rs$line$3.res0(rs$line$3)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at dotty.tools.repl.Rendering.$anonfun$3(Rendering.scala:72)
	at dotty.tools.repl.Rendering$$Lambda$1465.000000006884EA20.apply(Unknown Source)
	at scala.Option.map(Option.scala:242)
	at dotty.tools.repl.Rendering.valueOf(Rendering.scala:72)
	at dotty.tools.repl.Rendering.renderVal(Rendering.scala:95)
	at dotty.tools.repl.ReplDriver.displayMembers$3$$anonfun$3(ReplDriver.scala:291)
	at dotty.tools.repl.ReplDriver$$Lambda$1411.00000000649C0620.apply(Unknown Source)
	at scala.collection.immutable.List.map(List.scala:219)
	at scala.collection.immutable.List.map(List.scala:79)
	at dotty.tools.repl.ReplDriver.displayMembers$6(ReplDriver.scala:291)
	at dotty.tools.repl.ReplDriver.displayDefinitions$$anonfun$3$$anonfun$2(ReplDriver.scala:317)
	at dotty.tools.repl.ReplDriver$$Lambda$1370.0000000066279820.apply(Unknown Source)
	at scala.Option.map(Option.scala:242)
	at dotty.tools.repl.ReplDriver.displayDefinitions$$anonfun$1(ReplDriver.scala:317)
	at dotty.tools.repl.ReplDriver$$Lambda$1366.0000000066276E20.apply(Unknown Source)
	at dotty.tools.dotc.core.Phases.atPhase$$anonfun$1(Phases.scala:35)
	at dotty.tools.dotc.core.Phases$$Lambda$1031.000000006780A020.apply(Unknown Source)
	at dotty.tools.dotc.core.Periods.atPhase(Periods.scala:25)
	at dotty.tools.dotc.core.Phases.atPhase(Phases.scala:35)
	at dotty.tools.dotc.core.Contexts$Context.atPhase(Contexts.scala:75)
	at dotty.tools.repl.ReplDriver.displayDefinitions(ReplDriver.scala:323)
	at dotty.tools.repl.ReplDriver.compile$$anonfun$2(ReplDriver.scala:245)
	at dotty.tools.repl.ReplDriver$$Lambda$1362.0000000066274A20.apply(Unknown Source)
	at scala.util.Either.fold(Either.scala:189)
	at dotty.tools.repl.ReplDriver.compile(ReplDriver.scala:247)
	at dotty.tools.repl.ReplDriver.interpret(ReplDriver.scala:193)
	at dotty.tools.repl.ReplDriver.loop$1(ReplDriver.scala:127)
	at dotty.tools.repl.ReplDriver.runUntilQuit$$anonfun$1(ReplDriver.scala:130)
	at dotty.tools.repl.ReplDriver$$Lambda$230.00000000668CFA20.apply(Unknown Source)
	at dotty.tools.repl.ReplDriver.withRedirectedOutput(ReplDriver.scala:148)
	at dotty.tools.repl.ReplDriver.runUntilQuit(ReplDriver.scala:130)
	at dotty.tools.repl.Main$.main(Main.scala:6)
	at dotty.tools.repl.Main.main(Main.scala)

expectation

This works fine on Scala 2, and it works fine here if you remove the unused [A] on Ops, or if you remove the extends AnyVal, or if you do both. If you change the implicit class to something like this:

implicit def toOps(a: String): Ops[Unit] = new Ops(a)

class Ops[A](val a: String) extends AnyVal {
  def foo(e: => String): Unit = ()
}

def baz(e: => String): Unit = "".foo(e)

…Dotty still throws the ClassCastException.

@odersky
Copy link
Contributor

odersky commented Jan 20, 2020

It's a bad interaction between two miniphases: VCInlineMethods and ElimByName. They currently run in the same macro phase, but ElimByName should only run once VCInlineMethods has completed everywhere. If I split the macro phase into two groups, the first with VCInlineMethods and the second with ElimByName, it works as expected.

Unfortunately, we cannot move phases around to satisfy all constraints without creating an extra macro phase. But we thought for a while that it would be cleaner to run ElimByName as part of Erasure, since right now it requires an ugly hidden channel of information between the two phases (I am speaking of <cbn-arg>). If we did that, this issue would be solved as well.

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