Skip to content

Raise consistent NameError in ForwardRef.evaluate() #135646

Open
@Viicos

Description

@Viicos

Feature or enhancement

Proposal:

The new ForwardRef.evaluate() implementation had a fast path introduced in #124337 to not call eval():

if arg.isidentifier() and not keyword.iskeyword(arg):
if arg in locals:
return locals[arg]
elif arg in globals:
return globals[arg]
elif hasattr(builtins, arg):
return getattr(builtins, arg)
elif is_forwardref_format:
return self
else:
raise NameError(arg)

However, when the NameError is raised, the format is different from the one raised by eval():

The exception message is defined as:

#define NAME_ERROR_MSG "name '%.200s' is not defined"

and raised this way:

cpython/Python/ceval.c

Lines 3339 to 3351 in cb39410

_PyErr_Format(tstate, exc, format_str, obj_str);
if (exc == PyExc_NameError) {
// Include the name in the NameError exceptions to offer suggestions later.
PyObject *exc = PyErr_GetRaisedException();
if (PyErr_GivenExceptionMatches(exc, PyExc_NameError)) {
if (((PyNameErrorObject*)exc)->name == NULL) {
// We do not care if this fails because we are going to restore the
// NameError anyway.
(void)PyObject_SetAttr(exc, &_Py_ID(name), obj);
}
}
PyErr_SetRaisedException(exc);

To have consistent exceptions raised, would it be possible to change the Python fast path implementation to match the C eval code?

diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py
index 731817a9973..c83a1573ccd 100644
--- a/Lib/annotationlib.py
+++ b/Lib/annotationlib.py
@@ -27,6 +27,9 @@ class Format(enum.IntEnum):
 
 
 _sentinel = object()
+# Following `NAME_ERROR_MSG` in `ceval_macros.h`:
+_NAME_ERROR_MSG = "name '{name:.200}' is not defined"
+
 
 # Slots shared by ForwardRef and _Stringifier. The __forward__ names must be
 # preserved for compatibility with the old typing.ForwardRef class. The remaining
@@ -184,7 +187,7 @@ def evaluate(
             elif is_forwardref_format:
                 return self
             else:
-                raise NameError(arg)
+                raise NameError(_NAME_ERROR_MSG.format(name=arg), name=arg)
         else:
             code = self.__forward_code__
             try:

This requires _NAME_ERROR_MSG to be in sync with the one from ceval_macros.h.

Or at least, the NameError should have its name property set (especially as type stubs shows NameError.name as str currently).

cc @JelleZijlstra.

Has this already been discussed elsewhere?

This is a minor feature, which does not need previous discussion elsewhere

Links to previous discussion of this feature:

No response

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-featureA feature request or enhancement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions