From 01650df2a236e5082ab03d29cbafb367f5703ee4 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sat, 5 Nov 2022 18:40:40 +0000 Subject: [PATCH 1/7] gh-99139: Improve NameError error suggestion for instances --- Doc/whatsnew/3.12.rst | 21 ++++++++++++++++ Lib/test/test_traceback.py | 25 +++++++++++++++++++ Lib/traceback.py | 10 ++++++++ ...2-11-05-18-36-27.gh-issue-99139.cI9vV1.rst | 5 ++++ Python/suggestions.c | 25 +++++++++++++++++++ 5 files changed, 86 insertions(+) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2022-11-05-18-36-27.gh-issue-99139.cI9vV1.rst diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index b6daa6d5c9d968..5652df5a553f68 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -75,6 +75,27 @@ Important deprecations, removals or restrictions: 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.`` instead of the closest match in the method + scope. Controbuted 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 "", 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 from y`` instead of ``from y import x``. Contributed by Pablo Galindo in :gh:`98931`. diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 149d0234fe8a72..430daf69d2955d 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -3356,6 +3356,31 @@ def func(): actual = self.get_suggestion(func) 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 func(): diff --git a/Lib/traceback.py b/Lib/traceback.py index cf5f355ff04c3b..8d518728fa1b10 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -1037,6 +1037,16 @@ def _compute_suggestion_error(exc_value, tb, wrong_name): + list(frame.f_globals) + 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: return None wrong_name_len = len(wrong_name) diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-11-05-18-36-27.gh-issue-99139.cI9vV1.rst b/Misc/NEWS.d/next/Core and Builtins/2022-11-05-18-36-27.gh-issue-99139.cI9vV1.rst new file mode 100644 index 00000000000000..62b78b9b45310a --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-11-05-18-36-27.gh-issue-99139.cI9vV1.rst @@ -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.`` instead of the closest match in the method +scope. Patch by Pablo Galindo diff --git a/Python/suggestions.c b/Python/suggestions.c index 82376b6cd985a3..af36b42446d925 100644 --- a/Python/suggestions.c +++ b/Python/suggestions.c @@ -1,3 +1,5 @@ +#define NEEDS_PY_IDENTIFIER + #include "Python.h" #include "pycore_frame.h" @@ -226,6 +228,25 @@ get_suggestions_for_name_error(PyObject* name, PyFrameObject* frame) return NULL; } + // Are we inside a method and the instance has an attribute called 'name'? + _Py_IDENTIFIER(self); + PyObject* self_str = _PyUnicode_FromId(&PyId_self); /* borrowed */ + if (PySequence_Contains(dir, self_str) > 0) { + PyObject* locals = PyFrame_GetLocals(frame); + if (!locals) { + goto error; + } + PyObject* self = PyDict_GetItemString(locals, "self"); /* borrowed */ + Py_DECREF(locals); + if (!self) { + goto error; + } + + if (PyObject_HasAttr(self, name)) { + return PyUnicode_FromFormat("self.%S", name); + } + } + PyObject *suggestions = calculate_suggestions(dir, name); Py_DECREF(dir); if (suggestions != NULL) { @@ -250,6 +271,10 @@ get_suggestions_for_name_error(PyObject* name, PyFrameObject* frame) Py_DECREF(dir); return suggestions; + +error: + Py_XDECREF(dir); + return NULL; } static bool From b1546e5c29148cfb6445f7e4f4795e9084392e14 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Sat, 5 Nov 2022 19:54:24 +0000 Subject: [PATCH 2/7] Update Doc/whatsnew/3.12.rst Co-authored-by: Ammar Askar --- Doc/whatsnew/3.12.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 5652df5a553f68..fb28d6758d3c5f 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -79,7 +79,7 @@ Improved Error Messages 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.`` instead of the closest match in the method - scope. Controbuted by Pablo Galindo in :gh:`99139`. + scope. Contributed by Pablo Galindo in :gh:`99139`. >>> class A: ... def __init__(self): From 8a244b9e9f99173b928c5858d04cebfce12f6b65 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Sun, 6 Nov 2022 01:54:40 +0000 Subject: [PATCH 3/7] Update Python/suggestions.c Co-authored-by: Ammar Askar --- Python/suggestions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/suggestions.c b/Python/suggestions.c index af36b42446d925..daacb646c28907 100644 --- a/Python/suggestions.c +++ b/Python/suggestions.c @@ -236,7 +236,7 @@ get_suggestions_for_name_error(PyObject* name, PyFrameObject* frame) if (!locals) { goto error; } - PyObject* self = PyDict_GetItemString(locals, "self"); /* borrowed */ + PyObject* self = PyDict_GetItem(locals, self_str); /* borrowed */ Py_DECREF(locals); if (!self) { goto error; From d8933dd20198468692aad4cb1574fc96200d6406 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Sun, 6 Nov 2022 01:54:44 +0000 Subject: [PATCH 4/7] Update Python/suggestions.c Co-authored-by: Batuhan Taskaya --- Python/suggestions.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Python/suggestions.c b/Python/suggestions.c index daacb646c28907..9f7e5934b3a055 100644 --- a/Python/suggestions.c +++ b/Python/suggestions.c @@ -243,6 +243,7 @@ get_suggestions_for_name_error(PyObject* name, PyFrameObject* frame) } if (PyObject_HasAttr(self, name)) { + Py_DECREF(dir); return PyUnicode_FromFormat("self.%S", name); } } From 4925719c4748205be121d6bbcef6ab33feb4a635 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Sun, 6 Nov 2022 01:54:57 +0000 Subject: [PATCH 5/7] Update Python/suggestions.c Co-authored-by: Batuhan Taskaya --- Python/suggestions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/suggestions.c b/Python/suggestions.c index 9f7e5934b3a055..2349d86388b849 100644 --- a/Python/suggestions.c +++ b/Python/suggestions.c @@ -274,7 +274,7 @@ get_suggestions_for_name_error(PyObject* name, PyFrameObject* frame) return suggestions; error: - Py_XDECREF(dir); + Py_DECREF(dir); return NULL; } From ab2874a6e50bcd99f44b5c73a284fafc03cdc74b Mon Sep 17 00:00:00 2001 From: Pablo Galindo Salgado Date: Sun, 6 Nov 2022 11:40:02 +0000 Subject: [PATCH 6/7] Update Python/suggestions.c Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> --- Python/suggestions.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Python/suggestions.c b/Python/suggestions.c index 2349d86388b849..ce17606fd6d60b 100644 --- a/Python/suggestions.c +++ b/Python/suggestions.c @@ -229,14 +229,12 @@ get_suggestions_for_name_error(PyObject* name, PyFrameObject* frame) } // Are we inside a method and the instance has an attribute called 'name'? - _Py_IDENTIFIER(self); - PyObject* self_str = _PyUnicode_FromId(&PyId_self); /* borrowed */ - if (PySequence_Contains(dir, self_str) > 0) { + if (PySequence_Contains(dir, &_Py_ID(self)) > 0) { PyObject* locals = PyFrame_GetLocals(frame); if (!locals) { goto error; } - PyObject* self = PyDict_GetItem(locals, self_str); /* borrowed */ + PyObject* self = PyDict_GetItem(locals, &_Py_ID(self)); /* borrowed */ Py_DECREF(locals); if (!self) { goto error; From 00706e4a8142e4e5695a9309e34b73ac9f715d39 Mon Sep 17 00:00:00 2001 From: Pablo Galindo Date: Sun, 6 Nov 2022 12:34:36 +0000 Subject: [PATCH 7/7] fixup! Update Python/suggestions.c --- Include/internal/pycore_global_strings.h | 1 + Include/internal/pycore_runtime_init_generated.h | 7 +++++++ Python/suggestions.c | 3 +-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Include/internal/pycore_global_strings.h b/Include/internal/pycore_global_strings.h index e60bd4b94bbcdf..8912895b0de846 100644 --- a/Include/internal/pycore_global_strings.h +++ b/Include/internal/pycore_global_strings.h @@ -600,6 +600,7 @@ struct _Py_global_strings { STRUCT_FOR_ID(seek) STRUCT_FOR_ID(seekable) STRUCT_FOR_ID(selectors) + STRUCT_FOR_ID(self) STRUCT_FOR_ID(send) STRUCT_FOR_ID(sep) STRUCT_FOR_ID(sequence) diff --git a/Include/internal/pycore_runtime_init_generated.h b/Include/internal/pycore_runtime_init_generated.h index 8ce103700d6966..a1f1efdf43b765 100644 --- a/Include/internal/pycore_runtime_init_generated.h +++ b/Include/internal/pycore_runtime_init_generated.h @@ -1109,6 +1109,7 @@ extern "C" { INIT_ID(seek), \ INIT_ID(seekable), \ INIT_ID(selectors), \ + INIT_ID(self), \ INIT_ID(send), \ INIT_ID(sep), \ INIT_ID(sequence), \ @@ -2567,6 +2568,8 @@ _PyUnicode_InitStaticStrings(void) { PyUnicode_InternInPlace(&string); string = &_Py_ID(selectors); PyUnicode_InternInPlace(&string); + string = &_Py_ID(self); + PyUnicode_InternInPlace(&string); string = &_Py_ID(send); PyUnicode_InternInPlace(&string); string = &_Py_ID(sep); @@ -7104,6 +7107,10 @@ _PyStaticObjects_CheckRefcnt(void) { _PyObject_Dump((PyObject *)&_Py_ID(selectors)); 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) { _PyObject_Dump((PyObject *)&_Py_ID(send)); Py_FatalError("immortal object has less refcnt than expected _PyObject_IMMORTAL_REFCNT"); diff --git a/Python/suggestions.c b/Python/suggestions.c index ce17606fd6d60b..239245d73defc7 100644 --- a/Python/suggestions.c +++ b/Python/suggestions.c @@ -1,7 +1,6 @@ -#define NEEDS_PY_IDENTIFIER - #include "Python.h" #include "pycore_frame.h" +#include "pycore_runtime_init.h" // _Py_ID() #include "pycore_pyerrors.h" #include "pycore_code.h" // _PyCode_GetVarnames()