You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2026/04/06 04:14:57 UTC
(groovy) branch GROOVY-11905 updated (e0e80a6099 -> b9c0bce3e5)
This is an automated email from the ASF dual-hosted git repository.
sunlan pushed a change to branch GROOVY-11905
in repository https://gitbox.apache.org/repos/asf/groovy.git
omit e0e80a6099 GROOVY-11905: Optimize non-capturing lambdas
new b9c0bce3e5 GROOVY-11905: Optimize non-capturing lambdas
This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version. This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:
* -- * -- B -- O -- O -- O (e0e80a6099)
\
N -- N -- N refs/heads/GROOVY-11905 (b9c0bce3e5)
You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.
Any revisions marked "omit" are not gone; other references still
refer to them. Any revisions marked "discard" are gone forever.
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../groovy/groovy/transform/stc/LambdaTest.groovy | 624 +++++++++++++--------
1 file changed, 389 insertions(+), 235 deletions(-)
(groovy) 01/01: GROOVY-11905: Optimize non-capturing lambdas
Posted by su...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
sunlan pushed a commit to branch GROOVY-11905
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit b9c0bce3e59539b8c6b9b8b63412f6c779a37574
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sun Apr 5 22:28:54 2026 +0900
GROOVY-11905: Optimize non-capturing lambdas
---
.../classgen/asm/sc/StaticTypesLambdaWriter.java | 95 +++--
.../groovy/groovy/transform/stc/LambdaTest.groovy | 443 +++++++++++++++++++++
.../groovy/classgen/asm/TypeAnnotationsTest.groovy | 2 +-
3 files changed, 509 insertions(+), 31 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
index b804702754..ae5f63ddc8 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java
@@ -72,6 +72,7 @@ import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.DUP;
+import static org.objectweb.asm.Opcodes.H_INVOKESTATIC;
import static org.objectweb.asm.Opcodes.H_INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.ICONST_0;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
@@ -110,36 +111,61 @@ public class StaticTypesLambdaWriter extends LambdaWriter implements AbstractFun
expression.setSerializable(true);
}
+ boolean serializable = expression.isSerializable();
ClassNode lambdaClass = getOrAddLambdaClass(expression, abstractMethod);
MethodNode lambdaMethod = lambdaClass.getMethods(DO_CALL).get(0);
- boolean canDeserialize = controller.getClassNode().hasMethod(createDeserializeLambdaMethodName(lambdaClass), createDeserializeLambdaMethodParams());
- if (!canDeserialize) {
- if (expression.isSerializable()) {
- addDeserializeLambdaMethodForEachLambdaExpression(expression, lambdaClass);
+ Parameter[] lambdaSharedVariables = expression.getNodeMetaData(LAMBDA_SHARED_VARIABLES);
+ boolean accessingInstanceMembers = isAccessingInstanceMembersOfEnclosingClass(lambdaMethod);
+ // For non-capturing lambdas: make doCall static and use a capture-free invokedynamic,
+ // so LambdaMetafactory creates a singleton instance — just like Java non-capturing lambdas.
+ boolean nonCapturing = lambdaSharedVariables.length == 0 && !accessingInstanceMembers;
+ boolean deserializeLambdaMethodExists = hasDeserializeLambdaMethod(lambdaClass);
+
+ if (nonCapturing) {
+ lambdaMethod.setModifiers(lambdaMethod.getModifiers() | ACC_STATIC);
+ }
+
+ if (!deserializeLambdaMethodExists) {
+ if (serializable) {
+ addDeserializeLambdaMethodForLambdaExpression(expression, lambdaClass, lambdaMethod);
addDeserializeLambdaMethod();
}
- newGroovyLambdaWrapperAndLoad(lambdaClass, expression, isAccessingInstanceMembersOfEnclosingClass(lambdaMethod));
+ if (!nonCapturing) {
+ newGroovyLambdaWrapperAndLoad(lambdaClass, expression, accessingInstanceMembers);
+ }
}
MethodVisitor mv = controller.getMethodVisitor();
mv.visitInvokeDynamicInsn(
abstractMethod.getName(),
- createAbstractMethodDesc(functionalType.redirect(), lambdaClass),
- createBootstrapMethod(controller.getClassNode().isInterface(), expression.isSerializable()),
- createBootstrapMethodArguments(createMethodDescriptor(abstractMethod), H_INVOKEVIRTUAL, lambdaClass, lambdaMethod, lambdaMethod.getParameters(), expression.isSerializable())
+ nonCapturing
+ ? BytecodeHelper.getMethodDescriptor(functionalType.redirect(), Parameter.EMPTY_ARRAY)
+ : createAbstractMethodDesc(functionalType.redirect(), lambdaClass),
+ createBootstrapMethod(controller.getClassNode().isInterface(), serializable),
+ createBootstrapMethodArguments(createMethodDescriptor(abstractMethod),
+ nonCapturing ? H_INVOKESTATIC : H_INVOKEVIRTUAL,
+ lambdaClass, lambdaMethod, lambdaMethod.getParameters(), serializable)
);
- if (expression.isSerializable()) {
+ if (serializable) {
mv.visitTypeInsn(CHECKCAST, "java/io/Serializable");
}
- controller.getOperandStack().replace(functionalType.redirect(), 1);
+ if (nonCapturing) {
+ controller.getOperandStack().push(functionalType.redirect());
+ } else {
+ controller.getOperandStack().replace(functionalType.redirect(), 1);
+ }
}
private static Parameter[] createDeserializeLambdaMethodParams() {
return new Parameter[]{new Parameter(SERIALIZEDLAMBDA_TYPE, "serializedLambda")};
}
+ private boolean hasDeserializeLambdaMethod(final ClassNode lambdaClass) {
+ return controller.getClassNode().hasMethod(createDeserializeLambdaMethodName(lambdaClass), createDeserializeLambdaMethodParams());
+ }
+
private static boolean isAccessingInstanceMembersOfEnclosingClass(final MethodNode lambdaMethod) {
boolean[] result = new boolean[1];
@@ -325,27 +351,36 @@ public class StaticTypesLambdaWriter extends LambdaWriter implements AbstractFun
code);
}
- private void addDeserializeLambdaMethodForEachLambdaExpression(final LambdaExpression expression, final ClassNode lambdaClass) {
+ private static boolean requiresLambdaInstance(final MethodNode lambdaMethod) {
+ return 0 == (lambdaMethod.getModifiers() & ACC_STATIC);
+ }
+
+ private void addDeserializeLambdaMethodForLambdaExpression(final LambdaExpression expression, final ClassNode lambdaClass, final MethodNode lambdaMethod) {
ClassNode enclosingClass = controller.getClassNode();
- Statement code = block(
- new BytecodeSequence(new BytecodeInstruction() {
- @Override
- public void visit(final MethodVisitor mv) {
- mv.visitVarInsn(ALOAD, 0);
- mv.visitInsn(ICONST_0);
- mv.visitMethodInsn(
- INVOKEVIRTUAL,
- "java/lang/invoke/SerializedLambda",
- "getCapturedArg",
- "(I)Ljava/lang/Object;",
- false);
- mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(lambdaClass));
- OperandStack operandStack = controller.getOperandStack();
- operandStack.push(lambdaClass);
- }
- }),
- returnS(expression)
- );
+ Statement code;
+ if (!requiresLambdaInstance(lambdaMethod)) {
+ code = block(returnS(expression));
+ } else {
+ code = block(
+ new BytecodeSequence(new BytecodeInstruction() {
+ @Override
+ public void visit(final MethodVisitor mv) {
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ICONST_0);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/invoke/SerializedLambda",
+ "getCapturedArg",
+ "(I)Ljava/lang/Object;",
+ false);
+ mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(lambdaClass));
+ OperandStack operandStack = controller.getOperandStack();
+ operandStack.push(lambdaClass);
+ }
+ }),
+ returnS(expression)
+ );
+ }
enclosingClass.addSyntheticMethod(
createDeserializeLambdaMethodName(lambdaClass),
diff --git a/src/test/groovy/groovy/transform/stc/LambdaTest.groovy b/src/test/groovy/groovy/transform/stc/LambdaTest.groovy
index f69eb1a332..1554ca90db 100644
--- a/src/test/groovy/groovy/transform/stc/LambdaTest.groovy
+++ b/src/test/groovy/groovy/transform/stc/LambdaTest.groovy
@@ -18,6 +18,8 @@
*/
package groovy.transform.stc
+import org.codehaus.groovy.classgen.asm.AbstractBytecodeTestCase
+import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import static groovy.test.GroovyAssert.assertScript
@@ -1888,4 +1890,445 @@ final class LambdaTest {
assert this.class.classLoader.loadClass('Foo$_bar_lambda1').modifiers == 25 // public(1) + static(8) + final(16)
'''
}
+
+ // GROOVY-11905
+ @Nested
+ class NonCapturingLambdaOptimizationTest extends AbstractBytecodeTestCase {
+ @Test
+ void testNonCapturingLambdaWithFunctionInStaticMethod() {
+ assertScript shell, '''
+ class C {
+ static void test() {
+ assert [2, 3, 4] == [1, 2, 3].stream().map(e -> e + 1).toList()
+ }
+ }
+ C.test()
+ '''
+ }
+
+ @Test
+ void testNonCapturingLambdaWithFunctionInInstanceMethodWithoutThisAccess() {
+ assertScript shell, '''
+ class C {
+ void test() {
+ assert [2, 3, 4] == [1, 2, 3].stream().map(e -> e + 1).toList()
+ }
+ }
+ new C().test()
+ '''
+ }
+
+ @Test
+ void testNonCapturingLambdaWithPredicate() {
+ assertScript shell, '''
+ class C {
+ static void test() {
+ assert [2, 4] == [1, 2, 3, 4].stream().filter(e -> e % 2 == 0).toList()
+ }
+ }
+ C.test()
+ '''
+ }
+
+ @Test
+ void testNonCapturingLambdaWithSupplier() {
+ assertScript shell, '''
+ class C {
+ static void test() {
+ Supplier<String> s = () -> 'constant'
+ assert s.get() == 'constant'
+ assert 'hello' == Optional.<String>empty().orElseGet(() -> 'hello')
+ }
+ }
+ C.test()
+ '''
+ }
+
+ @Test
+ void testNonCapturingLambdaWithBiFunction() {
+ assertScript shell, '''
+ class C {
+ static void test() {
+ BiFunction<Integer, Integer, Integer> f = (a, b) -> a + b
+ assert f.apply(3, 4) == 7
+ }
+ }
+ C.test()
+ '''
+ }
+
+ @Test
+ void testNonCapturingLambdaWithComparator() {
+ assertScript shell, '''
+ class C {
+ static void test() {
+ assert [3, 2, 1] == [1, 2, 3].stream().sorted((a, b) -> b.compareTo(a)).toList()
+ }
+ }
+ C.test()
+ '''
+ }
+
+ @Test
+ void testNonCapturingLambdaWithPrimitiveParameterType() {
+ assertScript shell, '''
+ class C {
+ static void test() {
+ IntUnaryOperator op = (int i) -> i * 2
+ assert op.applyAsInt(5) == 10
+ }
+ }
+ C.test()
+ '''
+ }
+
+ @Test
+ void testNonCapturingLambdaWithCustomFunctionalInterface() {
+ assertScript shell, '''
+ interface Transformer<I, O> {
+ O transform(I input)
+ }
+ class C {
+ static void test() {
+ Transformer<String, Integer> t = (String s) -> s.length()
+ assert t.transform('hello') == 5
+ }
+ }
+ C.test()
+ '''
+ }
+
+ @Test
+ void testNonCapturingLambdaCallingStaticMethodOnly() {
+ assertScript shell, '''
+ class C {
+ static String prefix() { 'Hi ' }
+ static void test() {
+ assert ['Hi 1', 'Hi 2'] == [1, 2].stream().map(e -> C.prefix() + e).toList()
+ }
+ }
+ C.test()
+ '''
+ }
+
+ @Test
+ void testMultipleNonCapturingLambdasInSameMethod() {
+ assertScript shell, '''
+ class C {
+ static void test() {
+ Function<Integer, Integer> f = (Integer x) -> x + 1
+ Function<Integer, String> g = (Integer x) -> 'v' + x
+ Predicate<Integer> p = (Integer x) -> x > 2
+ assert f.apply(1) == 2
+ assert g.apply(1) == 'v1'
+ assert p.test(3) && !p.test(1)
+ }
+ }
+ C.test()
+ '''
+ }
+
+ @Test
+ void testNonCapturingLambdaInStaticInitializerBlock() {
+ assertScript shell, '''
+ class C {
+ static List<Integer> result
+ static { result = [1, 2, 3].stream().map(e -> e * 2).toList() }
+ }
+ assert C.result == [2, 4, 6]
+ '''
+ }
+
+ @Test
+ void testNonCapturingLambdaInFieldInitializer() {
+ assertScript shell, '''
+ class C {
+ IntUnaryOperator op = (int i) -> i + 1
+ void test() { assert op.applyAsInt(5) == 6 }
+ }
+ new C().test()
+ '''
+ }
+
+ @Test
+ void testNonCapturingLambdaInInterfaceDefaultMethod() {
+ assertScript shell, '''
+ interface Processor {
+ default List<Integer> process(List<Integer> input) {
+ input.stream().map(e -> e + 1).toList()
+ }
+ }
+ class C implements Processor {}
+ assert new C().process([1, 2, 3]) == [2, 3, 4]
+ '''
+ }
+
+ @Test
+ void testNonCapturingLambdaSingletonIdentity() {
+ assertScript shell, '''
+ class C {
+ static void test() {
+ def identities = new HashSet()
+ for (int i = 0; i < 5; i++) {
+ Function<Integer, Integer> f = (Integer x) -> x + 1
+ identities.add(System.identityHashCode(f))
+ }
+ assert identities.size() == 1 : 'non-capturing lambda should be a singleton'
+ }
+ }
+ C.test()
+ '''
+ }
+
+ @Test
+ void testCapturingLambdaCreatesDistinctInstances() {
+ assertScript shell, '''
+ class C {
+ static void test() {
+ def identities = new HashSet()
+ for (int i = 0; i < 3; i++) {
+ int captured = i
+ Function<Integer, Integer> f = (Integer x) -> x + captured
+ identities.add(System.identityHashCode(f))
+ assert f.apply(10) == 10 + i
+ }
+ assert identities.size() == 3 : 'capturing lambda should create different instances'
+ }
+ }
+ C.test()
+ '''
+ }
+
+ @Test
+ void testCapturingLocalVariableStillWorks() {
+ assertScript shell, '''
+ class C {
+ static void test() {
+ String x = '#'
+ assert ['#1', '#2'] == [1, 2].stream().map(e -> x + e).toList()
+ }
+ }
+ C.test()
+ '''
+ }
+
+ @Test
+ void testAccessingThisStillWorks() {
+ assertScript shell, '''
+ class C {
+ String prefix = 'Hi '
+ void test() {
+ assert ['Hi 1', 'Hi 2'] == [1, 2].stream().map(e -> this.prefix + e).toList()
+ }
+ }
+ new C().test()
+ '''
+ }
+
+ @Test
+ void testCallingInstanceMethodStillWorks() {
+ assertScript shell, '''
+ class C {
+ String greet(int i) { "Hello $i" }
+ void test() {
+ assert ['Hello 1', 'Hello 2'] == [1, 2].stream().map(e -> greet(e)).toList()
+ }
+ }
+ new C().test()
+ '''
+ }
+
+ @Test
+ void testNonCapturingSerializableLambdaCanBeSerialized() {
+ assertScript shell, '''
+ import java.io.*
+ interface SerFunc<I,O> extends Serializable, Function<I,O> {}
+ byte[] test() {
+ try (def out = new ByteArrayOutputStream()) {
+ out.withObjectOutputStream {
+ SerFunc<Integer, String> f = ((Integer i) -> 'a' + i)
+ it.writeObject(f)
+ }
+ out.toByteArray()
+ }
+ }
+ assert test().length > 0
+ '''
+ }
+
+ @Test
+ void testNonCapturingSerializableLambdaRoundTrips() {
+ assertScript shell, '''
+ package tests.lambda
+ class C {
+ static byte[] test() {
+ def out = new ByteArrayOutputStream()
+ out.withObjectOutputStream { it ->
+ SerFunc<Integer, String> f = (Integer i) -> 'a' + i
+ it.writeObject(f)
+ }
+ out.toByteArray()
+ }
+ static main(args) {
+ new ByteArrayInputStream(C.test()).withObjectInputStream(C.classLoader) {
+ SerFunc<Integer, String> f = (SerFunc<Integer, String>) it.readObject()
+ assert f.apply(1) == 'a1'
+ }
+ }
+ interface SerFunc<I,O> extends Serializable, Function<I,O> {}
+ }
+ '''
+ }
+
+ @Test
+ void testNonCapturingSerializableLambdaSingletonIdentity() {
+ assertScript shell, '''
+ interface SerFunc<I,O> extends Serializable, Function<I,O> {}
+ class C {
+ static void test() {
+ def identities = new HashSet()
+ for (int i = 0; i < 5; i++) {
+ SerFunc<Integer, Integer> f = (Integer x) -> x + 1
+ identities.add(System.identityHashCode(f))
+ }
+ assert identities.size() == 1 : 'non-capturing serializable lambda should be a singleton'
+ }
+ }
+ C.test()
+ '''
+ }
+
+ @Test
+ void testCapturingSerializableLambdaStillRoundTrips() {
+ assertScript shell, '''
+ package tests.lambda
+ class C {
+ byte[] test() {
+ def out = new ByteArrayOutputStream()
+ out.withObjectOutputStream {
+ String s = 'a'
+ SerFunc<Integer, String> f = (Integer i) -> s + i
+ it.writeObject(f)
+ }
+ out.toByteArray()
+ }
+ static main(args) {
+ new ByteArrayInputStream(C.newInstance().test()).withObjectInputStream(C.classLoader) {
+ SerFunc<Integer, String> f = (SerFunc<Integer, String>) it.readObject()
+ assert f.apply(1) == 'a1'
+ }
+ }
+ interface SerFunc<I,O> extends Serializable, Function<I,O> {}
+ }
+ '''
+ }
+
+ @Test
+ void testNonCapturingLambdaInStaticMethodUsesStaticDoCall() {
+ def bytecode = compileStaticBytecode(classNamePattern: 'C\\$_create_lambda1', method: 'doCall', '''
+ @CompileStatic
+ class C {
+ static IntUnaryOperator create() {
+ (int i) -> i * 2
+ }
+ }
+ ''')
+ assert bytecode.hasStrictSequence([
+ 'public static doCall(I)I',
+ 'L0'
+ ])
+ }
+
+ @Test
+ void testNonCapturingLambdaInInstanceMethodWithoutThisAccessUsesCaptureFreeInvokeDynamic() {
+ def bytecode = compileStaticBytecode(classNamePattern: 'C', method: 'create', '''
+ @CompileStatic
+ class C {
+ IntUnaryOperator create() {
+ (int i) -> i + 1
+ }
+ }
+ ''')
+ assert bytecode.hasSequence([
+ 'INVOKEDYNAMIC applyAsInt()Ljava/util/function/IntUnaryOperator;',
+ 'java/lang/invoke/LambdaMetafactory.metafactory',
+ 'C$_create_lambda1.doCall(I)I'
+ ])
+ assert !bytecode.hasSequence(['NEW C$_create_lambda1'])
+ }
+
+ @Test
+ void testCapturingLambdaRetainsInstanceDoCallAndCapturedReceiver() {
+ def lambdaBytecode = compileStaticBytecode(classNamePattern: 'C\\$_create_lambda1', method: 'doCall', '''
+ @CompileStatic
+ class C {
+ static IntUnaryOperator create() {
+ int captured = 1
+ IntUnaryOperator op = (int i) -> i + captured
+ op
+ }
+ }
+ ''')
+ assert lambdaBytecode.hasSequence(['public doCall(I)I'])
+ assert !lambdaBytecode.hasSequence(['public static doCall(I)I'])
+
+ def outerBytecode = compileStaticBytecode(classNamePattern: 'C', method: 'create', '''
+ @CompileStatic
+ class C {
+ static IntUnaryOperator create() {
+ int captured = 1
+ IntUnaryOperator op = (int i) -> i + captured
+ op
+ }
+ }
+ ''')
+ assert outerBytecode.hasSequence([
+ 'NEW C$_create_lambda1',
+ 'INVOKEDYNAMIC applyAsInt(LC$_create_lambda1;)Ljava/util/function/IntUnaryOperator;',
+ 'C$_create_lambda1.doCall(I)I'
+ ])
+ }
+
+ @Test
+ void testNonCapturingSerializableLambdaDeserializeHelperSkipsCapturedArgLookup() {
+ def bytecode = compileStaticBytecode(classNamePattern: 'C', method: '$deserializeLambda_C$_create_lambda1$', '''
+ @CompileStatic
+ class C {
+ static SerFunc<Integer, String> create() {
+ (Integer i) -> 'a' + i
+ }
+ interface SerFunc<I,O> extends Serializable, Function<I,O> {}
+ }
+ ''')
+ assert !bytecode.hasSequence([SERIALIZED_LAMBDA_GET_CAPTURED_ARG])
+ }
+
+ @Test
+ void testCapturingSerializableLambdaDeserializeHelperReadsCapturedArg() {
+ def bytecode = compileStaticBytecode(classNamePattern: 'C', method: '$deserializeLambda_C$_create_lambda1$', '''
+ @CompileStatic
+ class C {
+ static SerFunc<Integer, String> create() {
+ String prefix = 'a'
+ SerFunc<Integer, String> f = (Integer i) -> prefix + i
+ f
+ }
+ interface SerFunc<I,O> extends Serializable, Function<I,O> {}
+ }
+ ''')
+ assert bytecode.hasSequence([SERIALIZED_LAMBDA_GET_CAPTURED_ARG])
+ }
+
+ private compileStaticBytecode(final Map options = [:], final String script) {
+ compile(options, COMMON_IMPORTS + script)
+ }
+
+ private static final String COMMON_IMPORTS = '''\
+ import groovy.transform.CompileStatic
+ import java.io.Serializable
+ import java.util.function.Function
+ import java.util.function.IntUnaryOperator
+ '''.stripIndent()
+ private static final String SERIALIZED_LAMBDA_GET_CAPTURED_ARG = 'INVOKEVIRTUAL java/lang/invoke/SerializedLambda.getCapturedArg'
+ }
}
diff --git a/src/test/groovy/org/codehaus/groovy/classgen/asm/TypeAnnotationsTest.groovy b/src/test/groovy/org/codehaus/groovy/classgen/asm/TypeAnnotationsTest.groovy
index c5270ddec5..cdcf626338 100644
--- a/src/test/groovy/org/codehaus/groovy/classgen/asm/TypeAnnotationsTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/classgen/asm/TypeAnnotationsTest.groovy
@@ -299,7 +299,7 @@ final class TypeAnnotationsTest extends AbstractBytecodeTestCase {
}
''')
assert bytecode.hasStrictSequence([
- 'public doCall(I)I',
+ 'public static doCall(I)I',
'@LTypeAnno1;() : METHOD_FORMAL_PARAMETER 0, null',
'L0'
])