diff options
author | Pablo Galindo <Pablogsal@gmail.com> | 2021-04-14 01:36:07 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-14 01:36:07 (GMT) |
commit | 37494b441aced0362d7edd2956ab3ea7801e60c8 (patch) | |
tree | 45d9355cde5f7c71924e8df0f16ea2e0ad63eea8 /Objects | |
parent | 3bc694d5f3d4eb2e5d2f0b83e498b19662845d4e (diff) | |
download | cpython-37494b441aced0362d7edd2956ab3ea7801e60c8.zip cpython-37494b441aced0362d7edd2956ab3ea7801e60c8.tar.gz cpython-37494b441aced0362d7edd2956ab3ea7801e60c8.tar.bz2 |
bpo-38530: Offer suggestions on AttributeError (#16856)
When printing AttributeError, PyErr_Display will offer suggestions of similar
attribute names in the object that the exception was raised from:
>>> collections.namedtoplo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'collections' has no attribute 'namedtoplo'. Did you mean: namedtuple?
Diffstat (limited to 'Objects')
-rw-r--r-- | Objects/exceptions.c | 71 | ||||
-rw-r--r-- | Objects/object.c | 53 |
2 files changed, 112 insertions, 12 deletions
diff --git a/Objects/exceptions.c b/Objects/exceptions.c index dfa069e..4bb4153 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -1338,9 +1338,76 @@ SimpleExtendsException(PyExc_NameError, UnboundLocalError, /* * AttributeError extends Exception */ -SimpleExtendsException(PyExc_Exception, AttributeError, - "Attribute not found."); +static int +AttributeError_init(PyAttributeErrorObject *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = {"name", "obj", NULL}; + PyObject *name = NULL; + PyObject *obj = NULL; + + if (BaseException_init((PyBaseExceptionObject *)self, args, NULL) == -1) { + return -1; + } + + PyObject *empty_tuple = PyTuple_New(0); + if (!empty_tuple) { + return -1; + } + if (!PyArg_ParseTupleAndKeywords(empty_tuple, kwds, "|$OO:AttributeError", kwlist, + &name, &obj)) { + Py_DECREF(empty_tuple); + return -1; + } + Py_DECREF(empty_tuple); + + Py_XINCREF(name); + Py_XSETREF(self->name, name); + + Py_XINCREF(obj); + Py_XSETREF(self->obj, obj); + + return 0; +} + +static int +AttributeError_clear(PyAttributeErrorObject *self) +{ + Py_CLEAR(self->obj); + Py_CLEAR(self->name); + return BaseException_clear((PyBaseExceptionObject *)self); +} + +static void +AttributeError_dealloc(PyAttributeErrorObject *self) +{ + _PyObject_GC_UNTRACK(self); + AttributeError_clear(self); + Py_TYPE(self)->tp_free((PyObject *)self); +} + +static int +AttributeError_traverse(PyAttributeErrorObject *self, visitproc visit, void *arg) +{ + Py_VISIT(self->obj); + Py_VISIT(self->name); + return BaseException_traverse((PyBaseExceptionObject *)self, visit, arg); +} + +static PyMemberDef AttributeError_members[] = { + {"name", T_OBJECT, offsetof(PyAttributeErrorObject, name), 0, PyDoc_STR("attribute name")}, + {"obj", T_OBJECT, offsetof(PyAttributeErrorObject, obj), 0, PyDoc_STR("object")}, + {NULL} /* Sentinel */ +}; + +static PyMethodDef AttributeError_methods[] = { + {NULL} /* Sentinel */ +}; + +ComplexExtendsException(PyExc_Exception, AttributeError, + AttributeError, 0, + AttributeError_methods, AttributeError_members, + 0, BaseException_str, "Attribute not found."); /* * SyntaxError extends Exception diff --git a/Objects/object.c b/Objects/object.c index 4b67840..854cc85 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -884,29 +884,60 @@ _PyObject_SetAttrId(PyObject *v, _Py_Identifier *name, PyObject *w) return result; } +static inline int +set_attribute_error_context(PyObject* v, PyObject* name) +{ + assert(PyErr_Occurred()); + _Py_IDENTIFIER(name); + _Py_IDENTIFIER(obj); + // Intercept AttributeError exceptions and augment them to offer + // suggestions later. + if (PyErr_ExceptionMatches(PyExc_AttributeError)){ + PyObject *type, *value, *traceback; + PyErr_Fetch(&type, &value, &traceback); + PyErr_NormalizeException(&type, &value, &traceback); + if (PyErr_GivenExceptionMatches(value, PyExc_AttributeError) && + (_PyObject_SetAttrId(value, &PyId_name, name) || + _PyObject_SetAttrId(value, &PyId_obj, v))) { + return 1; + } + PyErr_Restore(type, value, traceback); + } + return 0; +} + PyObject * PyObject_GetAttr(PyObject *v, PyObject *name) { PyTypeObject *tp = Py_TYPE(v); - if (!PyUnicode_Check(name)) { PyErr_Format(PyExc_TypeError, "attribute name must be string, not '%.200s'", Py_TYPE(name)->tp_name); return NULL; } - if (tp->tp_getattro != NULL) - return (*tp->tp_getattro)(v, name); - if (tp->tp_getattr != NULL) { + + PyObject* result = NULL; + if (tp->tp_getattro != NULL) { + result = (*tp->tp_getattro)(v, name); + } + else if (tp->tp_getattr != NULL) { const char *name_str = PyUnicode_AsUTF8(name); - if (name_str == NULL) + if (name_str == NULL) { return NULL; - return (*tp->tp_getattr)(v, (char *)name_str); + } + result = (*tp->tp_getattr)(v, (char *)name_str); } - PyErr_Format(PyExc_AttributeError, - "'%.50s' object has no attribute '%U'", - tp->tp_name, name); - return NULL; + else { + PyErr_Format(PyExc_AttributeError, + "'%.50s' object has no attribute '%U'", + tp->tp_name, name); + } + + if (result == NULL) { + set_attribute_error_context(v, name); + } + return result; } int @@ -1165,6 +1196,8 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method) PyErr_Format(PyExc_AttributeError, "'%.50s' object has no attribute '%U'", tp->tp_name, name); + + set_attribute_error_context(obj, name); return 0; } |