From 03032b8797cd2ac196e85566496fec80e6521f09 Mon Sep 17 00:00:00 2001 From: Neil Mushell Date: Tue, 21 Jul 2026 14:05:02 +0000 Subject: [PATCH] UnnecessaryExplicitTypeArguments: retain load-bearing witness in argument position on instance methods too The argument-position branch only guarded static methods (via shouldRetainOnStaticMethod) against removing a witness whose type variables aren't inferable from the call's own arguments. Instance methods used as arguments skipped that guard entirely, so a witness like `t.source()` passed to a generic static factory `Transition.of(...)` got stripped even though STATE_VALUE has no argument to infer it from and the factory itself provides no target type, leaving STATE_VALUE unresolved. Generalize the check to apply regardless of static/instance, matching the reasoning already used for the sibling select-position case. --- .../UnnecessaryExplicitTypeArguments.java | 11 +-- .../UnnecessaryExplicitTypeArgumentsTest.java | 69 +++++++++++++++++++ 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArguments.java b/src/main/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArguments.java index b66d08bef..832f4af40 100644 --- a/src/main/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArguments.java +++ b/src/main/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArguments.java @@ -63,8 +63,8 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu return m; } } else { - // This invocation is an argument of the enclosing invocation. - if (shouldRetainOnStaticMethod(methodType)) { + // As above, retain unless inferable from this call's own arguments (static or not). + if (!canInferTypeArgumentsFromArguments(methodType)) { return m; } // Cannot remove type parameters if it would introduce ambiguity about which method should be called @@ -153,13 +153,6 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Execu return samReturn; } - private boolean shouldRetainOnStaticMethod(JavaType.Method methodType) { - // Without a target type, removing the explicit type arguments of a static method can break - // overload resolution in the enclosing call when the type variables are not inferable from - // the call's own arguments. - return methodType.hasFlags(Flag.Static) && !canInferTypeArgumentsFromArguments(methodType); - } - private boolean canInferTypeArgumentsFromArguments(JavaType.Method methodType) { // Without arguments, the type parameters cannot be inferred from call-site arguments. if (methodType.getParameterTypes().isEmpty()) { diff --git a/src/test/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArgumentsTest.java b/src/test/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArgumentsTest.java index 19bd9cfd5..58c7b81c6 100644 --- a/src/test/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArgumentsTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/UnnecessaryExplicitTypeArgumentsTest.java @@ -246,6 +246,75 @@ String test(Cursor c) { ); } + @Test + void retainsWitnessOnInstanceMethodPassedAsArgumentToGenericMethod() { + // STATE_VALUE isn't inferable from source()'s (zero) args or from of()'s target type. + rewriteRun( + //language=java + java( + """ + interface Transition { + STATE_VALUE source(); + } + + class Pair> { + static > Pair of(STATE_VALUE a, STATE_VALUE b) { + return new Pair<>(); + } + } + + class Test> { + Pair test(Transition t, STATE_VALUE b) { + return Pair.of(t.source(), b); + } + } + """ + ) + ); + } + + @Test + void removesWitnessOnInstanceMethodArgumentWhenInferableFromOwnArguments() { + // Unlike above, T is inferable from identity()'s own argument, so it's still removable. + rewriteRun( + //language=java + java( + """ + class Holder { + T identity(T value) { + return value; + } + } + + class Test { + void accept(Object o) { + } + + void test(Holder h) { + accept(h.identity("x")); + } + } + """, + """ + class Holder { + T identity(T value) { + return value; + } + } + + class Test { + void accept(Object o) { + } + + void test(Holder h) { + accept(h.identity("x")); + } + } + """ + ) + ); + } + @Nested class StaticMethods { static final SourceSpecs GENERIC_CLASS_SOURCE = java(