馃悰 fix(resolver): bind only what a guarded statement defines - #760
Merged
gaborbernat merged 1 commit intoAug 24, 2026
Merged
Conversation
tox-dev#757 recovered the names of a failed guarded statement with ast.walk over the whole subtree, which also picks up comprehension, loop and with-as targets. Those names do not exist at runtime, so setdefault created them: a guarded `REGISTRY = {type: Dataset[type] for type in (int, str)}` bound `type` in the module globals, and every later annotation reading `type` got a placeholder instead of the builtin. Reading the statement's own targets, and recursing into the bodies of the statements it wraps, keeps that namespace clean. Gating the warning on the statement being an import also lost the diagnostic for a version-gated or try/except import, where the import sits inside an ast.If or ast.Try. The gate now looks for an import anywhere in the statement, so those report the absent dependency again, and the statements that stay silent leave a debug line behind.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A
TYPE_CHECKINGstatement that fails to execute has its names stood in as forward references, so annotations using them still resolve. Collecting those names walked the statement's whole subtree for Store-context names, which also picks up comprehension, loop andwith ... astargets. The module holds no such names at runtime, so binding them created them: a guardedREGISTRY = {type: Dataset[type] for type in (int, str)}put a placeholder undertypein the module globals, after whichdef describe(value: type)resolved to the placeholder in place of the builtin. The parameter rendered as unlinked text, and calling the function in the same process raisedTypeError: TypeAliasForwardRef.__call__() takes 1 positional argument but 2 were given. Only the statement's own targets are read now, recursing into the bodies of the statements it wraps. 馃ЧThe
Failed guarded type importwarning asked whether the failing statement was anast.Importorast.ImportFrom, which misses the shapes guarded imports tend to take:There the import is a leaf of an
ast.Ifor anast.Try, so a dependency missing from the docs environment went unreported, and the name binding could not make up for it either, becauseast.aliasis not anast.Name. The check now looks for an import anywhere in the statement, restoring the diagnostic #741 asked for. Statements that stay silent leave a_LOGGER.debugline, so a typo in a guard stays traceable with-vv.