[red-knot] fix narrowing in nested scopes (#17630)

## Summary

This PR fixes #17595.

## Test Plan

New test cases are added to `mdtest/narrow/conditionals/nested.md`.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
Shunsuke Shibayama 2025-05-06 08:28:42 +09:00 committed by GitHub
parent a4c8e43c5f
commit fd76d70a31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 414 additions and 124 deletions

View file

@ -42,7 +42,7 @@ use crate::semantic_index::symbol::{
ScopedSymbolId, SymbolTableBuilder,
};
use crate::semantic_index::use_def::{
EagerBindingsKey, FlowSnapshot, ScopedEagerBindingsId, UseDefMapBuilder,
EagerSnapshotKey, FlowSnapshot, ScopedEagerSnapshotId, UseDefMapBuilder,
};
use crate::semantic_index::visibility_constraints::{
ScopedVisibilityConstraintId, VisibilityConstraintsBuilder,
@ -113,7 +113,7 @@ pub(super) struct SemanticIndexBuilder<'db> {
///
/// [generator functions]: https://docs.python.org/3/glossary.html#term-generator
generator_functions: FxHashSet<FileScopeId>,
eager_bindings: FxHashMap<EagerBindingsKey, ScopedEagerBindingsId>,
eager_snapshots: FxHashMap<EagerSnapshotKey, ScopedEagerSnapshotId>,
/// Errors collected by the `semantic_checker`.
semantic_syntax_errors: RefCell<Vec<SemanticSyntaxError>>,
}
@ -148,7 +148,7 @@ impl<'db> SemanticIndexBuilder<'db> {
imported_modules: FxHashSet::default(),
generator_functions: FxHashSet::default(),
eager_bindings: FxHashMap::default(),
eager_snapshots: FxHashMap::default(),
python_version: Program::get(db).python_version(db),
source_text: OnceCell::new(),
@ -253,13 +253,15 @@ impl<'db> SemanticIndexBuilder<'db> {
children_start..children_start,
reachability,
);
let is_class_scope = scope.kind().is_class();
self.try_node_context_stack_manager.enter_nested_scope();
let file_scope_id = self.scopes.push(scope);
self.symbol_tables.push(SymbolTableBuilder::default());
self.instance_attribute_tables
.push(SymbolTableBuilder::default());
self.use_def_maps.push(UseDefMapBuilder::default());
self.use_def_maps
.push(UseDefMapBuilder::new(is_class_scope));
let ast_id_scope = self.ast_ids.push(AstIdsBuilder::default());
let scope_id = ScopeId::new(self.db, self.file, file_scope_id, countme::Count::default());
@ -303,12 +305,6 @@ impl<'db> SemanticIndexBuilder<'db> {
let enclosing_scope_kind = self.scopes[enclosing_scope_id].kind();
let enclosing_symbol_table = &self.symbol_tables[enclosing_scope_id];
// Names bound in class scopes are never visible to nested scopes, so we never need to
// save eager scope bindings in a class scope.
if enclosing_scope_kind.is_class() {
continue;
}
for nested_symbol in self.symbol_tables[popped_scope_id].symbols() {
// Skip this symbol if this enclosing scope doesn't contain any bindings for it.
// Note that even if this symbol is bound in the popped scope,
@ -321,24 +317,26 @@ impl<'db> SemanticIndexBuilder<'db> {
continue;
};
let enclosing_symbol = enclosing_symbol_table.symbol(enclosing_symbol_id);
if !enclosing_symbol.is_bound() {
continue;
}
// Snapshot the bindings of this symbol that are visible at this point in this
// Snapshot the state of this symbol that are visible at this point in this
// enclosing scope.
let key = EagerBindingsKey {
let key = EagerSnapshotKey {
enclosing_scope: enclosing_scope_id,
enclosing_symbol: enclosing_symbol_id,
nested_scope: popped_scope_id,
};
let eager_bindings = self.use_def_maps[enclosing_scope_id]
.snapshot_eager_bindings(enclosing_symbol_id);
self.eager_bindings.insert(key, eager_bindings);
let eager_snapshot = self.use_def_maps[enclosing_scope_id].snapshot_eager_state(
enclosing_symbol_id,
enclosing_scope_kind,
enclosing_symbol.is_bound(),
);
self.eager_snapshots.insert(key, eager_snapshot);
}
// Lazy scopes are "sticky": once we see a lazy scope we stop doing lookups
// eagerly, even if we would encounter another eager enclosing scope later on.
// Also, narrowing constraints outside a lazy scope are not applicable.
// TODO: If the symbol has never been rewritten, they are applicable.
if !enclosing_scope_kind.is_eager() {
break;
}
@ -1085,8 +1083,8 @@ impl<'db> SemanticIndexBuilder<'db> {
self.scope_ids_by_scope.shrink_to_fit();
self.scopes_by_node.shrink_to_fit();
self.eager_bindings.shrink_to_fit();
self.generator_functions.shrink_to_fit();
self.eager_snapshots.shrink_to_fit();
SemanticIndex {
symbol_tables,
@ -1101,7 +1099,7 @@ impl<'db> SemanticIndexBuilder<'db> {
use_def_maps,
imported_modules: Arc::new(self.imported_modules),
has_future_annotations: self.has_future_annotations,
eager_bindings: self.eager_bindings,
eager_snapshots: self.eager_snapshots,
semantic_syntax_errors: self.semantic_syntax_errors.into_inner(),
generator_functions: self.generator_functions,
}