Skip to content

Conversation

@youknowone
Copy link
Member

@youknowone youknowone commented Feb 9, 2026

Summary by CodeRabbit

  • New Features
    • Lazy/eager TypeAlias with cached evaluation, improved subscripting, and a GenericAlias iterator.
  • Bug Fixes
    • Avoid deadlocks when repr-ing lists whose elements mutate the list.
    • Clearer error message for inaccessible free variables.
  • Improvements
    • More robust annotation and type-parameter scoping and conditional-annotation handling.
    • Revised annotations/annotate setter behavior.
    • TypeVar/ParamSpec now infer variance by default.
    • Preserve custom module getset descriptors and prevent setting attrs on immutable types.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Reworks compiler annotation and TypeParams scoping to emit closures for type-parameter values, updates symbol-table propagation for class scope and conditional annotations, converts TypeAlias to a lazy/eager compute model, generalizes GenericAlias.origin to PyObjectRef, and adjusts VM setters/getters and module handling across VM types.

Changes

Cohort / File(s) Summary
Compiler: annotation & TypeParams
crates/codegen/src/compile.rs
Rename annotation-scope entry to fixed __annotate__, change enter_annotation_scope signature, add outer/inner function-like TypeParams scopes, emit closures for type-param values, expand annotation traversal (Match/Try), and standardize loads via helpers.
Symbol table & class-scope propagation
crates/codegen/src/symboltable.rs
Introduce needs_class_symbols/can_see_class_scope; propagate/cloned class symbols into child/annotation/TypeParams scopes; register __classdict__ and __conditional_annotations__ usage; treat some enclosing-class bindings as GlobalImplicit.
TypeAlias lazy/eager model & typing surface
crates/vm/src/stdlib/typing.rs
Replace TypeAlias.value with compute_value + cached slot, add eager constructor, lazy __value__, __parameters__, __module__, __getitem__, __reduce__, mapping/iterable/hash impls, and richer pyclass metadata.
GenericAlias origin generalization
crates/vm/src/builtins/genericalias.rs, crates/vm/src/types/zoo.rs
Change origin: PyTypeReforigin: PyObjectRef; update constructors, repr, __origin__, __reduce__, call/hash paths; add PyGenericAliasIterator and register it in TypeZoo.
Annotations & VM setter semantics
crates/vm/src/builtins/function.rs, crates/vm/src/builtins/type.rs
__annotations__ setter switches to PySetterValue (Assign/Delete) with refined Assign/None/Delete semantics; conditional eviction of __annotations_cache__; clear __annotate__/__annotate_func__ adjusted.
List repr & frame messages
crates/vm/src/builtins/list.rs, crates/vm/src/frame.rs
Clone list elements under read-lock before per-element repr to avoid deadlocks; reword unbound free-variable error message; rename local valuecompute_value in TypeAlias handling.
TypeVar / ParamSpec defaults
crates/vm/src/stdlib/typevar.rs
Set infer_variance default to true for TypeVar and ParamSpec.
Preserve custom module getsets
crates/derive-impl/src/pymodule.rs, crates/vm/src/class.rs
Guard setting __module__ by checking for an existing PyGetSet descriptor; only set when absent.
AST mutability adjustments
crates/vm/src/stdlib/ast/pyast.rs, crates/vm/src/stdlib/ast/python.rs
Remove IMMUTABLETYPE flag during AST type init so node types can be mutated during initialization.
TypeZoo wiring
crates/vm/src/types/zoo.rs
Add and initialize generic_alias_iterator_type in TypeZoo.

Sequence Diagram(s)

sequenceDiagram
    participant Parser as Parser/AST
    participant Symtab as SymbolTable
    participant Codegen as Codegen
    participant VM as Runtime (VM types)

    Parser->>Symtab: register scopes (annotations, TypeParams)
    Symtab-->>Codegen: provide scope metadata (can_see_class_scope, cells)
    Codegen->>Symtab: push annotation / TypeParams scopes
    Codegen->>Codegen: compile type-param/value expressions → build closures
    Codegen->>Symtab: pop scopes / restore context
    Codegen->>VM: emit closures/references for TypeAlias/annotations
    VM->>VM: create TypeAlias (compute_value + cached_value) and GenericAlias (origin as PyObjectRef)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • arihant2math
  • coolreader18

Poem

🐰 I hop through scopes both near and far,

"annotate" tucked in like a star.
Type params curl up safe inside a closure nest,
Lazy aliases wait until they're truly pressed.
Origins welcome every object — now we're blessed.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.95% 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 'Upgrade typing from 3.14.2 and more impl' is vague and uses non-descriptive terms like 'more impl' that obscure the actual changes being made. Replace with a more specific title that clearly summarizes the main change, such as 'Implement TypeAliasType lazy evaluation and annotation scope refactoring' or 'Upgrade typing system with GenericAlias iterator support'.
✅ 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

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
crates/vm/src/builtins/genericalias.rs (1)

507-533: Recursive subs_parameters for nested list/tuple args re-runs __typing_prepare_subst__.

The recursive call on line 516 passes through the full subs_parameters pipeline including Step 2 (__typing_prepare_subst__), which means preparation callbacks run again on the same parameters for each nested list/tuple. CPython's _Py_subs_parameters handles nested containers more directly without re-running preparation. In practice, __typing_prepare_subst__ is typically idempotent so this likely won't cause issues, but it's a subtle behavioral divergence worth noting.

crates/vm/src/stdlib/typing.rs (3)

364-373: Iterable imports typing module on every __iter__ call.

Each iter() call does vm.import("typing", 0) followed by an attribute lookup for Unpack. Since import caches modules in sys.modules, this is effectively a dict lookup, but it's still heavier than caching the reference. Given that iterating a TypeAliasType is rare (only for *Alias unpacking in type expressions), this is acceptable.


376-397: Same typing import pattern in unpack_typevartuples.

This helper also imports typing and looks up Unpack on each call. Same observation as Iterable::iter — acceptable for the low frequency of __parameters__ access, but if this becomes a hot path, consider caching the Unpack reference.


212-247: check_type_params error on missing __default__ may be too broad.

Line 225 maps any failure to get __default__ to a "Expected a type param" error. If __default__ lookup fails for a reason other than the object not being a type param (e.g., a descriptor that raises RuntimeError), the original error context is lost. Consider checking specifically for AttributeError or passing through unexpected errors.

Sketch: distinguish AttributeError from other failures
-                let dflt = param.get_attr("__default__", vm).map_err(|_| {
+                let dflt = param.get_attr("__default__", vm).map_err(|e| {
+                    if e.fast_isinstance(vm.ctx.exceptions.attribute_error) {
                     vm.new_type_error(format!(
                         "Expected a type param, got {}",
                         param
                             .repr(vm)
                             .map(|s| s.to_string())
                             .unwrap_or_else(|_| "?".to_owned())
                     ))
+                    } else {
+                        e
+                    }
                 })?;

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.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 9, 2026

Code has been automatically formatted

The code in this PR has been formatted using:

  • cargo fmt --all
    Please pull the latest changes before pushing again:
git pull origin typing

@youknowone youknowone force-pushed the typing branch 3 times, most recently from ee8cb19 to 18b7d5c Compare February 9, 2026 08:13
@youknowone youknowone marked this pull request as ready for review February 9, 2026 12:06
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: 4

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/builtins/type.rs (1)

1003-1051: ⚠️ Potential issue | 🟠 Major

Assign(None) and Delete are conflated, causing cls.__annotations__ = None to raise AttributeError on fresh classes.

Lines 1015–1018 collapse both Assign(None) (Python cls.__annotations__ = None) and Delete (Python del cls.__annotations__) to a single value = None. The subsequent deletion logic (lines 1024–1046) then raises AttributeError if neither __annotations__ nor __annotations_cache__ exists.

When Python None is passed, TryFromObject converts it to Rust None (lines 97–105 of crates/vm/src/convert/try_from.rs), yielding Assign(None). The current code incorrectly treats this the same as an explicit Delete, failing if the attribute doesn't already exist.

Per Python semantics, cls.__annotations__ = None should succeed silently, while del cls.__annotations__ should error only if the attribute is missing. Separate the match arms to handle these three cases distinctly:

Proposed fix
-        let value = match value {
-            crate::function::PySetterValue::Assign(v) => v,
-            crate::function::PySetterValue::Delete => None,
-        };
-
         let mut attrs = self.attributes.write();
-        // conditional update based on __annotations__ presence
         let has_annotations = attrs.contains_key(identifier!(vm, __annotations__));
 
-        if has_annotations {
-            // If __annotations__ is in dict, update it
-            if let Some(value) = value {
-                attrs.insert(identifier!(vm, __annotations__), value);
-            } else if attrs
-                .swap_remove(identifier!(vm, __annotations__))
-                .is_none()
-            {
-                return Err(vm.new_attribute_error("__annotations__".to_owned()));
-            }
-            // Also clear __annotations_cache__
-            attrs.swap_remove(identifier!(vm, __annotations_cache__));
-        } else {
-            // Otherwise update only __annotations_cache__
-            if let Some(value) = value {
-                attrs.insert(identifier!(vm, __annotations_cache__), value);
-            } else if attrs
-                .swap_remove(identifier!(vm, __annotations_cache__))
-                .is_none()
-            {
-                return Err(vm.new_attribute_error("__annotations__".to_owned()));
-            }
+        match value {
+            crate::function::PySetterValue::Assign(v) => {
+                if has_annotations {
+                    if let Some(v) = v {
+                        attrs.insert(identifier!(vm, __annotations__), v);
+                    } else {
+                        attrs.swap_remove(identifier!(vm, __annotations__));
+                    }
+                    attrs.swap_remove(identifier!(vm, __annotations_cache__));
+                } else {
+                    if let Some(v) = v {
+                        attrs.insert(identifier!(vm, __annotations_cache__), v);
+                    } else {
+                        attrs.swap_remove(identifier!(vm, __annotations_cache__));
+                    }
+                }
+            }
+            crate::function::PySetterValue::Delete => {
+                if has_annotations {
+                    if attrs.swap_remove(identifier!(vm, __annotations__)).is_none() {
+                        return Err(vm.new_attribute_error("__annotations__".to_owned()));
+                    }
+                    attrs.swap_remove(identifier!(vm, __annotations_cache__));
+                } else if attrs
+                    .swap_remove(identifier!(vm, __annotations_cache__))
+                    .is_none()
+                {
+                    return Err(vm.new_attribute_error("__annotations__".to_owned()));
+                }
+            }
         }
🤖 Fix all issues with AI agents
In `@crates/codegen/src/symboltable.rs`:
- Around line 1463-1472: Replace checks that use self.class_name.is_some() when
deciding to set table.can_see_class_scope with a direct parent-scope check like
self.tables.last().is_some_and(|t| t.typ == CompilerScope::Class); update the
three places named in the review (the type-alias-with-params block that sets
can_see_class_scope and registers "__classdict__" where SymbolUsage::Used and
TextRange::default() are used, the else branch for the
type-alias-without-params, and the scan_type_param_bound_or_default
implementation) to use this pattern (see enter_type_param_block for the
canonical example) so only immediate class children flip can_see_class_scope
instead of any nested function within a class.
- Around line 1456-1495: The TypeAlias handling duplicates the same inner-scope
logic; extract that into a helper (e.g., scan_type_alias_value) and replace both
branches with a call to it: the helper should call enter_scope("TypeAlias",
CompilerScope::TypeParams, self.line_index_start(value.range())), set
tables.last_mut().can_see_class_scope = true when self.class_name.is_some(),
call register_name("__classdict__", SymbolUsage::Used, TextRange::default()) as
before, invoke scan_expression(value, ExpressionContext::Load), then
leave_scope() and return Ok(()); update the original code paths to call this
helper instead of repeating
enter_scope/register_name/scan_expression/leave_scope.

In `@crates/vm/src/stdlib/typing.rs`:
- Around line 175-209: In py_new for TypeAliasType, add validation to reject
duplicate positional+keyword arguments and unexpected kwargs: check args.kwargs
keys only allow "name", "value", "type_params" and if any other key present
return vm.new_type_error with an unexpected-keyword message; for each branch
(args.args.len() == 1 or 2 and the empty-args case) detect if a parameter was
supplied both positionally and via kwargs (e.g., when args.args.len() >= 1 and
args.kwargs.contains_key("name") or when args.args.len() == 2 and
args.kwargs.contains_key("value") or "name") and return vm.new_type_error
mimicking CPython’s "TypeAliasType() got multiple values for argument
'name'"/"'value'". Ensure these checks run before you assign name/value so
duplicates are rejected and unexpected keywords are not silently ignored.
- Around line 89-98: TypeAliasType currently declares the Hashable protocol
implementation which adds a custom __hash__ differing from CPython; remove
Hashable from the pyclass attribute so TypeAliasType only implements
Constructor, Representable, AsMapping, and AsNumber to match CPython's
identity-based hashing. Edit the #[pyclass(with(...))] on TypeAliasType to drop
Hashable and ensure no other code defines a custom __hash__ for TypeAliasType
(inspect any impls or trait blocks named Hashable or methods called __hash__
associated with TypeAliasType and delete them).
🧹 Nitpick comments (3)
crates/codegen/src/compile.rs (1)

1072-1076: Please confirm fmt/clippy + clean bytecode build were run.

Bytecode generation paths changed here, so please ensure cargo fmt, cargo clippy --all-targets --all-features, and the clean build step (rm -r target/debug/build/rustpython-* && find . | grep -E "\.pyc$" | xargs rm -r) were executed before finalizing.

As per coding guidelines: Follow the default rustfmt code style by running cargo fmt to format Rust code; Always run clippy to lint Rust code (cargo clippy) before completing tasks and fix any warnings or lints that are introduced by your changes; When modifying bytecode instructions, perform a clean build by running: rm -r target/debug/build/rustpython-* && find . | grep -E "\.pyc$" | xargs rm -r.

crates/vm/src/stdlib/typing.rs (2)

110-118: new_eager stores non-callable value as compute_value — latent risk if cache is ever cleared.

In new_eager, compute_value is set to value.clone(), which is the evaluated result (not a callable). If cached_value were ever None after construction (e.g., due to a future refactor), __value__ would attempt to call a non-callable, producing a confusing TypeError. Currently safe since cached_value is always Some after new_eager, but this is fragile.

Consider storing a sentinel or documenting the invariant:

Suggestion
         /// Create with an eagerly evaluated value (used by constructor)
         pub fn new_eager(name: PyStrRef, type_params: PyTupleRef, value: PyObjectRef) -> Self {
             Self {
                 name,
                 type_params,
-                compute_value: value.clone(),
+                compute_value: value.clone(), // N.B. not callable — cached_value must always be Some for eager aliases
                 cached_value: crate::common::lock::PyMutex::new(Some(value)),
             }
         }

272-277: Module attribute fix is reasonable but fragile.

If TypeAliasType is ever not exported as a pyattr (or renamed), the get_attr("TypeAliasType", vm) lookup silently fails and __module__ stays as "_typing". The let _ = on set_attr also silently swallows failures. This is acceptable as a workaround, but a comment explaining why this is needed (pyattr sets __module__ from the containing #[pymodule(name = "_typing")]) would help future maintainers.

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: 3

🤖 Fix all issues with AI agents
In `@crates/vm/src/stdlib/ast/pyast.rs`:
- Around line 79-86: The custom NodeExprConstant::extend_class_with_fields
implementation forgot to clear the IMMUTABLETYPE flag, making ast.Constant
immutable; update NodeExprConstant::extend_class_with_fields to mirror the
macro-based extend_class logic by unsafely obtaining a mutable pointer to
class.slots.flags (as *mut crate::types::PyTypeFlags) and calling
.remove(crate::types::PyTypeFlags::IMMUTABLETYPE) on it during type
initialization so the AST node becomes mutable like the others.

In `@crates/vm/src/stdlib/typing.rs`:
- Around line 113-127: The bug is that new_eager stores the actual value in
compute_value, so evaluate_value treats callable values as evaluator functions;
fix by making compute_value always be a zero-argument callable that returns the
eager value (i.e., wrap value in a closure) and keep cached_value set to
Some(value) as before; update new_eager (and the same eager-constructor variant
at the other location) so compute_value is a function/closure that ignores
incoming args and returns the stored value, ensuring evaluate_value will call
the wrapper rather than misinterpreting a callable value as the evaluator.
- Around line 307-309: The error message hardcodes "not int" when downcast of
name fails; change the map_err closure on
name.downcast::<crate::builtins::PyStr>() to inspect the actual object's type
and include that in the vm.new_type_error message (e.g., use the Err value
passed into the closure to get its runtime type/name via the object's class or
type-name accessor and format "typealias() argument 'name' must be str, not
{actual_type}"). Keep using vm.new_type_error and the same call site so the only
change is replacing the |_| closure with one that computes the real type name.
🧹 Nitpick comments (5)
crates/codegen/src/symboltable.rs (1)

399-413: Dead !is_class branch in needs_class_symbols computation.

The !is_class disjunct (lines 405-407) can never influence class_symbols_clone because line 409 gates cloning on is_class && needs_class_symbols. When !is_class, class_symbols_clone is always None regardless of needs_class_symbols, and child scopes receive class_entry directly (line 425). The extra condition adds noise without effect.

♻️ Proposed simplification
-        let needs_class_symbols = (is_class
-            && (sub_tables.iter().any(|st| st.can_see_class_scope)
-                || annotation_block
-                    .as_ref()
-                    .is_some_and(|b| b.can_see_class_scope)))
-            || (!is_class
-                && class_entry.is_some()
-                && sub_tables.iter().any(|st| st.can_see_class_scope));
-
-        let class_symbols_clone = if is_class && needs_class_symbols {
+        let needs_class_symbols = is_class
+            && (sub_tables.iter().any(|st| st.can_see_class_scope)
+                || annotation_block
+                    .as_ref()
+                    .is_some_and(|b| b.can_see_class_scope));
+
+        let class_symbols_clone = if needs_class_symbols {
             Some(symbols.clone())
         } else {
             None
         };
crates/vm/src/stdlib/typing.rs (4)

367-377: vm.import("typing", 0) on every iter() call is potentially expensive.

This performs a module import lookup each time a TypeAliasType is iterated (for Unpack semantics). The same pattern appears in unpack_typevartuples. While this follows other patterns in the codebase and the import is cached after first load, it's worth noting.


214-246: check_type_params return value is discarded by the caller.

Line 316 calls Self::check_type_params(&tp, vm)?; but ignores the Option<PyTupleRef> return value. The function is only used for its validation side-effect. Consider changing the return type to PyResult<()> to reflect this.

Suggested simplification
         fn check_type_params(
             type_params: &PyTupleRef,
             vm: &VirtualMachine,
-        ) -> PyResult<Option<PyTupleRef>> {
+        ) -> PyResult<()> {
             if type_params.is_empty() {
-                return Ok(None);
+                return Ok(());
             }
             // ... validation logic unchanged ...
-            Ok(Some(type_params.clone()))
+            Ok(())
         }

134-143: Avoid double-computation in __value__ via non-atomic check-then-set.

The lock is released after clone() on line 136, then re-acquired on line 141. Two concurrent threads could both observe None, both call compute_value, and both store results, causing wasteful redundant computation. Since compute_value is a Python callable, the double-computation is safe but inefficient.

Use a double-check-lock pattern to eliminate the race:

Suggested pattern
         fn __value__(&self, vm: &VirtualMachine) -> PyResult {
-            let cached = self.cached_value.lock().clone();
-            if let Some(value) = cached {
-                return Ok(value);
-            }
-            let value = self.compute_value.call((), vm)?;
-            *self.cached_value.lock() = Some(value.clone());
-            Ok(value)
+            {
+                let cached = self.cached_value.lock();
+                if let Some(value) = cached.as_ref() {
+                    return Ok(value.clone());
+                }
+            }
+            let value = self.compute_value.call((), vm)?;
+            let mut cached = self.cached_value.lock();
+            if let Some(existing) = cached.as_ref() {
+                Ok(existing.clone())
+            } else {
+                *cached = Some(value.clone());
+                Ok(value)
+            }
         }

This pattern computes compute_value outside the lock (safe for Python re-entry), then verifies another thread hasn't already cached the result before storing.


322-326: Use the established caller() pattern for consistency with TypeVar.

TypeAliasType retrieves the module by accessing func_obj.get_attr("__module__", vm), but TypeVar in the same codebase uses the caller() helper which accesses frame.globals.get_item("__name__", vm). A comment in typevar.rs explicitly notes that "RustPython's Frame architecture is different" from CPython and deliberately uses globals instead of func_obj. TypeAliasType should follow this same pattern—either by calling set_module_from_caller() after construction (like TypeVar does in slot_new) or by using the caller() function directly to retrieve the module name, ensuring consistency with RustPython's architectural approach.

@youknowone youknowone force-pushed the typing branch 2 times, most recently from 24bcfe6 to a14141c Compare February 10, 2026 16:12
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: 2

🤖 Fix all issues with AI agents
In `@crates/vm/src/stdlib/typing.rs`:
- Around line 93-95: evaluate_value confuses "was created eagerly" with "was
lazily evaluated" because it tests cached_value.is_some() and then wraps
compute_value in a ConstEvaluator, causing callers to receive the evaluator
function instead of the evaluated result; to fix, add an explicit boolean flag
(e.g., is_eager or has_compute_fn) to the lazy-alias struct to record whether
the alias was constructed eagerly vs lazily, update all places that construct
aliases (including where you currently create a ConstEvaluator) to set that flag
appropriately, change evaluate_value to check that new flag instead of
cached_value.is_some() and only wrap/replace compute_value with a ConstEvaluator
when the flag indicates an eager evaluator, and update the __value__ access /
cached_value-setting path to mark the alias as lazily-evaluated (leaving the
eager flag false) so evaluate_value returns the actual evaluated object rather
than the evaluator function; also apply the same change to the other relevant
branch(s) around the existing ConstEvaluator construction.
- Around line 326-332: TypeAliasType currently derives the module from
func_obj.__module__ (via f.func_obj) which can be reassigned or missing; change
it to read the caller frame's globals['__name__'] like the TypeVar
implementation does. In the block that obtains module before calling
Self::new_eager(name, ...), replace the func_obj-based lookup with a
globals-based lookup using vm.current_frame() and
f.globals().get_item("__name__") (mirror the caller() logic in typevar.rs), and
pass that value into TypeAliasType::new_eager so module resolution matches
TypeVar/ParamSpec behavior. Ensure you handle absent frames or missing __name__
by falling back to None as before.
🧹 Nitpick comments (2)
crates/vm/src/builtins/genericalias.rs (2)

150-168: List repr: borrow_vec() per-element access may panic on concurrent mutation.

The comment says "Use indexed access so list mutation during repr causes IndexError," but list.borrow_vec() acquires a borrow guard. If repr_item triggers a __repr__ that mutates this same list, the second borrow_vec() call will panic (borrow conflict) rather than returning an IndexError. Consider either borrowing the whole slice once upfront (clone elements) or documenting this difference from CPython's behavior.

Possible safer alternative: clone elements upfront
         if obj.class().is(vm.ctx.types.list_type) {
             let list = obj.downcast_ref::<crate::builtins::PyList>().unwrap();
-            let len = list.borrow_vec().len();
-            let mut parts = Vec::with_capacity(len);
-            // Use indexed access so list mutation during repr causes IndexError
-            for i in 0..len {
-                let item =
-                    list.borrow_vec().get(i).cloned().ok_or_else(|| {
-                        vm.new_index_error("list index out of range".to_owned())
-                    })?;
-                parts.push(repr_item(item, vm)?);
-            }
+            let items: Vec<PyObjectRef> = list.borrow_vec().to_vec();
+            let mut parts = Vec::with_capacity(items.len());
+            for item in items {
+                parts.push(repr_item(item, vm)?);
+            }
             Ok(format!("[{}]", parts.join(", ")))

670-721: PyGenericAliasIterator lacks traverse — potential GC concern.

The iterator holds a PyMutex<Option<PyObjectRef>> which references a PyGenericAlias. If the alias participates in a reference cycle, the GC won't be able to trace through this iterator. Consider adding traverse = "manual" or traverse to the #[pyclass] attribute, similar to how other iterators in the codebase handle this.

youknowone and others added 3 commits February 11, 2026 01:44
- TypeAliasType: lazy value evaluation via closures, __module__,
  __parameters__, __iter__, evaluate_value, check_type_params,
  IMMUTABLETYPE flag, Hashable/AsMapping/Iterable traits
- TypeAliasType constructor: positional-or-keyword arg validation,
  duplicate/unexpected kwarg rejection
- type.__annotations__ setter: distinguish None assignment from deletion
- Annotation scope: name as __annotate__, format as positional-only,
  __conditional_annotations__ uses Cell for both load and store
- Compiler: proper TypeParams/TypeAlias scope with closures,
  find_ann covers match/try-except handlers
- symboltable: deduplicate TypeAlias value scope code
- GenericAlias repr: handle list args, avoid deadlock in repr_arg
  by cloning items before calling repr
- AST types: remove IMMUTABLETYPE (heap types, mutable)
- pymodule macro: preserve existing __module__ getset descriptors
- TypeAliasType: lazy value evaluation via closures, __module__,
  __parameters__, __iter__, evaluate_value, check_type_params,
  IMMUTABLETYPE flag, Hashable/AsMapping/Iterable traits
- TypeAliasType constructor: positional-or-keyword arg validation,
  duplicate/unexpected kwarg rejection
- type.__annotations__ setter: distinguish None assignment from deletion
- Annotation scope: name as __annotate__, format as positional-only,
  __conditional_annotations__ uses Cell for both load and store
- Compiler: proper TypeParams/TypeAlias scope with closures,
  find_ann covers match/try-except handlers
- symboltable: deduplicate TypeAlias value scope code
- GenericAlias: implement gaiterobject (generic_alias_iterator),
  starred equality comparison, starred pickle via iterator reduce,
  split attr_exceptions/attr_blocked for correct __dir__,
  make_parameters/subs_parameters handle list/tuple args recursively,
  repr_arg indexed access for mutation safety
- AST types: remove IMMUTABLETYPE (heap types, mutable)
- pymodule macro: preserve existing __module__ getset descriptors
@github-actions
Copy link
Contributor

📦 Library Dependencies

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

[x] lib: cpython/Lib/dataclasses.py
[x] test: cpython/Lib/test/test_dataclasses (TODO: 11)

dependencies:

  • dataclasses

dependent tests: (6 tests)

  • dataclasses: test__colorize test_genericalias test_patma test_pprint test_regrtest test_zoneinfo

[x] lib: cpython/Lib/typing.py
[ ] test: cpython/Lib/test/test_typing.py (TODO: 11)
[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: 22)
[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

[x] lib: cpython/Lib/reprlib.py
[x] test: cpython/Lib/test/test_reprlib.py (TODO: 2)

dependencies:

  • reprlib

dependent tests: (163 tests)

  • reprlib: test_reprlib
    • collections: test_annotationlib test_array test_asyncio test_bisect test_builtin test_c_locale_coercion test_call test_collections test_configparser test_contains test_csv test_ctypes test_defaultdict test_deque test_dict test_dictviews test_enum test_exception_group test_file test_fileinput test_fileio test_functools test_genericalias test_hash test_httpservers test_inspect test_io test_ipaddress test_iter test_iterlen test_json test_ordered_dict test_pathlib test_patma test_pickle test_plistlib test_pprint test_random test_set test_shelve test_sqlite3 test_statistics test_string test_struct test_traceback test_types test_typing test_unittest test_urllib test_userdict test_userlist test_userstring test_weakref test_weakset test_with
      • concurrent.futures._base: test_concurrent_futures
      • configparser: test_logging
      • dbm.dumb: test_dbm_dumb
      • dbm.sqlite3: test_dbm_sqlite3
      • difflib: test_difflib test_sys_settrace
      • dis: test__opcode test_ast test_code test_compile test_compiler_assemble test_dis test_dtrace test_fstring test_opcache test_peepholer test_positional_only_arg
      • http.client: test_docxmlrpc test_hashlib test_ssl test_ucn test_unicodedata test_urllib2 test_wsgiref test_xmlrpc
      • importlib.metadata: test_importlib test_zoneinfo
      • inspect: test_abc test_argparse test_asyncgen test_coroutines test_decimal test_generators test_grammar test_ntpath test_operator test_posixpath test_signal test_type_annotations test_yield_from test_zipimport
      • multiprocessing: test_asyncio test_concurrent_futures test_fcntl test_multiprocessing_main_handling
      • pkgutil: test_pkgutil
      • platform: test__locale test__osx_support test_android test_asyncio test_baseexception test_cmath test_ctypes test_math test_mimetypes test_os test_platform test_posix test_regrtest test_socket test_sysconfig test_time test_winreg
      • pprint: test_htmlparser test_sys_setprofile
      • queue: test_asyncio test_concurrent_futures test_dummy_thread test_sched
      • selectors: test_selectors test_subprocess
      • shutil: test_bz2 test_compileall test_ctypes test_filecmp test_glob test_importlib test_largefile test_py_compile test_shutil test_site test_string_literals test_support test_tempfile test_venv
      • tokenize: test_linecache test_tabnanny test_tokenize test_unparse
      • traceback: test_asyncio test_code_module test_contextlib test_contextlib_async test_importlib test_listcomps test_pyexpat test_setcomps test_threadedtempfile test_threading test_unittest
      • urllib.parse: test_sqlite3 test_urllib2_localnet test_urllibnet test_urlparse
      • urllib.robotparser: test_robotparser
      • wave: test_wave
    • dataclasses: test__colorize

Legend:

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

@youknowone youknowone merged commit 6bfdfb1 into RustPython:main Feb 11, 2026
24 of 25 checks passed
@youknowone youknowone deleted the typing branch February 11, 2026 00:39
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