Skip to content

Commit

Permalink
GROOVY-11479: STC: closure or lambda parameter type must remain mutable
Browse files Browse the repository at this point in the history
do not use the type from the SAM directory
  • Loading branch information
eric-milles committed Sep 25, 2024
1 parent 0c2ed94 commit 7a7e7b2
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
import static org.codehaus.groovy.ast.ClassHelper.isDynamicTyped;
import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
import static org.codehaus.groovy.ast.tools.GenericsUtils.hasUnresolvedGenerics;
import static org.codehaus.groovy.classgen.asm.BytecodeHelper.getClassInternalName;
import static org.codehaus.groovy.classgen.asm.BytecodeHelper.getMethodDescriptor;

Expand Down Expand Up @@ -113,12 +114,14 @@ default ClassNode convertParameterType(final ClassNode targetType, final ClassNo
throw new RuntimeParserException("The inferred type[" + inferredType.redirect() + "] is not compatible with the parameter type[" + parameterType.redirect() + "]", parameterType);
}

ClassNode type = inferredType;
ClassNode type;
if (isPrimitiveType(parameterType)) {
if (!isPrimitiveType(inferredType)) {
// The non-primitive type and primitive type are not allowed to mix since Java 9+
// java.lang.invoke.LambdaConversionException: Type mismatch for instantiated parameter 0: class java.lang.Integer is not a subtype of int
type = getUnwrapper(inferredType);
type = getUnwrapper(inferredType).getPlainNodeReference(false);
} else {
type = inferredType.getPlainNodeReference(false);
}
} else if (isPrimitiveType(inferredType)) {
// GROOVY-9790: bootstrap method initialization exception raised when lambda parameter type is wrong
Expand All @@ -128,11 +131,21 @@ default ClassNode convertParameterType(final ClassNode targetType, final ClassNo
&& (parameterType.equals(getUnwrapper(parameterType)) || inferredType.equals(getWrapper(inferredType)))) { // (2)
// The non-primitive type and primitive type are not allowed to mix since Java 9+
// java.lang.invoke.LambdaConversionException: Type mismatch for instantiated parameter 0: int is not a subtype of class java.lang.Object
type = getWrapper(inferredType);
type = getWrapper(inferredType).getPlainNodeReference();
} else {
type = inferredType.getPlainNodeReference(false);
}
} else {
type = inferredType;
// GROOVY-11304: no placeholders
if (hasUnresolvedGenerics(type)) type = type.redirect();
// GROOVY-11479: mutable for node metadata or type annotations
if (type.toString(false).equals(parameterType.toString(false))) {
type = parameterType;
} else {
// TODO: deep copy if type args set
type = type.getPlainNodeReference();
}
}
if (type.isGenericsPlaceHolder()) {
type = type.redirect();
}
return type;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1035,7 +1035,10 @@ private void inferParameterAndReturnTypesOfClosureOnRHS(final ClassNode lhsType,
for (int i = 0; i < n; i += 1) {
Parameter parameter = closureParameters[i];
if (parameter.isDynamicTyped()) {
parameter.setType(expectedTypes[i]); // GROOVY-11083, GROOVY-11085, et al.
ClassNode type = expectedTypes[i].getPlainNodeReference(false); // GROOVY-11479
if (!expectedTypes[i].isGenericsPlaceHolder())
type.setGenericsTypes(expectedTypes[i].getGenericsTypes());
parameter.setType(type); // GROOVY-11083, GROOVY-11085, et al.
} else {
checkParamType(parameter, expectedTypes[i], i == n-1, rhsExpression instanceof LambdaExpression);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,42 @@ final class TypeAnnotationsTest extends AbstractBytecodeTestCase {
])
}

// GROOVY-11479
void testTypeAnnotationsForClosure() {
def bytecode = compile(classNamePattern: 'Foo\\$_closure1', method: 'doCall', imports + '''
@Retention(RUNTIME) @Target(TYPE_USE) @interface TypeAnno0 { }
@Retention(RUNTIME) @Target(TYPE_USE) @interface TypeAnno1 { }
@groovy.transform.CompileStatic
class Foo {
@TypeAnno0 java.util.function.IntUnaryOperator bar = { @TypeAnno1 def i -> 1 }
}
''')
assert bytecode.hasStrictSequence([
'public doCall(I)Ljava/lang/Integer;',
'@LTypeAnno1;() : METHOD_FORMAL_PARAMETER 0, null',
'L0'
])
}

// GROOVY-11479
void testTypeAnnotationsForLambda() {
def bytecode = compile(classNamePattern: 'Foo\\$_lambda1', method: 'doCall', imports + '''
@Retention(RUNTIME) @Target(TYPE_USE) @interface TypeAnno0 { }
@Retention(RUNTIME) @Target(TYPE_USE) @interface TypeAnno1 { }
@groovy.transform.CompileStatic
class Foo {
@TypeAnno0 java.util.function.IntUnaryOperator bar = (@TypeAnno1 int i) -> 1
}
''')
assert bytecode.hasStrictSequence([
'public doCall(I)I',
'@LTypeAnno1;() : METHOD_FORMAL_PARAMETER 0, null',
'L0'
])
}

void testTypeAnnotationsForField1() {
def bytecode = compile(classNamePattern: 'Foo', field: 'foo', imports + '''
@Retention(RUNTIME) @Target(FIELD) @interface FieldAnno { String value() }
Expand Down

0 comments on commit 7a7e7b2

Please sign in to comment.