openrewrite / openrewrite/rewrite
ClassCastException: Py.Await wrapped in Py.StatementExpression cannot be cast to Statement on RPC receive
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 3.7k
- Forks
- 570
- Avg merge
- 13h 12m
- Merged PRs (30d)
- 261
Description
Summary
A Py.Await node reaches Java's RPC receiver wrapped in a Py.StatementExpression, which is typed to hold a Statement. Py.Await is an Expression, not a Statement, so deserialization throws ClassCastException and the recipe errors out on the file.
Reproduced against agronholm/anyio@master while running org.openrewrite.python.migrate.UpgradeToPython313. The error surfaces under org.openrewrite.python.migrate.ReplaceReTemplate, but the defect is in the Py tree construction / RPC round trip, not in that recipe.
Stack trace
java.lang.ClassCastException: class org.openrewrite.python.tree.Py$Await cannot be cast to
class org.openrewrite.java.tree.Statement (org.openrewrite.python.tree.Py$Await and
org.openrewrite.java.tree.Statement are in unnamed module of loader 'app')
at org.openrewrite.rpc.RpcReceiveQueue.receive(RpcReceiveQueue.java:137)
at org.openrewrite.python.internal.rpc.PythonReceiver.visitStatementExpression(PythonReceiver.java:147)
at org.openrewrite.python.internal.rpc.PythonReceiver.visitStatementExpression(PythonReceiver.java:39)
at org.openrewrite.python.tree.Py$StatementExpression.acceptPython(Py.java:725)
at org.openrewrite.python.tree.Py.accept(Py.java:54)
at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:242)
at org.openrewrite.python.internal.rpc.PythonReceiver.visit(PythonReceiver.java:45)
at org.openrewrite.python.internal.rpc.PythonReceiver$PythonReceiverDelegate.visit(PythonReceiver.java:371)
at org.openrewrite.TreeVisitor.visitNonNull(TreeVisitor.java:167)
at org.openrewrite.java.internal.rpc.JavaReceiver.lambda$visitRightPadded$195(JavaReceiver.java:661)
at org.openrewrite.rpc.RpcReceiveQueue.receive(RpcReceiveQueue.java:137)
at org.openrewrite.java.internal.rpc.JavaReceiver.visitRightPadded(JavaReceiver.java:658)
at org.openrewrite.java.internal.rpc.JavaReceiver.lambda$visitBlock$26(JavaReceiver.java:117)
at org.openrewrite.rpc.RpcReceiveQueue.receive(RpcReceiveQueue.java:137)
at org.openrewrite.rpc.RpcReceiveQueue.receiveList(RpcReceiveQueue.java:183)
...
Analysis
The two wrapper types have mirrored contracts:
// Py.java
final class StatementExpression implements Py, Expression, Statement {
Statement statement; // <-- holds a Statement
}
final class ExpressionStatement implements Py, Expression, Statement {
Expression expression; // <-- holds an Expression
}
final class Await implements Py, Expression { ... } // Expression only
So StatementExpression(Await) violates the declared type — an expression used as a statement should be wrapped in Py.ExpressionStatement, not Py.StatementExpression.
Python's dataclasses don't enforce annotations at runtime, so the Python side constructs the invalid node silently. It only fails when the tree crosses into Java's typed receiver, where PythonReceiver.visitStatementExpression does the Statement cast.
Worth noting why this stays hidden for most code: many J nodes implement both Expression and Statement (J.MethodInvocation, J.Assignment, …), so wrapping those in a StatementExpression casts fine by accident. It only breaks for expression-only node types like Py.Await.
Reproduction
mod config recipes pip install openrewrite-migrate-python # 0.10.1
mkdir repos && cd repos
git clone --depth 1 https://github.com/agronholm/anyio.git
MODERNE_WRAPPER_VERSION=4.4.1 mod build .
MODERNE_WRAPPER_VERSION=4.4.1 mod run . --recipe org.openrewrite.python.migrate.UpgradeToPython313
2 errors of this kind, both attributed to ReplaceReTemplate:
tests/test_debugging.pytests/test_taskgroups.py
(The same run produces 82 unrelated errors from a separate AddImport-on-Python problem — ignore those for this issue.)
Not yet pinned down
I could not isolate the exact source construct. Bare await <name> statements appear in tests/streams/test_memory.py and src/anyio/_backends/_asyncio.py too, and neither of those files fails, so await <name> alone is not sufficient. Neither failing file uses re at all, so ReplaceReTemplate's preconditions (uses_method("*..* template(..)") / uses_type("re")) don't obviously explain the selection either. Narrowing to a minimal snippet is the next step.
A defensive fix on the Java side (clear error naming the offending node type instead of a raw CCE) would make this class of tree-shape bug much cheaper to diagnose.
Environment
- Moderne CLI 4.4.1
- openrewrite (Python) 8.87.7
- openrewrite-migrate-python 0.10.1
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with Py.java, especially StatementExpression, ExpressionStatement, and Await, then inspect PythonReceiver.visitStatementExpression and RpcReceiveQueue.receive. Reproduce with the anyio checkout and UpgradeToPython313 command from the issue, narrowing the failing source construct in the two named test files. Done means the invalid tree shape no longer causes the RPC receive failure, with coverage for expression-only nodes such as Py.Await.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, python
- Domain
- devtools, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100