Skip to content

ClassCastException when calling a trait method with call-by-name argument if implemented as single abstract method #11237

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

Open
ruslan2009 opened this issue Nov 1, 2018 · 2 comments · May be fixed by scala/scala#10830
Labels
byname fixed in Scala 3 This issue does not exist in the Scala 3 compiler (https://github.com/lampepfl/dotty/) has PR sam
Milestone

Comments

@ruslan2009
Copy link

ruslan2009 commented Nov 1, 2018

The following code:

trait Semigroup[F] { self =>
  def append(f1: F, f2: => F): F
  val z = 10
}

object bug extends App {
  case class Box(i: Int)
  val boxSemigroup: Semigroup[Box] = (x1, x2) => Box(Math.max(x1.i, x2.i))
  println(boxSemigroup.append(Box(1), Box(2)))
}

crashes at runtime with ClassCastException

Exception in thread "main" java.lang.ClassCastException: bug$Box cannot be cast to scala.Function0
	at bug$$anonfun$1.append(bug.scala:75)
	at bug$$anonfun$1.append(bug.scala:75)
	at bug$.delayedEndpoint$bug$1(bug.scala:76)
	at bug$delayedInit$body.apply(bug.scala:73)
	at scala.Function0.apply$mcV$sp(Function0.scala:34)
	at scala.Function0.apply$mcV$sp$(Function0.scala:34)
	at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
	at scala.App.$anonfun$main$1$adapted(App.scala:76)
	at scala.collection.immutable.List.foreach(List.scala:389)
	at scala.App.main(App.scala:76)
	at scala.App.main$(App.scala:74)
	at bug$.main(bug.scala:73)
	at bug.main(bug.scala)

Seems like the bytecode generated contains erroneous call to function representing call-by-name argument (instruction at 2) and then casting of it's result to Function0 (instruction at 7):

  public final bug$Box append(bug$Box, scala.Function0<bug$Box>);
    Code:
       0: aload_1
       1: aload_2
       2: invokeinterface #35,  1           // InterfaceMethod scala/Function0.apply:()Ljava/lang/Object;
       7: checkcast     #31                 // class scala/Function0
      10: invokestatic  #38                 // Method bug$.bug$$$anonfun$boxSemigroup$1:(Lbug$Box;Lscala/Function0;)Lbug$Box;
      13: areturn

Also note that if name of call-by-name argument in trait and SAM implementation are the same as in:

trait Semigroup[F] { self =>
  def append(f1: F, x2: => F): F
  val z = 10
}

object bug extends App {
  case class Box(i: Int)
  val boxSemigroup: Semigroup[Box] = (x1, x2) => Box(Math.max(x1.i, x2.i))
  println(boxSemigroup.append(Box(1), Box(2)))
}

then it works as expected and compiler generates correct bytecode for this function:

  public final bug$Box append(bug$Box, scala.Function0<bug$Box>);
    Code:
       0: aload_1
       1: aload_2
       2: invokestatic  #32                 // Method bug$.bug$$$anonfun$boxSemigroup$1:(Lbug$Box;Lscala/Function0;)Lbug$Box;
       5: areturn

Tried with Scala versions 2.12.7 and 2.13.0-M5.

Also seems like this issue is related to #10362 since same exception is being thrown at runtime and similar bytecode is being generated in both cases.

@SethTisue SethTisue added this to the Backlog milestone Feb 5, 2019
@SethTisue
Copy link
Member

Dotty: Exception in thread "main" java.lang.AssertionError: assertion failed: private method $anonfun in rs$line$1 accessed from method append in null #not-fixed-in-dotty

@som-snytt
Copy link

Fixed in 3.4.2, and verified that the function parameter is inferred "by-name". That is what the PR does.

That is, here, B is printed twice. There is precedent for forwarding thunks, and the justification here is that the types of the function literal are inferred. (You can't write (x: => Int) => in source.)

    val boxSemigroup: Semigroup[Box] = (x1, x2) => Box(x1.i + x2.i + x2.i)
    //val boxSemigroup: Semigroup[Box] = (x1: Box, x2: Box) => Box(Math.max(x1.i, x2.i)) // disallowed, by-name must be inferred
    assert(boxSemigroup.append(Box(1), {println("B");Box(2)}) == Box(3))

@som-snytt som-snytt added the fixed in Scala 3 This issue does not exist in the Scala 3 compiler (https://github.com/lampepfl/dotty/) label Aug 10, 2024
@SethTisue SethTisue modified the milestones: Backlog, 2.13.15 Aug 13, 2024
@SethTisue SethTisue modified the milestones: 2.13.15, 2.13.16 Aug 22, 2024
@SethTisue SethTisue modified the milestones: 2.13.16, 2.13.17 Dec 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
byname fixed in Scala 3 This issue does not exist in the Scala 3 compiler (https://github.com/lampepfl/dotty/) has PR sam
Projects
None yet
4 participants