← 输入框|github.com|刷新
Skip to content

gh-160948: Fix annotationlib.ForwardRef.evaluate() ignoring explicit globals/locals for closure-cell references - #160952

Open
arpittkhandelwal wants to merge 2 commits into
python:mainfrom
arpittkhandelwal:fix/annotationlib-forwardref-evaluate
Open

arpittkhandelwal wants to merge 2 commits into
python:mainfrom
arpittkhandelwal:fix/annotationlib-forwardref-evaluate

Conversation

@arpittkhandelwal

@arpittkhandelwal arpittkhandelwal commented Oct 30, 2026 •

Copy link
Copy Markdown

Summary

ForwardRef.evaluate() had an early return for references holding a single closure cell (__cell__ is a CellType) that ran before any namespace handling. As a result, an explicitly passed locals was silently ignored for such references, while every other path in the same method gives it precedence: for references holding a dict of cells, the cells are merged into the locals with setdefault, so explicit locals win over the captured scope.

As discussed in gh-160948, the cell holds a nonlocal binding, so it correctly takes precedence over globals (on the dict-of-cells path, the nonlocals sit between the locals and the globals). Only the locals precedence was wrong, and that is all this PR changes.

Fix

In Lib/annotationlib.py (ForwardRef.evaluate):

  • The single-cell shortcut now only applies when neither globals nor locals was passed, preserving the existing fast path and behaviour for the common no-argument call.
  • In the plain-identifier resolution branch, the cell value is consulted after locals and before globals, mirroring the dict-of-cells path. An empty cell (ValueError) falls through exactly as before.

No other paths are changed: the dict-of-cells and __extra_names__ handling, type-parameter injection, and the non-identifier eval path are untouched.

Tests

Two regression tests in Lib/test/test_annotationlib.py (TestForwardRefClass):

  • test_evaluate_cell_with_explicit_namespaces — an explicitly passed locals wins over the cell, in both VALUE and FORWARDREF formats; globals does not; the cell remains the fallback when the name is absent from locals.
  • test_evaluate_empty_cell_with_explicit_namespaces — an emptied cell still raises NameError with no arguments, explicit namespaces are consulted, and a refilled cell resumes its place between locals and globals.

Both tests fail on the unmodified code and pass with this change.

Results (local build of main @ 83fe630):

  • test_annotationlib: SUCCESS.
  • test_typing: SUCCESS.

Fixes gh-160948.

Disclosure: this change was drafted with AI assistance and reviewed by the author, who takes responsibility for it.

…licit globals/locals for closure-cell references
@picnixz

picnixz commented Oct 31, 2026

Copy link
Copy Markdown
Member

Considering the issue is still under discussion, I'm closing this.

@picnixz picnixz closed this Oct 31, 2026
@picnixz picnixz reopened this Oct 31, 2026
@picnixz

picnixz commented Oct 31, 2026

Copy link
Copy Markdown
Member

We may keep it open and only change the locals handling. Because the globals one may not be wrong.

@arpittkhandelwal

Copy link
Copy Markdown
Author

Thanks @DavidCEllis for the demonstration, and @picnixz for the review. You are both right: the cell holds a nonlocal binding, and David's generic example shows that the multi-name path already places the nonlocals between the locals and the globals. The globals expectation in my issue report was wrong, so I have narrowed this PR to the locals handling only:

  • The resolution order for a single-cell reference is now: explicit locals, then the cell, then globals, then builtins — matching the dict-of-cells path, where the cells are merged into the locals with setdefault.
  • The regression tests are updated accordingly: evaluate(globals={"x": 43}) keeps returning the cell value (99), while evaluate(locals={"x": 42}) now returns 42. test_annotationlib and test_typing pass, and both tests still fail on the unmodified code. I have also updated the PR description and the NEWS entry, and added a correction to the issue.

On the question about past issues: I searched the tracker for prior reports of this behaviour (annotationlib / ForwardRef / evaluate combined with cell, closure, globals and locals) and could not find one. The closest matches are gh-141174 (ForwardRef test coverage) and the separate issue about evaluate()'s type_params handling; neither covers the single-cell namespace precedence. If you remember where you saw it, I am happy to link it or close this as a duplicate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

annotationlib.ForwardRef.evaluate() ignores explicit globals/locals when the reference holds a closure cell

2 participants