Skip to content

Raise consistent NameError in ForwardRef.evaluate() #135646

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

Activity

  1. JelleZijlstra commented on Jun 18, 2025

    @JelleZijlstra
    Member

    Yes, let's change this. Feel free to send a PR!

  2. added 2 commits that reference this issue on Jun 18, 2025
  3. added a commit that references this issue on Jun 18, 2025
  4. added a commit that references this issue on Jun 18, 2025
  5. added a commit that references this issue on Jun 18, 2025
  6. added a commit that references this issue on Jun 19, 2025
  7. added a commit that references this issue on Jul 12, 2025
  8. added a commit that references this issue on Aug 4, 2025
  9. added a commit that references this issue on Aug 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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