Paul King created GROOVY-12154:
----------------------------------

             Summary: `@CompileStatic` lambda with a primitive-typed parameter 
targeting a generic functional interface fails at runtime with 
`LambdaConversionException`
                 Key: GROOVY-12154
                 URL: https://issues.apache.org/jira/browse/GROOVY-12154
             Project: Groovy
          Issue Type: Bug
          Components: Static compilation
    Affects Versions: 6.0.0-alpha-2
            Reporter: Paul King


Under {{@CompileStatic}}, a lambda whose parameter is declared with a 
*primitive* type (e.g. {{int}}) and whose target is a *generic* functional 
interface (e.g. {{Function<Integer, Integer>}}) compiles without error but 
throws {{BootstrapMethodError}} / {{LambdaConversionException}} the first time 
it is invoked. The generated implementation method keeps the primitive 
parameter type, but the erased/instantiated SAM signature is {{Object}}, and 
{{LambdaMetafactory}} will not insert the boxing conversion.

h2. Steps to Reproduce

{code:groovy}
import java.util.function.Function
import groovy.transform.CompileStatic

@CompileStatic
class T {
    Function<Integer, Integer> f = (int a) -> a * 2   // primitive param, 
generic SAM
    Integer m() { f.apply(5) }
}

new T().m()   // <-- throws
{code}

h2. Expected

Returns {{10}} — the same as the untyped and boxed equivalents (see 
workarounds). Or, if this form is genuinely unsupported, it should be a 
*compile-time* error, not a runtime crash.

h2. Actual

Compiles clean, then at first invocation:

{noformat}
java.lang.BootstrapMethodError: bootstrap method initialization exception
Caused by: java.lang.invoke.LambdaConversionException:
    Type mismatch for dynamic parameter 0: int is not a subtype of class 
java.lang.Object
{noformat}

h2. Workarounds

Declare the parameter untyped or boxed — both return {{10}}:

{code:groovy}
Function<Integer, Integer> f = (a) -> a * 2          // untyped   -> OK
Function<Integer, Integer> f = (Integer a) -> a * 2  // boxed     -> OK
{code}

h2. Notes / scope

* Reproduces with a *single* primitive parameter (above) and with multiple 
({{(int a, int b) -> a + b}} on {{BiFunction<Integer,Integer,Integer>}}).
* *Independent of default parameter values* — {{(int a, int b) -> …}} fails 
with no default at all; {{(a, b = 10) -> …}} and {{(Integer a, Integer b = 10) 
-> …}} both work. (Surfaced while checking GEP-27's lambda-default question; 
defaults themselves are fine.)
* *Independent of the {{groovy.target.lambda.hoist}} flag* — fails identically 
with it off and on.
* The lambda must target a *generic* SAM (type parameter erases to {{Object}}). 
A non-generic primitive SAM (e.g. {{IntUnaryOperator}}) is unaffected.

h2. Likely origin

{{org.codehaus.groovy.classgen.asm.sc.AbstractFunctionalInterfaceWriter#convertParameterType}}
 (the GROOVY-9790 primitive/boxing reconciliation). For {{inferredType = int}} 
against {{parameterType = Integer}}, the {{else if 
(isPrimitiveType(inferredType))}} branch's condition is false, so it falls 
through to {{type = inferredType…}} ({{int}}) instead of boxing to {{Integer}} 
— producing an impl method the metafactory cannot link to the {{Object}}-erased 
SAM.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to