From 3b4667ec320278d5a210b7d4a0318dfc6b71a495 Mon Sep 17 00:00:00 2001 From: Jack O'Connor Date: Fri, 11 Jul 2025 14:18:11 -0700 Subject: [PATCH] respect annotation-only declarations in `infer_place_load` --- .../resources/mdtest/scopes/nonlocal.md | 14 +++++++------- crates/ty_python_semantic/src/types/infer.rs | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/scopes/nonlocal.md b/crates/ty_python_semantic/resources/mdtest/scopes/nonlocal.md index 7080ab54c9..7edb116cc5 100644 --- a/crates/ty_python_semantic/resources/mdtest/scopes/nonlocal.md +++ b/crates/ty_python_semantic/resources/mdtest/scopes/nonlocal.md @@ -31,17 +31,17 @@ def f(): reveal_type(x) # revealed: Unknown | Literal[1] ``` -## Skips annotation-only assignment +## Reads respect annotation-only declarations ```py def f(): - x = 1 + x: int = 1 def g(): - # it's pretty weird to have an annotated assignment in a function where the - # name is otherwise not defined; maybe should be an error? - x: int + # TODO: This example should actually be an unbound variable error. However to avoid false + # positives, we'd need to analyze `nonlocal x` statements in other inner functions. + x: str def h(): - reveal_type(x) # revealed: Unknown | Literal[1] + reveal_type(x) # revealed: str ``` ## The `nonlocal` keyword @@ -229,7 +229,7 @@ def f(): nonlocal x # error: [invalid-syntax] "no binding for nonlocal `x` found" ``` -## `nonlocal` bindings respect declared types from the defining scope, even without a binding +## Assigning to a `nonlocal` respects the declared type from its defining scope, even without a binding in that scope ```py def f(): diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 6bc4d71758..1b94e27073 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -1618,8 +1618,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // here and just bail out of this loop. break; } - // We found the closest definition. Note that (unlike in `infer_place_load`) this - // does *not* need to be a binding. It could be just `x: int`. + // We found the closest definition. Note that (as in `infer_place_load`) this does + // *not* need to be a binding. It could be just a declaration, e.g. `x: int`. nonlocal_use_def_map = self.index.use_def_map(enclosing_scope_file_id); declarations = nonlocal_use_def_map.end_of_scope_declarations(enclosing_place_id); is_local = false; @@ -6079,7 +6079,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let Some(enclosing_place) = enclosing_place_table.place_by_expr(expr) else { continue; }; - if enclosing_place.is_bound() { + if enclosing_place.is_bound() || enclosing_place.is_declared() { // We can return early here, because the nearest function-like scope that // defines a name must be the only source for the nonlocal reference (at // runtime, it is the scope that creates the cell for our closure.) If the name