From 351121c5c590c9731aed49b982dab7c58e994ffb Mon Sep 17 00:00:00 2001 From: Shunsuke Shibayama <45118249+mtshiba@users.noreply.github.com> Date: Tue, 5 Aug 2025 12:32:08 +0900 Subject: [PATCH] [ty] fix incorrect lazy scope narrowing (#19744) ## Summary This is a follow-up to #19321. Narrowing constraints introduced in a class scope were not applied even when they can be applied in lazy nested scopes. This PR fixes so that they are now applied. Conversely, there were cases where narrowing constraints were being applied in places where they should not, so it is also fixed. ## Test Plan Some TODOs in `narrow/conditionals/nested.md` are now work correctly. --- .../resources/mdtest/narrow/conditionals/nested.md | 6 +++--- crates/ty_python_semantic/src/semantic_index/builder.rs | 4 +++- crates/ty_python_semantic/src/semantic_index/scope.rs | 4 ++++ crates/ty_python_semantic/src/semantic_index/symbol.rs | 3 ++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md b/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md index 77307c5110..74c1d42a49 100644 --- a/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md +++ b/crates/ty_python_semantic/resources/mdtest/narrow/conditionals/nested.md @@ -363,11 +363,12 @@ def f(x: str | Literal[1] | None): x = None def _(): + # No narrowing is performed on unresolved references. # error: [unresolved-reference] if x is not None: def _(): if x != 1: - reveal_type(x) # revealed: Never + reveal_type(x) # revealed: None x = None def f(const: str | Literal[1] | None): @@ -375,8 +376,7 @@ def f(const: str | Literal[1] | None): if const is not None: def _(): if const != 1: - # TODO: should be `str` - reveal_type(const) # revealed: str | None + reveal_type(const) # revealed: str class D: if const != 1: diff --git a/crates/ty_python_semantic/src/semantic_index/builder.rs b/crates/ty_python_semantic/src/semantic_index/builder.rs index 9783236902..c545d9868f 100644 --- a/crates/ty_python_semantic/src/semantic_index/builder.rs +++ b/crates/ty_python_semantic/src/semantic_index/builder.rs @@ -356,7 +356,9 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { for nested_symbol in self.place_tables[popped_scope_id].symbols() { // For the same reason, symbols declared as nonlocal or global are not recorded. // Also, if the enclosing scope allows its members to be modified from elsewhere, the snapshot will not be recorded. - if self.scopes[enclosing_scope_id].visibility().is_public() { + // (In the case of class scopes, class variables can be modified from elsewhere, but this has no effect in nested scopes, + // as class variables are not visible to them) + if self.scopes[enclosing_scope_id].kind().is_module() { continue; } diff --git a/crates/ty_python_semantic/src/semantic_index/scope.rs b/crates/ty_python_semantic/src/semantic_index/scope.rs index 89581d9194..29c262ef4c 100644 --- a/crates/ty_python_semantic/src/semantic_index/scope.rs +++ b/crates/ty_python_semantic/src/semantic_index/scope.rs @@ -251,6 +251,10 @@ impl ScopeKind { matches!(self, ScopeKind::Class) } + pub(crate) const fn is_module(self) -> bool { + matches!(self, ScopeKind::Module) + } + pub(crate) const fn is_type_parameter(self) -> bool { matches!(self, ScopeKind::Annotation | ScopeKind::TypeAlias) } diff --git a/crates/ty_python_semantic/src/semantic_index/symbol.rs b/crates/ty_python_semantic/src/semantic_index/symbol.rs index a8564ea950..7bf0939bbb 100644 --- a/crates/ty_python_semantic/src/semantic_index/symbol.rs +++ b/crates/ty_python_semantic/src/semantic_index/symbol.rs @@ -36,6 +36,7 @@ bitflags! { const IS_DECLARED = 1 << 2; const MARKED_GLOBAL = 1 << 3; const MARKED_NONLOCAL = 1 << 4; + /// true if the symbol is assigned more than once, or if it is assigned even though it is already in use const IS_REASSIGNED = 1 << 5; } } @@ -92,7 +93,7 @@ impl Symbol { } pub(super) fn mark_bound(&mut self) { - if self.is_bound() { + if self.is_bound() || self.is_used() { self.insert_flags(SymbolFlags::IS_REASSIGNED); }