Skip to content

Conversation

@noti0na1
Copy link
Member

@noti0na1 noti0na1 commented Dec 2, 2025

This PR fixes issue #24573 by adding stricter checks for platform SAM compatibility.

The regression issue starts from version 3.8.0 where Function1 and related traits gained the NoInits flag (I guess due to the way we read from jvm bytecode or from tasty?).
This changes how the compiler determines whether a SAM type qualifies as a platform SAM. When a trait is classified as a Java platform SAM, the compiler uses invokedynamic with LambdaMetaFactory (LMF) to create the lambda at runtime, rather than expanding it into an anonymous class at compile time. This is more efficient, but LMF has limitations on what type adaptations it can perform automatically.

It turns out the logic regarding SAM traits extending other traits has problems from the beginning, and even in Scala 2. The code from the issue works before 3.8 is totally by accident (because we always expand the SAM trait extending FunctionN).
We can reproduce the runtime exception using a custom function class (in Scala 2, and Scala 3.0~8):

trait F1[-T, +R]:
  def apply(t: T): R

trait SF[-T] extends F1[T, Unit]:
  def apply(t: T): Unit

@main def Test =
  val sf: SF[String] = i => println(i)
  sf("") // exception here
  // Caused by: java.lang.invoke.LambdaConversionException: 
  //Type mismatch for lambda expected return: void is not convertible to class java.lang.Object

From the view of Scala, SF is a valid SAM, because it has one abstract function def apply(t: T): Unit. However, after erasure, the apply of F1 becomes Object apply(Object), and the apply of SF becomes void apply(Object), which are two different abstract functions; hence, SF is not qualifies as a function interface on JVM. This is not only for Unit type, any primitive number type can cause the similar problem.

Therefore, we have to expand the rhs of val sf: SF[String] = i => println(i) into an anonymous class and implement all the bridge functions.

This PR strengthen the SAM check on Java platform by adding a function called samNotNeededExpansion to check if the SAM method's erased signature is compatible with all overridden methods. It mirrors the logic in Erasure.Boxing.adaptClosure to determine what LMF can and cannot auto-adapt.

@noti0na1
Copy link
Member Author

noti0na1 commented Dec 2, 2025

Still WIP

@noti0na1 noti0na1 changed the title Try to fix #24573: Don't treat SAM class with Unit result type as a platform SAM Try to fix #24573: Don't treat SAM class with imcompatible Unit result types as a platform SAM Dec 3, 2025
@noti0na1 noti0na1 marked this pull request as ready for review December 4, 2025 21:18
Copilot AI review requested due to automatic review settings December 4, 2025 21:18
Copilot finished reviewing on behalf of noti0na1 December 4, 2025 21:21

This comment was marked as resolved.

@noti0na1 noti0na1 changed the title Try to fix #24573: Don't treat SAM class with imcompatible Unit result types as a platform SAM Fix #24573: Add stricter checks for platform SAM compatibility Dec 5, 2025
@noti0na1 noti0na1 requested a review from sjrd December 5, 2025 14:52
@noti0na1 noti0na1 added this to the 3.8.0 milestone Dec 5, 2025
@noti0na1 noti0na1 added the backport:nominated If we agree to backport this PR, replace this tag with "backport:accepted", otherwise delete it. label Dec 5, 2025
@noti0na1 noti0na1 requested review from bracevac and odersky December 5, 2025 14:55
* @param cls The SAM class to check
* @return true if LMF can handle the required adaptation
*/
def samNotNeededExpansion(cls: ClassSymbol)(using Context): Boolean = cls.typeRef.possibleSamMethods match
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def samNotNeededExpansion(cls: ClassSymbol)(using Context): Boolean = cls.typeRef.possibleSamMethods match
def samExpansionNotNeeded(cls: ClassSymbol)(using Context): Boolean = cls.typeRef.possibleSamMethods match

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

Labels

backport:nominated If we agree to backport this PR, replace this tag with "backport:accepted", otherwise delete it.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants