From ad919b6a9381c2b9d1b60f0485979d6863fdc3b7 Mon Sep 17 00:00:00 2001 From: martinfrancois Date: Mon, 10 Aug 2026 13:25:50 +0200 Subject: [PATCH 1/3] UseLambdaForFunctionalInterface: keep the receiver of implicit getClass() An anonymous class has its own `this`; a lambda uses the enclosing lexical `this`. The recipe's `usesThis` guard only recognises an explicit `this` identifier, so an unqualified `getClass()` in the functional method slipped past it and the conversion silently changed the returned runtime class from the anonymous implementation to the enclosing class. Add a `usesImplicitGetClass` guard that keeps the anonymous class when the functional method resolves `java.lang.Object getClass()` with no receiver or with a bare `super` receiver, either as an invocation or as a `super::getClass` member reference. It does not descend into nested class or nested anonymous class bodies, which declare their own `this`, but it does visit the enclosing expression and the arguments of a qualified `new`, which are evaluated in the outer scope. A skipped site is reported in the data table as "calls `getClass()` on the anonymous instance". Worth weighing on review: a `getClass` call with no type attribution now blocks conversion, because its receiver cannot be proven; an outer-qualified `Test.super.getClass()` keeps its receiver either way and is deliberately left convertible; and `Test.this.getClass()` was already blocked by `usesThis`. No existing test expectation changes. --- .../UseLambdaForFunctionalInterface.java | 71 +++++ .../UseLambdaForFunctionalInterfaceTest.java | 251 ++++++++++++++++++ 2 files changed, 322 insertions(+) diff --git a/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java b/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java index f57df39ea..1184b3551 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java +++ b/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java @@ -21,6 +21,7 @@ import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.RemoveUnusedImports; import org.openrewrite.java.cleanup.UnnecessaryParenthesesVisitor; import org.openrewrite.java.tree.*; @@ -42,6 +43,8 @@ import static java.util.stream.Collectors.toList; public class UseLambdaForFunctionalInterface extends Recipe { + private static final MethodMatcher OBJECT_GET_CLASS = new MethodMatcher("java.lang.Object getClass()"); + @Getter final String displayName = "Use lambda expressions instead of anonymous classes"; @@ -489,6 +492,9 @@ private static boolean overridesObjectMethod(JavaType.Method method) { if (usesThis(cursor)) { return "references `this`"; } + if (usesImplicitGetClass(cursor)) { + return "calls `getClass()` on the anonymous instance"; + } if (shadowsLocalVariable(cursor)) { return "shadows a local variable"; } @@ -554,6 +560,71 @@ public J visitIdentifier(J.Identifier ident, Integer integer) { return hasThis.get(); } + /** + * An unqualified {@code getClass()} is dispatched on the anonymous class's own {@code this}, which a lambda + * does not have: the same call in a lambda reports the enclosing class instead. There is no {@code this} + * token for {@link #usesThis} to see, so the call has to be recognised by what it resolves to. Bare + * {@code super.getClass()} and {@code super::getClass} name the superclass part of the same {@code this} + * (and in a static enclosing method a lambda's {@code super} does not even compile), so they are treated + * identically; an outer-qualified {@code Test.super.getClass()} keeps its receiver either way, so it is + * deliberately not blocked and still converts; {@code Test.this.getClass()} also keeps its receiver but is + * already blocked by {@link #usesThis}. A call that lost its type attribution is also blocked, because its + * receiver cannot be proven either. + */ + private static boolean usesImplicitGetClass(Cursor cursor) { + J.NewClass n = cursor.getValue(); + assert n.getBody() != null; + AtomicBoolean hasImplicitGetClass = new AtomicBoolean(false); + new JavaVisitor() { + @Override + public J visitClassDeclaration(J.ClassDeclaration classDecl, Integer integer) { + // A local or member class declares its own `this`, so calls inside it keep their receiver. + return classDecl; + } + + @Override + public J visitNewClass(J.NewClass newClass, Integer integer) { + if (newClass.getBody() == null) { + return super.visitNewClass(newClass, integer); + } + // Same for a nested anonymous class, but its enclosing expression (of a qualified `new`) + // and its arguments are still evaluated in this scope. + if (newClass.getEnclosing() != null) { + visit(newClass.getEnclosing(), integer); + } + for (Expression argument : newClass.getArguments()) { + visit(argument, integer); + } + return newClass; + } + + @Override + public J visitMethodInvocation(J.MethodInvocation method, Integer integer) { + if ((method.getSelect() == null || isBareSuper(method.getSelect())) && + "getClass".equals(method.getSimpleName()) && + (method.getMethodType() == null || OBJECT_GET_CLASS.matches(method))) { + hasImplicitGetClass.set(true); + } + return super.visitMethodInvocation(method, integer); + } + + @Override + public J visitMemberReference(J.MemberReference memberRef, Integer integer) { + if (isBareSuper(memberRef.getContaining()) && + "getClass".equals(memberRef.getReference().getSimpleName()) && + (memberRef.getMethodType() == null || OBJECT_GET_CLASS.matches(memberRef))) { + hasImplicitGetClass.set(true); + } + return super.visitMemberReference(memberRef, integer); + } + + private boolean isBareSuper(@Nullable Expression expression) { + return expression instanceof J.Identifier && "super".equals(((J.Identifier) expression).getSimpleName()); + } + }.visit(n.getBody(), 0, cursor); + return hasImplicitGetClass.get(); + } + private static List parameterNames(J.MethodDeclaration method) { return method.getParameters().stream() .filter(J.VariableDeclarations.class::isInstance) diff --git a/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java b/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java index 3ddf0ae89..e57db1130 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java @@ -479,6 +479,232 @@ public Integer apply(Integer n) { ); } + @Test + void dontUseLambdaWhenImplicitGetClass() { + // An anonymous class introduces its own `this`, so an unqualified `getClass()` returns the + // anonymous implementation class; a lambda would return the enclosing class instead. + rewriteRun( + //language=java + java( + """ + import java.util.function.Supplier; + + class Test { + Supplier> supplier() { + return new Supplier>() { + @Override + public Class get() { + return getClass(); + } + }; + } + } + """ + ) + ); + } + + @Test + void dontUseLambdaWhenImplicitGetClassIsNested() { + rewriteRun( + //language=java + java( + """ + import java.util.function.Supplier; + + class Test { + Supplier supplier(boolean b) { + return new Supplier() { + @Override + public String get() { + Supplier> nested = () -> getClass(); + return b ? String.valueOf(nested.get()) : ""; + } + }; + } + } + """ + ) + ); + } + + @Test + void dontUseLambdaWhenSuperGetClass() { + // Bare `super` inside the anonymous class is the `Object` part of its own `this`, so + // `super.getClass()` returns the anonymous implementation class. In this static method a + // lambda's `super` would not even compile. + rewriteRun( + //language=java + java( + """ + import java.util.function.Supplier; + + class Test { + static Supplier> supplier() { + return new Supplier>() { + @Override + public Class get() { + return super.getClass(); + } + }; + } + } + """ + ) + ); + } + + @Test + void dontUseLambdaWhenSuperGetClassMethodReference() { + rewriteRun( + //language=java + java( + """ + import java.util.function.Supplier; + + class Test { + Supplier>> supplier() { + return new Supplier>>() { + @Override + public Supplier> get() { + return super::getClass; + } + }; + } + } + """ + ) + ); + } + + @Test + void dontUseLambdaWhenEnclosingExpressionOfQualifiedNewCallsGetClass() { + // The nested anonymous body keeps its own `this`, but the enclosing expression of the + // qualified `new` is evaluated in the outer anonymous class's scope. + rewriteRun( + //language=java + java( + """ + import java.util.function.Supplier; + + class Test { + class Inner { + } + + Test tag(Class c) { + return this; + } + + Supplier supplier() { + return new Supplier() { + @Override + public Object get() { + return tag(getClass()).new Inner() { + }; + } + }; + } + } + """ + ) + ); + } + + @Test + void dontUseLambdaWhenGetClassCannotBeAttributed() { + // Without a method type the receiver cannot be proven, so the site is left alone rather than + // converted on the assumption that a `getClass` reaching this far is somebody else's method. + rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), + //language=java + java( + """ + import java.util.function.Supplier; + + class Test { + Supplier s = new Supplier() { + @Override + public String get() { + return getClass(unknown); + } + }; + } + """ + ) + ); + } + + @Test + void useLambdaWhenGetClassIsCalledOnAnotherObject() { + rewriteRun( + //language=java + java( + """ + import java.util.function.Function; + + class Test { + Function> f = new Function>() { + @Override + public Class apply(Object o) { + return o.getClass(); + } + }; + } + """, + """ + import java.util.function.Function; + + class Test { + Function> f = o -> o.getClass(); + } + """ + ) + ); + } + + @Test + void useLambdaWhenOnlyANestedAnonymousClassCallsGetClass() { + // The nested anonymous class keeps its own `this` either way, so the outer one is safe to convert. + rewriteRun( + //language=java + java( + """ + import java.util.function.Supplier; + + class Test { + Supplier>> supplier() { + return new Supplier>>() { + @Override + public Supplier> get() { + return new Supplier>() { + @Override + public Class get() { + return getClass(); + } + }; + } + }; + } + } + """, + """ + import java.util.function.Supplier; + + class Test { + Supplier>> supplier() { + return () -> new Supplier>() { + @Override + public Class get() { + return getClass(); + } + }; + } + } + """ + ) + ); + } + @SuppressWarnings("UnnecessaryLocalVariable") @Test void dontUseLambdaWhenShadowsLocalVariable() { @@ -1176,6 +1402,31 @@ public Integer apply(Integer n) { ); } + @Test + void dataTableRecordsImplicitGetClass() { + rewriteRun( + spec -> spec.dataTable(AnonymousFunctionalInterfaceImplementations.Row.class, rows -> { + assertThat(rows).hasSize(1); + assertThat(rows.getFirst().isConvertible()).isFalse(); + assertThat(rows.getFirst().getReason()).isEqualTo("calls `getClass()` on the anonymous instance"); + }), + //language=java + java( + """ + import java.util.function.Supplier; + class Test { + Supplier> s = new Supplier>() { + @Override + public Class get() { + return getClass(); + } + }; + } + """ + ) + ); + } + @Test void dataTableRecordsAnonymousClassDeclaringMoreThanTheInterfaceMethod() { rewriteRun( From a729bf544df98338b07efc87f1025f1172abc41a Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Tue, 11 Aug 2026 09:58:36 +0200 Subject: [PATCH 2/3] Review fixes: block any unqualified call dispatched on the anonymous instance, not just getClass() --- .../UseLambdaForFunctionalInterface.java | 55 ++++---- .../UseLambdaForFunctionalInterfaceTest.java | 118 +++++++++++++++++- 2 files changed, 147 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java b/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java index 1184b3551..b82b7630e 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java +++ b/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java @@ -21,7 +21,6 @@ import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaTemplate; import org.openrewrite.java.JavaVisitor; -import org.openrewrite.java.MethodMatcher; import org.openrewrite.java.RemoveUnusedImports; import org.openrewrite.java.cleanup.UnnecessaryParenthesesVisitor; import org.openrewrite.java.tree.*; @@ -37,13 +36,15 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singleton; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; public class UseLambdaForFunctionalInterface extends Recipe { - private static final MethodMatcher OBJECT_GET_CLASS = new MethodMatcher("java.lang.Object getClass()"); + private static final Set OBJECT_METHOD_NAMES = new HashSet<>(asList( + "clone", "equals", "finalize", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait")); @Getter final String displayName = "Use lambda expressions instead of anonymous classes"; @@ -492,8 +493,8 @@ private static boolean overridesObjectMethod(JavaType.Method method) { if (usesThis(cursor)) { return "references `this`"; } - if (usesImplicitGetClass(cursor)) { - return "calls `getClass()` on the anonymous instance"; + if (callsMethodOnAnonymousInstance(n, cursor)) { + return "calls a method on the anonymous instance"; } if (shadowsLocalVariable(cursor)) { return "shadows a local variable"; @@ -561,20 +562,21 @@ public J visitIdentifier(J.Identifier ident, Integer integer) { } /** - * An unqualified {@code getClass()} is dispatched on the anonymous class's own {@code this}, which a lambda - * does not have: the same call in a lambda reports the enclosing class instead. There is no {@code this} - * token for {@link #usesThis} to see, so the call has to be recognised by what it resolves to. Bare - * {@code super.getClass()} and {@code super::getClass} name the superclass part of the same {@code this} - * (and in a static enclosing method a lambda's {@code super} does not even compile), so they are treated - * identically; an outer-qualified {@code Test.super.getClass()} keeps its receiver either way, so it is - * deliberately not blocked and still converts; {@code Test.this.getClass()} also keeps its receiver but is - * already blocked by {@link #usesThis}. A call that lost its type attribution is also blocked, because its - * receiver cannot be proven either. + * An unqualified call that resolves to a method the anonymous class inherits or declares is dispatched on that + * class's own {@code this}, which a lambda does not have: in a lambda the same call names a member of the + * enclosing class, so it reports a different receiver or does not compile at all. There is no {@code this} + * token for {@link #usesThis} to see, so the call is recognised by what it resolves to: the receiver is the + * anonymous instance exactly when its type is a subtype of the declaring type. + *

+ * Bare {@code super.m()} and {@code super::m} name the superclass part of that same {@code this}, so they are + * always blocked; an outer-qualified {@code Test.super.m()} keeps its receiver either way and still converts. + * A call that lost its type attribution is blocked only when it is named after an {@code Object} method, the + * one case where no enclosing class could have supplied it. */ - private static boolean usesImplicitGetClass(Cursor cursor) { - J.NewClass n = cursor.getValue(); + private static boolean callsMethodOnAnonymousInstance(J.NewClass n, Cursor cursor) { assert n.getBody() != null; - AtomicBoolean hasImplicitGetClass = new AtomicBoolean(false); + JavaType anonymousType = n.getType(); + AtomicBoolean callsAnonymousInstance = new AtomicBoolean(false); new JavaVisitor() { @Override public J visitClassDeclaration(J.ClassDeclaration classDecl, Integer integer) { @@ -600,29 +602,32 @@ public J visitNewClass(J.NewClass newClass, Integer integer) { @Override public J visitMethodInvocation(J.MethodInvocation method, Integer integer) { - if ((method.getSelect() == null || isBareSuper(method.getSelect())) && - "getClass".equals(method.getSimpleName()) && - (method.getMethodType() == null || OBJECT_GET_CLASS.matches(method))) { - hasImplicitGetClass.set(true); + if (isBareSuper(method.getSelect()) || + method.getSelect() == null && dispatchedOnThis(method.getMethodType(), method.getSimpleName())) { + callsAnonymousInstance.set(true); } return super.visitMethodInvocation(method, integer); } @Override public J visitMemberReference(J.MemberReference memberRef, Integer integer) { - if (isBareSuper(memberRef.getContaining()) && - "getClass".equals(memberRef.getReference().getSimpleName()) && - (memberRef.getMethodType() == null || OBJECT_GET_CLASS.matches(memberRef))) { - hasImplicitGetClass.set(true); + if (isBareSuper(memberRef.getContaining())) { + callsAnonymousInstance.set(true); } return super.visitMemberReference(memberRef, integer); } + private boolean dispatchedOnThis(JavaType.@Nullable Method methodType, String name) { + return methodType == null ? + OBJECT_METHOD_NAMES.contains(name) : + TypeUtils.isAssignableTo(methodType.getDeclaringType(), anonymousType); + } + private boolean isBareSuper(@Nullable Expression expression) { return expression instanceof J.Identifier && "super".equals(((J.Identifier) expression).getSimpleName()); } }.visit(n.getBody(), 0, cursor); - return hasImplicitGetClass.get(); + return callsAnonymousInstance.get(); } private static List parameterNames(J.MethodDeclaration method) { diff --git a/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java b/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java index e57db1130..5dce2f42f 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java @@ -705,6 +705,122 @@ public Class get() { ); } + @Test + void dontUseLambdaWhenImplicitObjectMethodOtherThanGetClass() { + // `hashCode()` is `this.hashCode()` on the anonymous instance; in a lambda it would be the + // enclosing instance's, so the value silently changes. + rewriteRun( + //language=java + java( + """ + import java.util.function.Supplier; + + class Test { + Supplier supplier() { + return new Supplier() { + @Override + public Integer get() { + return hashCode(); + } + }; + } + } + """ + ) + ); + } + + @Test + void dontUseLambdaWhenSuperMethodReferenceOtherThanGetClass() { + // Bare `super` names the `Object` part of the anonymous instance; in this static method a + // lambda's `super` would not even compile. + rewriteRun( + //language=java + java( + """ + import java.util.function.Supplier; + + class Test { + static Supplier> supplier() { + return new Supplier>() { + @Override + public Supplier get() { + return super::toString; + } + }; + } + } + """ + ) + ); + } + + @Test + void dontUseLambdaWhenTheInterfaceMethodCallsItself() { + // The recursive `get()` is dispatched on the anonymous instance; a lambda has no name to + // recurse through, so the call would not resolve at all. + rewriteRun( + //language=java + java( + """ + import java.util.function.Supplier; + + class Test { + Supplier supplier(boolean b) { + return new Supplier() { + @Override + public String get() { + return b ? get() : "x"; + } + }; + } + } + """ + ) + ); + } + + @Test + void useLambdaWhenCallingAMethodOfTheEnclosingClass() { + // An unqualified call that resolves to the enclosing class keeps its receiver in a lambda. + rewriteRun( + //language=java + java( + """ + import java.util.function.Supplier; + + class Test { + String name() { + return "x"; + } + + Supplier supplier() { + return new Supplier() { + @Override + public String get() { + return name(); + } + }; + } + } + """, + """ + import java.util.function.Supplier; + + class Test { + String name() { + return "x"; + } + + Supplier supplier() { + return () -> name(); + } + } + """ + ) + ); + } + @SuppressWarnings("UnnecessaryLocalVariable") @Test void dontUseLambdaWhenShadowsLocalVariable() { @@ -1408,7 +1524,7 @@ void dataTableRecordsImplicitGetClass() { spec -> spec.dataTable(AnonymousFunctionalInterfaceImplementations.Row.class, rows -> { assertThat(rows).hasSize(1); assertThat(rows.getFirst().isConvertible()).isFalse(); - assertThat(rows.getFirst().getReason()).isEqualTo("calls `getClass()` on the anonymous instance"); + assertThat(rows.getFirst().getReason()).isEqualTo("calls a method on the anonymous instance"); }), //language=java java( From 7e95fd9ec48b8bad42906d4448d8f266782ab7da Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Wed, 12 Aug 2026 00:21:19 +0200 Subject: [PATCH 3/3] Trim commentary --- .../UseLambdaForFunctionalInterface.java | 20 +++++--------- .../UseLambdaForFunctionalInterfaceTest.java | 26 +++++++------------ 2 files changed, 16 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java b/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java index b82b7630e..cd291be17 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java +++ b/src/main/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterface.java @@ -562,16 +562,10 @@ public J visitIdentifier(J.Identifier ident, Integer integer) { } /** - * An unqualified call that resolves to a method the anonymous class inherits or declares is dispatched on that - * class's own {@code this}, which a lambda does not have: in a lambda the same call names a member of the - * enclosing class, so it reports a different receiver or does not compile at all. There is no {@code this} - * token for {@link #usesThis} to see, so the call is recognised by what it resolves to: the receiver is the - * anonymous instance exactly when its type is a subtype of the declaring type. - *

- * Bare {@code super.m()} and {@code super::m} name the superclass part of that same {@code this}, so they are - * always blocked; an outer-qualified {@code Test.super.m()} keeps its receiver either way and still converts. - * A call that lost its type attribution is blocked only when it is named after an {@code Object} method, the - * one case where no enclosing class could have supplied it. + * An unqualified call resolving to a method the anonymous class inherits or declares is dispatched on that + * class's own {@code this}, which a lambda does not have. There is no {@code this} token for {@link #usesThis} + * to see, so it is recognised by what it resolves to. Unattributed calls are blocked only when named after an + * {@code Object} method, the one case no enclosing class could have supplied. */ private static boolean callsMethodOnAnonymousInstance(J.NewClass n, Cursor cursor) { assert n.getBody() != null; @@ -580,7 +574,7 @@ private static boolean callsMethodOnAnonymousInstance(J.NewClass n, Cursor curso new JavaVisitor() { @Override public J visitClassDeclaration(J.ClassDeclaration classDecl, Integer integer) { - // A local or member class declares its own `this`, so calls inside it keep their receiver. + // A local or member class declares its own `this` return classDecl; } @@ -589,8 +583,8 @@ public J visitNewClass(J.NewClass newClass, Integer integer) { if (newClass.getBody() == null) { return super.visitNewClass(newClass, integer); } - // Same for a nested anonymous class, but its enclosing expression (of a qualified `new`) - // and its arguments are still evaluated in this scope. + // Same for a nested anonymous class, but a qualified `new`'s enclosing expression and its + // arguments are still evaluated in this scope if (newClass.getEnclosing() != null) { visit(newClass.getEnclosing(), integer); } diff --git a/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java b/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java index 5dce2f42f..a439eb5aa 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UseLambdaForFunctionalInterfaceTest.java @@ -481,8 +481,7 @@ public Integer apply(Integer n) { @Test void dontUseLambdaWhenImplicitGetClass() { - // An anonymous class introduces its own `this`, so an unqualified `getClass()` returns the - // anonymous implementation class; a lambda would return the enclosing class instead. + // The anonymous class's own `this` makes `getClass()` return the implementation class, not the enclosing one rewriteRun( //language=java java( @@ -530,9 +529,7 @@ public String get() { @Test void dontUseLambdaWhenSuperGetClass() { - // Bare `super` inside the anonymous class is the `Object` part of its own `this`, so - // `super.getClass()` returns the anonymous implementation class. In this static method a - // lambda's `super` would not even compile. + // Bare `super` is the `Object` part of the anonymous instance; in a static method a lambda's would not compile rewriteRun( //language=java java( @@ -579,8 +576,7 @@ public Supplier> get() { @Test void dontUseLambdaWhenEnclosingExpressionOfQualifiedNewCallsGetClass() { - // The nested anonymous body keeps its own `this`, but the enclosing expression of the - // qualified `new` is evaluated in the outer anonymous class's scope. + // The qualified `new`'s enclosing expression is evaluated in the outer anonymous class's scope rewriteRun( //language=java java( @@ -612,8 +608,7 @@ public Object get() { @Test void dontUseLambdaWhenGetClassCannotBeAttributed() { - // Without a method type the receiver cannot be proven, so the site is left alone rather than - // converted on the assumption that a `getClass` reaching this far is somebody else's method. + // Without a method type the receiver cannot be proven, so the site is left alone rewriteRun( spec -> spec.typeValidationOptions(TypeValidation.none()), //language=java @@ -664,7 +659,7 @@ class Test { @Test void useLambdaWhenOnlyANestedAnonymousClassCallsGetClass() { - // The nested anonymous class keeps its own `this` either way, so the outer one is safe to convert. + // The nested anonymous class keeps its own `this` either way rewriteRun( //language=java java( @@ -707,8 +702,7 @@ public Class get() { @Test void dontUseLambdaWhenImplicitObjectMethodOtherThanGetClass() { - // `hashCode()` is `this.hashCode()` on the anonymous instance; in a lambda it would be the - // enclosing instance's, so the value silently changes. + // `hashCode()` is the anonymous instance's; in a lambda it would silently become the enclosing instance's rewriteRun( //language=java java( @@ -732,8 +726,7 @@ public Integer get() { @Test void dontUseLambdaWhenSuperMethodReferenceOtherThanGetClass() { - // Bare `super` names the `Object` part of the anonymous instance; in this static method a - // lambda's `super` would not even compile. + // Bare `super` names the `Object` part of the anonymous instance rewriteRun( //language=java java( @@ -757,8 +750,7 @@ public Supplier get() { @Test void dontUseLambdaWhenTheInterfaceMethodCallsItself() { - // The recursive `get()` is dispatched on the anonymous instance; a lambda has no name to - // recurse through, so the call would not resolve at all. + // A lambda has no name to recurse through, so the call would not resolve at all rewriteRun( //language=java java( @@ -782,7 +774,7 @@ public String get() { @Test void useLambdaWhenCallingAMethodOfTheEnclosingClass() { - // An unqualified call that resolves to the enclosing class keeps its receiver in a lambda. + // A call resolving to the enclosing class keeps its receiver in a lambda rewriteRun( //language=java java(