openrewrite / openrewrite/rewrite

ClassCastException: Py.Await wrapped in Py.StatementExpression cannot be cast to Statement on RPC receive

Open
#8,322 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug python
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.py
  • tests/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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.