mirror of
https://github.com/python/cpython.git
synced 2025-11-25 04:34:37 +00:00
bpo-38530: Offer suggestions on NameError (GH-25397)
When printing NameError raised by the interpreter, PyErr_Display
will offer suggestions of simmilar variable names in the function that the exception
was raised from:
>>> schwarzschild_black_hole = None
>>> schwarschild_black_hole
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'schwarschild_black_hole' is not defined. Did you mean: schwarzschild_black_hole?
This commit is contained in:
parent
c4073a24f9
commit
5bf8bf2267
8 changed files with 287 additions and 10 deletions
|
|
@ -1,17 +1,15 @@
|
|||
#include "Python.h"
|
||||
#include "frameobject.h"
|
||||
|
||||
#include "pycore_pyerrors.h"
|
||||
|
||||
#define MAX_DISTANCE 3
|
||||
#define MAX_CANDIDATE_ITEMS 100
|
||||
#define MAX_STRING_SIZE 20
|
||||
#define MAX_STRING_SIZE 25
|
||||
|
||||
/* Calculate the Levenshtein distance between string1 and string2 */
|
||||
static size_t
|
||||
levenshtein_distance(const char *a, const char *b) {
|
||||
if (a == NULL || b == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const size_t a_size = strlen(a);
|
||||
const size_t b_size = strlen(b);
|
||||
|
|
@ -89,14 +87,19 @@ calculate_suggestions(PyObject *dir,
|
|||
|
||||
Py_ssize_t suggestion_distance = PyUnicode_GetLength(name);
|
||||
PyObject *suggestion = NULL;
|
||||
const char *name_str = PyUnicode_AsUTF8(name);
|
||||
if (name_str == NULL) {
|
||||
PyErr_Clear();
|
||||
return NULL;
|
||||
}
|
||||
for (int i = 0; i < dir_size; ++i) {
|
||||
PyObject *item = PyList_GET_ITEM(dir, i);
|
||||
const char *name_str = PyUnicode_AsUTF8(name);
|
||||
if (name_str == NULL) {
|
||||
const char *item_str = PyUnicode_AsUTF8(item);
|
||||
if (item_str == NULL) {
|
||||
PyErr_Clear();
|
||||
continue;
|
||||
return NULL;
|
||||
}
|
||||
Py_ssize_t current_distance = levenshtein_distance(PyUnicode_AsUTF8(name), PyUnicode_AsUTF8(item));
|
||||
Py_ssize_t current_distance = levenshtein_distance(name_str, item_str);
|
||||
if (current_distance == 0 || current_distance > MAX_DISTANCE) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -132,6 +135,48 @@ offer_suggestions_for_attribute_error(PyAttributeErrorObject *exc) {
|
|||
return suggestions;
|
||||
}
|
||||
|
||||
|
||||
static PyObject *
|
||||
offer_suggestions_for_name_error(PyNameErrorObject *exc) {
|
||||
PyObject *name = exc->name; // borrowed reference
|
||||
PyTracebackObject *traceback = (PyTracebackObject *) exc->traceback; // borrowed reference
|
||||
// Abort if we don't have an attribute name or we have an invalid one
|
||||
if (name == NULL || traceback == NULL || !PyUnicode_CheckExact(name)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Move to the traceback of the exception
|
||||
while (traceback->tb_next != NULL) {
|
||||
traceback = traceback->tb_next;
|
||||
}
|
||||
|
||||
PyFrameObject *frame = traceback->tb_frame;
|
||||
assert(frame != NULL);
|
||||
PyCodeObject *code = frame->f_code;
|
||||
assert(code != NULL && code->co_varnames != NULL);
|
||||
PyObject *dir = PySequence_List(code->co_varnames);
|
||||
if (dir == NULL) {
|
||||
PyErr_Clear();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject *suggestions = calculate_suggestions(dir, name);
|
||||
Py_DECREF(dir);
|
||||
if (suggestions != NULL) {
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
dir = PySequence_List(frame->f_globals);
|
||||
if (dir == NULL) {
|
||||
PyErr_Clear();
|
||||
return NULL;
|
||||
}
|
||||
suggestions = calculate_suggestions(dir, name);
|
||||
Py_DECREF(dir);
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
// Offer suggestions for a given exception. Returns a python string object containing the
|
||||
// suggestions. This function does not raise exceptions and returns NULL if no suggestion was found.
|
||||
PyObject *_Py_Offer_Suggestions(PyObject *exception) {
|
||||
|
|
@ -139,6 +184,8 @@ PyObject *_Py_Offer_Suggestions(PyObject *exception) {
|
|||
assert(!PyErr_Occurred()); // Check that we are not going to clean any existing exception
|
||||
if (PyErr_GivenExceptionMatches(exception, PyExc_AttributeError)) {
|
||||
result = offer_suggestions_for_attribute_error((PyAttributeErrorObject *) exception);
|
||||
} else if (PyErr_GivenExceptionMatches(exception, PyExc_NameError)) {
|
||||
result = offer_suggestions_for_name_error((PyNameErrorObject *) exception);
|
||||
}
|
||||
assert(!PyErr_Occurred());
|
||||
return result;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue