Keep str's concat error when __radd__ declines - #8652
Conversation
`PyStr::__add__` delegated to the right operand's `__radd__` whenever it had
one, and returned whatever came back. For every numeric type that is a
`NotImplemented`, which then reached the generic binary-op handler:
>>> "a" + 1
TypeError: unsupported operand type(s) for +: 'str' and 'int'
CPython falls back to `str`'s `sq_concat` once the reflected call declines, so
it names the concatenation instead:
TypeError: can only concatenate str (not "int") to str
The specific message was already here, but only reachable for operands with no
`__radd__` at all, so `list`, `object`, `bytes` and `None` were correct while
`int`, `float`, `bool` and any class with a declining `__radd__` were not.
A `__radd__` that returns a value still wins, unchanged.
Verified against CPython 3.14.0: the new snippet asserts the message for
`int`, `float`, `bool`, `list`, `None`, `bytes` and a declining `__radd__`,
and passes under both interpreters. Reverting only the `str.rs` change fails
its first assertion.
Assisted-by: Claude Code:claude-opus-5
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (2)
Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthroughString concatenation now checks reflected ChangesString concatenation behavior
Estimated code review effort: 2 (Simple) | ~10 minutes Merge Risk: ⚪ Minimal · up to String concatenation now retains CPython-compatible error messages when reflected addition declines, while preserving successful reflected additions. The updated behavior is covered for relevant operand and reflection outcomes, with no remaining merge-blocking risk identified. Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |

"a" + 1reports the generic binary-op error instead of CPython's concatenation error:PyStr::__add__delegates to the right operand's__radd__whenever it has one and returns whatever comes back. Every numeric type has__radd__, and it returnsNotImplementedfor astrleft operand, so the result reached the generic handler. CPython instead falls back tostr'ssq_concatonce the reflected call declines, which is where its message comes from.The correct message was already in the
elsebranch, reachable only for operands with no__radd__at all — solist,object,bytesandNonewere already right, whileint,float,booland any class whose__radd__returnsNotImplementedwere not. The fix keeps the reflected call, and only falls through to the concat error when it declines. A__radd__that returns a value still wins, unchanged."a" + x1,1.5,Truecan only concatenate str (not "int"/"float"/"bool") to str__radd__can only concatenate str (not "Declines") to str[],None,b"b",object()__radd__returning a valueVerification:
extra_tests/snippets/builtin_str.pypasses under both RustPython and CPython 3.14.0, so its expectations are CPython's behaviour rather than my reading of it.str.rschange fails its first assertion.cargo test --workspace --exclude rustpython_wasm --exclude rustpython-venvlauncher --exclude rustpython-capi— all 42 test binaries pass.cargo run --release -- -m test test_str test_operator test_descr test_exceptions— SUCCESS for each.cargo fmt --checkandruff format --checkclean.Found by diffing ~70 expressions across both interpreters. Three other divergences turned up and are deliberately not in this PR, since they are unrelated one-line message fixes in different files:
'{'.format()and'{0.}'.format(1)produce differentValueErrortext, andbytes([256])saysbyte must be in range(0, 256)where CPython saysbytes. Happy to send those separately if wanted.AI disclosure per the AI policy: found, written and verified with Claude Code (Claude Opus 5), which is also recorded in the commit's
Assisted-by:trailer. @rawsun007 reviewed the change and authorised the push.Summary by CodeRabbit
Bug Fixes
Tests