Skip to content

Invariant SAM type that's not a platform SAM: AbstractMethodError (JVM) / Referring to non-existent method (JS) #16388

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
justcoon opened this issue Nov 21, 2022 · 5 comments

Comments

@justcoon
Copy link

Compiler version

scala 3.2.0, scalajs 1.11.0

Minimized code

import java.util.{function, Map => JMap}

trait Ops {

  private val comparatorCache: JMap[(Int, Int), Boolean] =
    new java.util.HashMap()

  def isLess(left: Int, right: Int): Boolean = {

    // working

//    val f = new function.Function[(Int, Int), Boolean] {
//      override def apply(t: (Int, Int)): Boolean = left.compareTo(right) <= 0
//    }
//
//    comparatorCache.computeIfAbsent((left, right), f)

    // NOT working
    comparatorCache.computeIfAbsent(
      (left, right),
      _ => left.compareTo(right) <= 0
    )
  }
}

object Test1 extends Ops {}

println(Test1.isLess(1, 2))
println(Test1.isLess(1, 2))
println(Test1.isLess(2, 1))

https://scastie.scala-lang.org/kwQJBQRlSneSBEP93zzz8w

Output

[error] Referring to non-existent method Ops$$anon$1.apply(java.lang.Object)java.lang.Object
[error]   called from java.util.Map.computeIfAbsent(java.lang.Object,java.util.function.Function)java.lang.Object
[error]   called from Ops.isLess(int,int)boolean
[error]   called from Test1$.isLess(int,int)boolean
[error]   called from private Test1Tests$.tests$$anonfun$1$$anonfun$1$$anonfun$1(scala.Function1)boolean
[error]   called from private Test1Tests$.tests$$anonfun$1$$anonfun$1()scala.util.Left
[error]   called from private Test1Tests$.tests$$anonfun$1()scala.util.Right
[error]   called from Test1Tests$.tests()utest.Tests
[error]   called from utest.runner.BaseRunner.runSuite(scala.collection.immutable.Seq,java.lang.String,sbt.testing.EventHandler,sbt.testing.TaskDef)scala.concurrent.Future
[error]   called from private utest.runner.BaseRunner.makeTask$$anonfun$1(sbt.testing.TaskDef,scala.collection.immutable.Seq,sbt.testing.EventHandler)scala.concurrent.Future
[error]   called from private utest.runner.BaseRunner.makeTask(sbt.testing.TaskDef)sbt.testing.Task
[error]   called from utest.runner.BaseRunner.deserializeTask(java.lang.String,scala.Function1)sbt.testing.Task
[error]   called from org.scalajs.testing.bridge.TaskInfoBuilder$.attachTask(org.scalajs.testing.common.TaskInfo,sbt.testing.Runner)sbt.testing.Task
[error]   called from private org.scalajs.testing.bridge.TestAdapterBridge$.$anonfun$executeFun$1(sbt.testing.Runner,int,org.scalajs.testing.common.ExecuteRequest)scala.concurrent.Future
[error]   called from private org.scalajs.testing.bridge.TestAdapterBridge$.executeFun(int,sbt.testing.Runner)scala.Function1
[error]   called from private org.scalajs.testing.bridge.TestAdapterBridge$.$anonfun$createRunnerFun$1(boolean,org.scalajs.testing.common.RunnerArgs)void
[error]   called from private org.scalajs.testing.bridge.TestAdapterBridge$.createRunnerFun(boolean)scala.Function1
[error]   called from org.scalajs.testing.bridge.TestAdapterBridge$.start()void
[error]   called from org.scalajs.testing.bridge.Bridge$.start()void
[error]   called from static org.scalajs.testing.bridge.Bridge.start()void
[error]   called from core module module initializers

Expectation

code will run without issues

@justcoon justcoon added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Nov 21, 2022
@sjrd
Copy link
Member

sjrd commented Nov 21, 2022

Thanks for the report.

@sjrd sjrd added area:scala.js and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Nov 21, 2022
@sjrd sjrd self-assigned this Nov 21, 2022
@sjrd
Copy link
Member

sjrd commented Feb 2, 2023

I could reproduce the issue without Scala.js nor Java classes. All that's really needed is an invariant SAM type that is not a platform SAM type:

abstract class Parent

trait Function[A, B] extends Parent {
  def apply(a: A): B
}

object Test {
  def test(x: (Int, Int), f: Function[? >: (Int, Int), ? <: Boolean]): Boolean =
    f.apply(x)

  def isLess(left: Int, right: Int): Boolean = {
    // working
    val f = new Function[(Int, Int), Boolean] {
      override def apply(t: (Int, Int)): Boolean = left.compareTo(right) <= 0
    }
    test((left, right), f)

    // NOT working
    test(
      (left, right),
      _ => left.compareTo(right) <= 0
    )
  }

  def main(args: Array[String]): Unit = {
    println(isLess(1, 2))
    println(isLess(1, 2))
    println(isLess(2, 1))
  }
}
> scalac tests/run/hello.scala
> scala Test
Exception in thread "main" java.lang.AbstractMethodError
        at Test$.test(hello.scala:9)
        at Test$.isLess(hello.scala:21)
        at Test$.main(hello.scala:26)
        at Test.main(hello.scala)

So this is not Scala.js-specific.

I think ExpandSAMs is doing something bad because it generates:

        Test.test(Tuple2.apply[Int, Int](left, right),
          {
            def $anonfun(_$1: (Int, Int)): Boolean =
              int2Integer(left).compareTo(int2Integer(right)).<=(0)
            {
              final class $anon() extends Parent(), 
                Function[? >: (Int, Int), ? <: Boolean] {
                final def apply(_$1: (Int, Int)): Boolean = $anonfun(_$1)
              }
              new Parent with Function[? >: (Int, Int), ? <: Boolean] {...}()
            }
          }
        )

Notice the ? type parameters in the extends clause. That's illegal. It should generate extends Function[(Int, Int), Boolean] instead.

@sjrd sjrd changed the title scalajs SAM Lambda - Referring to non-existent method Invariant SAM type that's not a platform SAM: AbstractMethodError (JVM) / Referring to non-existent method (JS) Feb 2, 2023
@sjrd
Copy link
Member

sjrd commented Feb 2, 2023

In fact, I wonder whether typer is not to blame instead. It already generates:

        Test.test(Tuple2.apply[Int, Int](left, right),
          {
            def $anonfun(_$1: (Int, Int)): Boolean =
              int2Integer(left).compareTo(int2Integer(right)).<=(0)
            closure($anonfun:Function[? >: (Int, Int), ? <: Boolean])
          }
        )

Not downright illegal, but since it obviously can correctly construct (Int, Int) and Boolean in def $anonfun (as opposed to ? >: (Int, Int) and ? <: Boolean), perhaps it is already equipped to use the correct types in the closure(...) node.

@sjrd
Copy link
Member

sjrd commented Feb 2, 2023

Also, if Function itself is an abstract class, instead of a trait, then the typer refuses the code:

-- Error: tests/run/hello.scala:21:37 ------------------------------------------
21 |      _ => left.compareTo(right) <= 0
   |                                     ^
   |result type of lambda is an underspecified SAM type Function[? >: (Int, Int), ? <: Boolean]

so apparently it is already trying to protect itself from this issue, but it only does that for classes and not for traits. That's clearly broken.

@sjrd
Copy link
Member

sjrd commented Feb 2, 2023

Aaaand ... I knew I had seen this somewhere before. It's a duplicate of #16065.

@sjrd sjrd closed this as not planned Won't fix, can't repro, duplicate, stale Feb 2, 2023
@sjrd sjrd removed their assignment Feb 2, 2023
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