summaryrefslogtreecommitdiffstats
path: root/Objects
diff options
context:
space:
mode:
authorPablo Galindo <Pablogsal@gmail.com>2021-04-14 01:36:07 (GMT)
committerGitHub <noreply@github.com>2021-04-14 01:36:07 (GMT)
commit37494b441aced0362d7edd2956ab3ea7801e60c8 (patch)
tree45d9355cde5f7c71924e8df0f16ea2e0ad63eea8 /Objects
parent3bc694d5f3d4eb2e5d2f0b83e498b19662845d4e (diff)
downloadcpython-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.c71
-rw-r--r--Objects/object.c53
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;
}