Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions crates/vm/src/builtins/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -653,10 +653,17 @@ impl PyStr {
Self::new_str_unchecked(bytes.into(), kind)
}
.to_pyobject(vm))
} else if let Some(radd) = vm.get_method(other.clone(), identifier!(vm, __radd__)) {
// hack to get around not distinguishing number add from seq concat
radd?.call((zelf,), vm)
} else {
// hack to get around not distinguishing number add from seq concat
if let Some(radd) = vm.get_method(other.clone(), identifier!(vm, __radd__)) {
let result = radd?.call((zelf,), vm)?;
// CPython reaches `str`'s sq_concat once the reflected call declines, so a
// `__radd__` returning NotImplemented must still report the concat error
// rather than the generic binary-op one.
if !result.is(&vm.ctx.not_implemented) {
return Ok(result);
}
}
Err(vm.new_type_error(format!(
r#"can only concatenate str (not "{}") to str"#,
other.class().slot_name()
Expand Down
36 changes: 36 additions & 0 deletions extra_tests/snippets/builtin_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -979,3 +979,39 @@ def test_expandtabs_zero_tabsize():


test_expandtabs_zero_tabsize()


def test_concat_error_message():
# CPython falls back to str's sequence concatenation once a reflected
# __radd__ declines, so the message names the concatenation, not the
# generic binary operation. Types that define __radd__ (every number)
# used to take the generic path here.
def message(other):
try:
"a" + other
except TypeError as err:
return str(err)
raise AssertionError("TypeError was not raised")

assert message(1) == 'can only concatenate str (not "int") to str'
assert message(1.5) == 'can only concatenate str (not "float") to str'
assert message(True) == 'can only concatenate str (not "bool") to str'
assert message([]) == 'can only concatenate str (not "list") to str'
assert message(None) == 'can only concatenate str (not "NoneType") to str'
assert message(b"b") == 'can only concatenate str (not "bytes") to str'

class Declines:
def __radd__(self, other):
return NotImplemented

assert message(Declines()) == 'can only concatenate str (not "Declines") to str'

# A __radd__ that returns a value still wins.
class Accepts:
def __radd__(self, other):
return "from-radd"

assert "a" + Accepts() == "from-radd"


test_concat_error_message()
Loading