mirror of https://github.com/python/cpython
gh-99139: Improve NameError error suggestion for instances (#99140)
This commit is contained in:
parent
a0bc75e2fd
commit
99e2e60cb2
|
@ -75,6 +75,27 @@ Important deprecations, removals or restrictions:
|
||||||
Improved Error Messages
|
Improved Error Messages
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
* Improve the error suggestion for :exc:`NameError` exceptions for instances.
|
||||||
|
Now if a :exc:`NameError` is raised in a method and the instance has an
|
||||||
|
attribute that's exactly equal to the name in the exception, the suggestion
|
||||||
|
will include ``self.<NAME>`` instead of the closest match in the method
|
||||||
|
scope. Contributed by Pablo Galindo in :gh:`99139`.
|
||||||
|
|
||||||
|
>>> class A:
|
||||||
|
... def __init__(self):
|
||||||
|
... self.blech = 1
|
||||||
|
...
|
||||||
|
... def foo(self):
|
||||||
|
... somethin = blech
|
||||||
|
|
||||||
|
>>> A().foo()
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "<stdin>", line 1
|
||||||
|
somethin = blech
|
||||||
|
^^^^^
|
||||||
|
NameError: name 'blech' is not defined. Did you mean: 'self.blech'?
|
||||||
|
|
||||||
|
|
||||||
* Improve the :exc:`SyntaxError` error message when the user types ``import x
|
* Improve the :exc:`SyntaxError` error message when the user types ``import x
|
||||||
from y`` instead of ``from y import x``. Contributed by Pablo Galindo in :gh:`98931`.
|
from y`` instead of ``from y import x``. Contributed by Pablo Galindo in :gh:`98931`.
|
||||||
|
|
||||||
|
|
|
@ -600,6 +600,7 @@ struct _Py_global_strings {
|
||||||
STRUCT_FOR_ID(seek)
|
STRUCT_FOR_ID(seek)
|
||||||
STRUCT_FOR_ID(seekable)
|
STRUCT_FOR_ID(seekable)
|
||||||
STRUCT_FOR_ID(selectors)
|
STRUCT_FOR_ID(selectors)
|
||||||
|
STRUCT_FOR_ID(self)
|
||||||
STRUCT_FOR_ID(send)
|
STRUCT_FOR_ID(send)
|
||||||
STRUCT_FOR_ID(sep)
|
STRUCT_FOR_ID(sep)
|
||||||
STRUCT_FOR_ID(sequence)
|
STRUCT_FOR_ID(sequence)
|
||||||
|
|
|
@ -1109,6 +1109,7 @@ extern "C" {
|
||||||
INIT_ID(seek), \
|
INIT_ID(seek), \
|
||||||
INIT_ID(seekable), \
|
INIT_ID(seekable), \
|
||||||
INIT_ID(selectors), \
|
INIT_ID(selectors), \
|
||||||
|
INIT_ID(self), \
|
||||||
INIT_ID(send), \
|
INIT_ID(send), \
|
||||||
INIT_ID(sep), \
|
INIT_ID(sep), \
|
||||||
INIT_ID(sequence), \
|
INIT_ID(sequence), \
|
||||||
|
@ -2567,6 +2568,8 @@ _PyUnicode_InitStaticStrings(void) {
|
||||||
PyUnicode_InternInPlace(&string);
|
PyUnicode_InternInPlace(&string);
|
||||||
string = &_Py_ID(selectors);
|
string = &_Py_ID(selectors);
|
||||||
PyUnicode_InternInPlace(&string);
|
PyUnicode_InternInPlace(&string);
|
||||||
|
string = &_Py_ID(self);
|
||||||
|
PyUnicode_InternInPlace(&string);
|
||||||
string = &_Py_ID(send);
|
string = &_Py_ID(send);
|
||||||
PyUnicode_InternInPlace(&string);
|
PyUnicode_InternInPlace(&string);
|
||||||
string = &_Py_ID(sep);
|
string = &_Py_ID(sep);
|
||||||
|
@ -7104,6 +7107,10 @@ _PyStaticObjects_CheckRefcnt(void) {
|
||||||
_PyObject_Dump((PyObject *)&_Py_ID(selectors));
|
_PyObject_Dump((PyObject *)&_Py_ID(selectors));
|
||||||
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
|
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
|
||||||
};
|
};
|
||||||
|
if (Py_REFCNT((PyObject *)&_Py_ID(self)) < _PyObject_IMMORTAL_REFCNT) {
|
||||||
|
_PyObject_Dump((PyObject *)&_Py_ID(self));
|
||||||
|
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
|
||||||
|
};
|
||||||
if (Py_REFCNT((PyObject *)&_Py_ID(send)) < _PyObject_IMMORTAL_REFCNT) {
|
if (Py_REFCNT((PyObject *)&_Py_ID(send)) < _PyObject_IMMORTAL_REFCNT) {
|
||||||
_PyObject_Dump((PyObject *)&_Py_ID(send));
|
_PyObject_Dump((PyObject *)&_Py_ID(send));
|
||||||
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
|
Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT");
|
||||||
|
|
|
@ -3356,6 +3356,31 @@ class SuggestionFormattingTestBase:
|
||||||
|
|
||||||
actual = self.get_suggestion(func)
|
actual = self.get_suggestion(func)
|
||||||
self.assertNotIn("blech", actual)
|
self.assertNotIn("blech", actual)
|
||||||
|
|
||||||
|
def test_name_error_with_instance(self):
|
||||||
|
class A:
|
||||||
|
def __init__(self):
|
||||||
|
self.blech = None
|
||||||
|
def foo(self):
|
||||||
|
blich = 1
|
||||||
|
x = blech
|
||||||
|
|
||||||
|
instance = A()
|
||||||
|
actual = self.get_suggestion(instance.foo)
|
||||||
|
self.assertIn("self.blech", actual)
|
||||||
|
|
||||||
|
def test_unbound_local_error_with_instance(self):
|
||||||
|
class A:
|
||||||
|
def __init__(self):
|
||||||
|
self.blech = None
|
||||||
|
def foo(self):
|
||||||
|
blich = 1
|
||||||
|
x = blech
|
||||||
|
blech = 1
|
||||||
|
|
||||||
|
instance = A()
|
||||||
|
actual = self.get_suggestion(instance.foo)
|
||||||
|
self.assertNotIn("self.blech", actual)
|
||||||
|
|
||||||
def test_unbound_local_error_does_not_match(self):
|
def test_unbound_local_error_does_not_match(self):
|
||||||
def func():
|
def func():
|
||||||
|
|
|
@ -1037,6 +1037,16 @@ def _compute_suggestion_error(exc_value, tb, wrong_name):
|
||||||
+ list(frame.f_globals)
|
+ list(frame.f_globals)
|
||||||
+ list(frame.f_builtins)
|
+ list(frame.f_builtins)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Check first if we are in a method and the instance
|
||||||
|
# has the wrong name as attribute
|
||||||
|
if 'self' in frame.f_locals:
|
||||||
|
self = frame.f_locals['self']
|
||||||
|
if hasattr(self, wrong_name):
|
||||||
|
return f"self.{wrong_name}"
|
||||||
|
|
||||||
|
# Compute closest match
|
||||||
|
|
||||||
if len(d) > _MAX_CANDIDATE_ITEMS:
|
if len(d) > _MAX_CANDIDATE_ITEMS:
|
||||||
return None
|
return None
|
||||||
wrong_name_len = len(wrong_name)
|
wrong_name_len = len(wrong_name)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
Improve the error suggestion for :exc:`NameError` exceptions for instances.
|
||||||
|
Now if a :exc:`NameError` is raised in a method and the instance has an
|
||||||
|
attribute that's exactly equal to the name in the exception, the suggestion
|
||||||
|
will include ``self.<NAME>`` instead of the closest match in the method
|
||||||
|
scope. Patch by Pablo Galindo
|
|
@ -1,5 +1,6 @@
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
#include "pycore_frame.h"
|
#include "pycore_frame.h"
|
||||||
|
#include "pycore_runtime_init.h" // _Py_ID()
|
||||||
|
|
||||||
#include "pycore_pyerrors.h"
|
#include "pycore_pyerrors.h"
|
||||||
#include "pycore_code.h" // _PyCode_GetVarnames()
|
#include "pycore_code.h" // _PyCode_GetVarnames()
|
||||||
|
@ -226,6 +227,24 @@ get_suggestions_for_name_error(PyObject* name, PyFrameObject* frame)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Are we inside a method and the instance has an attribute called 'name'?
|
||||||
|
if (PySequence_Contains(dir, &_Py_ID(self)) > 0) {
|
||||||
|
PyObject* locals = PyFrame_GetLocals(frame);
|
||||||
|
if (!locals) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
PyObject* self = PyDict_GetItem(locals, &_Py_ID(self)); /* borrowed */
|
||||||
|
Py_DECREF(locals);
|
||||||
|
if (!self) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PyObject_HasAttr(self, name)) {
|
||||||
|
Py_DECREF(dir);
|
||||||
|
return PyUnicode_FromFormat("self.%S", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PyObject *suggestions = calculate_suggestions(dir, name);
|
PyObject *suggestions = calculate_suggestions(dir, name);
|
||||||
Py_DECREF(dir);
|
Py_DECREF(dir);
|
||||||
if (suggestions != NULL) {
|
if (suggestions != NULL) {
|
||||||
|
@ -250,6 +269,10 @@ get_suggestions_for_name_error(PyObject* name, PyFrameObject* frame)
|
||||||
Py_DECREF(dir);
|
Py_DECREF(dir);
|
||||||
|
|
||||||
return suggestions;
|
return suggestions;
|
||||||
|
|
||||||
|
error:
|
||||||
|
Py_DECREF(dir);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
|
|
Loading…
Reference in New Issue