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
32 changes: 25 additions & 7 deletions pyre/pyre-interpreter/src/baseobjspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8729,11 +8729,12 @@ pub(crate) fn exception_attr_get(obj: PyObjectRef, name: &str) -> PyResult {
let written =
unsafe { pyre_object::interp_exceptions::w_exception_get_written(obj) };
if written == -1 {
// `descr_get_written` raises AttributeError under the
// descriptor's own name for an unset slot. Falling through
// to the ordinary lookup would instead report the receiver
// as having no such attribute.
return Err(PyError::attribute_error("characters_written"));
// Unset slot: `descr_get_written` raises, but only after
// MRO lookup. A subclass class attribute must win first
// (`class E(OSError): characters_written = 42`). `PY_NULL`
// lets the caller continue; the OSError getset fget still
// raises `AttributeError("characters_written")`.
return Ok(pyre_object::PY_NULL);
}
return Ok(pyre_object::w_int_new(written));
}
Expand Down Expand Up @@ -14484,7 +14485,16 @@ pub fn object_setattr(obj: PyObjectRef, name: &str, value: PyObjectRef) -> PyRes
// `argparse.ArgumentTypeError`'s `e.message = ...` pattern).
// Non-special names land in the lazily allocated instance dict on
// `W_BaseException.w_dict` (interp_exceptions.py, 222-225).
if unsafe { pyre_object::is_exception(obj) } {
//
// These arms stand in for a `GetSetProperty.__set__`, so they only fire
// where the walk above found no attribute at all. A subclass that shadows
// one of the names with a plain class attribute
// (`class E(OSError): errno = 99`) contributes no `__set__`, so
// `descr__setattr__` falls through to `setdictvalue` and the instance dict
// wins; the real getset is never reached. Running the arm regardless wrote
// the interpreter slot instead, and the shadowing class attribute then read
// back in place of the value just assigned.
if w_descr.is_none() && unsafe { pyre_object::is_exception(obj) } {
let handled = exception_attr_set(obj, name, value)?;
if !handled.is_null() {
return Ok(handled);
Expand Down Expand Up @@ -15267,7 +15277,15 @@ pub fn object_delattr(obj: PyObjectRef, name: &str) -> PyResult {
// `None` store. All of this runs before the generic instance-dict
// removal below, which would otherwise silently succeed on a name that
// happens to have an entry there.
if unsafe { pyre_object::is_exception(obj) } {
//
// Like the setattr arms, this stands in for a `GetSetProperty.__delete__`
// and so only fires where the walk above found no attribute: a subclass
// shadowing the name with a plain class attribute is no data descriptor,
// so `descr__delattr__` falls through to `deldictvalue` and the instance
// dict entry is what goes. Running it regardless reset the interpreter
// slot and, for a name whose deleter refuses an unset slot, raised where
// the dict entry should simply have been removed.
if w_descr.is_none() && unsafe { pyre_object::is_exception(obj) } {
let handled = exception_attr_delete(obj, name)?;
if !handled.is_null() {
return Ok(handled);
Expand Down
5 changes: 5 additions & 0 deletions pyre/pyre-interpreter/src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9664,6 +9664,11 @@ fn exception_getset_fget(args: &[PyObjectRef]) -> crate::PyResult {
let name = exception_getset_name(w_descr);
let found = crate::baseobjspace::exception_attr_get(w_obj, &name)?;
if found.is_null() {
// `interp_exceptions.py descr_get_written` raises under the
// descriptor's own name for an unset slot.
if name == "characters_written" {
return Err(crate::PyError::attribute_error("characters_written"));
}
return Err(exception_getset_absent(w_obj, &name));
}
Ok(found)
Expand Down
29 changes: 22 additions & 7 deletions pyre/pyre-interpreter/src/typedef.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9083,10 +9083,21 @@ fn traceback_c_int_arg(obj: PyObjectRef) -> Result<i64, crate::PyError> {
}

fn traceback_descr_new(args: &[PyObjectRef]) -> Result<PyObjectRef, crate::PyError> {
if args.len() != 5 {
// 3.14's `traceback()` names its four arguments, so a short call reports
// the first one still missing and a long one reports the limit; both
// messages carry the type's own name, not the `types` alias.
const ARG_NAMES: [&str; 4] = ["tb_next", "tb_frame", "tb_lasti", "tb_lineno"];
let given = args.len().saturating_sub(1);
if given < ARG_NAMES.len() {
return Err(crate::PyError::type_error(format!(
"TracebackType() takes exactly 4 arguments ({} given)",
args.len().saturating_sub(1)
"traceback() missing required argument '{}' (pos {})",
ARG_NAMES[given],
given + 1
)));
}
if given > ARG_NAMES.len() {
return Err(crate::PyError::type_error(format!(
"traceback() takes at most 4 arguments ({given} given)"
)));
}
let w_next = args[1];
Expand All @@ -9111,7 +9122,7 @@ fn traceback_descr_new(args: &[PyObjectRef]) -> Result<PyObjectRef, crate::PyErr
|| !unsafe { pyre_object::py_type_check(w_frame, &crate::pyframe::FRAME_TYPE) }
{
return Err(crate::PyError::type_error(format!(
"TracebackType() argument 'tb_frame' must be frame, not {}",
"traceback() argument 'tb_frame' must be frame, not {}",
type_name_of(w_frame)
)));
}
Expand Down Expand Up @@ -9338,9 +9349,13 @@ fn init_pytraceback_type(ns: PyObjectRef) {
if w_new.is_null() || unsafe { pyre_object::is_none(w_new) } {
w_new = pyre_object::PY_NULL;
} else if !unsafe { crate::pytraceback::is_pytraceback(w_new) } {
return Err(crate::PyError::type_error(
"expected traceback object or None".to_string(),
));
// `check_traceback`'s caller passes the message in; 3.14's
// `tb_next` setter names the rejected object's type and omits
// the `or None` it has already accepted above.
return Err(crate::PyError::type_error(format!(
"expected traceback object, got '{}'",
type_name_of(w_new)
)));
}
if unsafe { crate::pytraceback::w_pytraceback_set_w_next(tb, w_new) }.is_err() {
return Err(crate::PyError::new(
Expand Down
Loading