Update tests

This commit is contained in:
Glyphack 2025-10-16 20:12:02 +02:00
parent 8d53802bc7
commit 7b7c8425b8
3 changed files with 54 additions and 15 deletions

View File

@ -380,6 +380,11 @@ reveal_type(c_instance.y) # revealed: Unknown | int
#### Attributes defined in comprehensions #### Attributes defined in comprehensions
```toml
[environment]
python-version = "3.12"
```
```py ```py
class IntIterator: class IntIterator:
def __next__(self) -> int: def __next__(self) -> int:
@ -405,13 +410,6 @@ class C:
[[... for self.f in IntIterable()] for _ in IntIterable()] [[... for self.f in IntIterable()] for _ in IntIterable()]
[[... for self.g in IntIterable()] for self in [D()]] [[... 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: class D:
g: int g: int
@ -432,14 +430,56 @@ reveal_type(c_instance.f) # revealed: Unknown | int
# This one is correctly not resolved as an attribute: # This one is correctly not resolved as an attribute:
# error: [unresolved-attribute] # error: [unresolved-attribute]
reveal_type(c_instance.g) # revealed: Unknown 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 # This attribute is in the function f and is not reachable
# error: [unresolved-attribute] # 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 # TODO: Even though g method is called and is reachable we do not record this attribute assignment
# error: [unresolved-attribute] # 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 #### Conditionally declared / bound attributes

View File

@ -1167,7 +1167,6 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs):
" "
class C: class C:
def __init__(self): def __init__(self):
self.x = 1
[None for self.z in range(1)] [None for self.z in range(1)]
", ",
); );

View File

@ -185,10 +185,10 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
} }
/// Returns the scope ID of the method scope if the current scope /// 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 /// is a method inside a class body or current scope is in eagerly executed scope in a method.
/// scope is a function body outside of a class, or if the current scope is not a /// 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. /// function body.
fn is_method_of_class(&self) -> Option<FileScopeId> { fn is_method_or_eagerly_executed_in_method(&self) -> Option<FileScopeId> {
let mut scopes_rev = self let mut scopes_rev = self
.scope_stack .scope_stack
.iter() .iter()
@ -1678,7 +1678,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
self.visit_expr(&node.annotation); self.visit_expr(&node.annotation);
if let Some(value) = &node.value { if let Some(value) = &node.value {
self.visit_expr(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 // 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 // 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`, // 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::Attribute(ast::ExprAttribute { ctx, .. })
| ast::Expr::Subscript(ast::ExprSubscript { ctx, .. }) => { | ast::Expr::Subscript(ast::ExprSubscript { ctx, .. }) => {
if let Some(mut place_expr) = PlaceExpr::try_from_expr(expr) { 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 let PlaceExpr::Member(member) = &mut place_expr {
if member.is_instance_attribute_candidate() { if member.is_instance_attribute_candidate() {
// We specifically mark attribute assignments to the first parameter of a method, // We specifically mark attribute assignments to the first parameter of a method,