Skip to content

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Feb 11, 2026

Summary by CodeRabbit

  • New Features

    • Generic type (subscription) support for Interpolation, MemoryView, and Template; new const-evaluator used for typing/value evaluation.
  • Bug Fixes

    • Fixed module scoping for Pattern and Match in the re module.
  • Improvements

    • Stricter type-parameter validation and ordering; nonlocal/type-parameter binding checks; expanded annotation visibility and enforced format/positional handling; lazy evaluation and getters for TypeVar/ParamSpec/TypeVarTuple defaults.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

Adds PEP 695 style type‑parameter and annotation evaluation support: registers a format parameter in TypeParams/annotation scopes, enforces type‑param ordering and nonlocal restrictions, introduces a ConstEvaluator for formatted annotation values, migrates TypeVar/ParamSpec/TypeVarTuple to PyMutex lazy evaluation, and adds class_getitem to some builtins.

Changes

Cohort / File(s) Summary
Codegen
crates/codegen/src/compile.rs
Register pos‑only format argcount in newly created TypeParams scopes and emit format validation during TypeAlias/type‑param evaluation.
Symbol table & scope checks
crates/codegen/src/symboltable.rs
Reject nonlocal bindings to type params, broaden annotation visibility of class scopes, auto-register a format parameter in annotation/type‑param scopes, and enforce ordering of default vs non‑default type parameters.
Typing runtime & ConstEvaluator
crates/vm/src/stdlib/typing.rs
Add a new _ConstEvaluator pyclass with Callable/Representable/Constructor behavior, helper repr/format functions, const_evaluator_alloc, and change TypeAlias value evaluation to use format=1 and return ConstEvaluator for non‑lazy values.
TypeVar/ParamSpec/TypeVarTuple
crates/vm/src/stdlib/typevar.rs
Replace parking_lot::Mutex with PyMutex for bound/constraints/default_value, add evaluate_bound/constraints/default helpers, change evaluator call to accept a single format arg, and update constructors/slot_new paths.
Builtin generic subscripting
crates/vm/src/builtins/interpolation.rs, crates/vm/src/builtins/template.rs, crates/vm/src/builtins/memory.rs
Add/uncomment __class_getitem__ classmethods returning PyGenericAlias::from_args to enable subscription syntax on these builtins.
Exports / minor API surface
crates/vm/src/stdlib/sre.rs, crates/vm/src/builtins/type.rs
Add module scoping to Pattern/Match pyclass attributes; adjust a small error message formatting in type.rs.
Misc
.cspell.dict/cpython.txt
Add constevaluator token to spell dictionary.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Parser
participant Compiler
participant SymbolTable
participant Codegen
participant VM
participant TypeAliasRuntime
Parser->>Compiler: parse TypeAlias / type params
Compiler->>SymbolTable: enter TypeParams scope
SymbolTable->>SymbolTable: register format param, check type-param rules
Compiler->>Codegen: emit code with posonly argcount/format validation
Codegen->>VM: runtime TypeAlias evaluation (format=1)
VM->>TypeAliasRuntime: create/return _ConstEvaluator or evaluate lazily
TypeAliasRuntime-->>VM: ConstEvaluator(callable) wrapped value
VM-->>Caller: formatted annotation or raw value

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • arihant2math

Poem

🐰 I nibbled through scopes and names,
I planted format in type‑param plains,
ConstEvaluator curls a tasty bite,
Generics bloom when subscripts alight,
Hoppity hops — annotations take flight! 🥕✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Implement more typing' is vague and generic, using non-descriptive terms that don't clearly convey the specific changes made across multiple complex components. Provide a more specific title that highlights the main change, such as 'Add typing system enhancements for type parameters and generics' or 'Implement generic alias support and TypeVar evaluation mechanisms'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@youknowone youknowone marked this pull request as ready for review February 11, 2026 07:09
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/vm/src/stdlib/typevar.rs (1)

95-107: ⚠️ Potential issue | 🟠 Major

Python call while holding constraints lock.

self.evaluate_constraints.call((1i32,), vm)? at Line 101 executes Python code while constraints is locked. If the called code accesses __constraints__ on the same TypeVar, this deadlocks (non-reentrant mutex). The same issue exists in __bound__ (Line 116). Consider cloning the evaluator, dropping the lock, calling, then re-acquiring to store.

Proposed fix for `__constraints__`
 fn __constraints__(&self, vm: &VirtualMachine) -> PyResult {
-    let mut constraints = self.constraints.lock();
-    if !vm.is_none(&constraints) {
-        return Ok(constraints.clone());
+    {
+        let constraints = self.constraints.lock();
+        if !vm.is_none(&constraints) {
+            return Ok(constraints.clone());
+        }
     }
-    let r = if !vm.is_none(&self.evaluate_constraints) {
-        *constraints = self.evaluate_constraints.call((1i32,), vm)?;
-        constraints.clone()
-    } else {
-        vm.ctx.empty_tuple.clone().into()
-    };
-    Ok(r)
+    if !vm.is_none(&self.evaluate_constraints) {
+        let value = self.evaluate_constraints.call((1i32,), vm)?;
+        *self.constraints.lock() = value.clone();
+        Ok(value)
+    } else {
+        Ok(vm.ctx.empty_tuple.clone().into())
+    }
 }

Apply the same pattern to __bound__.

🤖 Fix all issues with AI agents
In `@crates/codegen/src/compile.rs`:
- Around line 2464-2468: The evaluator scopes set argcount/posonlyargcount then
call emit_format_validation(), but they don't register the "format" local in the
current code object's varnames so emit_format_validation()'s LoadFast(0) can
read the wrong slot or panic; update the relevant evaluator scope blocks (the
ones around the calls to
self.current_code_info().metadata.argcount/posonlyargcount and
emit_format_validation(), including the similar blocks at the other locations
noted) to push "format" into self.current_code_info().metadata.varnames
(mirroring enter_annotation_scope's behavior) before calling
emit_format_validation(), ensuring varnames contains "format" and preserving
Rust error handling conventions when modifying the metadata.

In `@crates/vm/src/stdlib/typevar.rs`:
- Around line 178-189: In evaluate_default, the lock acquisition order is
reversed and holds multiple locks while potentially making Python calls, risking
deadlock; change the logic in fn evaluate_default to avoid holding both
MutexGuards at once: first lock self.evaluate_default, clone the value into a
local and drop the guard; if that clone is not None return it; otherwise lock
self.default_value, clone that into a local, drop the guard, then compare the
cloned default against vm.ctx.typing_no_default and, if not equal, call
const_evaluator_alloc using the cloned value (ensure no locks are held when
calling const_evaluator_alloc); reference symbols: evaluate_default (method),
self.evaluate_default, self.default_value, const_evaluator_alloc,
vm.ctx.typing_no_default.
- Around line 140-152: The method __default__ in typevar.rs (and the analogous
ParamSpec::__default__ and TypeVarTuple::__default__) currently acquires
default_value then evaluate_default and even calls evaluate_default.call(...)
while both mutexes are held, which risks deadlock due to inconsistent lock
ordering and calling into Python under a lock. Fix by changing the sequence to
first lock evaluate_default (to check/isolate the callable) and clone or take
its value, then drop that lock before acquiring default_value; perform the
Python call outside any mutex, then re-acquire default_value to update it if
needed. Ensure the lock acquisition order matches the evaluate_default getter
(evaluate_default before default_value) across all three __default__
implementations and never call Python while holding either mutex.

In `@crates/vm/src/stdlib/typing.rs`:
- Around line 159-169: The function typing_type_repr_value incorrectly formats
single-element tuples because parts.join(", ") yields "(item)" instead of
"(item,)" — update typing_type_repr_value: after collecting parts from the
PyTuple (using value.try_to_ref::<PyTuple> and typing_type_repr for each item),
detect when tuple.len() == 1 and format the string with a trailing comma (e.g.
"({},)" style) otherwise use "({})" with parts.join(", "); keep using
vm.ctx.new_str(...) and the existing return path.
- Around line 109-123: The code currently only treats ANNOTATE_FORMAT_STRING
(const ANNOTATE_FORMAT_STRING) specially in ConstEvaluator::call by delegating
to typing_type_repr_value, while formats VALUE, VALUE_WITH_FAKE_GLOBALS and
FORWARDREF fall through to returning the raw value; update the
ConstEvaluator::call implementation to either (a) add a clear comment above the
const and inside the call method stating that only annotationlib.Format.STRING
(4) is intentionally handled here and other formats (1,2,3) are returned
unchanged by design, referencing ANNOTATE_FORMAT_STRING and
typing_type_repr_value, or (b) implement the missing handling for formats 1/2/3
as required (e.g., call the same or different helpers) so behavior matches
callers that expect format=1 at runtime; pick one approach and apply it
consistently so future maintainers know why formats besides STRING are not
processed.
🧹 Nitpick comments (1)
crates/vm/src/stdlib/typing.rs (1)

127-155: Attribute-existence checks via get_attr().is_ok() swallow real errors.

Lines 137–138 use get_attr(...).is_ok() to test whether __origin__ and __args__ exist. This conflates "attribute not found" with "error during lookup" (e.g., a __getattr__ that raises RuntimeError). This mirrors CPython's approach so it's not a blocker, but worth noting.

Comment on lines +2464 to 2468
// Evaluator takes a positional-only format parameter
self.current_code_info().metadata.argcount = 1;
self.current_code_info().metadata.posonlyargcount = 1;
self.emit_format_validation()?;
self.compile_expression(value)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add the format local before emit_format_validation() to keep LoadFast(0) valid.

emit_format_validation() uses LoadFast(0), but these evaluator scopes only set argcount/posonlyargcount and don’t insert "format" into varnames. That can misread a different local or panic if the varnames set is empty. Add "format" to varnames in these evaluator scopes (mirroring enter_annotation_scope).

✅ Suggested fix
@@
-                    // Evaluator takes a positional-only format parameter
+                    // Evaluator takes a positional-only format parameter
                     self.current_code_info().metadata.argcount = 1;
                     self.current_code_info().metadata.posonlyargcount = 1;
+                    self.current_code_info()
+                        .metadata
+                        .varnames
+                        .insert("format".to_owned());
                     self.emit_format_validation()?;
@@
-                    // Evaluator takes a positional-only format parameter
+                    // Evaluator takes a positional-only format parameter
                     self.current_code_info().metadata.argcount = 1;
                     self.current_code_info().metadata.posonlyargcount = 1;
+                    self.current_code_info()
+                        .metadata
+                        .varnames
+                        .insert("format".to_owned());
                     self.emit_format_validation()?;
@@
-        // Evaluator takes a positional-only format parameter
+        // Evaluator takes a positional-only format parameter
         self.current_code_info().metadata.argcount = 1;
         self.current_code_info().metadata.posonlyargcount = 1;
+        self.current_code_info()
+            .metadata
+            .varnames
+            .insert("format".to_owned());
 
         self.emit_format_validation()?;

As per coding guidelines, follow Rust best practices for error handling and memory management.

Also applies to: 2501-2505, 2646-2651

🤖 Prompt for AI Agents
In `@crates/codegen/src/compile.rs` around lines 2464 - 2468, The evaluator scopes
set argcount/posonlyargcount then call emit_format_validation(), but they don't
register the "format" local in the current code object's varnames so
emit_format_validation()'s LoadFast(0) can read the wrong slot or panic; update
the relevant evaluator scope blocks (the ones around the calls to
self.current_code_info().metadata.argcount/posonlyargcount and
emit_format_validation(), including the similar blocks at the other locations
noted) to push "format" into self.current_code_info().metadata.varnames
(mirroring enter_annotation_scope's behavior) before calling
emit_format_validation(), ensuring varnames contains "format" and preserving
Rust error handling conventions when modifying the metadata.

Comment on lines 140 to 152
fn __default__(&self, vm: &VirtualMachine) -> PyResult {
let mut default_value = self.default_value.lock();
// Check if default_value is NoDefault (not just None)
if !default_value.is(&vm.ctx.typing_no_default) {
return Ok(default_value.clone());
}
let evaluate_default = self.evaluate_default.lock();
if !vm.is_none(&evaluate_default) {
*default_value = evaluate_default.call((), vm)?;
*default_value = evaluate_default.call((1i32,), vm)?;
Ok(default_value.clone())
} else {
// Return NoDefault singleton
Ok(vm.ctx.typing_no_default.clone().into())
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Potential deadlock: inconsistent lock ordering and Python calls under lock.

Two issues with this method:

  1. Lock ordering inversion: __default__ acquires default_value (Line 141) then evaluate_default (Line 145), but evaluate_default getter (Line 180) acquires them in reverse order. Concurrent access to both properties on the same object would deadlock.

  2. Python call under lock: evaluate_default.call((1i32,), vm)? at Line 147 executes arbitrary Python code while holding both mutex locks. If the callee accesses __default__ or evaluate_default on the same object, it deadlocks even single-threaded (assuming non-reentrant PyMutex).

The same pattern is repeated in ParamSpec::__default__ (Line 503–513) and TypeVarTuple::__default__ (Line 698–708).

Proposed fix: release locks before calling Python
 fn __default__(&self, vm: &VirtualMachine) -> PyResult {
-    let mut default_value = self.default_value.lock();
-    if !default_value.is(&vm.ctx.typing_no_default) {
-        return Ok(default_value.clone());
+    {
+        let default_value = self.default_value.lock();
+        if !default_value.is(&vm.ctx.typing_no_default) {
+            return Ok(default_value.clone());
+        }
     }
-    let evaluate_default = self.evaluate_default.lock();
-    if !vm.is_none(&evaluate_default) {
-        *default_value = evaluate_default.call((1i32,), vm)?;
-        Ok(default_value.clone())
-    } else {
-        Ok(vm.ctx.typing_no_default.clone().into())
+    let evaluator = {
+        let evaluate_default = self.evaluate_default.lock();
+        if vm.is_none(&evaluate_default) {
+            return Ok(vm.ctx.typing_no_default.clone().into());
+        }
+        evaluate_default.clone()
+    };
+    let value = evaluator.call((1i32,), vm)?;
+    *self.default_value.lock() = value.clone();
+    Ok(value)
-    }
 }

Apply the same pattern to ParamSpec::__default__ and TypeVarTuple::__default__.

🤖 Prompt for AI Agents
In `@crates/vm/src/stdlib/typevar.rs` around lines 140 - 152, The method
__default__ in typevar.rs (and the analogous ParamSpec::__default__ and
TypeVarTuple::__default__) currently acquires default_value then
evaluate_default and even calls evaluate_default.call(...) while both mutexes
are held, which risks deadlock due to inconsistent lock ordering and calling
into Python under a lock. Fix by changing the sequence to first lock
evaluate_default (to check/isolate the callable) and clone or take its value,
then drop that lock before acquiring default_value; perform the Python call
outside any mutex, then re-acquire default_value to update it if needed. Ensure
the lock acquisition order matches the evaluate_default getter (evaluate_default
before default_value) across all three __default__ implementations and never
call Python while holding either mutex.

Comment on lines +159 to +169
fn typing_type_repr_value(value: &PyObjectRef, vm: &VirtualMachine) -> PyResult {
if let Ok(tuple) = value.try_to_ref::<PyTuple>(vm) {
let mut parts = Vec::with_capacity(tuple.len());
for item in tuple.iter() {
parts.push(typing_type_repr(item, vm)?);
}
Ok(vm.ctx.new_str(format!("({})", parts.join(", "))).into())
} else {
Ok(vm.ctx.new_str(typing_type_repr(value, vm)?).into())
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Single-element tuple formatting omits the trailing comma.

parts.join(", ") for a 1-element tuple produces "(item)" rather than "(item,)". While single-element tuples are rare in annotation contexts (single constraints are disallowed), if this path is ever hit, the output would be ambiguous.

Proposed fix
         if let Ok(tuple) = value.try_to_ref::<PyTuple>(vm) {
             let mut parts = Vec::with_capacity(tuple.len());
             for item in tuple.iter() {
                 parts.push(typing_type_repr(item, vm)?);
             }
-            Ok(vm.ctx.new_str(format!("({})", parts.join(", "))).into())
+            let inner = if parts.len() == 1 {
+                format!("{},", parts[0])
+            } else {
+                parts.join(", ")
+            };
+            Ok(vm.ctx.new_str(format!("({})", inner)).into())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fn typing_type_repr_value(value: &PyObjectRef, vm: &VirtualMachine) -> PyResult {
if let Ok(tuple) = value.try_to_ref::<PyTuple>(vm) {
let mut parts = Vec::with_capacity(tuple.len());
for item in tuple.iter() {
parts.push(typing_type_repr(item, vm)?);
}
Ok(vm.ctx.new_str(format!("({})", parts.join(", "))).into())
} else {
Ok(vm.ctx.new_str(typing_type_repr(value, vm)?).into())
}
}
fn typing_type_repr_value(value: &PyObjectRef, vm: &VirtualMachine) -> PyResult {
if let Ok(tuple) = value.try_to_ref::<PyTuple>(vm) {
let mut parts = Vec::with_capacity(tuple.len());
for item in tuple.iter() {
parts.push(typing_type_repr(item, vm)?);
}
let inner = if parts.len() == 1 {
format!("{},", parts[0])
} else {
parts.join(", ")
};
Ok(vm.ctx.new_str(format!("({})", inner)).into())
} else {
Ok(vm.ctx.new_str(typing_type_repr(value, vm)?).into())
}
}
🤖 Prompt for AI Agents
In `@crates/vm/src/stdlib/typing.rs` around lines 159 - 169, The function
typing_type_repr_value incorrectly formats single-element tuples because
parts.join(", ") yields "(item)" instead of "(item,)" — update
typing_type_repr_value: after collecting parts from the PyTuple (using
value.try_to_ref::<PyTuple> and typing_type_repr for each item), detect when
tuple.len() == 1 and format the string with a trailing comma (e.g. "({},)"
style) otherwise use "({})" with parts.join(", "); keep using
vm.ctx.new_str(...) and the existing return path.

- Implement _ConstEvaluator type in _typing module with STRING
  format support via typing_type_repr (port of _Py_typing_type_repr)
- Add evaluate_bound, evaluate_constraints, evaluate_default
  pygetset properties to TypeVar, ParamSpec, TypeVarTuple
- Emit format validation in evaluator scopes
  (compile_type_param_bound_or_default, TypeAlias value scopes)
  so evaluators raise NotImplementedError for unsupported formats
- Add non-default-after-default SyntaxError in scan_type_params
- Fix ParamSpec default_value to use Mutex for proper caching
- Fix TypeVar constructor: evaluate_constraints set to None
  instead of constraints tuple for eager-constructed TypeVars
- Pass format=1 (FORMAT_VALUE) to all lazy evaluator calls
- Remove 6 expectedFailure markers from test_type_params
…sre module names

- Reject nonlocal binding for type parameters in symboltable
- Enable __class_getitem__ on memoryview, Template, Interpolation
- Fix __set_name__ error note to quote attribute name
- Set re.Match and re.Pattern module to "re" instead of "_sre"
- Migrate parking_lot::Mutex to PyMutex in typevar.rs
- Collapse nested if-let chains in typing_type_repr (clippy)
- Remove 5 expectedFailure markers from test files
Annotation scopes now inherit can_see_class_scope from parent scopes
(not just direct Class parents), allowing annotations in generic
methods inside classes to access class variables via __classdict__.

Remove 3 expectedFailure markers from test_type_params.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
crates/vm/src/stdlib/typevar.rs (2)

94-107: ⚠️ Potential issue | 🟠 Major

Python call while holding mutex lock in __constraints__.

Line 101 calls self.evaluate_constraints.call((1i32,), vm)? while the constraints mutex is held (locked at Line 96). If the callable transitively accesses __constraints__ on the same TypeVar, this will deadlock on a non-reentrant PyMutex. Consider cloning the evaluator reference and releasing the lock before making the call.

Proposed fix: release lock before Python call
 fn __constraints__(&self, vm: &VirtualMachine) -> PyResult {
-    let mut constraints = self.constraints.lock();
-    if !vm.is_none(&constraints) {
-        return Ok(constraints.clone());
+    {
+        let constraints = self.constraints.lock();
+        if !vm.is_none(&constraints) {
+            return Ok(constraints.clone());
+        }
     }
-    let r = if !vm.is_none(&self.evaluate_constraints) {
-        *constraints = self.evaluate_constraints.call((1i32,), vm)?;
-        constraints.clone()
-    } else {
-        vm.ctx.empty_tuple.clone().into()
-    };
-    Ok(r)
+    if !vm.is_none(&self.evaluate_constraints) {
+        let value = self.evaluate_constraints.call((1i32,), vm)?;
+        *self.constraints.lock() = value.clone();
+        Ok(value)
+    } else {
+        Ok(vm.ctx.empty_tuple.clone().into())
+    }
 }

109-122: ⚠️ Potential issue | 🟠 Major

Same issue: Python call while holding bound mutex lock.

Line 116 calls self.evaluate_bound.call((1i32,), vm)? while bound is locked. Apply the same drop-before-call pattern.

Proposed fix
 fn __bound__(&self, vm: &VirtualMachine) -> PyResult {
-    let mut bound = self.bound.lock();
-    if !vm.is_none(&bound) {
-        return Ok(bound.clone());
+    {
+        let bound = self.bound.lock();
+        if !vm.is_none(&bound) {
+            return Ok(bound.clone());
+        }
     }
-    let r = if !vm.is_none(&self.evaluate_bound) {
-        *bound = self.evaluate_bound.call((1i32,), vm)?;
-        bound.clone()
-    } else {
-        vm.ctx.none()
-    };
-    Ok(r)
+    if !vm.is_none(&self.evaluate_bound) {
+        let value = self.evaluate_bound.call((1i32,), vm)?;
+        *self.bound.lock() = value.clone();
+        Ok(value)
+    } else {
+        Ok(vm.ctx.none())
+    }
 }
crates/codegen/src/symboltable.rs (1)

952-987: ⚠️ Potential issue | 🟠 Major

"format" should be registered as a symbol in the annotation scope for consistency.

The annotation scope adds "format" to varnames (line 969) but never calls register_name() to add it to the symbols map. This is inconsistent with TypeAlias value scopes (line 1508) and type-parameter bound/default scopes (line 2015), which both use register_name("format", SymbolUsage::Parameter, ...) to register "format" as a parameter symbol.

While the current code works because emit_format_validation() uses LoadFast(0) to access format by position, the inconsistency means future code that inspects symbol metadata or scope information will not find "format" in the annotation scope's symbols map.

Call register_name("format", SymbolUsage::Parameter, TextRange::default()) after pushing the annotation table to the stack (after line 975), following the same pattern used elsewhere:

Proposed fix
         self.tables.push(*annotation_table);
         // Save parent's varnames and seed with existing annotation varnames (e.g., "format")
         self.varnames_stack
             .push(core::mem::take(&mut self.current_varnames));
         self.current_varnames = self.tables.last().unwrap().varnames.clone();
+        // Register "format" as a parameter symbol (varnames was already seeded above)
+        self.register_name("format", SymbolUsage::Parameter, TextRange::default())?;
 
         if can_see_class_scope && !self.future_annotations {

@github-actions
Copy link
Contributor

📦 Library Dependencies

The following Lib/ modules were modified. Here are their dependencies:

[x] lib: cpython/Lib/re
[x] lib: cpython/Lib/sre_compile.py
[x] lib: cpython/Lib/sre_constants.py
[x] lib: cpython/Lib/sre_parse.py
[ ] test: cpython/Lib/test/test_re.py (TODO: 18)

dependencies:

  • re

dependent tests: (58 tests)

  • re: test_android test_ast test_asyncio test_binascii test_builtin test_bytes test_ctypes test_dict test_dis test_docxmlrpc test_dtrace test_email test_faulthandler test_filecmp test_fileinput test_format test_fstring test_functools test_future_stmt test_genericalias test_glob test_http_cookiejar test_httplib test_httpservers test_imaplib test_importlib test_ipaddress test_logging test_mailbox test_mmap test_optparse test_pprint test_re test_regrtest test_runpy test_site test_smtplib test_socket test_ssl test_strftime test_strtod test_symtable test_syntax test_sysconfig test_tempfile test_tools test_traceback test_typing test_unittest test_venv test_webbrowser test_winapi test_wsgiref test_xmlrpc test_zipfile test_zipimport test_zoneinfo test_zstd

[x] lib: cpython/Lib/typing.py
[ ] test: cpython/Lib/test/test_typing.py (TODO: 9)
[x] test: cpython/Lib/test/test_type_aliases.py
[x] test: cpython/Lib/test/test_type_annotations.py (TODO: 3)
[x] test: cpython/Lib/test/test_type_params.py (TODO: 12)
[x] test: cpython/Lib/test/test_genericalias.py (TODO: 2)

dependencies:

  • typing

dependent tests: (12 tests)

  • typing: test_annotationlib test_builtin test_enum test_fractions test_functools test_genericalias test_grammar test_isinstance test_type_aliases test_type_params test_types test_typing

Legend:

  • [+] path exists in CPython
  • [x] up-to-date, [ ] outdated

@youknowone youknowone merged commit 0d11154 into RustPython:main Feb 11, 2026
14 checks passed
@youknowone youknowone deleted the typing branch February 11, 2026 09:33
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.

1 participant