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)