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 663722a867f4eb50b7da78e20214624fab4aff3d
Author: Daniel Sun <[email protected]>
AuthorDate: Sun Apr 5 22:28:54 2026 +0900

    GROOVY-11905: Optimize non-capturing lambdas
---
 .../java/org/codehaus/groovy/ast/ClassNode.java    |    8 +-
 .../classgen/asm/sc/StaticTypesLambdaAnalyzer.java |  437 ++++++
 .../classgen/asm/sc/StaticTypesLambdaWriter.java   |  304 ++--
 .../groovy/groovy/transform/stc/LambdaTest.groovy  | 1586 ++++++++++++++++++++
 .../groovy/classgen/asm/TypeAnnotationsTest.groovy |    2 +-
 5 files changed, 2218 insertions(+), 119 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/ClassNode.java 
b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
index b91da80224..3bbd62afd8 100644
--- a/src/main/java/org/codehaus/groovy/ast/ClassNode.java
+++ b/src/main/java/org/codehaus/groovy/ast/ClassNode.java
@@ -1452,17 +1452,21 @@ faces:  if (method == null && 
asBoolean(getInterfaces())) { // GROOVY-11323
         return null;
     }
 
+    private List<ClassNode> outerClasses;
     public List<ClassNode> getOuterClasses() {
+        List<ClassNode> ocs = outerClasses;
+        if (ocs != null) return ocs;
+
         ClassNode outer = getOuterClass();
         if (outer == null) {
-            return Collections.emptyList();
+            return outerClasses = Collections.emptyList();
         }
         List<ClassNode> result = new ArrayList<>(4);
         do {
             result.add(outer);
         } while ((outer = outer.getOuterClass()) != null);
 
-        return result;
+        return outerClasses = Collections.unmodifiableList(result);
     }
 
     /**
diff --git 
a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaAnalyzer.java
 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaAnalyzer.java
new file mode 100644
index 0000000000..564cffd551
--- /dev/null
+++ 
b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaAnalyzer.java
@@ -0,0 +1,437 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.classgen.asm.sc;
+
+import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.CodeVisitorSupport;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.Variable;
+import org.codehaus.groovy.ast.expr.AttributeExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.apache.groovy.util.BeanUtils.capitalize;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
+import static 
org.codehaus.groovy.transform.stc.StaticTypesMarker.DIRECT_METHOD_CALL_TARGET;
+
+/**
+ * Analysis helper for lambda expression
+ */
+class StaticTypesLambdaAnalyzer {
+
+    StaticTypesLambdaAnalyzer(final SourceUnit sourceUnit) {
+        this.sourceUnit = sourceUnit;
+    }
+
+    boolean isNonCapturing(final MethodNode lambdaMethod, final Parameter[] 
sharedVariables) {
+        return (sharedVariables == null || sharedVariables.length == 0)
+            && !accessesInstanceMembers(lambdaMethod);
+    }
+
+    boolean accessesInstanceMembers(final MethodNode lambdaMethod) {
+        Boolean accessingInstanceMembers = 
lambdaMethod.getNodeMetaData(AccessesInstanceMembers.class);
+        if (accessingInstanceMembers != null) {
+            return accessingInstanceMembers;
+        }
+
+        InstanceMemberAccessFinder finder = new 
InstanceMemberAccessFinder(resolveOuterStaticMemberOwner(lambdaMethod));
+        lambdaMethod.getCode().visit(finder);
+
+        accessingInstanceMembers = finder.isAccessingInstanceMembers();
+        lambdaMethod.putNodeMetaData(AccessesInstanceMembers.class, 
accessingInstanceMembers);
+        return accessingInstanceMembers;
+    }
+
+    void qualifyOuterStaticMemberReferences(final MethodNode lambdaMethod) {
+        lambdaMethod.getCode().visit(new 
OuterStaticMemberQualifier(sourceUnit, 
resolveOuterStaticMemberOwner(lambdaMethod)));
+    }
+
+    private static OuterStaticMemberResolver 
resolveOuterStaticMemberOwner(final MethodNode lambdaMethod) {
+        return new 
OuterStaticMemberResolver(lambdaMethod.getDeclaringClass().getOuterClasses());
+    }
+
+    private static boolean isThisReceiver(final Expression expression) {
+        return expression instanceof VariableExpression receiver && 
receiver.isThisExpression();
+    }
+
+    private static boolean isEnclosingInstanceReceiver(final Expression 
expression) {
+        return isThisReceiver(expression) || 
isQualifiedEnclosingInstanceReference(expression);
+    }
+
+    private static boolean isQualifiedEnclosingInstanceReference(final 
Expression expression) {
+        if (!(expression instanceof PropertyExpression propertyExpression)) {
+            return false;
+        }
+        if (!(propertyExpression.getObjectExpression() instanceof 
ClassExpression)) {
+            return false;
+        }
+
+        String property = propertyExpression.getPropertyAsString();
+        return "this".equals(property) || "super".equals(property);
+    }
+
+    private static final class OuterStaticMemberQualifier extends 
ClassCodeExpressionTransformer {
+
+        private final SourceUnit sourceUnit;
+        private final OuterStaticMemberResolver resolver;
+
+        private OuterStaticMemberQualifier(final SourceUnit sourceUnit, final 
OuterStaticMemberResolver resolver) {
+            this.sourceUnit = sourceUnit;
+            this.resolver = resolver;
+        }
+
+        @Override
+        protected SourceUnit getSourceUnit() {
+            return sourceUnit;
+        }
+
+        @Override
+        public Expression transform(final Expression expression) {
+            if (expression instanceof VariableExpression variableExpression) {
+                Expression qualifiedReference = qualify(variableExpression);
+                if (qualifiedReference != null) {
+                    return qualifiedReference;
+                }
+            }
+            if (expression instanceof AttributeExpression attributeExpression) 
{
+                Expression qualifiedReference = qualify(attributeExpression);
+                if (qualifiedReference != null) {
+                    return qualifiedReference;
+                }
+            }
+            if (expression instanceof PropertyExpression propertyExpression) {
+                Expression qualifiedReference = qualify(propertyExpression);
+                if (qualifiedReference != null) {
+                    return qualifiedReference;
+                }
+            }
+            if (expression instanceof MethodCallExpression 
methodCallExpression) {
+                Expression qualifiedReference = qualify(methodCallExpression);
+                if (qualifiedReference != null) {
+                    return qualifiedReference;
+                }
+            }
+            return super.transform(expression);
+        }
+
+        private Expression qualify(final VariableExpression expression) {
+            ClassNode owner = resolver.findOwner(expression);
+            if (owner == null) {
+                return null;
+            }
+
+            PropertyExpression qualifiedReference = new 
PropertyExpression(classX(owner), expression.getName());
+            qualifiedReference.setImplicitThis(false);
+            qualifiedReference.copyNodeMetaData(expression);
+            setSourcePosition(qualifiedReference, expression);
+            return qualifiedReference;
+        }
+
+        private Expression qualify(final AttributeExpression expression) {
+            ClassNode owner = resolver.findOwner(expression);
+            if (owner == null) {
+                return null;
+            }
+
+            AttributeExpression qualifiedReference = new AttributeExpression(
+                classX(owner),
+                transform(expression.getProperty()),
+                expression.isSafe()
+            );
+            qualifiedReference.setImplicitThis(false);
+            qualifiedReference.setSpreadSafe(expression.isSpreadSafe());
+            qualifiedReference.copyNodeMetaData(expression);
+            setSourcePosition(qualifiedReference, expression);
+            return qualifiedReference;
+        }
+
+        private Expression qualify(final PropertyExpression expression) {
+            ClassNode owner = resolver.findOwner(expression);
+            if (owner == null) {
+                return null;
+            }
+
+            PropertyExpression qualifiedReference = new PropertyExpression(
+                classX(owner),
+                transform(expression.getProperty()),
+                expression.isSafe()
+            );
+            qualifiedReference.setImplicitThis(false);
+            qualifiedReference.setSpreadSafe(expression.isSpreadSafe());
+            qualifiedReference.copyNodeMetaData(expression);
+            setSourcePosition(qualifiedReference, expression);
+            return qualifiedReference;
+        }
+
+        private Expression qualify(final MethodCallExpression expression) {
+            ClassNode owner = resolver.findOwner(expression);
+            if (owner == null) {
+                return null;
+            }
+
+            MethodCallExpression qualifiedReference = new MethodCallExpression(
+                classX(owner),
+                transform(expression.getMethod()),
+                transform(expression.getArguments())
+            );
+            qualifiedReference.setImplicitThis(false);
+            qualifiedReference.setSafe(expression.isSafe());
+            qualifiedReference.setSpreadSafe(expression.isSpreadSafe());
+            qualifiedReference.setGenericsTypes(expression.getGenericsTypes());
+            qualifiedReference.setMethodTarget(expression.getMethodTarget());
+            qualifiedReference.copyNodeMetaData(expression);
+            setSourcePosition(qualifiedReference, expression);
+            return qualifiedReference;
+        }
+    }
+
+    private static final class OuterStaticMemberResolver {
+
+        private final List<ClassNode> referenceOwners;
+        private final Set<String> referenceOwnerNames;
+
+        private OuterStaticMemberResolver(final List<ClassNode> outerClasses) {
+            LinkedHashMap<String, ClassNode> referenceOwnerIndex = new 
LinkedHashMap<>();
+            for (ClassNode outerClass : outerClasses) {
+                collectReferenceOwners(outerClass, referenceOwnerIndex);
+            }
+            this.referenceOwners = new 
ArrayList<>(referenceOwnerIndex.values());
+            this.referenceOwnerNames = new 
LinkedHashSet<>(referenceOwnerIndex.keySet());
+        }
+
+        private ClassNode findOwner(final VariableExpression expression) {
+            ClassNode owner = 
expression.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER);
+            if (!isReferenceOwner(owner)) {
+                return null;
+            }
+            return isStaticReference(expression, owner) ? owner : null;
+        }
+
+        private ClassNode findOwner(final MethodCallExpression expression) {
+            if (!expression.isImplicitThis() && 
!isQualifiedEnclosingInstanceReference(expression.getObjectExpression())) {
+                return null;
+            }
+
+            MethodNode directMethodCallTarget = expression.getMethodTarget();
+            if (directMethodCallTarget == null) {
+                directMethodCallTarget = 
expression.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
+            }
+            if (directMethodCallTarget == null || 
!directMethodCallTarget.isStatic()) {
+                return null;
+            }
+
+            ClassNode owner = directMethodCallTarget.getDeclaringClass();
+            if (!isReferenceOwner(owner)) {
+                return null;
+            }
+
+            return 
isEnclosingInstanceReceiver(expression.getObjectExpression())
+                ? owner
+                : null;
+        }
+
+        private ClassNode findOwner(final PropertyExpression expression) {
+            if (!expression.isImplicitThis() && 
!isQualifiedEnclosingInstanceReference(expression.getObjectExpression())) {
+                return null;
+            }
+
+            if 
(!isEnclosingInstanceReceiver(expression.getObjectExpression())) {
+                return null;
+            }
+
+            MethodNode directMethodCallTarget = 
expression.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
+            if (directMethodCallTarget != null && 
directMethodCallTarget.isStatic() && 
isReferenceOwner(directMethodCallTarget.getDeclaringClass())) {
+                return directMethodCallTarget.getDeclaringClass();
+            }
+
+            String propertyName = expression.getPropertyAsString();
+            if (propertyName == null) {
+                return null;
+            }
+
+            for (ClassNode referenceOwner : referenceOwners) {
+                if (isStaticMemberNamed(propertyName, referenceOwner)) {
+                    return referenceOwner;
+                }
+            }
+
+            return null;
+        }
+
+        private boolean isReferenceOwner(final ClassNode owner) {
+            return owner != null && 
referenceOwnerNames.contains(owner.redirect().getName());
+        }
+
+        private static void collectReferenceOwners(final ClassNode owner, 
final Map<String, ClassNode> referenceOwnerIndex) {
+            if (owner == null) {
+                return;
+            }
+
+            ClassNode redirectedOwner = owner.redirect();
+            if (referenceOwnerIndex.putIfAbsent(redirectedOwner.getName(), 
redirectedOwner) != null) {
+                return;
+            }
+
+            collectReferenceOwners(redirectedOwner.getSuperClass(), 
referenceOwnerIndex);
+            for (ClassNode interfaceNode : redirectedOwner.getInterfaces()) {
+                collectReferenceOwners(interfaceNode, referenceOwnerIndex);
+            }
+        }
+
+        private static boolean isStaticReference(final VariableExpression 
expression, final ClassNode owner) {
+            Variable accessedVariable = expression.getAccessedVariable();
+            if (accessedVariable instanceof FieldNode) {
+                return accessedVariable.isStatic();
+            }
+            if (accessedVariable instanceof PropertyNode) {
+                return accessedVariable.isStatic();
+            }
+            if (accessedVariable instanceof Parameter) {
+                return false;
+            }
+
+            MethodNode directMethodCallTarget = 
expression.getNodeMetaData(DIRECT_METHOD_CALL_TARGET);
+            if (directMethodCallTarget != null) {
+                return directMethodCallTarget.isStatic() && 
directMethodCallTarget.getParameters().length == 0;
+            }
+
+            return isStaticMemberNamed(expression.getName(), owner);
+        }
+
+        private static boolean isStaticMemberNamed(final String propertyName, 
final ClassNode owner) {
+            FieldNode field = owner.getField(propertyName);
+            if (field != null && field.isStatic()) {
+                return true;
+            }
+
+            PropertyNode property = owner.getProperty(propertyName);
+            if (property != null && property.isStatic()) {
+                return true;
+            }
+
+            MethodNode getter = owner.getGetterMethod("is" + 
capitalize(propertyName));
+            if (getter == null) {
+                getter = owner.getGetterMethod("get" + 
capitalize(propertyName));
+            }
+            return getter != null && getter.isStatic();
+        }
+    }
+
+    private static final class InstanceMemberAccessFinder extends 
CodeVisitorSupport {
+
+        private final OuterStaticMemberResolver resolver;
+        private boolean accessingInstanceMembers;
+
+        private InstanceMemberAccessFinder(final OuterStaticMemberResolver 
resolver) {
+            this.resolver = resolver;
+        }
+
+        @Override
+        public void visitVariableExpression(final VariableExpression 
expression) {
+            if (accessingInstanceMembers) {
+                return;
+            }
+            if (expression.isThisExpression() || 
expression.isSuperExpression() || "thisObject".equals(expression.getName())) {
+                accessingInstanceMembers = true;
+                return;
+            }
+
+            ClassNode owner = 
expression.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER);
+            if (owner != null && resolver.isReferenceOwner(owner) && 
resolver.findOwner(expression) == null) {
+                accessingInstanceMembers = true;
+                return;
+            }
+
+            super.visitVariableExpression(expression);
+        }
+
+        @Override
+        public void visitPropertyExpression(final PropertyExpression 
expression) {
+            if (accessingInstanceMembers) {
+                return;
+            }
+            if (resolver.findOwner(expression) != null) {
+                expression.getProperty().visit(this);
+                return;
+            }
+            if (isQualifiedEnclosingInstanceReference(expression)) {
+                accessingInstanceMembers = true;
+                return;
+            }
+
+            super.visitPropertyExpression(expression);
+        }
+
+        @Override
+        public void visitAttributeExpression(final AttributeExpression 
expression) {
+            if (accessingInstanceMembers) {
+                return;
+            }
+            if (resolver.findOwner(expression) != null) {
+                expression.getProperty().visit(this);
+                return;
+            }
+            if 
(isQualifiedEnclosingInstanceReference(expression.getObjectExpression())) {
+                accessingInstanceMembers = true;
+                return;
+            }
+
+            super.visitAttributeExpression(expression);
+        }
+
+        @Override
+        public void visitMethodCallExpression(final MethodCallExpression 
expression) {
+            if (accessingInstanceMembers) {
+                return;
+            }
+            if (resolver.findOwner(expression) != null) {
+                expression.getMethod().visit(this);
+                expression.getArguments().visit(this);
+                return;
+            }
+
+            super.visitMethodCallExpression(expression);
+        }
+
+        private boolean isAccessingInstanceMembers() {
+            return accessingInstanceMembers;
+        }
+    }
+
+    private static final class AccessesInstanceMembers {
+    }
+
+    private final SourceUnit sourceUnit;
+}
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..b61dc6c930 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
@@ -21,17 +21,14 @@ package org.codehaus.groovy.classgen.asm.sc;
 import org.codehaus.groovy.GroovyBugError;
 import org.codehaus.groovy.ast.ClassHelper;
 import org.codehaus.groovy.ast.ClassNode;
-import org.codehaus.groovy.ast.CodeVisitorSupport;
 import org.codehaus.groovy.ast.ConstructorNode;
 import org.codehaus.groovy.ast.InnerClassNode;
 import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.ast.Parameter;
 import org.codehaus.groovy.ast.builder.AstStringCompiler;
 import org.codehaus.groovy.ast.expr.ClosureExpression;
-import org.codehaus.groovy.ast.expr.ConstantExpression;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.LambdaExpression;
-import org.codehaus.groovy.ast.expr.VariableExpression;
 import org.codehaus.groovy.ast.stmt.BlockStatement;
 import org.codehaus.groovy.ast.stmt.Statement;
 import org.codehaus.groovy.classgen.BytecodeInstruction;
@@ -46,10 +43,8 @@ import 
org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys;
 import org.objectweb.asm.MethodVisitor;
 
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 
 import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.GENERATED_LAMBDA_TYPE;
@@ -72,6 +67,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;
@@ -83,99 +79,119 @@ import static org.objectweb.asm.Opcodes.NEW;
  */
 public class StaticTypesLambdaWriter extends LambdaWriter implements 
AbstractFunctionalInterfaceWriter {
 
-    private static final String IS_GENERATED_CONSTRUCTOR = 
"__IS_GENERATED_CONSTRUCTOR";
-    private static final String LAMBDA_SHARED_VARIABLES = 
"__LAMBDA_SHARED_VARIABLES";
-    private static final String DO_CALL = "doCall";
-
-    private final Map<Expression, ClassNode> lambdaClassNodes = new 
HashMap<>();
-    private final StaticTypesClosureWriter staticTypesClosureWriter;
-
     public StaticTypesLambdaWriter(final WriterController controller) {
         super(controller);
         this.staticTypesClosureWriter = new 
StaticTypesClosureWriter(controller);
+        this.lambdaAnalyzer = new 
StaticTypesLambdaAnalyzer(controller.getSourceUnit());
     }
 
     @Override
     public void writeLambda(final LambdaExpression expression) {
         // functional interface target is required for native lambda generation
-        ClassNode  functionalType = expression.getNodeMetaData(PARAMETER_TYPE);
-        MethodNode abstractMethod = ClassHelper.findSAM(functionalType);
-        if (abstractMethod == null || !functionalType.isInterface()) {
+        ClassNode functionalType = expression.getNodeMetaData(PARAMETER_TYPE);
+        MethodNode abstractMethod = 
resolveFunctionalInterfaceMethod(functionalType);
+        if (abstractMethod == null) {
             // generate bytecode for closure
             super.writeLambda(expression);
             return;
         }
 
+        boolean serializable = makeSerializableIfNeeded(expression, 
functionalType);
+        GeneratedLambda generatedLambda = getOrAddGeneratedLambda(expression, 
abstractMethod);
+
+        ensureDeserializeLambdaSupport(expression, generatedLambda, 
serializable);
+        if (generatedLambda.isCapturing() && 
!isPreloadedLambdaReceiver(generatedLambda)) {
+            loadLambdaReceiver(generatedLambda);
+        }
+
+        writeLambdaFactoryInvocation(functionalType.redirect(), 
abstractMethod, generatedLambda, serializable);
+    }
+
+    private static Parameter[] createDeserializeLambdaMethodParams() {
+        return new Parameter[]{new Parameter(SERIALIZEDLAMBDA_TYPE, 
"serializedLambda")};
+    }
+
+    private static MethodNode resolveFunctionalInterfaceMethod(final ClassNode 
functionalType) {
+        if (functionalType == null || !functionalType.isInterface()) {
+            return null;
+        }
+        return ClassHelper.findSAM(functionalType);
+    }
+
+    private boolean makeSerializableIfNeeded(final LambdaExpression 
expression, final ClassNode functionalType) {
         if (!expression.isSerializable() && 
functionalType.implementsInterface(SERIALIZABLE_TYPE)) {
             expression.setSerializable(true);
         }
+        return 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);
-                addDeserializeLambdaMethod();
-            }
-            newGroovyLambdaWrapperAndLoad(lambdaClass, expression, 
isAccessingInstanceMembersOfEnclosingClass(lambdaMethod));
+    private void ensureDeserializeLambdaSupport(final LambdaExpression 
expression, final GeneratedLambda generatedLambda, final boolean serializable) {
+        if (!serializable || 
hasDeserializeLambdaMethod(generatedLambda.lambdaClass)) {
+            return;
         }
 
+        addDeserializeLambdaMethodForLambdaExpression(expression, 
generatedLambda);
+        addDeserializeLambdaMethod();
+    }
+
+    private void writeLambdaFactoryInvocation(final ClassNode functionalType, 
final MethodNode abstractMethod, final GeneratedLambda generatedLambda, final 
boolean serializable) {
         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())
+            abstractMethod.getName(),
+            createLambdaFactoryMethodDescriptor(functionalType, 
generatedLambda),
+            createBootstrapMethod(controller.getClassNode().isInterface(), 
serializable),
+            
createBootstrapMethodArguments(createMethodDescriptor(abstractMethod),
+                generatedLambda.getMethodHandleKind(),
+                generatedLambda.lambdaClass, generatedLambda.lambdaMethod, 
generatedLambda.lambdaMethod.getParameters(), serializable)
         );
-        if (expression.isSerializable()) {
+        if (serializable) {
             mv.visitTypeInsn(CHECKCAST, "java/io/Serializable");
         }
 
-        controller.getOperandStack().replace(functionalType.redirect(), 1);
+        if (generatedLambda.nonCapturing()) {
+            controller.getOperandStack().push(functionalType);
+        } else {
+            controller.getOperandStack().replace(functionalType, 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];
+    private static MethodNode getLambdaMethod(final ClassNode lambdaClass) {
+        List<MethodNode> lambdaMethods = lambdaClass.getMethods(DO_CALL);
+        if (lambdaMethods.isEmpty()) {
+            throw new GroovyBugError("Failed to find the synthetic lambda 
method in " + lambdaClass.getName());
+        }
+        return lambdaMethods.get(0);
+    }
 
-        lambdaMethod.getCode().visit(new CodeVisitorSupport() {
-            @Override
-            public void visitConstantExpression(final ConstantExpression 
expression) {
-                if ("this".equals(expression.getValue())) { // as in 
Type.this.name
-                    result[0] = true;
-                }
-            }
-            @Override
-            public void visitVariableExpression(final VariableExpression 
expression) {
-                if ("this".equals(expression.getName()) || 
"thisObject".equals(expression.getName())) {
-                    result[0] = true;
-                } else {
-                    var owner = 
expression.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER);
-                    if (owner != null && 
lambdaMethod.getDeclaringClass().getOuterClasses().contains(owner)) {
-                        result[0] = true;
-                    }
-                }
+    private static ConstructorNode getGeneratedConstructor(final ClassNode 
lambdaClass) {
+        for (ConstructorNode constructorNode : 
lambdaClass.getDeclaredConstructors()) {
+            if 
(Boolean.TRUE.equals(constructorNode.getNodeMetaData(MetaDataKey.GENERATED_CONSTRUCTOR)))
 {
+                return constructorNode;
             }
-        });
+        }
+        throw new GroovyBugError("Failed to find the generated constructor in 
" + lambdaClass.getName());
+    }
 
-        return result[0];
+    private boolean isPreloadedLambdaReceiver(final GeneratedLambda 
generatedLambda) {
+        MethodNode enclosingMethod = controller.getMethodNode();
+        return enclosingMethod != null
+            && 
enclosingMethod.getNodeMetaData(MetaDataKey.PRELOADED_LAMBDA_RECEIVER) == 
generatedLambda.lambdaClass;
     }
 
-    private void newGroovyLambdaWrapperAndLoad(final ClassNode lambdaClass, 
final LambdaExpression expression, final boolean accessingInstanceMembers) {
+    private void loadLambdaReceiver(final GeneratedLambda generatedLambda) {
         CompileStack compileStack = controller.getCompileStack();
         OperandStack operandStack = controller.getOperandStack();
         MethodVisitor mv = controller.getMethodVisitor();
 
-        String lambdaClassInternalName = 
BytecodeHelper.getClassInternalName(lambdaClass);
+        String lambdaClassInternalName = 
BytecodeHelper.getClassInternalName(generatedLambda.lambdaClass);
         mv.visitTypeInsn(NEW, lambdaClassInternalName);
         mv.visitInsn(DUP);
 
-        if (controller.isStaticMethod() || 
compileStack.isInSpecialConstructorCall() || !accessingInstanceMembers) {
+        if (controller.isStaticMethod() || 
compileStack.isInSpecialConstructorCall() || 
!generatedLambda.accessingInstanceMembers) {
             classX(controller.getThisType()).visit(controller.getAcg());
         } else {
             loadThis();
@@ -183,23 +199,15 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
 
         operandStack.dup();
 
-        loadSharedVariables(expression);
-
-        Optional<ConstructorNode> generatedConstructor = 
lambdaClass.getDeclaredConstructors().stream()
-                .filter(ctor -> 
Boolean.TRUE.equals(ctor.getNodeMetaData(IS_GENERATED_CONSTRUCTOR))).findFirst();
-        if (generatedConstructor.isEmpty()) {
-            throw new GroovyBugError("Failed to find the generated 
constructor");
-        }
+        loadSharedVariables(generatedLambda.sharedVariables);
 
-        Parameter[] lambdaClassConstructorParameters = 
generatedConstructor.get().getParameters();
-        mv.visitMethodInsn(INVOKESPECIAL, lambdaClassInternalName, "<init>", 
BytecodeHelper.getMethodDescriptor(VOID_TYPE, 
lambdaClassConstructorParameters), lambdaClass.isInterface());
+        Parameter[] lambdaClassConstructorParameters = 
generatedLambda.constructor.getParameters();
+        mv.visitMethodInsn(INVOKESPECIAL, lambdaClassInternalName, "<init>", 
BytecodeHelper.getMethodDescriptor(VOID_TYPE, 
lambdaClassConstructorParameters), generatedLambda.lambdaClass.isInterface());
 
         operandStack.replace(CLOSURE_TYPE, 
lambdaClassConstructorParameters.length);
     }
 
-    private void loadSharedVariables(final LambdaExpression expression) {
-        Parameter[] lambdaSharedVariableParameters = 
expression.getNodeMetaData(LAMBDA_SHARED_VARIABLES);
-
+    private void loadSharedVariables(final Parameter[] 
lambdaSharedVariableParameters) {
         for (Parameter parameter : lambdaSharedVariableParameters) {
             loadReference(parameter.getName(), controller);
             if (parameter.getNodeMetaData(UseExistingReference.class) == null) 
{
@@ -208,20 +216,35 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
         }
     }
 
-    private String createAbstractMethodDesc(final ClassNode 
functionalInterface, final ClassNode lambdaClass) {
-        List<Parameter> lambdaSharedVariables = new LinkedList<>();
-        prependParameter(lambdaSharedVariables, "__lambda_this", lambdaClass);
-        return BytecodeHelper.getMethodDescriptor(functionalInterface, 
lambdaSharedVariables.toArray(Parameter.EMPTY_ARRAY));
+    private String createLambdaFactoryMethodDescriptor(final ClassNode 
functionalInterface, final GeneratedLambda generatedLambda) {
+        if (generatedLambda.nonCapturing()) {
+            return BytecodeHelper.getMethodDescriptor(functionalInterface, 
Parameter.EMPTY_ARRAY);
+        }
+        return BytecodeHelper.getMethodDescriptor(functionalInterface, new 
Parameter[]{createLambdaReceiverParameter(generatedLambda.lambdaClass)});
+    }
+
+    private static Parameter createLambdaReceiverParameter(final ClassNode 
lambdaClass) {
+        Parameter parameter = new Parameter(lambdaClass, "__lambda_this");
+        parameter.setClosureSharedVariable(false);
+        return parameter;
     }
 
-    private ClassNode getOrAddLambdaClass(final LambdaExpression expression, 
final MethodNode abstractMethod) {
-        return lambdaClassNodes.computeIfAbsent(expression, expr -> {
-            ClassNode lambdaClass = createLambdaClass((LambdaExpression) expr, 
ACC_FINAL | ACC_PUBLIC | ACC_STATIC, abstractMethod);
+    private GeneratedLambda getOrAddGeneratedLambda(final LambdaExpression 
expression, final MethodNode abstractMethod) {
+        return generatedLambdas.computeIfAbsent(expression, expr -> {
+            ClassNode lambdaClass = createLambdaClass(expr, ACC_FINAL | 
ACC_PUBLIC | ACC_STATIC, abstractMethod);
             controller.getAcg().addInnerClass(lambdaClass);
             lambdaClass.addInterface(GENERATED_LAMBDA_TYPE);
             
lambdaClass.putNodeMetaData(StaticCompilationMetadataKeys.STATIC_COMPILE_NODE, 
Boolean.TRUE);
             lambdaClass.putNodeMetaData(WriterControllerFactory.class, 
(WriterControllerFactory) x -> controller);
-            return lambdaClass;
+            MethodNode lambdaMethod = getLambdaMethod(lambdaClass);
+            return new GeneratedLambda(
+                lambdaClass,
+                lambdaMethod,
+                getGeneratedConstructor(lambdaClass),
+                getStoredLambdaSharedVariables(expr),
+                !requiresLambdaInstance(lambdaMethod),
+                lambdaAnalyzer.accessesInstanceMembers(lambdaMethod)
+            );
         });
     }
 
@@ -243,7 +266,7 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
             lambdaClass.setScriptBody(true);
         }
         if (controller.isStaticMethod()
-                || enclosingClass.isStaticClass()) {
+            || enclosingClass.isStaticClass()) {
             lambdaClass.setStaticClass(true);
         }
         if (expression.isSerializable()) {
@@ -252,11 +275,11 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
 
         MethodNode syntheticLambdaMethodNode = 
addSyntheticLambdaMethodNode(expression, lambdaClass, abstractMethod);
 
-        Parameter[] localVariableParameters = 
expression.getNodeMetaData(LAMBDA_SHARED_VARIABLES);
+        Parameter[] localVariableParameters = 
getStoredLambdaSharedVariables(expression);
         addFieldsForLocalVariables(lambdaClass, localVariableParameters);
 
         ConstructorNode constructorNode = addConstructor(expression, 
localVariableParameters, lambdaClass, 
createBlockStatementForConstructor(expression, outermostClass, enclosingClass));
-        constructorNode.putNodeMetaData(IS_GENERATED_CONSTRUCTOR, 
Boolean.TRUE);
+        constructorNode.putNodeMetaData(MetaDataKey.GENERATED_CONSTRUCTOR, 
Boolean.TRUE);
 
         syntheticLambdaMethodNode.getCode().visit(new 
CorrectAccessedVariableVisitor(lambdaClass));
 
@@ -274,17 +297,21 @@ public class StaticTypesLambdaWriter extends LambdaWriter 
implements AbstractFun
         Parameter[] localVariableParameters = 
getLambdaSharedVariables(expression);
         removeInitialValues(localVariableParameters);
 
-        expression.putNodeMetaData(LAMBDA_SHARED_VARIABLES, 
localVariableParameters);
+        expression.putNodeMetaData(MetaDataKey.STORED_LAMBDA_SHARED_VARIABLES, 
localVariableParameters);
 
         MethodNode doCallMethod = lambdaClass.addMethod(
-                DO_CALL,
-                ACC_PUBLIC,
-                abstractMethod.getReturnType(),
-                parametersWithExactType.clone(),
-                ClassNode.EMPTY_ARRAY,
-                expression.getCode()
+            DO_CALL,
+            ACC_PUBLIC,
+            abstractMethod.getReturnType(),
+            parametersWithExactType.clone(),
+            ClassNode.EMPTY_ARRAY,
+            expression.getCode()
         );
         doCallMethod.setSourcePosition(expression);
+        if (lambdaAnalyzer.isNonCapturing(doCallMethod, 
localVariableParameters)) {
+            lambdaAnalyzer.qualifyOuterStaticMemberReferences(doCallMethod);
+            doCallMethod.setModifiers(doCallMethod.getModifiers() | 
ACC_STATIC);
+        }
         return doCallMethod;
     }
 
@@ -308,55 +335,100 @@ public class StaticTypesLambdaWriter extends 
LambdaWriter implements AbstractFun
         }
 
         Statement code = block(
-                declS(localVarX("enclosingClass", OBJECT_TYPE), 
classX(enclosingClass)),
-                ((BlockStatement) new AstStringCompiler().compile(
-                        "return enclosingClass" +
-                                
".getDeclaredMethod(\"\\$deserializeLambda_${serializedLambda.getImplClass().replace('/',
 '$')}\\$\", serializedLambda.getClass())" +
-                                ".invoke(null, serializedLambda)"
-                ).get(0)).getStatements().get(0)
+            declS(localVarX("enclosingClass", OBJECT_TYPE), 
classX(enclosingClass)),
+            ((BlockStatement) new AstStringCompiler().compile(
+                "return enclosingClass" +
+                    
".getDeclaredMethod(\"\\$deserializeLambda_${serializedLambda.getImplClass().replace('/',
 '$')}\\$\", serializedLambda.getClass())" +
+                    ".invoke(null, serializedLambda)"
+            ).get(0)).getStatements().get(0)
         );
 
         enclosingClass.addSyntheticMethod(
-                "$deserializeLambda$",
-                ACC_PRIVATE | ACC_STATIC,
-                OBJECT_TYPE,
-                parameters,
-                ClassNode.EMPTY_ARRAY,
-                code);
+            "$deserializeLambda$",
+            ACC_PRIVATE | ACC_STATIC,
+            OBJECT_TYPE,
+            parameters,
+            ClassNode.EMPTY_ARRAY,
+            code);
+    }
+
+    private static boolean requiresLambdaInstance(final MethodNode 
lambdaMethod) {
+        return 0 == (lambdaMethod.getModifiers() & ACC_STATIC);
     }
 
-    private void addDeserializeLambdaMethodForEachLambdaExpression(final 
LambdaExpression expression, final ClassNode lambdaClass) {
+    private void addDeserializeLambdaMethodForLambdaExpression(final 
LambdaExpression expression, final GeneratedLambda generatedLambda) {
         ClassNode enclosingClass = controller.getClassNode();
-        Statement code = block(
+        Statement code;
+        if (generatedLambda.nonCapturing()) {
+            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));
+                            INVOKEVIRTUAL,
+                            "java/lang/invoke/SerializedLambda",
+                            "getCapturedArg",
+                            "(I)Ljava/lang/Object;",
+                            false);
+                        mv.visitTypeInsn(CHECKCAST, 
BytecodeHelper.getClassInternalName(generatedLambda.lambdaClass));
                         OperandStack operandStack = 
controller.getOperandStack();
-                        operandStack.push(lambdaClass);
+                        operandStack.push(generatedLambda.lambdaClass);
                     }
                 }),
                 returnS(expression)
-        );
+            );
+        }
 
-        enclosingClass.addSyntheticMethod(
-                createDeserializeLambdaMethodName(lambdaClass),
-                ACC_PUBLIC | ACC_STATIC,
-                OBJECT_TYPE,
-                createDeserializeLambdaMethodParams(),
-                ClassNode.EMPTY_ARRAY,
-                code);
+        MethodNode deserializeLambdaMethod = enclosingClass.addSyntheticMethod(
+            createDeserializeLambdaMethodName(generatedLambda.lambdaClass),
+            ACC_PUBLIC | ACC_STATIC,
+            OBJECT_TYPE,
+            createDeserializeLambdaMethodParams(),
+            ClassNode.EMPTY_ARRAY,
+            code);
+        if (generatedLambda.isCapturing()) {
+            // The deserialize helper preloads the captured receiver before it 
reuses the original lambda expression.
+            
deserializeLambdaMethod.putNodeMetaData(MetaDataKey.PRELOADED_LAMBDA_RECEIVER, 
generatedLambda.lambdaClass);
+        }
     }
 
     private static String createDeserializeLambdaMethodName(final ClassNode 
lambdaClass) {
         return "$deserializeLambda_" + lambdaClass.getName().replace('.', '$') 
+ "$";
     }
+
+    private static Parameter[] getStoredLambdaSharedVariables(final 
LambdaExpression expression) {
+        Parameter[] sharedVariables = 
expression.getNodeMetaData(MetaDataKey.STORED_LAMBDA_SHARED_VARIABLES);
+        if (sharedVariables == null) {
+            throw new GroovyBugError("Failed to find shared variables for 
lambda expression");
+        }
+        return sharedVariables;
+    }
+
+    private enum MetaDataKey {
+        GENERATED_CONSTRUCTOR,
+        STORED_LAMBDA_SHARED_VARIABLES,
+        PRELOADED_LAMBDA_RECEIVER
+    }
+
+    private record GeneratedLambda(ClassNode lambdaClass, MethodNode 
lambdaMethod, ConstructorNode constructor,
+                                   Parameter[] sharedVariables, boolean 
nonCapturing,
+                                   boolean accessingInstanceMembers) {
+
+        private boolean isCapturing() {
+            return !nonCapturing;
+        }
+
+        private int getMethodHandleKind() {
+            return nonCapturing ? H_INVOKESTATIC : H_INVOKEVIRTUAL;
+        }
+    }
+
+    private static final String DO_CALL = "doCall";
+    private final Map<LambdaExpression, GeneratedLambda> generatedLambdas = 
new HashMap<>();
+    private final StaticTypesClosureWriter staticTypesClosureWriter;
+    private final StaticTypesLambdaAnalyzer lambdaAnalyzer;
 }
diff --git a/src/test/groovy/groovy/transform/stc/LambdaTest.groovy 
b/src/test/groovy/groovy/transform/stc/LambdaTest.groovy
index f69eb1a332..6673e1bdb4 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,1588 @@ 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 testCapturingLocalVariable() {
+            assertScript shell,  '''
+                class C {
+                    static void test() {
+                        String x = '#'
+                        assert ['#1', '#2'] == [1, 2].stream().map(e -> x + 
e).toList()
+                    }
+                }
+                C.test()
+            '''
+        }
+
+        @Test
+        void testAccessingThis() {
+            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 testCallingInstanceMethod() {
+            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 testCallingSuperMethod() {
+            assertScript shell,  '''
+                class Base {
+                    String greet(int i) { "Hello $i" }
+                }
+                class C extends Base {
+                    void test() {
+                        assert ['Hello 1', 'Hello 2'] == [1, 2].stream().map(e 
-> super.greet(e)).toList()
+                    }
+                }
+                new C().test()
+            '''
+        }
+
+        @Test
+        void testNonCapturingLambdaWithThisStringLiteralRemainsSingleton() {
+            assertScript shell,  '''
+                class C {
+                    static void test() {
+                        def identities = new HashSet()
+                        for (int i = 0; i < 5; i++) {
+                            Supplier<String> supplier = () -> 'this'
+                            identities.add(System.identityHashCode(supplier))
+                            assert supplier.get() == 'this'
+                        }
+                        assert identities.size() == 1 : 'non-capturing lambda 
with string literal this should still be a singleton'
+                    }
+                }
+                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 testCapturingLambdaWithRunnable() {
+            assertScript shell,  '''
+                import java.util.concurrent.atomic.AtomicBoolean
+                class C {
+                    static void test() {
+                        AtomicBoolean ran = new AtomicBoolean(false)
+                        Runnable r = () -> ran.set(true)
+                        r.run()
+                        assert ran.get()
+                    }
+                }
+                C.test()
+            '''
+        }
+
+        @Test
+        void testCapturingLambdaWithConsumer() {
+            assertScript shell,  '''
+                class C {
+                    static void test() {
+                        def result = []
+                        Consumer<Integer> c = (Integer x) -> result.add(x * 2)
+                        c.accept(3)
+                        c.accept(5)
+                        assert result == [6, 10]
+                    }
+                }
+                C.test()
+            '''
+        }
+
+        @Test
+        void testNonCapturingLambdaAccessingStaticField() {
+            assertScript shell,  '''
+                class C {
+                    static final int OFFSET = 100
+                    static void test() {
+                        assert [101, 102, 103] == [1, 2, 3].stream().map(e -> 
e + OFFSET).toList()
+                    }
+                }
+                C.test()
+            '''
+        }
+
+        @Test
+        void testQualifiedOuterThisRemainsCapturing() {
+            assertScript shell,  '''
+                class Outer {
+                    String name = 'outer'
+                    class Inner {
+                        void test() {
+                            Function<Integer, String> f = (Integer x) -> 
Outer.this.name + x
+                            assert f.apply(1) == 'outer1'
+                        }
+                    }
+                    void test() { new Inner().test() }
+                }
+                new Outer().test()
+            '''
+        }
+
+        @Test
+        void testNestedNonCapturingLambdas() {
+            assertScript shell,  '''
+                class C {
+                    static void test() {
+                        Function<List<Integer>, List<Integer>> f = 
(List<Integer> list) ->
+                            list.stream().map(e -> e * 2).toList()
+                        assert f.apply([1, 2, 3]) == [2, 4, 6]
+                    }
+                }
+                C.test()
+            '''
+        }
+
+        @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 
testNonCapturingLambdaWithThisStringLiteralUsesCaptureFreeInvokeDynamic() {
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'C\\$_create_lambda1', method: 'doCall', '''
+                @CompileStatic
+                class C {
+                    static Supplier<String> create() {
+                        () -> 'this'
+                    }
+                }
+            ''')
+            assert lambdaBytecode.hasStrictSequence([
+                'public static doCall()Ljava/lang/Object;',
+                'L0'
+            ])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 'C', 
method: 'create', '''
+                @CompileStatic
+                class C {
+                    static Supplier<String> create() {
+                        () -> 'this'
+                    }
+                }
+            ''')
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC get()Ljava/util/function/Supplier;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'C$_create_lambda1.doCall()Ljava/lang/Object;'
+            ])
+            assert !outerBytecode.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 testSuperMethodCallRetainsInstanceDoCallAndCapturedReceiver() {
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'C\\$_create_lambda1', method: 'doCall', '''
+                @CompileStatic
+                class Base {
+                    String greet(int i) { "Hello $i" }
+                }
+                @CompileStatic
+                class C extends Base {
+                    Function<Integer, String> create() {
+                        (Integer i) -> super.greet(i)
+                    }
+                }
+            ''')
+            assert lambdaBytecode.hasSequence(['public 
doCall(Ljava/lang/Integer;)Ljava/lang/Object;'])
+            assert !lambdaBytecode.hasSequence(['public static 
doCall(Ljava/lang/Integer;)Ljava/lang/Object;'])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 'C', 
method: 'create', '''
+                @CompileStatic
+                class Base {
+                    String greet(int i) { "Hello $i" }
+                }
+                @CompileStatic
+                class C extends Base {
+                    Function<Integer, String> create() {
+                        (Integer i) -> super.greet(i)
+                    }
+                }
+            ''')
+            assert outerBytecode.hasSequence([
+                'NEW C$_create_lambda1',
+                'INVOKEDYNAMIC 
apply(LC$_create_lambda1;)Ljava/util/function/Function;',
+                
'C$_create_lambda1.doCall(Ljava/lang/Integer;)Ljava/lang/Object;'
+            ])
+        }
+
+        @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])
+        }
+
+        @Test
+        void 
testNonCapturingLambdaAccessingStaticFieldUsesCaptureFreeInvokeDynamic() {
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'C\\$_create_lambda1', method: 'doCall', '''
+                @CompileStatic
+                class C {
+                    static final int OFFSET = 100
+                    static IntUnaryOperator create() {
+                        (int i) -> i + OFFSET
+                    }
+                }
+            ''')
+            assert lambdaBytecode.hasStrictSequence([
+                'public static doCall(I)I',
+                'L0'
+            ])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 'C', 
method: 'create', '''
+                @CompileStatic
+                class C {
+                    static final int OFFSET = 100
+                    static IntUnaryOperator create() {
+                        (int i) -> i + OFFSET
+                    }
+                }
+            ''')
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC 
applyAsInt()Ljava/util/function/IntUnaryOperator;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'C$_create_lambda1.doCall(I)I'
+            ])
+            assert !outerBytecode.hasSequence(['NEW C$_create_lambda1'])
+        }
+
+        @Test
+        void 
testNonCapturingLambdaCallingQualifiedStaticMethodOnlyUsesCaptureFreeInvokeDynamic()
 {
+            def script = '''
+                @CompileStatic
+                class C {
+                    static String prefix() { 'Hi ' }
+                    static Function<Integer, String> create() {
+                        (Integer i) -> C.prefix() + i
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'C\\$_create_lambda1', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static 
doCall(Ljava/lang/Integer;)Ljava/lang/Object;'])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 'C', 
method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC apply()Ljava/util/function/Function;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                
'C$_create_lambda1.doCall(Ljava/lang/Integer;)Ljava/lang/Object;'
+            ])
+            assert !outerBytecode.hasSequence(['NEW C$_create_lambda1'])
+        }
+
+        @Test
+        void testNonCapturingComparatorLambdaUsesCaptureFreeInvokeDynamic() {
+            def script = '''
+                @CompileStatic
+                class C {
+                    static java.util.Comparator<Integer> create() {
+                        (Integer left, Integer right) -> right.compareTo(left)
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'C\\$_create_lambda1', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static 
doCall(Ljava/lang/Integer;Ljava/lang/Integer;)I'])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 'C', 
method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC compare()Ljava/util/Comparator;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                
'C$_create_lambda1.doCall(Ljava/lang/Integer;Ljava/lang/Integer;)I'
+            ])
+            assert !outerBytecode.hasSequence(['NEW C$_create_lambda1'])
+        }
+
+        @Test
+        void 
testNonCapturingLambdaWithCustomFunctionalInterfaceUsesCaptureFreeInvokeDynamic()
 {
+            def script = '''
+                interface Transformer<I, O> {
+                    O transform(I input)
+                }
+                @CompileStatic
+                class C {
+                    static Transformer<String, Integer> create() {
+                        (String s) -> s.length()
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'C\\$_create_lambda1', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static 
doCall(Ljava/lang/String;)Ljava/lang/Object;'])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 'C', 
method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC transform()LTransformer;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                
'C$_create_lambda1.doCall(Ljava/lang/String;)Ljava/lang/Object;'
+            ])
+            assert !outerBytecode.hasSequence(['NEW C$_create_lambda1'])
+        }
+
+        @Test
+        void testNonCapturingSerializableLambdaUsesCaptureFreeAltMetafactory() 
{
+            def script = '''
+                @CompileStatic
+                class C {
+                    static SerFunc<Integer, String> create() {
+                        (Integer i) -> 'a' + i
+                    }
+                    interface SerFunc<I,O> extends Serializable, Function<I,O> 
{}
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'C\\$_create_lambda1', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static 
doCall(Ljava/lang/Integer;)Ljava/lang/Object;'])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 'C', 
method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC apply()LC$SerFunc;',
+                'java/lang/invoke/LambdaMetafactory.altMetafactory',
+                
'C$_create_lambda1.doCall(Ljava/lang/Integer;)Ljava/lang/Object;'
+            ])
+            assert outerBytecode.hasSequence(['CHECKCAST 
java/io/Serializable'])
+            assert !outerBytecode.hasSequence(['NEW C$_create_lambda1'])
+        }
+
+        @Test
+        void 
testNonCapturingLambdaInStaticInitializerUsesCaptureFreeInvokeDynamic() {
+            def script = '''
+                @CompileStatic
+                class C {
+                    static IntUnaryOperator op
+                    static {
+                        op = (int i) -> i + 1
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'C\\$__clinit__lambda1', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static doCall(I)I'])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 'C', 
method: '<clinit>', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC 
applyAsInt()Ljava/util/function/IntUnaryOperator;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'C$__clinit__lambda1.doCall(I)I'
+            ])
+            assert !outerBytecode.hasSequence(['NEW C$__clinit__lambda1'])
+        }
+
+        @Test
+        void 
testNonCapturingLambdaInFieldInitializerUsesCaptureFreeInvokeDynamic() {
+            def script = '''
+                @CompileStatic
+                class C {
+                    IntUnaryOperator op = (int i) -> i + 1
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'C\\$_lambda1', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static doCall(I)I'])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 'C', 
method: '<init>', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC 
applyAsInt()Ljava/util/function/IntUnaryOperator;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'C$_lambda1.doCall(I)I'
+            ])
+            assert !outerBytecode.hasSequence(['NEW C$_lambda1'])
+        }
+
+        @Test
+        void 
testNonCapturingLambdaInInterfaceDefaultMethodUsesCaptureFreeInvokeDynamic() {
+            def script = '''
+                @CompileStatic
+                interface Processor {
+                    default IntUnaryOperator process() {
+                        (int i) -> i + 1
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'Processor\\$_process_lambda1', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static doCall(I)I'])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 
'Processor', method: 'process', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC 
applyAsInt()Ljava/util/function/IntUnaryOperator;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'Processor$_process_lambda1.doCall(I)I'
+            ])
+            assert !outerBytecode.hasSequence(['NEW 
Processor$_process_lambda1'])
+        }
+
+        @Test
+        void testNonCapturingLambdaWithExceptionInBody() {
+            assertScript shell,  '''
+                class C {
+                    static void test() {
+                        Function<String, Integer> f = (String s) -> {
+                            if (s == null) throw new 
IllegalArgumentException('null input')
+                            return s.length()
+                        }
+                        assert f.apply('hello') == 5
+                        try {
+                            f.apply(null)
+                            assert false : 'should have thrown'
+                        } catch (IllegalArgumentException e) {
+                            assert e.message == 'null input'
+                        }
+                    }
+                }
+                C.test()
+            '''
+        }
+
+        @Test
+        void testAccessingThisObjectRemainsCapturing() {
+            assertScript shell,  '''
+                class C {
+                    String name = 'test'
+                    void test() {
+                        Function<Integer, String> f = (Integer x) -> 
thisObject.name + x
+                        assert f.apply(1) == 'test1'
+                    }
+                }
+                new C().test()
+            '''
+        }
+
+        @Test
+        void testAccessingThisObjectRetainsInstanceDoCall() {
+            def bytecode = compileStaticBytecode(classNamePattern: 
'C\\$_test_lambda1', method: 'doCall', '''
+                @CompileStatic
+                class C {
+                    String name = 'test'
+                    Function<Integer, String> test() {
+                        (Integer x) -> thisObject.name + x
+                    }
+                }
+            ''')
+            assert bytecode.hasSequence(['public 
doCall(Ljava/lang/Integer;)Ljava/lang/Object;'])
+            assert !bytecode.hasSequence(['public static 
doCall(Ljava/lang/Integer;)Ljava/lang/Object;'])
+        }
+
+        @Test
+        void testInnerClassLambdaUsingOuterStaticMembersQualifiesStaticCalls() 
{
+            assertScript shell, '''
+                class Outer {
+                    static String label = 'outer'
+                    static boolean isReady() { true }
+                    static String suffix() { '!' }
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> ready ? label.toUpperCase() + suffix() : 
'never'
+                        }
+                    }
+                }
+
+                def left = new Outer().new Inner().create()
+                def right = new Outer().new Inner().create()
+                assert left.get() == 'OUTER!'
+                assert right.get() == 'OUTER!'
+            '''
+
+            def script = '''
+                @CompileStatic
+                class Outer {
+                    static String label = 'outer'
+                    static boolean isReady() { true }
+                    static String suffix() { '!' }
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> ready ? label.toUpperCase() + suffix() : 
'never'
+                        }
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner\\$_create_lambda\\d+', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static 
doCall()Ljava/lang/Object;'])
+            assert lambdaBytecode.hasSequence([
+                'INVOKESTATIC Outer.isReady ()Z',
+                'INVOKESTATIC Outer.getLabel ()Ljava/lang/String;',
+                'INVOKESTATIC Outer.suffix ()Ljava/lang/String;'
+            ])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner', method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC get()Ljava/util/function/Supplier;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'Outer$Inner$_create_lambda1.doCall()Ljava/lang/Object;'
+            ])
+            assert !outerBytecode.hasSequence(['NEW 
Outer$Inner$_create_lambda1'])
+        }
+
+        @Test
+        void 
testSerializableLambdasInSameClassShareSingleDeserializeDispatcher() {
+            assertScript shell, COMMON_IMPORTS + '''
+                import java.io.ByteArrayInputStream
+                import java.io.ByteArrayOutputStream
+
+                @CompileStatic
+                class C {
+                    interface SerFunc<I, O> extends Serializable, Function<I, 
O> {}
+
+                    static SerFunc<Integer, String> createLeft() {
+                        (Integer i) -> 'a' + i
+                    }
+
+                    static SerFunc<Integer, String> createRight() {
+                        (Integer i) -> 'b' + (i * 2)
+                    }
+
+                    static byte[] serialize(Serializable value) {
+                        def out = new ByteArrayOutputStream()
+                        out.withObjectOutputStream { it.writeObject(value) }
+                        out.toByteArray()
+                    }
+
+                    static <T> T deserialize(byte[] bytes) {
+                        new 
ByteArrayInputStream(bytes).withObjectInputStream(C.classLoader) {
+                            (T) it.readObject()
+                        }
+                    }
+                }
+
+                assert C.declaredMethods.count { it.name == 
'$deserializeLambda$' } == 1
+                C.SerFunc<Integer, String> left = 
C.deserialize(C.serialize(C.createLeft()))
+                C.SerFunc<Integer, String> right = 
C.deserialize(C.serialize(C.createRight()))
+                assert left.apply(1) == 'a1'
+                assert right.apply(2) == 'b4'
+            '''
+        }
+
+        @Test
+        void 
testInnerClassLambdaUsingOuterStaticBeanPropertiesStaysCaptureFree() {
+            assertScript shell, COMMON_IMPORTS + '''
+                class Outer {
+                    static boolean isReady() { true }
+                    static String getLabel() { 'outer' }
+                    static String getSuffix() { '!' }
+
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> ready ? label.toUpperCase() + suffix : 
'never'
+                        }
+                    }
+                }
+
+                def supplier = new Outer().new Inner().create()
+                assert supplier.get() == 'OUTER!'
+            '''
+
+            def script = '''
+                @CompileStatic
+                class Outer {
+                    static boolean isReady() { true }
+                    static String getLabel() { 'outer' }
+                    static String getSuffix() { '!' }
+
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> ready ? label.toUpperCase() + suffix : 
'never'
+                        }
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner\\$_create_lambda\\d+', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static 
doCall()Ljava/lang/Object;'])
+            assert lambdaBytecode.hasSequence([
+                'INVOKESTATIC Outer.isReady ()Z',
+                'INVOKESTATIC Outer.getLabel ()Ljava/lang/String;',
+                'INVOKESTATIC Outer.getSuffix ()Ljava/lang/String;'
+            ])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner', method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC get()Ljava/util/function/Supplier;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'Outer$Inner$_create_lambda1.doCall()Ljava/lang/Object;'
+            ])
+            assert !outerBytecode.hasSequence(['NEW 
Outer$Inner$_create_lambda1'])
+        }
+
+        @Test
+        void testInnerClassLambdaUsingInterfaceStaticMembersStaysCaptureFree() 
{
+            assertScript shell, COMMON_IMPORTS + '''
+                interface Labels {
+                    String LABEL = 'outer'
+                    static boolean isReady() { true }
+                    static String getSuffix() { '!' }
+                }
+
+                class Outer implements Labels {
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> ready ? LABEL.toUpperCase() + suffix : 
'never'
+                        }
+                    }
+                }
+
+                def left = new Outer().new Inner().create()
+                def right = new Outer().new Inner().create()
+                assert left.is(right) : 'interface static members should keep 
the lambda capture-free'
+                assert left.get() == 'OUTER!'
+                assert right.get() == 'OUTER!'
+            '''
+
+            def script = '''
+                @CompileStatic
+                interface Labels {
+                    String LABEL = 'outer'
+                    static boolean isReady() { true }
+                    static String getSuffix() { '!' }
+                }
+
+                @CompileStatic
+                class Outer implements Labels {
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> ready ? LABEL.toUpperCase() + suffix : 
'never'
+                        }
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner\\$_create_lambda\\d+', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static 
doCall()Ljava/lang/Object;'])
+            assert lambdaBytecode.hasSequence([
+                'INVOKESTATIC Labels.isReady ()Z',
+                'GETSTATIC Labels.LABEL :',
+                'INVOKESTATIC Labels.getSuffix ()Ljava/lang/String;'
+            ])
+            assert !lambdaBytecode.hasSequence(['CHECKCAST 
groovy/lang/GroovyObject'])
+            assert !lambdaBytecode.hasSequence(['INVOKEINTERFACE 
groovy/lang/GroovyObject.getProperty'])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner', method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC get()Ljava/util/function/Supplier;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'Outer$Inner$_create_lambda1.doCall()Ljava/lang/Object;'
+            ])
+            assert !outerBytecode.hasSequence(['NEW 
Outer$Inner$_create_lambda1'])
+        }
+
+        @Test
+        void testInnerClassLambdaUsingInheritedStaticMembersStaysCaptureFree() 
{
+            assertScript shell, COMMON_IMPORTS + '''
+                class Base {
+                    static boolean isReady() { true }
+                    static String getLabel() { 'outer' }
+                    static String getSuffix() { '!' }
+                }
+
+                class Outer extends Base {
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> ready ? label.toUpperCase() + suffix : 
'never'
+                        }
+                    }
+                }
+
+                def left = new Outer().new Inner().create()
+                def right = new Outer().new Inner().create()
+                assert left.is(right) : 'inherited static members should keep 
the lambda capture-free'
+                assert left.get() == 'OUTER!'
+                assert right.get() == 'OUTER!'
+            '''
+
+            def script = '''
+                @CompileStatic
+                class Base {
+                    static boolean isReady() { true }
+                    static String getLabel() { 'outer' }
+                    static String getSuffix() { '!' }
+                }
+
+                @CompileStatic
+                class Outer extends Base {
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> ready ? label.toUpperCase() + suffix : 
'never'
+                        }
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner\\$_create_lambda\\d+', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static 
doCall()Ljava/lang/Object;'])
+            assert lambdaBytecode.hasSequence([
+                'INVOKESTATIC Base.isReady ()Z',
+                'INVOKESTATIC Base.getLabel ()Ljava/lang/String;',
+                'INVOKESTATIC Base.getSuffix ()Ljava/lang/String;'
+            ])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner', method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC get()Ljava/util/function/Supplier;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'Outer$Inner$_create_lambda1.doCall()Ljava/lang/Object;'
+            ])
+            assert !outerBytecode.hasSequence(['NEW 
Outer$Inner$_create_lambda1'])
+        }
+
+        @Test
+        void 
testInnerClassLambdaUsingQualifiedOuterThisStaticMembersStaysCaptureFree() {
+            assertScript shell, COMMON_IMPORTS + '''
+                class Outer {
+                    static String label = 'outer'
+                    static String suffix() { '!' }
+
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> [email protected]() + 
Outer.this.suffix()
+                        }
+                    }
+                }
+
+                def left = new Outer().new Inner().create()
+                def right = new Outer().new Inner().create()
+                assert left.is(right) : 'qualified outer-this static access 
should stay capture-free'
+                assert left.get() == 'OUTER!'
+                assert right.get() == 'OUTER!'
+            '''
+
+            def script = '''
+                @CompileStatic
+                class Outer {
+                    static String label = 'outer'
+                    static String suffix() { '!' }
+
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> [email protected]() + 
Outer.this.suffix()
+                        }
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner\\$_create_lambda\\d+', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static 
doCall()Ljava/lang/Object;'])
+            assert lambdaBytecode.hasSequence([
+                'GETSTATIC Outer.label :',
+                'INVOKESTATIC Outer.suffix ()Ljava/lang/String;'
+            ])
+            assert !lambdaBytecode.hasSequence(['CHECKCAST 
groovy/lang/GroovyObject'])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner', method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC get()Ljava/util/function/Supplier;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'Outer$Inner$_create_lambda1.doCall()Ljava/lang/Object;'
+            ])
+            assert !outerBytecode.hasSequence(['NEW 
Outer$Inner$_create_lambda1'])
+        }
+
+        @Test
+        void 
testStaticNestedClassLambdaUsingOwnStaticMethodStaysNonCapturing() {
+            assertScript shell, COMMON_IMPORTS + '''
+                class Outer {
+                    static class Nested {
+                        static String helper() { 'nested' }
+
+                        Supplier<String> create() {
+                            () -> helper().toUpperCase()
+                        }
+                    }
+                }
+
+                def left = new Outer.Nested().create()
+                def right = new Outer.Nested().create()
+                assert left.is(right) : 'static nested-class helpers should 
not be mistaken for outer-instance capture'
+                assert left.get() == 'NESTED'
+                assert right.get() == 'NESTED'
+            '''
+
+            def script = '''
+                @CompileStatic
+                class Outer {
+                    static class Nested {
+                        static String helper() { 'nested' }
+
+                        Supplier<String> create() {
+                            () -> helper().toUpperCase()
+                        }
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Nested\\$_create_lambda\\d+', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static 
doCall()Ljava/lang/Object;'])
+            assert lambdaBytecode.hasSequence([
+                'INVOKESTATIC Outer$Nested.helper ()Ljava/lang/String;',
+                'INVOKEVIRTUAL java/lang/String.toUpperCase 
()Ljava/lang/String;'
+            ])
+
+            def nestedBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Nested', method: 'create', script)
+            assert nestedBytecode.hasSequence([
+                'INVOKEDYNAMIC get()Ljava/util/function/Supplier;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'Outer$Nested$_create_lambda1.doCall()Ljava/lang/Object;'
+            ])
+            assert !nestedBytecode.hasSequence(['NEW 
Outer$Nested$_create_lambda1'])
+        }
+
+        @Test
+        void 
testInnerClassLambdaUsingStaticallyImportedHelperMethodStaysNonCapturing() {
+            assertScript shell, COMMON_IMPORTS + '''
+                import static Helper.helper
+
+                class Helper {
+                    static String helper() { 'helper' }
+                }
+
+                class Outer {
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> helper().toUpperCase()
+                        }
+                    }
+                }
+
+                def left = new Outer().new Inner().create()
+                def right = new Outer().new Inner().create()
+                assert left.is(right) : 'statically imported helper methods 
should not be treated as captured outer state'
+                assert left.get() == 'HELPER'
+                assert right.get() == 'HELPER'
+            '''
+
+            def script = '''
+                import static Helper.helper
+
+                @CompileStatic
+                class Helper {
+                    static String helper() { 'helper' }
+                }
+
+                @CompileStatic
+                class Outer {
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> helper().toUpperCase()
+                        }
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner\\$_create_lambda\\d+', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static 
doCall()Ljava/lang/Object;'])
+            assert lambdaBytecode.hasSequence([
+                'INVOKESTATIC Helper.helper ()Ljava/lang/String;',
+                'INVOKEVIRTUAL java/lang/String.toUpperCase 
()Ljava/lang/String;'
+            ])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner', method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC get()Ljava/util/function/Supplier;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'Outer$Inner$_create_lambda1.doCall()Ljava/lang/Object;'
+            ])
+            assert !outerBytecode.hasSequence(['NEW 
Outer$Inner$_create_lambda1'])
+        }
+
+        @Test
+        void 
testInnerClassLambdaUsingStaticallyImportedInterfaceMethodStaysNonCapturing() {
+            assertScript shell, COMMON_IMPORTS + '''
+                import static HelperApi.helper
+
+                interface HelperApi {
+                    static String helper() { 'iface' }
+                }
+
+                class Outer implements HelperApi {
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> helper().toUpperCase()
+                        }
+                    }
+                }
+
+                def left = new Outer().new Inner().create()
+                def right = new Outer().new Inner().create()
+                assert left.is(right) : 'statically imported interface methods 
should stay receiver-free inside inner lambdas'
+                assert left.get() == 'IFACE'
+                assert right.get() == 'IFACE'
+            '''
+
+            def script = '''
+                import static HelperApi.helper
+
+                @CompileStatic
+                interface HelperApi {
+                    static String helper() { 'iface' }
+                }
+
+                @CompileStatic
+                class Outer implements HelperApi {
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> helper().toUpperCase()
+                        }
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner\\$_create_lambda\\d+', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static 
doCall()Ljava/lang/Object;'])
+            assert lambdaBytecode.hasSequence([
+                'INVOKESTATIC HelperApi.helper ()Ljava/lang/String;',
+                'INVOKEVIRTUAL java/lang/String.toUpperCase 
()Ljava/lang/String;'
+            ])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner', method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC get()Ljava/util/function/Supplier;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'Outer$Inner$_create_lambda1.doCall()Ljava/lang/Object;'
+            ])
+            assert !outerBytecode.hasSequence(['NEW 
Outer$Inner$_create_lambda1'])
+        }
+
+        @Test
+        void 
testInnerClassLambdaMutatingImplicitStaticPropertyStaysCaptureFree() {
+            assertScript shell, COMMON_IMPORTS + '''
+                class Outer {
+                    private static String backing = 'outer'
+                    static String getLabel() { backing }
+                    static void setLabel(String value) { backing = value }
+
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> {
+                                label += '!'
+                                label
+                            }
+                        }
+                    }
+                }
+
+                Outer.label = 'outer'
+                def left = new Outer().new Inner().create()
+                def right = new Outer().new Inner().create()
+                assert left.is(right) : 'static property mutation should not 
force receiver capture'
+                assert left.get() == 'outer!'
+                Outer.label = 'outer'
+                assert right.get() == 'outer!'
+            '''
+
+            def script = '''
+                @CompileStatic
+                class Outer {
+                    private static String backing = 'outer'
+                    static String getLabel() { backing }
+                    static void setLabel(String value) { backing = value }
+
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> {
+                                label += '!'
+                                label
+                            }
+                        }
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner\\$_create_lambda\\d+', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static 
doCall()Ljava/lang/Object;'])
+            assert lambdaBytecode.hasSequence([
+                'INVOKESTATIC Outer.getLabel ()Ljava/lang/String;',
+                'INVOKESTATIC 
org/codehaus/groovy/runtime/ScriptBytecodeAdapter.setProperty'
+            ])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner', method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC get()Ljava/util/function/Supplier;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'Outer$Inner$_create_lambda1.doCall()Ljava/lang/Object;'
+            ])
+            assert !outerBytecode.hasSequence(['NEW 
Outer$Inner$_create_lambda1'])
+        }
+
+        @Test
+        void 
testInnerClassLambdaMutatingQualifiedOuterThisStaticPropertyStaysCaptureFree() {
+            assertScript shell, COMMON_IMPORTS + '''
+                class Outer {
+                    private static String backing = 'outer'
+                    static String getLabel() { backing }
+                    static void setLabel(String value) { backing = value }
+
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> {
+                                Outer.this.label += '!'
+                                Outer.this.label
+                            }
+                        }
+                    }
+                }
+
+                Outer.label = 'outer'
+                def left = new Outer().new Inner().create()
+                def right = new Outer().new Inner().create()
+                assert left.is(right) : 'qualified outer-this static property 
mutation should stay capture-free'
+                assert left.get() == 'outer!'
+                Outer.label = 'outer'
+                assert right.get() == 'outer!'
+            '''
+
+            def script = '''
+                @CompileStatic
+                class Outer {
+                    private static String backing = 'outer'
+                    static String getLabel() { backing }
+                    static void setLabel(String value) { backing = value }
+
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> {
+                                Outer.this.label += '!'
+                                Outer.this.label
+                            }
+                        }
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner\\$_create_lambda\\d+', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public static 
doCall()Ljava/lang/Object;'])
+            assert lambdaBytecode.hasSequence([
+                'INVOKESTATIC Outer.getLabel ()Ljava/lang/String;',
+                'INVOKESTATIC 
org/codehaus/groovy/runtime/ScriptBytecodeAdapter.setProperty'
+            ])
+            assert !lambdaBytecode.hasSequence(['CHECKCAST 
groovy/lang/GroovyObject'])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner', method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'INVOKEDYNAMIC get()Ljava/util/function/Supplier;',
+                'java/lang/invoke/LambdaMetafactory.metafactory',
+                'Outer$Inner$_create_lambda1.doCall()Ljava/lang/Object;'
+            ])
+            assert !outerBytecode.hasSequence(['NEW 
Outer$Inner$_create_lambda1'])
+        }
+
+        @Test
+        void 
testInnerClassLambdaReadingQualifiedOuterThisInstancePropertyRemainsCapturing() 
{
+            assertScript shell, COMMON_IMPORTS + '''
+                class Outer {
+                    String name = 'outer'
+
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> Outer.this.name + ':' + Outer.this.name
+                        }
+                    }
+                }
+
+                def inner = new Outer().new Inner()
+                def left = inner.create()
+                def right = inner.create()
+                assert !left.is(right) : 'qualified outer-instance property 
access must keep the lambda capturing'
+                assert left.get() == 'outer:outer'
+                assert right.get() == 'outer:outer'
+            '''
+
+            def script = '''
+                @CompileStatic
+                class Outer {
+                    String name = 'outer'
+
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> Outer.this.name + ':' + Outer.this.name
+                        }
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner\\$_create_lambda\\d+', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public 
doCall()Ljava/lang/Object;'])
+            assert !lambdaBytecode.hasSequence(['public static 
doCall()Ljava/lang/Object;'])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner', method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'NEW Outer$Inner$_create_lambda1',
+                'INVOKEDYNAMIC 
get(LOuter$Inner$_create_lambda1;)Ljava/util/function/Supplier;',
+                'Outer$Inner$_create_lambda1.doCall()Ljava/lang/Object;'
+            ])
+        }
+
+        @Test
+        void 
testInnerClassReadingQualifiedOuterThisInstanceFieldRemainsCapturing() {
+            assertScript shell, COMMON_IMPORTS + '''
+                class Outer {
+                    String name = 'outer'
+
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> Outer.this.@name + ':' + Outer.this.@name
+                        }
+                    }
+                }
+
+                def inner = new Outer().new Inner()
+                def left = inner.create()
+                def right = inner.create()
+                assert !left.is(right) : 'qualified outer-instance field 
access must keep the lambda capturing'
+                assert left.get() == 'outer:outer'
+                assert right.get() == 'outer:outer'
+            '''
+
+            def script = '''
+                @CompileStatic
+                class Outer {
+                    String name = 'outer'
+
+                    class Inner {
+                        Supplier<String> create() {
+                            () -> Outer.this.@name + ':' + Outer.this.@name
+                        }
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner\\$_create_lambda\\d+', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public 
doCall()Ljava/lang/Object;'])
+            assert !lambdaBytecode.hasSequence(['public static 
doCall()Ljava/lang/Object;'])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner', method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'NEW Outer$Inner$_create_lambda1',
+                'INVOKEDYNAMIC 
get(LOuter$Inner$_create_lambda1;)Ljava/util/function/Supplier;',
+                'Outer$Inner$_create_lambda1.doCall()Ljava/lang/Object;'
+            ])
+        }
+
+        @Test
+        void 
testInnerClassLambdaUsingImplicitOuterInstancePropertyRemainsCapturing() {
+            assertScript shell, COMMON_IMPORTS + '''
+                class Outer {
+                    String name = 'outer'
+
+                    class Inner {
+                        Function<Integer, String> create() {
+                            (Integer i) -> name + i
+                        }
+                    }
+                }
+
+                def fn = new Outer().new Inner().create()
+                assert fn.apply(1) == 'outer1'
+            '''
+
+            def script = '''
+                @CompileStatic
+                class Outer {
+                    String name = 'outer'
+
+                    class Inner {
+                        Function<Integer, String> create() {
+                            (Integer i) -> name + i
+                        }
+                    }
+                }
+            '''
+            def lambdaBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner\\$_create_lambda\\d+', method: 'doCall', script)
+            assert lambdaBytecode.hasSequence(['public 
doCall(Ljava/lang/Integer;)Ljava/lang/Object;'])
+            assert !lambdaBytecode.hasSequence(['public static 
doCall(Ljava/lang/Integer;)Ljava/lang/Object;'])
+
+            def outerBytecode = compileStaticBytecode(classNamePattern: 
'Outer\\$Inner', method: 'create', script)
+            assert outerBytecode.hasSequence([
+                'NEW Outer$Inner$_create_lambda1',
+                'INVOKEDYNAMIC 
apply(LOuter$Inner$_create_lambda1;)Ljava/util/function/Function;',
+                
'Outer$Inner$_create_lambda1.doCall(Ljava/lang/Integer;)Ljava/lang/Object;'
+            ])
+        }
+
+        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.Consumer
+            import java.util.function.Function
+            import java.util.function.IntUnaryOperator
+            import java.util.function.Supplier
+            '''.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'
         ])

Reply via email to