Skip to content

Commit 0e0eaf0

Browse files
Fix UnsupportedOperationException in Py.ExpressionTypeTree.withType() (#7314)
`ExpressionTypeTree.withType()` delegates to the wrapped reference's `withType()`, which throws for `J.MethodInvocation` and `J.MethodDeclaration` (they require `withMethodType()` instead). This caused `ChangeType` to fail on repos containing Python files, because its `postVisit()` calls `withType()` on the `ExpressionTypeTree` via its catch-all `TypedTree` branch. This affected any recipe using `ChangeType` (e.g., `NoGuavaPredicate`) — 18 errors across spring-data-commons, spring-data-rest, spring-framework, and spring-security in flagship recipe runs. The fix catches `UnsupportedOperationException` in `ExpressionTypeTree.withType()`. This is safe because the visitor already handles the inner node's type correctly when visiting the reference directly.
1 parent a46a5ac commit 0e0eaf0

2 files changed

Lines changed: 97 additions & 5 deletions

File tree

rewrite-python/src/main/java/org/openrewrite/python/tree/Py.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -675,11 +675,17 @@ public <P> J acceptPython(PythonVisitor<P> v, P p) {
675675
@SuppressWarnings("unchecked")
676676
@Override
677677
public ExpressionTypeTree withType(@Nullable JavaType type) {
678-
if (reference instanceof Expression) {
679-
return withReference(((Expression) reference).withType(type));
680-
}
681-
if (reference instanceof TypedTree) {
682-
return withReference(((TypedTree) reference).withType(type));
678+
try {
679+
if (reference instanceof Expression) {
680+
return withReference(((Expression) reference).withType(type));
681+
}
682+
if (reference instanceof TypedTree) {
683+
return withReference(((TypedTree) reference).withType(type));
684+
}
685+
} catch (UnsupportedOperationException ignored) {
686+
// Some J types (e.g., MethodInvocation, MethodDeclaration) throw on withType()
687+
// and require withMethodType() instead. When wrapped in ExpressionTypeTree,
688+
// the inner node's type is already handled by the visitor directly.
683689
}
684690
return this;
685691
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.python.tree;
17+
18+
import org.junit.jupiter.api.Test;
19+
import org.openrewrite.Tree;
20+
import org.openrewrite.java.tree.J;
21+
import org.openrewrite.java.tree.JContainer;
22+
import org.openrewrite.java.tree.JavaType;
23+
import org.openrewrite.java.tree.Space;
24+
import org.openrewrite.marker.Markers;
25+
26+
import java.util.Collections;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
30+
class ExpressionTypeTreeTest {
31+
32+
@Test
33+
void withTypeOnWrappedMethodInvocation() {
34+
J.MethodInvocation mi = new J.MethodInvocation(
35+
Tree.randomId(), Space.EMPTY, Markers.EMPTY,
36+
null, null,
37+
new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), "foo", null, null),
38+
JContainer.empty(),
39+
null
40+
);
41+
Py.ExpressionTypeTree ett = new Py.ExpressionTypeTree(
42+
Tree.randomId(), Space.EMPTY, Markers.EMPTY, mi
43+
);
44+
45+
// Should not throw UnsupportedOperationException
46+
Py.ExpressionTypeTree result = ett.withType(JavaType.Primitive.Int);
47+
assertThat(result).isSameAs(ett);
48+
}
49+
50+
@Test
51+
void withTypeOnWrappedMethodDeclaration() {
52+
J.MethodDeclaration md = new J.MethodDeclaration(
53+
Tree.randomId(), Space.EMPTY, Markers.EMPTY,
54+
Collections.emptyList(), Collections.emptyList(), null,
55+
null,
56+
new J.MethodDeclaration.IdentifierWithAnnotations(
57+
new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, Collections.emptyList(), "bar", null, null),
58+
Collections.emptyList()
59+
),
60+
JContainer.empty(), null, null, null, null
61+
);
62+
Py.ExpressionTypeTree ett = new Py.ExpressionTypeTree(
63+
Tree.randomId(), Space.EMPTY, Markers.EMPTY, md
64+
);
65+
66+
// Should not throw UnsupportedOperationException
67+
Py.ExpressionTypeTree result = ett.withType(JavaType.Primitive.Int);
68+
assertThat(result).isSameAs(ett);
69+
}
70+
71+
@Test
72+
void withTypeOnWrappedIdentifier() {
73+
J.Identifier ident = new J.Identifier(
74+
Tree.randomId(), Space.EMPTY, Markers.EMPTY,
75+
Collections.emptyList(), "x", JavaType.Primitive.String, null
76+
);
77+
Py.ExpressionTypeTree ett = new Py.ExpressionTypeTree(
78+
Tree.randomId(), Space.EMPTY, Markers.EMPTY, ident
79+
);
80+
81+
// Should update the type normally
82+
Py.ExpressionTypeTree result = ett.withType(JavaType.Primitive.Int);
83+
assertThat(result).isNotSameAs(ett);
84+
assertThat(result.getType()).isEqualTo(JavaType.Primitive.Int);
85+
}
86+
}

0 commit comments

Comments
 (0)