Skip to content

[TVMScript] Fix PEP 563 closure variable resolution#18856

Merged
tqchen merged 8 commits into
apache:mainfrom
tqchen:fix-pep563-closure-vars
Mar 1, 2026
Merged

[TVMScript] Fix PEP 563 closure variable resolution#18856
tqchen merged 8 commits into
apache:mainfrom
tqchen:fix-pep563-closure-vars

Conversation

@tqchen

@tqchen tqchen commented Feb 28, 2026

Copy link
Copy Markdown
Member

With from __future__ import annotations, Python stores annotations as strings
and does not capture annotation-only variables in __closure__. This broke
TVMScript when buffer shapes/dtypes referenced closure variables.

Fix: wrap extra_vars in a collections.ChainMap with snapshots of all live
caller-frame locals (from inspect.stack()) as fallback layers in both
tir/entry.py (prim_func) and ir/entry.py (ir_module). The ir_module
function now also captures outer_stack = inspect.stack() at its entry point,
mirroring the existing pattern in prim_func. Lookup falls back to frame locals
only on cache miss, preserving existing behavior for non-PEP-563 code.

Add tests/python/tvmscript/test_tvmscript_pep563_closure.py (requires
from __future__ import annotations at the top) covering closure variables in
buffer shapes, dtypes, nested scopes, ir_module, and mixed annotation+body use.

…lback

With `from __future__ import annotations`, Python stores annotations as strings
and does not capture annotation-only variables in `__closure__`. This broke
TVMScript when buffer shapes/dtypes referenced closure variables.

Fix: wrap `extra_vars` in a `collections.ChainMap` with snapshots of all live
caller-frame locals (from `inspect.stack()`) as fallback layers in both
`tir/entry.py` (`prim_func`) and `ir/entry.py` (`ir_module`). The `ir_module`
function now also captures `outer_stack = inspect.stack()` at its entry point,
mirroring the existing pattern in `prim_func`. Lookup falls back to frame locals
only on cache miss, preserving existing behavior for non-PEP-563 code.

Add `tests/python/tvmscript/test_tvmscript_pep563_closure.py` (requires
`from __future__ import annotations` at the top) covering closure variables in
buffer shapes, dtypes, nested scopes, ir_module, and mixed annotation+body use.
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a critical compatibility issue with Python's PEP 563 (from __future__ import annotations) in TVMScript. The changes ensure that TVMScript can correctly parse and resolve variables used within type annotations, even when those variables are part of an outer scope's closure and not explicitly captured by Python's default closure mechanism. This significantly improves the robustness of TVMScript when used with modern Python features.

Highlights

  • PEP 563 Closure Variable Resolution: Addressed an issue where TVMScript failed to resolve closure variables used in annotations when from __future__ import annotations was enabled, as Python does not capture annotation-only variables in __closure__ under this future import.
  • Enhanced Variable Capture Mechanism: Implemented a fix by wrapping extra_vars in a collections.ChainMap. This ChainMap includes snapshots of all live caller-frame locals (obtained via inspect.stack()) as fallback layers, ensuring proper variable lookup for both prim_func and ir_module.
  • New Test Coverage: Introduced a new test file, test_tvmscript_pep563_closure.py, to validate the fix across various scenarios, including closure variables in buffer shapes, dtypes, nested scopes, ir_module contexts, and mixed annotation/body usage.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • python/tvm/script/parser/ir/entry.py
    • Imported collections.ChainMap for enhanced variable lookup.
    • Captured the inspect.stack() at the entry point of the ir_module decorator.
    • Modified extra_vars to use ChainMap for lazy fallback to caller frame locals, addressing PEP 563 compatibility.
  • python/tvm/script/parser/tir/entry.py
    • Imported collections.ChainMap for enhanced variable lookup.
    • Modified extra_vars to use ChainMap for lazy fallback to caller frame locals, addressing PEP 563 compatibility.
  • tests/python/tvmscript/test_tvmscript_pep563_closure.py
    • Added a new test file to verify correct closure variable resolution with from __future__ import annotations.
    • Included tests for prim_func with closure variables in buffer shapes and dtypes.
    • Added tests for nested closures and ir_module closure variable usage.
    • Included a regression test for mixed annotation and body usage of closure variables.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly addresses an issue with PEP 563 closure variable resolution in TVMScript. The approach of using inspect.stack and collections.ChainMap to create a fallback for resolving variables from caller frames is sound. The new test suite provides excellent coverage for various scenarios, including nested closures and usage in IR modules. My review includes a few suggestions to improve code style and consistency.

Comment thread python/tvm/script/parser/ir/entry.py
Comment thread python/tvm/script/parser/ir/entry.py Outdated
Comment thread python/tvm/script/parser/tir/entry.py
Comment thread tests/python/tvmscript/test_tvmscript_pep563_closure.py Outdated
tqchen added 4 commits March 1, 2026 01:35
The previous with_caller_frame_fallback added ALL caller-frame locals
to the TVMScript namespace, causing name collisions (e.g., `gv` from
a test function shadowing a nested @R.function def gv).

Replace with resolve_closure_vars: parses the class source AST to find
names used in function annotations, then looks up only those names in
enclosing frames. This resolves PEP 563 closure variables (like M in
T.Buffer((M,), "float32")) without polluting the namespace.
…cals

The previous ChainMap approach added ALL caller-frame locals to the
TVMScript namespace, causing name collisions (e.g. `gv` from a test
function shadowing a nested @R.function def gv).

Replace with resolve_closure_vars: parses the source AST to find names
used in function annotations, then looks up only those names in enclosing
frames. This resolves PEP 563 closure variables (like M in
T.Buffer((M,), "float32")) without polluting the namespace.

Applied to both @T.prim_func and @I.ir_module paths.
- Only trigger resolve_closure_vars when annotations are actually
  strings (PEP 563 active), skipping the AST parse for normal code.
- Use __qualname__ to identify lexically enclosing scopes instead of
  walking the entire caller stack, preventing false matches from
  unrelated caller frames.
@tqchen tqchen merged commit 61f8081 into apache:main Mar 1, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants