-
-
Notifications
You must be signed in to change notification settings - Fork 32.5k
bpo-38530: Offer suggestions on AttributeError #16856
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
c9c85b5
61d510b
dbd88f0
a3dd16a
c760303
71e8766
45061e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#include "Python.h" | ||
|
||
#ifndef Py_INTERNAL_SUGGESTIONS_H | ||
#define Py_INTERNAL_SUGGESTIONS_H | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you want to keep the header file, you should add extern "C". Third party C++ code is allowed to use it. |
||
|
||
#ifndef Py_BUILD_CORE | ||
# error "this header requires Py_BUILD_CORE define" | ||
#endif | ||
|
||
int _Py_offer_suggestions_for_attribute_error(PyAttributeErrorObject* exception_value); | ||
|
||
|
||
#endif /* !Py_INTERNAL_SUGGESTIONS_H */ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
When printing :exc:`AttributeError`, :c:func:`PyErr_Display` will offer | ||
suggestions of simmilar attribute names in the object that the exception was | ||
raised from. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
#include "pycore_pystate.h" // _PyThreadState_GET() | ||
#include "pycore_symtable.h" // PySTEntry_Type | ||
#include "pycore_unionobject.h" // _Py_UnionType | ||
#include "pycore_suggestions.h" | ||
#include "frameobject.h" | ||
#include "interpreteridobject.h" | ||
|
||
|
@@ -884,10 +885,31 @@ _PyObject_SetAttrId(PyObject *v, _Py_Identifier *name, PyObject *w) | |
return result; | ||
} | ||
|
||
static inline int | ||
add_context_to_attribute_error_exception(PyObject* v, PyObject* name) | ||
pablogsal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
assert(PyErr_Occurred()); | ||
// 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_SetAttrString(value, "name", name) || | ||
PyObject_SetAttrString(value, "obj", v))) { | ||
pablogsal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return 1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it really useful to report setattr() failed? the callers don't seem to care. |
||
} | ||
PyErr_Restore(type, value, traceback); | ||
} | ||
return 0; | ||
} | ||
|
||
PyObject * | ||
PyObject_GetAttr(PyObject *v, PyObject *name) | ||
{ | ||
PyTypeObject *tp = Py_TYPE(v); | ||
PyObject* result = NULL; | ||
|
||
if (!PyUnicode_Check(name)) { | ||
PyErr_Format(PyExc_TypeError, | ||
|
@@ -896,17 +918,23 @@ PyObject_GetAttr(PyObject *v, PyObject *name) | |
return NULL; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nitpick: I like to only declare variables just before they are initialized (here) thanks to C99.
|
||
if (tp->tp_getattro != NULL) | ||
pablogsal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return (*tp->tp_getattro)(v, name); | ||
if (tp->tp_getattr != NULL) { | ||
result = (*tp->tp_getattro)(v, name); | ||
else if (tp->tp_getattr != NULL) { | ||
const char *name_str = PyUnicode_AsUTF8(name); | ||
if (name_str == NULL) | ||
return NULL; | ||
return (*tp->tp_getattr)(v, (char *)name_str); | ||
result = (*tp->tp_getattr)(v, (char *)name_str); | ||
} else { | ||
pablogsal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
PyErr_Format(PyExc_AttributeError, | ||
"'%.50s' object has no attribute '%U'", | ||
tp->tp_name, name); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe only set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is code from before, I prefer to not change it in case we introduce some unwanted change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh ok. It was just a suggestion, you can ignore it. it's just a coding style preference ;-) |
||
} | ||
PyErr_Format(PyExc_AttributeError, | ||
"'%.50s' object has no attribute '%U'", | ||
tp->tp_name, name); | ||
return NULL; | ||
|
||
if (!result && add_context_to_attribute_error_exception(v, name)) { | ||
return NULL; | ||
} | ||
pablogsal marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return result; | ||
} | ||
|
||
int | ||
|
@@ -1165,6 +1193,11 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method) | |
PyErr_Format(PyExc_AttributeError, | ||
"'%.50s' object has no attribute '%U'", | ||
tp->tp_name, name); | ||
|
||
if (add_context_to_attribute_error_exception(obj, name)) { | ||
return 0; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://www.python.org/dev/peps/pep-0473/ was rejected because it was too broad, but this PR adds a single exception, which sounds ok according to the resolution: https://mail.python.org/pipermail/python-dev/2019-March/156692.html
My main worry is the risk of creating "more" and "worse" exception cycles, but Pablo says that it sounds unlikely: https://bugs.python.org/issue38530#msg354975