-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
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
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -889,15 +889,17 @@ 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_SetAttrString(value, "name", name) || | ||
PyObject_SetAttrString(value, "obj", v))) { | ||
(_PyObject_SetAttrId(value, &PyId_name, name) || | ||
_PyObject_SetAttrId(value, &PyId_obj, v))) { | ||
return 1; | ||
} | ||
PyErr_Restore(type, value, traceback); | ||
|
@@ -922,19 +924,20 @@ PyObject_GetAttr(PyObject *v, PyObject *name) | |
} | ||
else if (tp->tp_getattr != NULL) { | ||
const char *name_str = PyUnicode_AsUTF8(name); | ||
if (name_str == NULL) | ||
if (name_str == NULL) { | ||
return NULL; | ||
} | ||
result = (*tp->tp_getattr)(v, (char *)name_str); | ||
} else { | ||
} | ||
else { | ||
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 ;-) |
||
} | ||
|
||
if (!result && set_attribute_error_context(v, name)) { | ||
return NULL; | ||
if (result == NULL) { | ||
set_attribute_error_context(v, name); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
|
@@ -1195,10 +1198,7 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method) | |
"'%.50s' object has no attribute '%U'", | ||
tp->tp_name, name); | ||
|
||
if (set_attribute_error_context(obj, name)) { | ||
return 0; | ||
} | ||
|
||
set_attribute_error_context(obj, name); | ||
return 0; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,7 +59,7 @@ distance(const char *string1, const char *string2) | |
/* initalize first row */ | ||
row = (size_t*)PyMem_Malloc(len2*sizeof(size_t)); | ||
if (!row) { | ||
return (size_t)(-1); | ||
return (Py_ssize_t)(-1); | ||
} | ||
end = row + len2 - 1; | ||
for (i = 0; i < len2 - half; i++) { | ||
|
@@ -72,7 +72,7 @@ distance(const char *string1, const char *string2) | |
* necessary */ | ||
row[0] = len1 - half - 1; | ||
for (i = 1; i < len1; i++) { | ||
size_t *p; | ||
size_t *scan_ptr; | ||
const char char1 = string1[i - 1]; | ||
const char *char2p; | ||
size_t D, x; | ||
|
@@ -82,17 +82,18 @@ distance(const char *string1, const char *string2) | |
size_t c3; | ||
|
||
char2p = string2 + offset; | ||
p = row + offset; | ||
c3 = *(p++) + (char1 != *(char2p++)); | ||
x = *p; | ||
scan_ptr = row + offset; | ||
c3 = *(scan_ptr++) + (char1 != *(char2p++)); | ||
x = *scan_ptr; | ||
x++; | ||
D = x; | ||
if (x > c3) | ||
if (x > c3) { | ||
x = c3; | ||
*(p++) = x; | ||
} | ||
*(scan_ptr++) = x; | ||
} | ||
else { | ||
p = row + 1; | ||
scan_ptr = row + 1; | ||
char2p = string2; | ||
D = x = i; | ||
} | ||
|
@@ -101,24 +102,26 @@ distance(const char *string1, const char *string2) | |
end = row + len2 + i - half - 2; | ||
} | ||
/* main */ | ||
while (p <= end) { | ||
while (scan_ptr <= end) { | ||
size_t c3 = --D + (char1 != *(char2p++)); | ||
x++; | ||
if (x > c3) | ||
if (x > c3) { | ||
x = c3; | ||
D = *p; | ||
} | ||
D = *scan_ptr; | ||
D++; | ||
if (x > D) | ||
x = D; | ||
*(p++) = x; | ||
*(scan_ptr++) = x; | ||
} | ||
/* lower triangle sentinel */ | ||
if (i <= half) { | ||
size_t c3 = --D + (char1 != *char2p); | ||
x++; | ||
if (x > c3) | ||
if (x > c3) { | ||
x = c3; | ||
*p = x; | ||
} | ||
*scan_ptr = x; | ||
} | ||
} | ||
i = *end; | ||
|
@@ -131,6 +134,7 @@ calculate_suggestions(PyObject* dir, | |
PyObject* name, | ||
PyObject* oldexceptionvalue) | ||
{ | ||
assert(!PyErr_Occurred()); | ||
assert(PyList_CheckExact(dir)); | ||
|
||
Py_ssize_t dir_size = PyList_GET_SIZE(dir); | ||
|
@@ -166,13 +170,13 @@ calculate_suggestions(PyObject* dir, | |
|
||
static int | ||
offer_suggestions_for_attribute_error(PyAttributeErrorObject* exc) { | ||
int return_val = 0; | ||
int return_val = -1; | ||
|
||
PyObject* name = exc->name; | ||
PyObject* v = exc->obj; | ||
PyObject* name = exc->name; // borrowed reference | ||
PyObject* obj = exc->obj; // borrowed reference | ||
|
||
// Aboirt if we don't have an attribute name or we have an invalid one | ||
if ((name == NULL) || (v == NULL) || !PyUnicode_CheckExact(name)) { | ||
// Abort if we don't have an attribute name or we have an invalid one | ||
if (name == NULL || obj == NULL || !PyUnicode_CheckExact(name)) { | ||
return -1; | ||
} | ||
|
||
|
@@ -181,13 +185,15 @@ offer_suggestions_for_attribute_error(PyAttributeErrorObject* exc) { | |
switch (nargs) { | ||
case 0: | ||
oldexceptionvalue = PyUnicode_New(0, 0); | ||
if (oldexceptionvalue == NULL) { | ||
return -1; | ||
} | ||
break; | ||
case 1: | ||
oldexceptionvalue = PyTuple_GET_ITEM(exc->args, 0); | ||
Py_INCREF(oldexceptionvalue); | ||
// Check that the the message is an uncode objects that we can use. | ||
// Check that the the message is an unicode objects that we can use. | ||
if (!PyUnicode_CheckExact(oldexceptionvalue)) { | ||
return_val = -1; | ||
goto exit; | ||
} | ||
break; | ||
|
@@ -197,8 +203,8 @@ offer_suggestions_for_attribute_error(PyAttributeErrorObject* exc) { | |
return 0; | ||
} | ||
|
||
PyObject* dir = PyObject_Dir(v); | ||
if (!dir) { | ||
PyObject* dir = PyObject_Dir(obj); | ||
if (dir == NULL) { | ||
goto exit; | ||
} | ||
|
||
|
@@ -210,15 +216,14 @@ offer_suggestions_for_attribute_error(PyAttributeErrorObject* exc) { | |
goto exit; | ||
} | ||
|
||
PyObject* old_args = exc->args; | ||
PyObject* new_args = PyTuple_Pack(1, newexceptionvalue); | ||
Py_DECREF(newexceptionvalue); | ||
if (new_args == NULL) { | ||
return_val = -1; | ||
goto exit; | ||
} | ||
Py_SETREF(exc->args, new_args); | ||
exc->args = new_args; | ||
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. Why not storing suggestions in a new Exception field, rather than replacing existing args? I may break some applications. 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. Because I don't want to touch the reporting facility to inspect said field or allowing users to inspect it manually 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. Also, this substitution only happens before printing the value because it has been not caught. Nobody should be able to inspect this exception later, no? Notice this only happens in the default exception display, so if someone has a custom handler, they will not see this AFAIK. 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. Code level comment: Higher level comment: 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.
I forgot to remove that after updated the code after @vstinner suggestions.
Actually, this is a very good point, this can make the code cleaner. I will implement this |
||
Py_DECREF(old_args); | ||
return_val = 0; | ||
|
||
exit: | ||
Py_DECREF(oldexceptionvalue); | ||
|
@@ -229,7 +234,7 @@ offer_suggestions_for_attribute_error(PyAttributeErrorObject* exc) { | |
} | ||
|
||
|
||
int _Py_offer_suggestions(PyObject* exception, PyObject* value) { | ||
int _Py_Offer_Suggestions(PyObject* exception, PyObject* value) { | ||
if (PyErr_GivenExceptionMatches(exception, PyExc_AttributeError) && | ||
offer_suggestions_for_attribute_error((PyAttributeErrorObject*) value) != 0) { | ||
PyErr_Clear(); | ||
|
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.
Is it really useful to report setattr() failed? the callers don't seem to care.