From 7b7c8425b83c2336c53044af9af7b2208550332c Mon Sep 17 00:00:00 2001 From: Glyphack Date: Thu, 16 Oct 2025 20:12:02 +0200 Subject: [PATCH] Update tests --- .../resources/mdtest/attributes.md | 58 ++++++++++++++++--- .../ty_python_semantic/src/semantic_index.rs | 1 - .../src/semantic_index/builder.rs | 10 ++-- 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/attributes.md b/crates/ty_python_semantic/resources/mdtest/attributes.md index 8ef83ad6ff..4ee4a3aea7 100644 --- a/crates/ty_python_semantic/resources/mdtest/attributes.md +++ b/crates/ty_python_semantic/resources/mdtest/attributes.md @@ -380,6 +380,11 @@ reveal_type(c_instance.y) # revealed: Unknown | int #### Attributes defined in comprehensions +```toml +[environment] +python-version = "3.12" +``` + ```py class IntIterator: def __next__(self) -> int: @@ -405,13 +410,6 @@ class C: [[... for self.f in IntIterable()] for _ in IntIterable()] [[... for self.g in IntIterable()] for self in [D()]] - def f(): - [... for self.h in IntIterable()] - - def g(): - [... for self.i in IntIterable()] - g() - class D: g: int @@ -432,14 +430,56 @@ reveal_type(c_instance.f) # revealed: Unknown | int # This one is correctly not resolved as an attribute: # error: [unresolved-attribute] reveal_type(c_instance.g) # revealed: Unknown +``` + +It does not matter how much the comprehension is nested. + +Similarly attributes defined by the comprehension in a generic method are recognized. + +```py +class C: + def f[T](self): + [... for self.a in [1]] + [[... for self.b in [1]] for _ in [1]] + +c_instance = C() + +reveal_type(c_instance.a) # revealed: Unknown | int +reveal_type(c_instance.b) # revealed: Unknown | int +``` + +If the comprehension is inside another scope like function then that attribute is not inferred. + +```py +class C: + def __init__(self): + def f(): + [... for self.a in IntIterable()] + + def g(): + [... for self.b in IntIterable()] + g() + +c_instance = C() # This attribute is in the function f and is not reachable # error: [unresolved-attribute] -reveal_type(c_instance.h) # revealed: Unknown +reveal_type(c_instance.a) # revealed: Unknown # TODO: Even though g method is called and is reachable we do not record this attribute assignment # error: [unresolved-attribute] -reveal_type(c_instance.i) # revealed: Unknown +reveal_type(c_instance.b) # revealed: Unknown +``` + +If the comprehension is nested in any other eager scope it still can assign attributes. + +```py +class C: + def __init__(self): + class D: + [[... for self.a in IntIterable()] for _ in IntIterable()] + +reveal_type(C().a) # revealed: Unknown | int ``` #### Conditionally declared / bound attributes diff --git a/crates/ty_python_semantic/src/semantic_index.rs b/crates/ty_python_semantic/src/semantic_index.rs index 5be8d6e92f..21dd6d6870 100644 --- a/crates/ty_python_semantic/src/semantic_index.rs +++ b/crates/ty_python_semantic/src/semantic_index.rs @@ -1167,7 +1167,6 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): " class C: def __init__(self): - self.x = 1 [None for self.z in range(1)] ", ); diff --git a/crates/ty_python_semantic/src/semantic_index/builder.rs b/crates/ty_python_semantic/src/semantic_index/builder.rs index e8f0efb217..9087b57149 100644 --- a/crates/ty_python_semantic/src/semantic_index/builder.rs +++ b/crates/ty_python_semantic/src/semantic_index/builder.rs @@ -185,10 +185,10 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { } /// Returns the scope ID of the method scope if the current scope - /// is a method inside a class body. Returns `None` otherwise, e.g. if the current - /// scope is a function body outside of a class, or if the current scope is not a + /// is a method inside a class body or current scope is in eagerly executed scope in a method. + /// Returns `None` otherwise, e.g. if the current scope is a function body outside of a class, or if the current scope is not a /// function body. - fn is_method_of_class(&self) -> Option { + fn is_method_or_eagerly_executed_in_method(&self) -> Option { let mut scopes_rev = self .scope_stack .iter() @@ -1678,7 +1678,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { self.visit_expr(&node.annotation); if let Some(value) = &node.value { self.visit_expr(value); - if self.is_method_of_class().is_some() { + if self.is_method_or_eagerly_executed_in_method().is_some() { // Record the right-hand side of the assignment as a standalone expression // if we're inside a method. This allows type inference to infer the type // of the value for annotated assignments like `self.CONSTANT: Final = 1`, @@ -2350,7 +2350,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { | ast::Expr::Attribute(ast::ExprAttribute { ctx, .. }) | ast::Expr::Subscript(ast::ExprSubscript { ctx, .. }) => { if let Some(mut place_expr) = PlaceExpr::try_from_expr(expr) { - if let Some(method_scope_id) = self.is_method_of_class() { + if let Some(method_scope_id) = self.is_method_or_eagerly_executed_in_method() { if let PlaceExpr::Member(member) = &mut place_expr { if member.is_instance_attribute_candidate() { // We specifically mark attribute assignments to the first parameter of a method,