From 7dd009dfba640e465d8d0d8b83af5e2e2b928f6a Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 24 Aug 2026 21:04:53 +0200 Subject: [PATCH 1/2] RemoveRedundantTypeCast: keep casts on signature-polymorphic methods MethodHandle#invoke/invokeExact and the VarHandle accessors are signature-polymorphic (JLS 15.12.3): the enclosing cast is what gives the call site its return type, so removing it reverts the call to Object and breaks compilation. Fixes #1024 --- .../RemoveRedundantTypeCast.java | 21 ++++++++++ .../RemoveRedundantTypeCastTest.java | 42 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java index d7635a5b0..992df59b7 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java @@ -67,6 +67,13 @@ public J visitTypeCast(J.TypeCast typeCast, ExecutionContext ctx) { return visited; } + // A signature-polymorphic method (JLS 15.12.3) has no fixed return type at the call + // site; the enclosing cast is what gives it one, so removing the cast reverts the + // call to `Object` and breaks compilation. + if (isSignaturePolymorphic(typeCast.getExpression())) { + return visited; + } + Cursor parent = getCursor().dropParentUntil(is -> is instanceof J.VariableDeclarations || is instanceof J.Lambda || is instanceof J.Return || @@ -230,6 +237,20 @@ public J visitParentheses(J.Parentheses parens, ExecutionContex return parentheses; } + private boolean isSignaturePolymorphic(Expression expression) { + Expression expr = expression; + while (expr instanceof J.Parentheses) { + expr = (Expression) ((J.Parentheses) expr).getTree(); + } + if (!(expr instanceof J.MethodInvocation)) { + return false; + } + JavaType.Method methodType = ((J.MethodInvocation) expr).getMethodType(); + return methodType != null && + (TypeUtils.isOfClassType(methodType.getDeclaringType(), "java.lang.invoke.MethodHandle") || + TypeUtils.isOfClassType(methodType.getDeclaringType(), "java.lang.invoke.VarHandle")); + } + private boolean returnsDeclaredTypeParameter(Expression expression) { if (!(expression instanceof J.MethodInvocation)) { return false; diff --git a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java index 6b11c2191..bb17c0b0a 100644 --- a/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCastTest.java @@ -930,4 +930,46 @@ static void sink(Object... args) {} ) ); } + + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/1024") + @Test + void doNotRemoveCastOnMethodHandleInvoke() { + rewriteRun( + //language=java + java( + """ + import java.lang.invoke.MethodHandle; + + class Example { + String hello(MethodHandle handle) throws Throwable { + return (String) handle.invoke(); + } + + void assign(MethodHandle handle) throws Throwable { + String s = (String) handle.invokeExact(); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-static-analysis/issues/1024") + @Test + void doNotRemoveCastOnVarHandleAccessor() { + rewriteRun( + //language=java + java( + """ + import java.lang.invoke.VarHandle; + + class Example { + String read(VarHandle handle, Object target) { + return (String) handle.get(target); + } + } + """ + ) + ); + } } From d5ff1ac66bf4fb8be15c24f57622229ef21ce47c Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Mon, 24 Aug 2026 21:14:29 +0200 Subject: [PATCH 2/2] Use Expression#unwrap and move rationale to javadoc --- .../staticanalysis/RemoveRedundantTypeCast.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java index 992df59b7..0b451e54a 100644 --- a/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java +++ b/src/main/java/org/openrewrite/staticanalysis/RemoveRedundantTypeCast.java @@ -67,9 +67,6 @@ public J visitTypeCast(J.TypeCast typeCast, ExecutionContext ctx) { return visited; } - // A signature-polymorphic method (JLS 15.12.3) has no fixed return type at the call - // site; the enclosing cast is what gives it one, so removing the cast reverts the - // call to `Object` and breaks compilation. if (isSignaturePolymorphic(typeCast.getExpression())) { return visited; } @@ -237,11 +234,9 @@ public J visitParentheses(J.Parentheses parens, ExecutionContex return parentheses; } + /// Signature-polymorphic methods (JLS 15.12.3) take their return type from the enclosing cast, so removing it breaks compilation. private boolean isSignaturePolymorphic(Expression expression) { - Expression expr = expression; - while (expr instanceof J.Parentheses) { - expr = (Expression) ((J.Parentheses) expr).getTree(); - } + Expression expr = expression.unwrap(); if (!(expr instanceof J.MethodInvocation)) { return false; }