diff --git a/Cargo.lock b/Cargo.lock index e409e30866..122efc3c0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4305,7 +4305,6 @@ dependencies = [ "strum_macros", "tempfile", "test-case", - "thin-vec", "thiserror 2.0.12", "tracing", "ty_python_semantic", diff --git a/Cargo.toml b/Cargo.toml index 089b201fdc..da9383c64f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -166,7 +166,6 @@ strum_macros = { version = "0.27.0" } syn = { version = "2.0.55" } tempfile = { version = "3.9.0" } test-case = { version = "3.3.1" } -thin-vec = { version = "0.2.14" } thiserror = { version = "2.0.0" } tikv-jemallocator = { version = "0.6.0" } toml = { version = "0.9.0" } diff --git a/crates/ruff_python_ast/src/name.rs b/crates/ruff_python_ast/src/name.rs index 598257529b..ae87bbed6e 100644 --- a/crates/ruff_python_ast/src/name.rs +++ b/crates/ruff_python_ast/src/name.rs @@ -29,6 +29,10 @@ impl Name { Self(compact_str::CompactString::const_new(name)) } + pub fn shrink_to_fit(&mut self) { + self.0.shrink_to_fit(); + } + pub fn as_str(&self) -> &str { self.0.as_str() } diff --git a/crates/ty_python_semantic/Cargo.toml b/crates/ty_python_semantic/Cargo.toml index d4961a8a8a..370b0a4154 100644 --- a/crates/ty_python_semantic/Cargo.toml +++ b/crates/ty_python_semantic/Cargo.toml @@ -36,7 +36,6 @@ indexmap = { workspace = true } itertools = { workspace = true } ordermap = { workspace = true } salsa = { workspace = true, features = ["compact_str"] } -thin-vec = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } rustc-hash = { workspace = true } diff --git a/crates/ty_python_semantic/src/place.rs b/crates/ty_python_semantic/src/place.rs index 2a35d2e37d..14dadba5bf 100644 --- a/crates/ty_python_semantic/src/place.rs +++ b/crates/ty_python_semantic/src/place.rs @@ -3,7 +3,8 @@ use ruff_db::files::File; use crate::dunder_all::dunder_all_names; use crate::module_resolver::file_to_module; use crate::semantic_index::definition::{Definition, DefinitionState}; -use crate::semantic_index::place::{PlaceExpr, ScopeId, ScopedPlaceId}; +use crate::semantic_index::place::{PlaceExprRef, ScopedPlaceId}; +use crate::semantic_index::scope::ScopeId; use crate::semantic_index::{ BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, place_table, }; @@ -214,13 +215,13 @@ pub(crate) fn symbol<'db>( pub(crate) fn place<'db>( db: &'db dyn Db, scope: ScopeId<'db>, - expr: &PlaceExpr, + member: PlaceExprRef, considered_definitions: ConsideredDefinitions, ) -> PlaceAndQualifiers<'db> { place_impl( db, scope, - expr, + member, RequiresExplicitReExport::No, considered_definitions, ) @@ -234,12 +235,12 @@ pub(crate) fn class_symbol<'db>( name: &str, ) -> PlaceAndQualifiers<'db> { place_table(db, scope) - .place_id_by_name(name) - .map(|place| { + .symbol_id(name) + .map(|symbol_id| { let place_and_quals = place_by_id( db, scope, - place, + symbol_id.into(), RequiresExplicitReExport::No, ConsideredDefinitions::EndOfScope, ); @@ -256,7 +257,7 @@ pub(crate) fn class_symbol<'db>( { // Otherwise, we need to check if the symbol has bindings let use_def = use_def_map(db, scope); - let bindings = use_def.end_of_scope_bindings(place); + let bindings = use_def.end_of_scope_symbol_bindings(symbol_id); let inferred = place_from_bindings_impl(db, bindings, RequiresExplicitReExport::No); // TODO: we should not need to calculate inferred type second time. This is a temporary @@ -763,10 +764,12 @@ fn place_by_id<'db>( // `TYPE_CHECKING` is a special variable that should only be assigned `False` // at runtime, but is always considered `True` in type checking. // See mdtest/known_constants.md#user-defined-type_checking for details. - let is_considered_non_modifiable = place_table(db, scope) - .place_expr(place_id) - .expr - .is_name_and(|name| matches!(name, "__slots__" | "TYPE_CHECKING")); + let is_considered_non_modifiable = place_id.as_symbol().is_some_and(|symbol_id| { + matches!( + place_table(db, scope).symbol(symbol_id).name().as_str(), + "__slots__" | "TYPE_CHECKING" + ) + }); if scope.file(db).is_stub(db) || scope.scope(db).visibility().is_private() { // We generally trust module-level undeclared places in stubs and do not union @@ -832,12 +835,12 @@ fn symbol_impl<'db>( } place_table(db, scope) - .place_id_by_name(name) + .symbol_id(name) .map(|symbol| { place_by_id( db, scope, - symbol, + symbol.into(), requires_explicit_reexport, considered_definitions, ) @@ -845,18 +848,17 @@ fn symbol_impl<'db>( .unwrap_or_default() } -/// Implementation of [`place`]. fn place_impl<'db>( db: &'db dyn Db, scope: ScopeId<'db>, - expr: &PlaceExpr, + place: PlaceExprRef, requires_explicit_reexport: RequiresExplicitReExport, considered_definitions: ConsideredDefinitions, ) -> PlaceAndQualifiers<'db> { - let _span = tracing::trace_span!("place", ?expr).entered(); + let _span = tracing::trace_span!("place_impl", ?place).entered(); place_table(db, scope) - .place_id_by_expr(expr) + .place_id(place) .map(|place| { place_by_id( db, @@ -1265,7 +1267,8 @@ fn is_reexported(db: &dyn Db, definition: Definition<'_>) -> bool { return false; }; let table = place_table(db, definition.scope(db)); - let symbol_name = table.place_expr(definition.place(db)).expect_name(); + let symbol_id = definition.place(db).expect_symbol(); + let symbol_name = table.symbol(symbol_id).name(); all_names.contains(symbol_name) } @@ -1274,19 +1277,19 @@ mod implicit_globals { use crate::db::Db; use crate::place::PlaceAndQualifiers; - use crate::semantic_index::place::PlaceExpr; - use crate::semantic_index::{self, place_table, use_def_map}; + use crate::semantic_index::symbol::Symbol; + use crate::semantic_index::{place_table, use_def_map}; use crate::types::{KnownClass, Type}; use super::{Place, PlaceFromDeclarationsResult, place_from_declarations}; pub(crate) fn module_type_implicit_global_declaration<'db>( db: &'db dyn Db, - expr: &PlaceExpr, + name: &str, ) -> PlaceFromDeclarationsResult<'db> { if !module_type_symbols(db) .iter() - .any(|module_type_member| Some(module_type_member) == expr.as_name()) + .any(|module_type_member| module_type_member == name) { return Ok(Place::Unbound.into()); } @@ -1296,12 +1299,12 @@ mod implicit_globals { }; let module_type_scope = module_type_class.body_scope(db); let place_table = place_table(db, module_type_scope); - let Some(place_id) = place_table.place_id_by_expr(expr) else { + let Some(symbol_id) = place_table.symbol_id(name) else { return Ok(Place::Unbound.into()); }; place_from_declarations( db, - use_def_map(db, module_type_scope).end_of_scope_declarations(place_id), + use_def_map(db, module_type_scope).end_of_scope_symbol_declarations(symbol_id), ) } @@ -1380,11 +1383,14 @@ mod implicit_globals { let module_type_symbol_table = place_table(db, module_type_scope); module_type_symbol_table - .places() - .filter(|place| place.is_declared() && place.is_name()) - .map(semantic_index::place::PlaceExprWithFlags::expect_name) + .symbols() + .filter(|symbol| symbol.is_declared()) + .map(Symbol::name) .filter(|symbol_name| { - !matches!(&***symbol_name, "__dict__" | "__getattr__" | "__init__") + !matches!( + symbol_name.as_str(), + "__dict__" | "__getattr__" | "__init__" + ) }) .cloned() .collect() diff --git a/crates/ty_python_semantic/src/semantic_index.rs b/crates/ty_python_semantic/src/semantic_index.rs index c64bb64718..ef86c22b3c 100644 --- a/crates/ty_python_semantic/src/semantic_index.rs +++ b/crates/ty_python_semantic/src/semantic_index.rs @@ -20,10 +20,12 @@ use crate::semantic_index::builder::SemanticIndexBuilder; use crate::semantic_index::definition::{Definition, DefinitionNodeKey, Definitions}; use crate::semantic_index::expression::Expression; use crate::semantic_index::narrowing_constraints::ScopedNarrowingConstraint; -use crate::semantic_index::place::{ - FileScopeId, NodeWithScopeKey, NodeWithScopeRef, PlaceExpr, PlaceTable, Scope, ScopeId, - ScopeKind, ScopedPlaceId, +use crate::semantic_index::place::{PlaceExprRef, PlaceTable}; +pub use crate::semantic_index::scope::FileScopeId; +use crate::semantic_index::scope::{ + NodeWithScopeKey, NodeWithScopeRef, Scope, ScopeId, ScopeKind, ScopeLaziness, }; +use crate::semantic_index::symbol::ScopedSymbolId; use crate::semantic_index::use_def::{EnclosingSnapshotKey, ScopedEnclosingSnapshotId, UseDefMap}; use crate::semantic_model::HasTrackedScope; use crate::util::get_size::untracked_arc_size; @@ -32,11 +34,14 @@ pub mod ast_ids; mod builder; pub mod definition; pub mod expression; +pub(crate) mod member; pub(crate) mod narrowing_constraints; pub mod place; pub(crate) mod predicate; mod re_exports; mod reachability_constraints; +pub(crate) mod scope; +pub(crate) mod symbol; mod use_def; pub(crate) use self::use_def::{ @@ -44,8 +49,6 @@ pub(crate) use self::use_def::{ DeclarationWithConstraint, DeclarationsIterator, }; -type PlaceSet = hashbrown::HashTable; - /// Returns the semantic index for `file`. /// /// Prefer using [`symbol_table`] when working with symbols from a single scope. @@ -117,10 +120,10 @@ pub(crate) fn attribute_assignments<'db, 's>( attribute_scopes(db, class_body_scope).filter_map(|function_scope_id| { let place_table = index.place_table(function_scope_id); - let place = place_table.place_id_by_instance_attribute_name(name)?; + let member = place_table.member_id_by_instance_attribute_name(name)?; let use_def = &index.use_def_maps[function_scope_id]; Some(( - use_def.inner.all_reachable_bindings(place), + use_def.inner.all_reachable_member_bindings(member), function_scope_id, )) }) @@ -141,10 +144,10 @@ pub(crate) fn attribute_declarations<'db, 's>( attribute_scopes(db, class_body_scope).filter_map(|function_scope_id| { let place_table = index.place_table(function_scope_id); - let place = place_table.place_id_by_instance_attribute_name(name)?; + let member = place_table.member_id_by_instance_attribute_name(name)?; let use_def = &index.use_def_maps[function_scope_id]; Some(( - use_def.inner.all_reachable_declarations(place), + use_def.inner.all_reachable_member_declarations(member), function_scope_id, )) }) @@ -188,38 +191,6 @@ pub(crate) fn global_scope(db: &dyn Db, file: File) -> ScopeId<'_> { FileScopeId::global().to_scope_id(db, file) } -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, get_size2::GetSize)] -pub(crate) enum ScopeVisibility { - /// The scope is private (e.g. function, type alias, comprehension scope). - Private, - /// The scope is public (e.g. module, class scope). - Public, -} - -impl ScopeVisibility { - pub(crate) const fn is_public(self) -> bool { - matches!(self, ScopeVisibility::Public) - } - - pub(crate) const fn is_private(self) -> bool { - matches!(self, ScopeVisibility::Private) - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, get_size2::GetSize)] -pub(crate) enum ScopeLaziness { - /// The scope is evaluated lazily (e.g. function, type alias scope). - Lazy, - /// The scope is evaluated eagerly (e.g. module, class, comprehension scope). - Eager, -} - -impl ScopeLaziness { - pub(crate) const fn is_eager(self) -> bool { - matches!(self, ScopeLaziness::Eager) - } -} - pub(crate) enum EnclosingSnapshotResult<'map, 'db> { FoundConstraint(ScopedNarrowingConstraint), FoundBindings(BindingWithConstraintsIterator<'map, 'db>), @@ -337,22 +308,18 @@ impl<'db> SemanticIndex<'db> { pub(crate) fn symbol_is_global_in_scope( &self, - symbol: ScopedPlaceId, + symbol: ScopedSymbolId, scope: FileScopeId, ) -> bool { - self.place_table(scope) - .place_expr(symbol) - .is_marked_global() + self.place_table(scope).symbol(symbol).is_global() } pub(crate) fn symbol_is_nonlocal_in_scope( &self, - symbol: ScopedPlaceId, + symbol: ScopedSymbolId, scope: FileScopeId, ) -> bool { - self.place_table(scope) - .place_expr(symbol) - .is_marked_nonlocal() + self.place_table(scope).symbol(symbol).is_nonlocal() } /// Returns the id of the parent scope. @@ -523,7 +490,7 @@ impl<'db> SemanticIndex<'db> { pub(crate) fn enclosing_snapshot( &self, enclosing_scope: FileScopeId, - expr: &PlaceExpr, + expr: PlaceExprRef, nested_scope: FileScopeId, ) -> EnclosingSnapshotResult<'_, 'db> { for (ancestor_scope_id, ancestor_scope) in self.ancestor_scopes(nested_scope) { @@ -531,13 +498,13 @@ impl<'db> SemanticIndex<'db> { break; } if !ancestor_scope.is_eager() { - if expr.is_name() { + if let PlaceExprRef::Symbol(symbol) = expr { if let Some(place_id) = - self.place_tables[enclosing_scope].place_id_by_expr(expr) + self.place_tables[enclosing_scope].symbol_id(symbol.name()) { let key = EnclosingSnapshotKey { enclosing_scope, - enclosing_place: place_id, + enclosing_place: place_id.into(), nested_scope, nested_laziness: ScopeLaziness::Lazy, }; @@ -551,7 +518,7 @@ impl<'db> SemanticIndex<'db> { return EnclosingSnapshotResult::NoLongerInEagerContext; } } - let Some(place_id) = self.place_tables[enclosing_scope].place_id_by_expr(expr) else { + let Some(place_id) = self.place_tables[enclosing_scope].place_id(expr) else { return EnclosingSnapshotResult::NotFound; }; let key = EnclosingSnapshotKey { @@ -595,7 +562,7 @@ impl<'db> ArcUseDefMap<'db> { } } -pub struct AncestorsIter<'a> { +pub(crate) struct AncestorsIter<'a> { scopes: &'a IndexSlice, next_id: Option, } @@ -623,7 +590,7 @@ impl<'a> Iterator for AncestorsIter<'a> { impl FusedIterator for AncestorsIter<'_> {} -pub struct VisibleAncestorsIter<'a> { +pub(crate) struct VisibleAncestorsIter<'a> { inner: AncestorsIter<'a>, starting_scope_kind: ScopeKind, yielded_count: usize, @@ -670,7 +637,7 @@ impl<'a> Iterator for VisibleAncestorsIter<'a> { impl FusedIterator for VisibleAncestorsIter<'_> {} -pub struct DescendantsIter<'a> { +pub(crate) struct DescendantsIter<'a> { next_id: FileScopeId, descendants: std::slice::Iter<'a, Scope>, } @@ -707,7 +674,7 @@ impl FusedIterator for DescendantsIter<'_> {} impl ExactSizeIterator for DescendantsIter<'_> {} -pub struct ChildrenIter<'a> { +pub(crate) struct ChildrenIter<'a> { parent: FileScopeId, descendants: DescendantsIter<'a>, } @@ -777,13 +744,15 @@ mod tests { use crate::db::tests::{TestDb, TestDbBuilder}; use crate::semantic_index::ast_ids::{HasScopedUseId, ScopedUseId}; use crate::semantic_index::definition::{Definition, DefinitionKind}; - use crate::semantic_index::place::{FileScopeId, PlaceTable, Scope, ScopeKind, ScopedPlaceId}; + use crate::semantic_index::place::PlaceTable; + use crate::semantic_index::scope::{FileScopeId, Scope, ScopeKind}; + use crate::semantic_index::symbol::ScopedSymbolId; use crate::semantic_index::use_def::UseDefMap; use crate::semantic_index::{global_scope, place_table, semantic_index, use_def_map}; impl UseDefMap<'_> { - fn first_public_binding(&self, symbol: ScopedPlaceId) -> Option> { - self.end_of_scope_bindings(symbol) + fn first_public_binding(&self, symbol: ScopedSymbolId) -> Option> { + self.end_of_scope_symbol_bindings(symbol) .find_map(|constrained_binding| constrained_binding.binding.definition()) } @@ -813,8 +782,8 @@ mod tests { fn names(table: &PlaceTable) -> Vec { table - .places() - .filter_map(|expr| Some(expr.as_name()?.to_string())) + .symbols() + .map(|expr| expr.name().to_string()) .collect() } @@ -852,7 +821,7 @@ mod tests { let global_table = place_table(&db, scope); assert_eq!(names(global_table), vec!["foo"]); - let foo = global_table.place_id_by_name("foo").unwrap(); + let foo = global_table.symbol_id("foo").unwrap(); let use_def = use_def_map(&db, scope); let binding = use_def.first_public_binding(foo).unwrap(); @@ -884,18 +853,14 @@ mod tests { assert_eq!(names(global_table), vec!["foo"]); assert!( global_table - .place_by_name("foo") + .symbol_by_name("foo") .is_some_and(|symbol| { symbol.is_bound() && !symbol.is_used() }), "symbols that are defined get the defined flag" ); let use_def = use_def_map(&db, scope); let binding = use_def - .first_public_binding( - global_table - .place_id_by_name("foo") - .expect("symbol to exist"), - ) + .first_public_binding(global_table.symbol_id("foo").expect("symbol to exist")) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::ImportFrom(_))); } @@ -909,13 +874,13 @@ mod tests { assert_eq!(names(global_table), vec!["foo", "x"]); assert!( global_table - .place_by_name("foo") + .symbol_by_name("foo") .is_some_and(|symbol| { !symbol.is_bound() && symbol.is_used() }), "a symbol used but not bound in a scope should have only the used flag" ); let use_def = use_def_map(&db, scope); let binding = use_def - .first_public_binding(global_table.place_id_by_name("x").expect("symbol exists")) + .first_public_binding(global_table.symbol_id("x").expect("symbol exists")) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_))); } @@ -930,7 +895,7 @@ mod tests { let use_def = use_def_map(&db, scope); let binding = use_def - .first_public_binding(global_table.place_id_by_name("x").unwrap()) + .first_public_binding(global_table.symbol_id("x").unwrap()) .unwrap(); assert!(matches!( @@ -972,7 +937,7 @@ y = 2 let use_def = index.use_def_map(class_scope_id); let binding = use_def - .first_public_binding(class_table.place_id_by_name("x").expect("symbol exists")) + .first_public_binding(class_table.symbol_id("x").expect("symbol exists")) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_))); } @@ -1009,7 +974,7 @@ y = 2 let use_def = index.use_def_map(function_scope_id); let binding = use_def - .first_public_binding(function_table.place_id_by_name("x").expect("symbol exists")) + .first_public_binding(function_table.symbol_id("x").expect("symbol exists")) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_))); } @@ -1044,31 +1009,19 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): let use_def = index.use_def_map(function_scope_id); for name in ["a", "b", "c", "d"] { let binding = use_def - .first_public_binding( - function_table - .place_id_by_name(name) - .expect("symbol exists"), - ) + .first_public_binding(function_table.symbol_id(name).expect("symbol exists")) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::Parameter(_))); } let args_binding = use_def - .first_public_binding( - function_table - .place_id_by_name("args") - .expect("symbol exists"), - ) + .first_public_binding(function_table.symbol_id("args").expect("symbol exists")) .unwrap(); assert!(matches!( args_binding.kind(&db), DefinitionKind::VariadicPositionalParameter(_) )); let kwargs_binding = use_def - .first_public_binding( - function_table - .place_id_by_name("kwargs") - .expect("symbol exists"), - ) + .first_public_binding(function_table.symbol_id("kwargs").expect("symbol exists")) .unwrap(); assert!(matches!( kwargs_binding.kind(&db), @@ -1101,27 +1054,19 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): let use_def = index.use_def_map(lambda_scope_id); for name in ["a", "b", "c", "d"] { let binding = use_def - .first_public_binding(lambda_table.place_id_by_name(name).expect("symbol exists")) + .first_public_binding(lambda_table.symbol_id(name).expect("symbol exists")) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::Parameter(_))); } let args_binding = use_def - .first_public_binding( - lambda_table - .place_id_by_name("args") - .expect("symbol exists"), - ) + .first_public_binding(lambda_table.symbol_id("args").expect("symbol exists")) .unwrap(); assert!(matches!( args_binding.kind(&db), DefinitionKind::VariadicPositionalParameter(_) )); let kwargs_binding = use_def - .first_public_binding( - lambda_table - .place_id_by_name("kwargs") - .expect("symbol exists"), - ) + .first_public_binding(lambda_table.symbol_id("kwargs").expect("symbol exists")) .unwrap(); assert!(matches!( kwargs_binding.kind(&db), @@ -1169,7 +1114,7 @@ def f(a: str, /, b: str, c: int = 1, *args, d: int = 2, **kwargs): let binding = use_def .first_public_binding( comprehension_symbol_table - .place_id_by_name(name) + .symbol_id(name) .expect("symbol exists"), ) .unwrap(); @@ -1298,7 +1243,7 @@ with item1 as x, item2 as y: let use_def = index.use_def_map(FileScopeId::global()); for name in ["x", "y"] { let binding = use_def - .first_public_binding(global_table.place_id_by_name(name).expect("symbol exists")) + .first_public_binding(global_table.symbol_id(name).expect("symbol exists")) .expect("Expected with item definition for {name}"); assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_))); } @@ -1321,7 +1266,7 @@ with context() as (x, y): let use_def = index.use_def_map(FileScopeId::global()); for name in ["x", "y"] { let binding = use_def - .first_public_binding(global_table.place_id_by_name(name).expect("symbol exists")) + .first_public_binding(global_table.symbol_id(name).expect("symbol exists")) .expect("Expected with item definition for {name}"); assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_))); } @@ -1371,11 +1316,7 @@ def func(): let use_def = index.use_def_map(FileScopeId::global()); let binding = use_def - .first_public_binding( - global_table - .place_id_by_name("func") - .expect("symbol exists"), - ) + .first_public_binding(global_table.symbol_id("func").expect("symbol exists")) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::Function(_))); } @@ -1452,7 +1393,7 @@ class C[T]: assert_eq!(names(&ann_table), vec!["T"]); assert!( ann_table - .place_by_name("T") + .symbol_by_name("T") .is_some_and(|s| s.is_bound() && !s.is_used()), "type parameters are defined by the scope that introduces them" ); @@ -1600,7 +1541,7 @@ match subject: let global_scope_id = global_scope(&db, file); let global_table = place_table(&db, global_scope_id); - assert!(global_table.place_by_name("Foo").unwrap().is_used()); + assert!(global_table.symbol_by_name("Foo").unwrap().is_used()); assert_eq!( names(global_table), vec![ @@ -1624,7 +1565,7 @@ match subject: ("l", 1), ] { let binding = use_def - .first_public_binding(global_table.place_id_by_name(name).expect("symbol exists")) + .first_public_binding(global_table.symbol_id(name).expect("symbol exists")) .expect("Expected with item definition for {name}"); if let DefinitionKind::MatchPattern(pattern) = binding.kind(&db) { assert_eq!(pattern.index(), expected_index); @@ -1654,7 +1595,7 @@ match 1: let use_def = use_def_map(&db, global_scope_id); for (name, expected_index) in [("first", 0), ("second", 0)] { let binding = use_def - .first_public_binding(global_table.place_id_by_name(name).expect("symbol exists")) + .first_public_binding(global_table.symbol_id(name).expect("symbol exists")) .expect("Expected with item definition for {name}"); if let DefinitionKind::MatchPattern(pattern) = binding.kind(&db) { assert_eq!(pattern.index(), expected_index); @@ -1674,7 +1615,7 @@ match 1: let use_def = use_def_map(&db, scope); let binding = use_def - .first_public_binding(global_table.place_id_by_name("x").unwrap()) + .first_public_binding(global_table.symbol_id("x").unwrap()) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::For(_))); @@ -1690,10 +1631,10 @@ match 1: let use_def = use_def_map(&db, scope); let x_binding = use_def - .first_public_binding(global_table.place_id_by_name("x").unwrap()) + .first_public_binding(global_table.symbol_id("x").unwrap()) .unwrap(); let y_binding = use_def - .first_public_binding(global_table.place_id_by_name("y").unwrap()) + .first_public_binding(global_table.symbol_id("y").unwrap()) .unwrap(); assert!(matches!(x_binding.kind(&db), DefinitionKind::For(_))); @@ -1710,7 +1651,7 @@ match 1: let use_def = use_def_map(&db, scope); let binding = use_def - .first_public_binding(global_table.place_id_by_name("a").unwrap()) + .first_public_binding(global_table.symbol_id("a").unwrap()) .unwrap(); assert!(matches!(binding.kind(&db), DefinitionKind::For(_))); diff --git a/crates/ty_python_semantic/src/semantic_index/ast_ids.rs b/crates/ty_python_semantic/src/semantic_index/ast_ids.rs index 6d12d22916..cc2c65526e 100644 --- a/crates/ty_python_semantic/src/semantic_index/ast_ids.rs +++ b/crates/ty_python_semantic/src/semantic_index/ast_ids.rs @@ -6,7 +6,7 @@ use ruff_python_ast::ExprRef; use crate::Db; use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey; -use crate::semantic_index::place::ScopeId; +use crate::semantic_index::scope::ScopeId; use crate::semantic_index::semantic_index; /// AST ids for a single scope. @@ -40,7 +40,7 @@ fn ast_ids<'db>(db: &'db dyn Db, scope: ScopeId) -> &'db AstIds { semantic_index(db, scope.file(db)).ast_ids(scope.file_scope_id(db)) } -/// Uniquely identifies a use of a name in a [`crate::semantic_index::place::FileScopeId`]. +/// Uniquely identifies a use of a name in a [`crate::semantic_index::FileScopeId`]. #[newtype_index] #[derive(get_size2::GetSize)] pub struct ScopedUseId; diff --git a/crates/ty_python_semantic/src/semantic_index/builder.rs b/crates/ty_python_semantic/src/semantic_index/builder.rs index 2a938d5e88..0098db4c95 100644 --- a/crates/ty_python_semantic/src/semantic_index/builder.rs +++ b/crates/ty_python_semantic/src/semantic_index/builder.rs @@ -30,10 +30,7 @@ use crate::semantic_index::definition::{ StarImportDefinitionNodeRef, WithItemDefinitionNodeRef, }; use crate::semantic_index::expression::{Expression, ExpressionKind}; -use crate::semantic_index::place::{ - FileScopeId, NodeWithScopeKey, NodeWithScopeKind, NodeWithScopeRef, PlaceExpr, - PlaceExprWithFlags, PlaceTableBuilder, Scope, ScopeId, ScopeKind, ScopedPlaceId, -}; +use crate::semantic_index::place::{PlaceExpr, PlaceTableBuilder, ScopedPlaceId}; use crate::semantic_index::predicate::{ CallableAndCallExpr, ClassPatternKind, PatternPredicate, PatternPredicateKind, Predicate, PredicateNode, PredicateOrLiteral, ScopedPredicateId, StarImportPlaceholderPredicate, @@ -42,10 +39,15 @@ use crate::semantic_index::re_exports::exported_names; use crate::semantic_index::reachability_constraints::{ ReachabilityConstraintsBuilder, ScopedReachabilityConstraintId, }; +use crate::semantic_index::scope::{ + FileScopeId, NodeWithScopeKey, NodeWithScopeKind, NodeWithScopeRef, +}; +use crate::semantic_index::scope::{Scope, ScopeId, ScopeKind, ScopeLaziness}; +use crate::semantic_index::symbol::{ScopedSymbolId, Symbol}; use crate::semantic_index::use_def::{ EnclosingSnapshotKey, FlowSnapshot, ScopedEnclosingSnapshotId, UseDefMapBuilder, }; -use crate::semantic_index::{ArcUseDefMap, ExpressionsScopeMap, ScopeLaziness, SemanticIndex}; +use crate::semantic_index::{ArcUseDefMap, ExpressionsScopeMap, SemanticIndex}; use crate::semantic_model::HasTrackedScope; use crate::unpack::{Unpack, UnpackKind, UnpackPosition, UnpackValue}; use crate::{Db, Program}; @@ -295,18 +297,16 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { let enclosing_scope_kind = self.scopes[enclosing_scope_id].kind(); let enclosing_place_table = &self.place_tables[enclosing_scope_id]; - for nested_place in self.place_tables[popped_scope_id].places() { + for nested_place in self.place_tables[popped_scope_id].iter() { // Skip this place if this enclosing scope doesn't contain any bindings for it. // Note that even if this place is bound in the popped scope, // it may refer to the enclosing scope bindings // so we also need to snapshot the bindings of the enclosing scope. - let Some(enclosing_place_id) = - enclosing_place_table.place_id_by_expr(&nested_place.expr) - else { + let Some(enclosing_place_id) = enclosing_place_table.place_id(nested_place) else { continue; }; - let enclosing_place = enclosing_place_table.place_expr(enclosing_place_id); + let enclosing_place = enclosing_place_table.place(enclosing_place_id); // Snapshot the state of this place that are visible at this point in this // enclosing scope. @@ -332,11 +332,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { } } - fn bound_scope( - &self, - enclosing_scope: FileScopeId, - place_expr: &PlaceExpr, - ) -> Option { + fn bound_scope(&self, enclosing_scope: FileScopeId, symbol: &Symbol) -> Option { self.scope_stack .iter() .rev() @@ -344,12 +340,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { .find_map(|scope_info| { let scope_id = scope_info.file_scope_id; let place_table = &self.place_tables[scope_id]; - let place_id = place_table.place_id_by_expr(place_expr)?; - if place_table.place_expr(place_id).is_bound() { - Some(scope_id) - } else { - None - } + let place_id = place_table.symbol_id(symbol.name())?; + place_table.place(place_id).is_bound().then_some(scope_id) }) } @@ -360,13 +352,11 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { let enclosing_scope_kind = self.scopes[enclosing_scope_id].kind(); let enclosing_place_table = &self.place_tables[enclosing_scope_id]; - for nested_place in self.place_tables[popped_scope_id].places() { - // We don't record lazy snapshots of attributes or subscripts, because these are difficult to track as they modify. + // We don't record lazy snapshots of attributes or subscripts, because these are difficult to track as they modify. + 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 !nested_place.is_name() - || self.scopes[enclosing_scope_id].visibility().is_public() - { + if self.scopes[enclosing_scope_id].visibility().is_public() { continue; } @@ -374,17 +364,16 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { // Note that even if this place is bound in the popped scope, // it may refer to the enclosing scope bindings // so we also need to snapshot the bindings of the enclosing scope. - - let Some(enclosing_place_id) = - enclosing_place_table.place_id_by_expr(&nested_place.expr) + let Some(enclosed_symbol_id) = + enclosing_place_table.symbol_id(nested_symbol.name()) else { continue; }; - let enclosing_place = enclosing_place_table.place_expr(enclosing_place_id); + let enclosing_place = enclosing_place_table.symbol(enclosed_symbol_id); if !enclosing_place.is_bound() { // If the bound scope of a place can be modified from elsewhere, the snapshot will not be recorded. if self - .bound_scope(enclosing_scope_id, &nested_place.expr) + .bound_scope(enclosing_scope_id, nested_symbol) .is_none_or(|scope| self.scopes[scope].visibility().is_public()) { continue; @@ -395,14 +384,14 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { // enclosing scope (this may later be invalidated and swept away). let key = EnclosingSnapshotKey { enclosing_scope: enclosing_scope_id, - enclosing_place: enclosing_place_id, + enclosing_place: enclosed_symbol_id.into(), nested_scope: popped_scope_id, nested_laziness: ScopeLaziness::Lazy, }; let lazy_snapshot = self.use_def_maps[enclosing_scope_id].snapshot_outer_state( - enclosing_place_id, + enclosed_symbol_id.into(), enclosing_scope_kind, - enclosing_place, + enclosing_place.into(), ); self.enclosing_snapshots.insert(key, lazy_snapshot); } @@ -415,7 +404,10 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { let place_table = &self.place_tables[key.enclosing_scope]; key.nested_laziness.is_eager() || key.enclosing_scope != popped_scope_id - || !place_table.is_place_reassigned(key.enclosing_place) + || !key + .enclosing_place + .as_symbol() + .is_some_and(|symbol_id| place_table.symbol(symbol_id).is_reassigned()) }); } @@ -423,24 +415,27 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { self.enclosing_snapshots.retain(|key, _| { let place_table = &self.place_tables[key.enclosing_scope]; - let is_place_bound_and_nonlocal = || -> bool { - let place_expr = place_table.place_expr(key.enclosing_place); + let is_bound_and_non_local = || -> bool { + let ScopedPlaceId::Symbol(symbol_id) = key.enclosing_place else { + return false; + }; + + let symbol = place_table.symbol(symbol_id); self.scopes .iter_enumerated() .skip_while(|(scope_id, _)| *scope_id != key.enclosing_scope) .any(|(scope_id, _)| { let other_scope_place_table = &self.place_tables[scope_id]; - let Some(place_id) = - other_scope_place_table.place_id_by_expr(&place_expr.expr) + let Some(symbol_id) = other_scope_place_table.symbol_id(symbol.name()) else { return false; }; - let place = other_scope_place_table.place_expr(place_id); - place.is_marked_nonlocal() && place.is_bound() + let symbol = other_scope_place_table.symbol(symbol_id); + symbol.is_nonlocal() && symbol.is_bound() }) }; - key.nested_laziness.is_eager() || !is_place_bound_and_nonlocal() + key.nested_laziness.is_eager() || !is_bound_and_non_local() }); } @@ -515,17 +510,17 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { /// Add a symbol to the place table and the use-def map. /// Return the [`ScopedPlaceId`] that uniquely identifies the symbol in both. - fn add_symbol(&mut self, name: Name) -> ScopedPlaceId { - let (place_id, added) = self.current_place_table_mut().add_symbol(name); + fn add_symbol(&mut self, name: Name) -> ScopedSymbolId { + let (symbol_id, added) = self.current_place_table_mut().add_symbol(Symbol::new(name)); if added { - self.current_use_def_map_mut().add_place(place_id); + self.current_use_def_map_mut().add_place(symbol_id.into()); } - place_id + symbol_id } /// Add a place to the place table and the use-def map. /// Return the [`ScopedPlaceId`] that uniquely identifies the place in both. - fn add_place(&mut self, place_expr: PlaceExprWithFlags) -> ScopedPlaceId { + fn add_place(&mut self, place_expr: PlaceExpr) -> ScopedPlaceId { let (place_id, added) = self.current_place_table_mut().add_place(place_expr); if added { self.current_use_def_map_mut().add_place(place_id); @@ -533,16 +528,19 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { place_id } + #[track_caller] fn mark_place_bound(&mut self, id: ScopedPlaceId) { - self.current_place_table_mut().mark_place_bound(id); + self.current_place_table_mut().mark_bound(id); } + #[track_caller] fn mark_place_declared(&mut self, id: ScopedPlaceId) { - self.current_place_table_mut().mark_place_declared(id); + self.current_place_table_mut().mark_declared(id); } - fn mark_place_used(&mut self, id: ScopedPlaceId) { - self.current_place_table_mut().mark_place_used(id); + #[track_caller] + fn mark_symbol_used(&mut self, id: ScopedSymbolId) { + self.current_place_table_mut().symbol_mut(id).mark_used(); } fn add_entry_for_definition_key(&mut self, key: DefinitionNodeKey) -> &mut Definitions<'db> { @@ -572,23 +570,20 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { fn delete_associated_bindings(&mut self, place: ScopedPlaceId) { let scope = self.current_scope(); // Don't delete associated bindings if the scope is a class scope & place is a name (it's never visible to nested scopes) - if self.scopes[scope].kind() == ScopeKind::Class - && self.place_tables[scope].place_expr(place).is_name() - { + if self.scopes[scope].kind() == ScopeKind::Class && place.is_symbol() { return; } - for associated_place in self.place_tables[scope].associated_place_ids(place) { - let is_place_name = self.place_tables[scope] - .place_expr(associated_place) - .is_name(); - self.use_def_maps[scope].delete_binding(associated_place, is_place_name); + for associated_place in self.place_tables[scope] + .associated_place_ids(place) + .iter() + .copied() + { + self.use_def_maps[scope].delete_binding(associated_place.into()); } } fn delete_binding(&mut self, place: ScopedPlaceId) { - let is_place_name = self.current_place_table().place_expr(place).is_name(); - self.current_use_def_map_mut() - .delete_binding(place, is_place_name); + self.current_use_def_map_mut().delete_binding(place); } /// Push a new [`Definition`] onto the list of definitions @@ -637,16 +632,15 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { self.mark_place_declared(place); } - let is_place_name = self.current_place_table().place_expr(place).is_name(); let use_def = self.current_use_def_map_mut(); match category { DefinitionCategory::DeclarationAndBinding => { - use_def.record_declaration_and_binding(place, definition, is_place_name); + use_def.record_declaration_and_binding(place, definition); self.delete_associated_bindings(place); } DefinitionCategory::Declaration => use_def.record_declaration(place, definition), DefinitionCategory::Binding => { - use_def.record_binding(place, definition, is_place_name); + use_def.record_binding(place, definition); self.delete_associated_bindings(place); } } @@ -963,8 +957,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { // TODO create Definition for PEP 695 typevars // note that the "bound" on the typevar is a totally different thing than whether // or not a name is "bound" by a typevar declaration; the latter is always true. - self.mark_place_bound(symbol); - self.mark_place_declared(symbol); + self.mark_place_bound(symbol.into()); + self.mark_place_declared(symbol.into()); if let Some(bounds) = bound { self.visit_expr(bounds); } @@ -972,9 +966,9 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { self.visit_expr(default); } match type_param { - ast::TypeParam::TypeVar(node) => self.add_definition(symbol, node), - ast::TypeParam::ParamSpec(node) => self.add_definition(symbol, node), - ast::TypeParam::TypeVarTuple(node) => self.add_definition(symbol, node), + ast::TypeParam::TypeVar(node) => self.add_definition(symbol.into(), node), + ast::TypeParam::ParamSpec(node) => self.add_definition(symbol.into(), node), + ast::TypeParam::TypeVarTuple(node) => self.add_definition(symbol.into(), node), }; } } @@ -1061,20 +1055,23 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> { if let Some(vararg) = parameters.vararg.as_ref() { let symbol = self.add_symbol(vararg.name.id().clone()); self.add_definition( - symbol, + symbol.into(), DefinitionNodeRef::VariadicPositionalParameter(vararg), ); } if let Some(kwarg) = parameters.kwarg.as_ref() { let symbol = self.add_symbol(kwarg.name.id().clone()); - self.add_definition(symbol, DefinitionNodeRef::VariadicKeywordParameter(kwarg)); + self.add_definition( + symbol.into(), + DefinitionNodeRef::VariadicKeywordParameter(kwarg), + ); } } fn declare_parameter(&mut self, parameter: &'ast ast::ParameterWithDefault) { let symbol = self.add_symbol(parameter.name().id().clone()); - let definition = self.add_definition(symbol, parameter); + let definition = self.add_definition(symbol.into(), parameter); // Insert a mapping from the inner Parameter node to the same definition. This // ensures that calling `HasType::inferred_type` on the inner parameter returns @@ -1295,12 +1292,15 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { // used to collect all the overloaded definitions of a function. This needs to be // done on the `Identifier` node as opposed to `ExprName` because that's what the // AST uses. - self.mark_place_used(symbol); + self.mark_symbol_used(symbol); let use_id = self.current_ast_ids().record_use(name); - self.current_use_def_map_mut() - .record_use(symbol, use_id, NodeKey::from_node(name)); + self.current_use_def_map_mut().record_use( + symbol.into(), + use_id, + NodeKey::from_node(name), + ); - self.add_definition(symbol, function_def); + self.add_definition(symbol.into(), function_def); } ast::Stmt::ClassDef(class) => { for decorator in &class.decorator_list { @@ -1324,7 +1324,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { // In Python runtime semantics, a class is registered after its scope is evaluated. let symbol = self.add_symbol(class.name.id.clone()); - self.add_definition(symbol, class); + self.add_definition(symbol.into(), class); } ast::Stmt::TypeAlias(type_alias) => { let symbol = self.add_symbol( @@ -1334,7 +1334,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { .map(|name| name.id.clone()) .unwrap_or("".into()), ); - self.add_definition(symbol, type_alias); + self.add_definition(symbol.into(), type_alias); self.visit_expr(&type_alias.name); self.with_type_params( @@ -1366,7 +1366,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { let symbol = self.add_symbol(symbol_name); self.add_definition( - symbol, + symbol.into(), ImportDefinitionNodeRef { node, alias_index, @@ -1438,10 +1438,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { // For more details, see the doc-comment on `StarImportPlaceholderPredicate`. for export in exported_names(self.db, referenced_module) { let symbol_id = self.add_symbol(export.clone()); - let node_ref = StarImportDefinitionNodeRef { - node, - place_id: symbol_id, - }; + let node_ref = StarImportDefinitionNodeRef { node, symbol_id }; let star_import = StarImportPlaceholderPredicate::new( self.db, self.file, @@ -1451,8 +1448,9 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { let star_import_predicate = self.add_predicate(star_import.into()); - let pre_definition = - self.current_use_def_map().single_place_snapshot(symbol_id); + let pre_definition = self + .current_use_def_map() + .single_symbol_place_snapshot(symbol_id); let pre_definition_reachability = self.current_use_def_map().reachability; @@ -1469,7 +1467,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { ); self.current_use_def_map_mut().reachability = definition_reachability; - self.push_additional_definition(symbol_id, node_ref); + self.push_additional_definition(symbol_id.into(), node_ref); self.current_use_def_map_mut() .record_and_negate_star_import_reachability_constraint( @@ -1503,7 +1501,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { let symbol = self.add_symbol(symbol_name.clone()); self.add_definition( - symbol, + symbol.into(), ImportFromDefinitionNodeRef { node, alias_index, @@ -1580,9 +1578,9 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { if let ast::Expr::Name(name) = &*node.target { let symbol_id = self.add_symbol(name.id.clone()); - let symbol = self.current_place_table().place_expr(symbol_id); + let symbol = self.current_place_table().symbol(symbol_id); // Check whether the variable has been declared global. - if symbol.is_marked_global() { + if symbol.is_global() { self.report_semantic_error(SemanticSyntaxError { kind: SemanticSyntaxErrorKind::AnnotatedGlobal(name.id.as_str().into()), range: name.range, @@ -1590,7 +1588,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { }); } // Check whether the variable has been declared nonlocal. - if symbol.is_marked_nonlocal() { + if symbol.is_nonlocal() { self.report_semantic_error(SemanticSyntaxError { kind: SemanticSyntaxErrorKind::AnnotatedNonlocal( name.id.as_str().into(), @@ -2006,7 +2004,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { let symbol = self.add_symbol(symbol_name.id.clone()); self.add_definition( - symbol, + symbol.into(), DefinitionNodeRef::ExceptHandler(ExceptHandlerDefinitionNodeRef { handler: except_handler, is_star: *is_star, @@ -2020,7 +2018,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { self.visit_body(handler_body); // The caught exception is cleared at the end of the except clause if let Some(symbol) = symbol { - self.delete_binding(symbol); + self.delete_binding(symbol.into()); } // Each `except` block is mutually exclusive with all other `except` blocks. post_except_states.push(self.flow_snapshot()); @@ -2078,7 +2076,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { }) => { for name in names { let symbol_id = self.add_symbol(name.id.clone()); - let symbol = self.current_place_table().place_expr(symbol_id); + let symbol = self.current_place_table().symbol(symbol_id); // Check whether the variable has already been accessed in this scope. if symbol.is_bound() || symbol.is_declared() || symbol.is_used() { self.report_semantic_error(SemanticSyntaxError { @@ -2091,14 +2089,16 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { }); } // Check whether the variable has also been declared nonlocal. - if symbol.is_marked_nonlocal() { + if symbol.is_nonlocal() { self.report_semantic_error(SemanticSyntaxError { kind: SemanticSyntaxErrorKind::NonlocalAndGlobal(name.to_string()), range: name.range, python_version: self.python_version, }); } - self.current_place_table_mut().mark_place_global(symbol_id); + self.current_place_table_mut() + .symbol_mut(symbol_id) + .mark_global(); } walk_stmt(self, stmt); } @@ -2109,7 +2109,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { }) => { for name in names { let symbol_id = self.add_symbol(name.id.clone()); - let symbol = self.current_place_table().place_expr(symbol_id); + let symbol = self.current_place_table().symbol(symbol_id); // Check whether the variable has already been accessed in this scope. if symbol.is_bound() || symbol.is_declared() || symbol.is_used() { self.report_semantic_error(SemanticSyntaxError { @@ -2122,7 +2122,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { }); } // Check whether the variable has also been declared global. - if symbol.is_marked_global() { + if symbol.is_global() { self.report_semantic_error(SemanticSyntaxError { kind: SemanticSyntaxErrorKind::NonlocalAndGlobal(name.to_string()), range: name.range, @@ -2139,7 +2139,8 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { // x = 1 // ``` self.current_place_table_mut() - .mark_place_nonlocal(symbol_id); + .symbol_mut(symbol_id) + .mark_nonlocal(); } walk_stmt(self, stmt); } @@ -2151,11 +2152,8 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { // We will check the target expressions and then delete them. walk_stmt(self, stmt); for target in targets { - if let Ok(target) = PlaceExpr::try_from(target) { - let is_name = target.is_name(); - let place_id = self.add_place(PlaceExprWithFlags::new(target)); - let place_table = self.current_place_table_mut(); - if is_name { + if let Some(mut target) = PlaceExpr::try_from_expr(target) { + if let PlaceExpr::Symbol(symbol) = &mut target { // `del x` behaves like an assignment in that it forces all references // to `x` in the current scope (including *prior* references) to refer // to the current scope's binding (unless `x` is declared `global` or @@ -2169,9 +2167,11 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { // del x // foo() // ``` - place_table.mark_place_bound(place_id); + symbol.mark_bound(); + symbol.mark_used(); } - place_table.mark_place_used(place_id); + + let place_id = self.add_place(target); self.delete_binding(place_id); } } @@ -2238,19 +2238,20 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { ast::Expr::Name(ast::ExprName { ctx, .. }) | ast::Expr::Attribute(ast::ExprAttribute { ctx, .. }) | ast::Expr::Subscript(ast::ExprSubscript { ctx, .. }) => { - if let Ok(place_expr) = PlaceExpr::try_from(expr) { - let mut place_expr = PlaceExprWithFlags::new(place_expr); - if self.is_method_of_class().is_some() - && place_expr.is_instance_attribute_candidate() - { - // We specifically mark attribute assignments to the first parameter of a method, - // i.e. typically `self` or `cls`. - let accessed_object_refers_to_first_parameter = self - .current_first_parameter_name - .is_some_and(|fst| place_expr.expr.root_name() == fst); + if let Some(mut place_expr) = PlaceExpr::try_from_expr(expr) { + if self.is_method_of_class().is_some() { + 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, + // i.e. typically `self` or `cls`. + let accessed_object_refers_to_first_parameter = self + .current_first_parameter_name + .is_some_and(|first| member.symbol_name() == first); - if accessed_object_refers_to_first_parameter && place_expr.is_member() { - place_expr.mark_instance_attribute(); + if accessed_object_refers_to_first_parameter { + member.mark_instance_attribute(); + } + } } } @@ -2267,7 +2268,9 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { let place_id = self.add_place(place_expr); if is_use { - self.mark_place_used(place_id); + if let ScopedPlaceId::Symbol(symbol_id) = place_id { + self.mark_symbol_used(symbol_id); + } let use_id = self.current_ast_ids().record_use(expr); self.current_use_def_map_mut() .record_use(place_id, use_id, node_key); @@ -2561,7 +2564,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { let symbol = self.add_symbol(name.id().clone()); let state = self.current_match_case.as_ref().unwrap(); self.add_definition( - symbol, + symbol.into(), MatchPatternDefinitionNodeRef { pattern: state.pattern, identifier: name, @@ -2582,7 +2585,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> { let symbol = self.add_symbol(name.id().clone()); let state = self.current_match_case.as_ref().unwrap(); self.add_definition( - symbol, + symbol.into(), MatchPatternDefinitionNodeRef { pattern: state.pattern, identifier: name, diff --git a/crates/ty_python_semantic/src/semantic_index/definition.rs b/crates/ty_python_semantic/src/semantic_index/definition.rs index 6064c2246c..6d1ce38299 100644 --- a/crates/ty_python_semantic/src/semantic_index/definition.rs +++ b/crates/ty_python_semantic/src/semantic_index/definition.rs @@ -8,7 +8,9 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Db; use crate::ast_node_ref::AstNodeRef; use crate::node_key::NodeKey; -use crate::semantic_index::place::{FileScopeId, ScopeId, ScopedPlaceId}; +use crate::semantic_index::place::ScopedPlaceId; +use crate::semantic_index::scope::{FileScopeId, ScopeId}; +use crate::semantic_index::symbol::ScopedSymbolId; use crate::unpack::{Unpack, UnpackPosition}; /// A definition of a place. @@ -305,7 +307,7 @@ pub(crate) struct ImportDefinitionNodeRef<'ast> { #[derive(Copy, Clone, Debug)] pub(crate) struct StarImportDefinitionNodeRef<'ast> { pub(crate) node: &'ast ast::StmtImportFrom, - pub(crate) place_id: ScopedPlaceId, + pub(crate) symbol_id: ScopedSymbolId, } #[derive(Copy, Clone, Debug)] @@ -395,10 +397,10 @@ impl<'db> DefinitionNodeRef<'_, 'db> { is_reexported, }), DefinitionNodeRef::ImportStar(star_import) => { - let StarImportDefinitionNodeRef { node, place_id } = star_import; + let StarImportDefinitionNodeRef { node, symbol_id } = star_import; DefinitionKind::StarImport(StarImportDefinitionKind { node: AstNodeRef::new(parsed, node), - place_id, + symbol_id, }) } DefinitionNodeRef::Function(function) => { @@ -522,7 +524,7 @@ impl<'db> DefinitionNodeRef<'_, 'db> { // INVARIANT: for an invalid-syntax statement such as `from foo import *, bar, *`, // we only create a `StarImportDefinitionKind` for the *first* `*` alias in the names list. - Self::ImportStar(StarImportDefinitionNodeRef { node, place_id: _ }) => node + Self::ImportStar(StarImportDefinitionNodeRef { node, symbol_id: _ }) => node .names .iter() .find(|alias| &alias.name == "*") @@ -822,7 +824,7 @@ impl<'db> From)>> for TargetKind<'db> { #[derive(Clone, Debug)] pub struct StarImportDefinitionKind { node: AstNodeRef, - place_id: ScopedPlaceId, + symbol_id: ScopedSymbolId, } impl StarImportDefinitionKind { @@ -844,8 +846,8 @@ impl StarImportDefinitionKind { ) } - pub(crate) fn place_id(&self) -> ScopedPlaceId { - self.place_id + pub(crate) fn symbol_id(&self) -> ScopedSymbolId { + self.symbol_id } } diff --git a/crates/ty_python_semantic/src/semantic_index/expression.rs b/crates/ty_python_semantic/src/semantic_index/expression.rs index 99200c89cc..72e6160c7c 100644 --- a/crates/ty_python_semantic/src/semantic_index/expression.rs +++ b/crates/ty_python_semantic/src/semantic_index/expression.rs @@ -1,6 +1,6 @@ use crate::ast_node_ref::AstNodeRef; use crate::db::Db; -use crate::semantic_index::place::{FileScopeId, ScopeId}; +use crate::semantic_index::scope::{FileScopeId, ScopeId}; use ruff_db::files::File; use ruff_db::parsed::ParsedModuleRef; use ruff_python_ast as ast; diff --git a/crates/ty_python_semantic/src/semantic_index/member.rs b/crates/ty_python_semantic/src/semantic_index/member.rs new file mode 100644 index 0000000000..8728eee4d6 --- /dev/null +++ b/crates/ty_python_semantic/src/semantic_index/member.rs @@ -0,0 +1,423 @@ +use bitflags::bitflags; +use hashbrown::hash_table::Entry; +use ruff_index::{IndexVec, newtype_index}; +use ruff_python_ast::{self as ast, name::Name}; +use rustc_hash::FxHasher; +use smallvec::SmallVec; +use std::hash::{Hash as _, Hasher as _}; +use std::ops::{Deref, DerefMut}; + +/// A member access, e.g. `x.y` or `x[1]` or `x["foo"]`. +#[derive(Clone, Debug, PartialEq, Eq, get_size2::GetSize)] +pub(crate) struct Member { + expression: MemberExpr, + flags: MemberFlags, +} + +impl Member { + pub(crate) fn new(expression: MemberExpr) -> Self { + Self { + expression, + flags: MemberFlags::empty(), + } + } + + /// Returns the left most part of the member expression, e.g. `x` in `x.y.z`. + /// + /// This is the symbol on which the member access is performed. + pub(crate) fn symbol_name(&self) -> &Name { + self.expression.symbol_name() + } + + pub(crate) fn expression(&self) -> &MemberExpr { + &self.expression + } + + /// Is the place given a value in its containing scope? + pub(crate) const fn is_bound(&self) -> bool { + self.flags.contains(MemberFlags::IS_BOUND) + } + + /// Is the place declared in its containing scope? + pub(crate) fn is_declared(&self) -> bool { + self.flags.contains(MemberFlags::IS_DECLARED) + } + + pub(super) fn mark_bound(&mut self) { + self.insert_flags(MemberFlags::IS_BOUND); + } + + pub(super) fn mark_declared(&mut self) { + self.insert_flags(MemberFlags::IS_DECLARED); + } + + pub(super) fn mark_instance_attribute(&mut self) { + self.flags.insert(MemberFlags::IS_INSTANCE_ATTRIBUTE); + } + + /// Is the place an instance attribute? + pub(crate) fn is_instance_attribute(&self) -> bool { + let is_instance_attribute = self.flags.contains(MemberFlags::IS_INSTANCE_ATTRIBUTE); + if is_instance_attribute { + debug_assert!(self.is_instance_attribute_candidate()); + } + is_instance_attribute + } + + fn insert_flags(&mut self, flags: MemberFlags) { + self.flags.insert(flags); + } + + /// If the place expression has the form `.` + /// (meaning it *may* be an instance attribute), + /// return `Some()`. Else, return `None`. + /// + /// This method is internal to the semantic-index submodule. + /// It *only* checks that the AST structure of the `Place` is + /// correct. It does not check whether the `Place` actually occurred in + /// a method context, or whether the `` actually refers to the first + /// parameter of the method (i.e. `self`). To answer those questions, + /// use [`Self::as_instance_attribute`]. + pub(super) fn as_instance_attribute_candidate(&self) -> Option<&Name> { + match &*self.expression.segments { + [MemberSegment::Attribute(name)] => Some(name), + _ => None, + } + } + + /// Return `true` if the place expression has the form `.`, + /// indicating that it *may* be an instance attribute if we are in a method context. + /// + /// This method is internal to the semantic-index submodule. + /// It *only* checks that the AST structure of the `Place` is + /// correct. It does not check whether the `Place` actually occurred in + /// a method context, or whether the `` actually refers to the first + /// parameter of the method (i.e. `self`). To answer those questions, + /// use [`Self::is_instance_attribute`]. + pub(super) fn is_instance_attribute_candidate(&self) -> bool { + self.as_instance_attribute_candidate().is_some() + } + + /// Does the place expression have the form `self.{name}` (`self` is the first parameter of the method)? + pub(super) fn is_instance_attribute_named(&self, name: &str) -> bool { + self.as_instance_attribute().map(Name::as_str) == Some(name) + } + + /// Return `Some()` if the place expression is an instance attribute. + pub(crate) fn as_instance_attribute(&self) -> Option<&Name> { + if self.is_instance_attribute() { + debug_assert!(self.as_instance_attribute_candidate().is_some()); + self.as_instance_attribute_candidate() + } else { + None + } + } +} + +impl std::fmt::Display for Member { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.expression, f) + } +} + +bitflags! { + /// Flags that can be queried to obtain information about a member in a given scope. + /// + /// See the doc-comment at the top of [`super::use_def`] for explanations of what it + /// means for a member to be *bound* as opposed to *declared*. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + struct MemberFlags: u8 { + const IS_BOUND = 1 << 0; + const IS_DECLARED = 1 << 1; + const IS_INSTANCE_ATTRIBUTE = 1 << 2; + } +} + +impl get_size2::GetSize for MemberFlags {} + +/// An expression accessing a member on a symbol named `symbol_name`, e.g. `x.y.z`. +/// +/// The parts after the symbol name are called segments, and they can be either: +/// * An attribute access, e.g. `.y` in `x.y` +/// * An integer-based subscript, e.g. `[1]` in `x[1]` +/// * A string-based subscript, e.g. `["foo"]` in `x["foo"]` +/// +/// Internally, the segments are stored in reverse order. This allows constructing +/// a `MemberExpr` from an ast expression without having to reverse the segments. +#[derive(Clone, Debug, PartialEq, Eq, get_size2::GetSize, Hash)] +pub(crate) struct MemberExpr { + symbol_name: Name, + segments: SmallVec<[MemberSegment; 1]>, +} + +impl MemberExpr { + pub(super) fn new(symbol_name: Name, segments: SmallVec<[MemberSegment; 1]>) -> Self { + debug_assert!( + !segments.is_empty(), + "A member without segments is a symbol." + ); + + Self { + symbol_name, + segments, + } + } + + fn shrink_to_fit(&mut self) { + self.segments.shrink_to_fit(); + self.segments.shrink_to_fit(); + } + + /// Returns the left most part of the member expression, e.g. `x` in `x.y.z`. + /// + /// This is the symbol on which the member access is performed. + pub(crate) fn symbol_name(&self) -> &Name { + &self.symbol_name + } + + /// Returns the segments of the member expression, e.g. `[MemberSegment::Attribute("y"), MemberSegment::IntSubscript(1)]` for `x.y[1]`. + pub(crate) fn member_segments( + &self, + ) -> impl ExactSizeIterator + DoubleEndedIterator { + self.segments.iter().rev() + } + + pub(crate) fn as_ref(&self) -> MemberExprRef { + MemberExprRef { + name: self.symbol_name.as_str(), + segments: self.segments.as_slice(), + } + } +} + +impl std::fmt::Display for MemberExpr { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.symbol_name.as_str())?; + + for segment in self.member_segments() { + match segment { + MemberSegment::Attribute(name) => write!(f, ".{name}")?, + MemberSegment::IntSubscript(int) => write!(f, "[{int}]")?, + MemberSegment::StringSubscript(string) => write!(f, "[\"{string}\"]")?, + } + } + + Ok(()) + } +} + +impl PartialEq> for MemberExpr { + fn eq(&self, other: &MemberExprRef) -> bool { + self.as_ref() == *other + } +} + +impl PartialEq> for &MemberExpr { + fn eq(&self, other: &MemberExprRef) -> bool { + self.as_ref() == *other + } +} + +impl PartialEq for MemberExprRef<'_> { + fn eq(&self, other: &MemberExpr) -> bool { + other == self + } +} + +impl PartialEq<&MemberExpr> for MemberExprRef<'_> { + fn eq(&self, other: &&MemberExpr) -> bool { + *other == self + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)] +pub(crate) enum MemberSegment { + /// An attribute access, e.g. `.y` in `x.y` + Attribute(Name), + /// An integer-based index access, e.g. `[1]` in `x[1]` + IntSubscript(ast::Int), + /// A string-based index access, e.g. `["foo"]` in `x["foo"]` + StringSubscript(String), +} + +/// Reference to a member expression. +#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +pub(crate) struct MemberExprRef<'a> { + name: &'a str, + segments: &'a [MemberSegment], +} + +impl<'a> MemberExprRef<'a> { + pub(super) fn symbol_name(&self) -> &'a str { + self.name + } + + /// Create a new `MemberExprRef` from a name and segments. + /// + /// Note that the segments are expected to be in reverse order, i.e. the last segment is the first one in the expression. + pub(super) fn from_raw(name: &'a str, segments: &'a [MemberSegment]) -> Self { + debug_assert!( + !segments.is_empty(), + "A member without segments is a symbol." + ); + Self { name, segments } + } + + /// Returns a slice over the member segments. The segments are in reverse order, + pub(super) fn rev_member_segments(&self) -> &'a [MemberSegment] { + self.segments + } +} + +impl<'a> From<&'a MemberExpr> for MemberExprRef<'a> { + fn from(value: &'a MemberExpr) -> Self { + value.as_ref() + } +} + +/// Uniquely identifies a member in a scope. +#[newtype_index] +#[derive(get_size2::GetSize, salsa::Update)] +pub struct ScopedMemberId; + +/// The members of a scope. Allows lookup by member path and [`ScopedMemberId`]. +#[derive(Default, get_size2::GetSize)] +pub(super) struct MemberTable { + members: IndexVec, + + /// Map from member path to its ID. + /// + /// Uses a hash table to avoid storing the path twice. + map: hashbrown::HashTable, +} + +impl MemberTable { + /// Returns the member with the given ID. + /// + /// ## Panics + /// If the ID is not valid for this table. + #[track_caller] + pub(crate) fn member(&self, id: ScopedMemberId) -> &Member { + &self.members[id] + } + + /// Returns a mutable reference to the member with the given ID. + /// + /// ## Panics + /// If the ID is not valid for this table. + #[track_caller] + pub(super) fn member_mut(&mut self, id: ScopedMemberId) -> &mut Member { + &mut self.members[id] + } + + /// Returns an iterator over all members in the table. + pub(crate) fn iter(&self) -> std::slice::Iter { + self.members.iter() + } + + fn hash_member_expression_ref(member: MemberExprRef) -> u64 { + let mut h = FxHasher::default(); + member.hash(&mut h); + h.finish() + } + + /// Returns the ID of the member with the given expression, if it exists. + pub(crate) fn member_id<'a>( + &self, + member: impl Into>, + ) -> Option { + let member = member.into(); + let hash = Self::hash_member_expression_ref(member); + self.map + .find(hash, |id| self.members[*id].expression == member) + .copied() + } + + pub(crate) fn place_id_by_instance_attribute_name(&self, name: &str) -> Option { + for (id, member) in self.members.iter_enumerated() { + if member.is_instance_attribute_named(name) { + return Some(id); + } + } + + None + } +} + +impl PartialEq for MemberTable { + fn eq(&self, other: &Self) -> bool { + // It's sufficient to compare the members as the map is only a reverse lookup. + self.members == other.members + } +} + +impl Eq for MemberTable {} + +impl std::fmt::Debug for MemberTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("MemberTable").field(&self.members).finish() + } +} + +#[derive(Debug, Default)] +pub(super) struct MemberTableBuilder { + table: MemberTable, +} + +impl MemberTableBuilder { + /// Adds a member to the table or updates the flags of an existing member if it already exists. + /// + /// Members are identified by their expression, which is hashed to find the entry in the table. + pub(super) fn add(&mut self, mut member: Member) -> (ScopedMemberId, bool) { + let hash = MemberTable::hash_member_expression_ref(member.expression.as_ref()); + let entry = self.table.map.entry( + hash, + |id| self.table.members[*id].expression == member.expression, + |id| { + MemberTable::hash_member_expression_ref(self.table.members[*id].expression.as_ref()) + }, + ); + + match entry { + Entry::Occupied(entry) => { + let id = *entry.get(); + + if !member.flags.is_empty() { + self.members[id].flags.insert(member.flags); + } + + (id, false) + } + Entry::Vacant(entry) => { + member.expression.shrink_to_fit(); + + let id = self.table.members.push(member); + entry.insert(id); + (id, true) + } + } + } + + pub(super) fn build(self) -> MemberTable { + let mut table = self.table; + table.members.shrink_to_fit(); + table.map.shrink_to_fit(|id| { + MemberTable::hash_member_expression_ref(table.members[*id].expression.as_ref()) + }); + table + } +} + +impl Deref for MemberTableBuilder { + type Target = MemberTable; + + fn deref(&self) -> &Self::Target { + &self.table + } +} + +impl DerefMut for MemberTableBuilder { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.table + } +} diff --git a/crates/ty_python_semantic/src/semantic_index/narrowing_constraints.rs b/crates/ty_python_semantic/src/semantic_index/narrowing_constraints.rs index b787e6a8af..8d27dd1e20 100644 --- a/crates/ty_python_semantic/src/semantic_index/narrowing_constraints.rs +++ b/crates/ty_python_semantic/src/semantic_index/narrowing_constraints.rs @@ -30,8 +30,8 @@ use crate::list::{List, ListBuilder, ListSetReverseIterator, ListStorage}; use crate::semantic_index::ast_ids::ScopedUseId; -use crate::semantic_index::place::FileScopeId; use crate::semantic_index::predicate::ScopedPredicateId; +use crate::semantic_index::scope::FileScopeId; /// A narrowing constraint associated with a live binding. /// diff --git a/crates/ty_python_semantic/src/semantic_index/place.rs b/crates/ty_python_semantic/src/semantic_index/place.rs index 27dd8dc05f..b36b683a2d 100644 --- a/crates/ty_python_semantic/src/semantic_index/place.rs +++ b/crates/ty_python_semantic/src/semantic_index/place.rs @@ -1,436 +1,481 @@ -use std::convert::Infallible; -use std::hash::{Hash, Hasher}; -use std::ops::Range; - -use bitflags::bitflags; -use hashbrown::hash_table::Entry; -use ruff_db::files::File; -use ruff_db::parsed::ParsedModuleRef; -use ruff_index::{IndexVec, newtype_index}; -use ruff_python_ast as ast; -use ruff_python_ast::name::Name; -use rustc_hash::FxHasher; -use smallvec::SmallVec; - -use crate::Db; -use crate::ast_node_ref::AstNodeRef; -use crate::node_key::NodeKey; -use crate::semantic_index::reachability_constraints::ScopedReachabilityConstraintId; -use crate::semantic_index::{ - PlaceSet, ScopeLaziness, ScopeVisibility, SemanticIndex, semantic_index, +use crate::semantic_index::member::{ + Member, MemberExpr, MemberExprRef, MemberSegment, MemberTable, MemberTableBuilder, + ScopedMemberId, }; - -#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)] -pub(crate) enum PlaceExprSubSegment { - /// A member access, e.g. `.y` in `x.y` - Member(ast::name::Name), - /// An integer-based index access, e.g. `[1]` in `x[1]` - IntSubscript(ast::Int), - /// A string-based index access, e.g. `["foo"]` in `x["foo"]` - StringSubscript(String), -} - -impl PlaceExprSubSegment { - pub(crate) fn as_member(&self) -> Option<&ast::name::Name> { - match self { - PlaceExprSubSegment::Member(name) => Some(name), - _ => None, - } - } -} +use crate::semantic_index::scope::FileScopeId; +use crate::semantic_index::symbol::{ScopedSymbolId, Symbol, SymbolTable, SymbolTableBuilder}; +use ruff_index::IndexVec; +use ruff_python_ast as ast; +use smallvec::SmallVec; +use std::hash::Hash; +use std::iter::FusedIterator; /// An expression that can be the target of a `Definition`. #[derive(Eq, PartialEq, Debug, get_size2::GetSize)] -pub struct PlaceExpr { - root_name: Name, - #[get_size(size_fn=sub_segments_size)] - sub_segments: thin_vec::ThinVec, +pub(crate) enum PlaceExpr { + /// A simple symbol, e.g. `x`. + Symbol(Symbol), + + /// A member expression, e.g. `x.y.z[0]`. + Member(Member), } -fn sub_segments_size(segments: &thin_vec::ThinVec) -> usize { - segments.capacity() * std::mem::size_of::() - + segments - .iter() - .map(get_size2::GetSize::get_heap_size) - .sum::() +impl PlaceExpr { + /// Create a new `PlaceExpr` from a name. + /// + /// This always returns a `PlaceExpr::Symbol` with empty flags and `name`. + pub(crate) fn from_expr_name(name: &ast::ExprName) -> Self { + PlaceExpr::Symbol(Symbol::new(name.id.clone())) + } + + /// Tries to create a `PlaceExpr` from an expression. + /// + /// Returns `None` if the expression is not a valid place expression and `Some` otherwise. + /// + /// Valid expressions are: + /// * name: `x` + /// * attribute: `x.y` + /// * subscripts with integer or string literals: `x[0]`, `x['key']` + pub(crate) fn try_from_expr<'e>(expr: impl Into>) -> Option { + let mut current = expr.into(); + let mut segments = smallvec::SmallVec::new_const(); + + loop { + match current { + ast::ExprRef::Name(name) => { + if segments.is_empty() { + return Some(PlaceExpr::Symbol(Symbol::new(name.id.clone()))); + } + + return Some(PlaceExpr::Member(Member::new(MemberExpr::new( + name.id.clone(), + segments, + )))); + } + ast::ExprRef::Attribute(attribute) => { + segments.push(MemberSegment::Attribute(attribute.attr.id.clone())); + current = ast::ExprRef::from(&attribute.value); + } + ast::ExprRef::Subscript(subscript) => { + match &*subscript.slice { + ast::Expr::NumberLiteral(ast::ExprNumberLiteral { + value: ast::Number::Int(index), + .. + }) => { + segments.push(MemberSegment::IntSubscript(index.clone())); + } + ast::Expr::StringLiteral(string) => { + segments.push(MemberSegment::StringSubscript(string.value.to_string())); + } + _ => { + return None; + } + } + + current = ast::ExprRef::from(&subscript.value); + } + _ => { + return None; + } + } + } + } } impl std::fmt::Display for PlaceExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.root_name)?; - for segment in &self.sub_segments { - match segment { - PlaceExprSubSegment::Member(name) => write!(f, ".{name}")?, - PlaceExprSubSegment::IntSubscript(int) => write!(f, "[{int}]")?, - PlaceExprSubSegment::StringSubscript(string) => write!(f, "[\"{string}\"]")?, - } - } - Ok(()) - } -} - -impl TryFrom<&ast::name::Name> for PlaceExpr { - type Error = Infallible; - - fn try_from(name: &ast::name::Name) -> Result { - Ok(PlaceExpr::name(name.clone())) - } -} - -impl TryFrom for PlaceExpr { - type Error = Infallible; - - fn try_from(name: ast::name::Name) -> Result { - Ok(PlaceExpr::name(name)) - } -} - -impl TryFrom<&ast::ExprAttribute> for PlaceExpr { - type Error = (); - - fn try_from(attr: &ast::ExprAttribute) -> Result { - let mut place = PlaceExpr::try_from(&*attr.value)?; - place - .sub_segments - .push(PlaceExprSubSegment::Member(attr.attr.id.clone())); - Ok(place) - } -} - -impl TryFrom for PlaceExpr { - type Error = (); - - fn try_from(attr: ast::ExprAttribute) -> Result { - let mut place = PlaceExpr::try_from(&*attr.value)?; - place - .sub_segments - .push(PlaceExprSubSegment::Member(attr.attr.id)); - Ok(place) - } -} - -impl TryFrom<&ast::ExprSubscript> for PlaceExpr { - type Error = (); - - fn try_from(subscript: &ast::ExprSubscript) -> Result { - let mut place = PlaceExpr::try_from(&*subscript.value)?; - match &*subscript.slice { - ast::Expr::NumberLiteral(ast::ExprNumberLiteral { - value: ast::Number::Int(index), - .. - }) => { - place - .sub_segments - .push(PlaceExprSubSegment::IntSubscript(index.clone())); - } - ast::Expr::StringLiteral(string) => { - place - .sub_segments - .push(PlaceExprSubSegment::StringSubscript( - string.value.to_string(), - )); - } - _ => { - return Err(()); - } - } - Ok(place) - } -} - -impl TryFrom for PlaceExpr { - type Error = (); - - fn try_from(subscript: ast::ExprSubscript) -> Result { - PlaceExpr::try_from(&subscript) - } -} - -impl TryFrom<&ast::Expr> for PlaceExpr { - type Error = (); - - fn try_from(expr: &ast::Expr) -> Result { - match expr { - ast::Expr::Name(name) => Ok(PlaceExpr::name(name.id.clone())), - ast::Expr::Attribute(attr) => PlaceExpr::try_from(attr), - ast::Expr::Subscript(subscript) => PlaceExpr::try_from(subscript), - _ => Err(()), + match self { + Self::Symbol(symbol) => std::fmt::Display::fmt(symbol, f), + Self::Member(member) => std::fmt::Display::fmt(member, f), } } } -impl TryFrom> for PlaceExpr { - type Error = (); - - fn try_from(expr: ast::ExprRef) -> Result { - match expr { - ast::ExprRef::Name(name) => Ok(PlaceExpr::name(name.id.clone())), - ast::ExprRef::Attribute(attr) => PlaceExpr::try_from(attr), - ast::ExprRef::Subscript(subscript) => PlaceExpr::try_from(subscript), - _ => Err(()), - } - } +/// Reference to a place expression, which can be a symbol or a member expression. +/// +/// Needed so that we can iterate over all places without cloning them. +#[derive(Eq, PartialEq, Debug, Copy, Clone)] +pub(crate) enum PlaceExprRef<'a> { + Symbol(&'a Symbol), + Member(&'a Member), } -impl PlaceExpr { - pub(crate) fn name(name: Name) -> Self { - Self { - root_name: name, - sub_segments: thin_vec::ThinVec::new(), - } - } - - pub(crate) fn root_name(&self) -> &Name { - &self.root_name - } - - pub(crate) fn sub_segments(&self) -> &[PlaceExprSubSegment] { - &self.sub_segments - } - - pub(crate) fn as_name(&self) -> Option<&Name> { - if self.is_name() { - Some(&self.root_name) +impl<'a> PlaceExprRef<'a> { + /// Returns `Some` if the reference is a `Symbol`, otherwise `None`. + pub(crate) const fn as_symbol(self) -> Option<&'a Symbol> { + if let PlaceExprRef::Symbol(symbol) = self { + Some(symbol) } else { None } } - /// Assumes that the place expression is a name. - #[track_caller] - pub(crate) fn expect_name(&self) -> &Name { - debug_assert_eq!(self.sub_segments.len(), 0); - &self.root_name + /// Returns `true` if the reference is a `Symbol`, otherwise `false`. + pub(crate) const fn is_symbol(self) -> bool { + matches!(self, PlaceExprRef::Symbol(_)) } - /// Is the place just a name? - pub fn is_name(&self) -> bool { - self.sub_segments.is_empty() + pub(crate) fn is_declared(self) -> bool { + match self { + Self::Symbol(symbol) => symbol.is_declared(), + Self::Member(member) => member.is_declared(), + } } - pub fn is_name_and(&self, f: impl FnOnce(&str) -> bool) -> bool { - self.is_name() && f(&self.root_name) + pub(crate) const fn is_bound(self) -> bool { + match self { + PlaceExprRef::Symbol(symbol) => symbol.is_bound(), + PlaceExprRef::Member(member) => member.is_bound(), + } } - /// Does the place expression have the form `.member`? - pub fn is_member(&self) -> bool { - self.sub_segments - .last() - .is_some_and(|last| last.as_member().is_some()) - } - - fn root_exprs(&self) -> RootExprs<'_> { - RootExprs { - expr_ref: self.into(), - len: self.sub_segments.len(), + pub(crate) fn num_member_segments(self) -> usize { + match self { + PlaceExprRef::Symbol(_) => 0, + PlaceExprRef::Member(member) => member.expression().member_segments().len(), } } } -/// A [`PlaceExpr`] with flags, e.g. whether it is used, bound, an instance attribute, etc. -#[derive(Eq, PartialEq, Debug, get_size2::GetSize)] -pub struct PlaceExprWithFlags { - pub(crate) expr: PlaceExpr, - flags: PlaceFlags, +impl<'a> From<&'a Symbol> for PlaceExprRef<'a> { + fn from(value: &'a Symbol) -> Self { + Self::Symbol(value) + } } -impl std::fmt::Display for PlaceExprWithFlags { +impl<'a> From<&'a Member> for PlaceExprRef<'a> { + fn from(value: &'a Member) -> Self { + Self::Member(value) + } +} + +impl<'a> From<&'a PlaceExpr> for PlaceExprRef<'a> { + fn from(value: &'a PlaceExpr) -> Self { + match value { + PlaceExpr::Symbol(symbol) => PlaceExprRef::Symbol(symbol), + PlaceExpr::Member(member) => PlaceExprRef::Member(member), + } + } +} + +impl std::fmt::Display for PlaceExprRef<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.expr.fmt(f) + match self { + Self::Symbol(symbol) => std::fmt::Display::fmt(symbol, f), + Self::Member(member) => std::fmt::Display::fmt(member, f), + } } } -impl PlaceExprWithFlags { - pub(crate) fn new(expr: PlaceExpr) -> Self { - PlaceExprWithFlags { - expr, - flags: PlaceFlags::empty(), - } - } +/// ID that uniquely identifies a place inside a [`Scope`](super::FileScopeId). +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, get_size2::GetSize, salsa::Update)] +pub enum ScopedPlaceId { + Symbol(ScopedSymbolId), + Member(ScopedMemberId), +} - fn name(name: Name) -> Self { - PlaceExprWithFlags { - expr: PlaceExpr::name(name), - flags: PlaceFlags::empty(), - } - } +#[derive(Debug, Eq, PartialEq, salsa::Update, get_size2::GetSize)] +pub(crate) struct PlaceTable { + symbols: SymbolTable, + members: MemberTable, +} - fn insert_flags(&mut self, flags: PlaceFlags) { - self.flags.insert(flags); - } - - pub(super) fn mark_instance_attribute(&mut self) { - self.flags.insert(PlaceFlags::IS_INSTANCE_ATTRIBUTE); - } - - /// If the place expression has the form `.` - /// (meaning it *may* be an instance attribute), - /// return `Some()`. Else, return `None`. +impl PlaceTable { + /// Iterate over the "root" expressions of the place (e.g. `x.y.z`, `x.y`, `x` for `x.y.z[0]`). /// - /// This method is internal to the semantic-index submodule. - /// It *only* checks that the AST structure of the `Place` is - /// correct. It does not check whether the `Place` actually occurred in - /// a method context, or whether the `` actually refers to the first - /// parameter of the method (i.e. `self`). To answer those questions, - /// use [`Self::as_instance_attribute`]. - pub(super) fn as_instance_attribute_candidate(&self) -> Option<&Name> { - if self.expr.sub_segments.len() == 1 { - self.expr.sub_segments[0].as_member() + /// Note, this iterator may skip some parents if they are not defined in the current scope. + pub(crate) fn parents<'a>( + &'a self, + place_expr: impl Into>, + ) -> ParentPlaceIter<'a> { + match place_expr.into() { + PlaceExprRef::Symbol(_) => ParentPlaceIter::for_symbol(), + PlaceExprRef::Member(member) => { + ParentPlaceIter::for_member(member.expression(), &self.symbols, &self.members) + } + } + } + + /// Iterator over all symbols in this scope. + pub(crate) fn symbols(&self) -> std::slice::Iter { + self.symbols.iter() + } + + /// Iterator over all members in this scope. + pub(crate) fn members(&self) -> std::slice::Iter { + self.members.iter() + } + + /// Looks up a symbol by its ID and returns a reference to it. + /// + /// ## Panics + /// If the symbol ID is not found in the table. + #[track_caller] + pub(crate) fn symbol(&self, id: ScopedSymbolId) -> &Symbol { + self.symbols.symbol(id) + } + + /// Looks up a symbol by its name and returns a reference to it, if it exists. + #[cfg(test)] + pub(crate) fn symbol_by_name(&self, name: &str) -> Option<&Symbol> { + self.symbols.symbol_id(name).map(|id| self.symbol(id)) + } + + /// Looks up a member by its ID and returns a reference to it. + /// + /// ## Panics + /// If the member ID is not found in the table. + #[track_caller] + pub(crate) fn member(&self, id: ScopedMemberId) -> &Member { + self.members.member(id) + } + + /// Returns the [`ScopedSymbolId`] of the place named `name`. + pub(crate) fn symbol_id(&self, name: &str) -> Option { + self.symbols.symbol_id(name) + } + + /// Returns the [`ScopedPlaceId`] of the place expression. + pub(crate) fn place_id<'e>( + &self, + place_expr: impl Into>, + ) -> Option { + let place_expr = place_expr.into(); + + match place_expr { + PlaceExprRef::Symbol(symbol) => self.symbols.symbol_id(symbol.name()).map(Into::into), + PlaceExprRef::Member(member) => { + self.members.member_id(member.expression()).map(Into::into) + } + } + } + + /// Returns the place expression for the given place ID. + /// + /// ## Panics + /// If the place ID is not found in the table. + #[track_caller] + pub(crate) fn place(&self, place_id: impl Into) -> PlaceExprRef { + match place_id.into() { + ScopedPlaceId::Symbol(symbol) => self.symbol(symbol).into(), + ScopedPlaceId::Member(member) => self.member(member).into(), + } + } + + pub(crate) fn member_id_by_instance_attribute_name( + &self, + name: &str, + ) -> Option { + self.members.place_id_by_instance_attribute_name(name) + } +} + +#[derive(Default)] +pub(crate) struct PlaceTableBuilder { + symbols: SymbolTableBuilder, + member: MemberTableBuilder, + + associated_symbol_members: IndexVec>, + associated_sub_members: IndexVec>, +} + +impl PlaceTableBuilder { + /// Looks up a place ID by its expression. + pub(super) fn place_id(&self, expression: PlaceExprRef) -> Option { + match expression { + PlaceExprRef::Symbol(symbol) => self.symbols.symbol_id(symbol.name()).map(Into::into), + PlaceExprRef::Member(member) => { + self.member.member_id(member.expression()).map(Into::into) + } + } + } + + #[track_caller] + pub(super) fn symbol(&self, id: ScopedSymbolId) -> &Symbol { + self.symbols.symbol(id) + } + + pub(super) fn symbol_id(&self, name: &str) -> Option { + self.symbols.symbol_id(name) + } + + #[track_caller] + pub(super) fn symbol_mut(&mut self, id: ScopedSymbolId) -> &mut Symbol { + self.symbols.symbol_mut(id) + } + + #[track_caller] + pub(super) fn member_mut(&mut self, id: ScopedMemberId) -> &mut Member { + self.member.member_mut(id) + } + + #[track_caller] + pub(crate) fn place(&self, place_id: impl Into) -> PlaceExprRef { + match place_id.into() { + ScopedPlaceId::Symbol(id) => PlaceExprRef::Symbol(self.symbols.symbol(id)), + ScopedPlaceId::Member(id) => PlaceExprRef::Member(self.member.member(id)), + } + } + + pub(crate) fn associated_place_ids(&self, place: ScopedPlaceId) -> &[ScopedMemberId] { + match place { + ScopedPlaceId::Symbol(symbol) => &self.associated_symbol_members[symbol], + ScopedPlaceId::Member(member) => &self.associated_sub_members[member], + } + } + + pub(crate) fn iter(&self) -> impl Iterator { + self.symbols + .iter() + .map(Into::into) + .chain(self.member.iter().map(PlaceExprRef::Member)) + } + + pub(crate) fn symbols(&self) -> impl Iterator { + self.symbols.iter() + } + + pub(crate) fn add_symbol(&mut self, symbol: Symbol) -> (ScopedSymbolId, bool) { + let (id, is_new) = self.symbols.add(symbol); + + if is_new { + let new_id = self.associated_symbol_members.push(SmallVec::new_const()); + debug_assert_eq!(new_id, id); + } + + (id, is_new) + } + + pub(crate) fn add_member(&mut self, member: Member) -> (ScopedMemberId, bool) { + let (id, is_new) = self.member.add(member); + + if is_new { + let new_id = self.associated_sub_members.push(SmallVec::new_const()); + debug_assert_eq!(new_id, id); + + let member = self.member.member(id); + + // iterate over parents + for parent_id in + ParentPlaceIter::for_member(member.expression(), &self.symbols, &self.member) + { + match parent_id { + ScopedPlaceId::Symbol(scoped_symbol_id) => { + self.associated_symbol_members[scoped_symbol_id].push(id); + } + ScopedPlaceId::Member(scoped_member_id) => { + self.associated_sub_members[scoped_member_id].push(id); + } + } + } + } + + (id, is_new) + } + + pub(crate) fn add_place(&mut self, place: PlaceExpr) -> (ScopedPlaceId, bool) { + match place { + PlaceExpr::Symbol(symbol) => { + let (id, is_new) = self.add_symbol(symbol); + (ScopedPlaceId::Symbol(id), is_new) + } + PlaceExpr::Member(member) => { + let (id, is_new) = self.add_member(member); + (ScopedPlaceId::Member(id), is_new) + } + } + } + + #[track_caller] + pub(super) fn mark_bound(&mut self, id: ScopedPlaceId) { + match id { + ScopedPlaceId::Symbol(symbol_id) => { + self.symbol_mut(symbol_id).mark_bound(); + } + ScopedPlaceId::Member(member_id) => { + self.member_mut(member_id).mark_bound(); + } + } + } + + #[track_caller] + pub(super) fn mark_declared(&mut self, id: ScopedPlaceId) { + match id { + ScopedPlaceId::Symbol(symbol_id) => { + self.symbol_mut(symbol_id).mark_declared(); + } + ScopedPlaceId::Member(member_id) => { + self.member_mut(member_id).mark_declared(); + } + } + } + + pub(crate) fn finish(self) -> PlaceTable { + PlaceTable { + symbols: self.symbols.build(), + members: self.member.build(), + } + } +} + +impl ScopedPlaceId { + pub const fn is_symbol(self) -> bool { + matches!(self, ScopedPlaceId::Symbol(_)) + } + + pub const fn is_member(self) -> bool { + matches!(self, ScopedPlaceId::Member(_)) + } + + pub const fn as_symbol(self) -> Option { + if let ScopedPlaceId::Symbol(id) = self { + Some(id) } else { None } } - /// Return `true` if the place expression has the form `.`, - /// indicating that it *may* be an instance attribute if we are in a method context. - /// - /// This method is internal to the semantic-index submodule. - /// It *only* checks that the AST structure of the `Place` is - /// correct. It does not check whether the `Place` actually occurred in - /// a method context, or whether the `` actually refers to the first - /// parameter of the method (i.e. `self`). To answer those questions, - /// use [`Self::is_instance_attribute`]. - pub(super) fn is_instance_attribute_candidate(&self) -> bool { - self.as_instance_attribute_candidate().is_some() + pub const fn expect_symbol(self) -> ScopedSymbolId { + match self { + ScopedPlaceId::Symbol(symbol) => symbol, + ScopedPlaceId::Member(_) => { + panic!("Expected ScopedPlaceId::Symbol, found ScopedPlaceId::Member") + } + } } - /// Does the place expression have the form `self.{name}` (`self` is the first parameter of the method)? - pub(super) fn is_instance_attribute_named(&self, name: &str) -> bool { - self.as_instance_attribute().map(Name::as_str) == Some(name) - } - - /// Return `Some()` if the place expression is an instance attribute. - pub(crate) fn as_instance_attribute(&self) -> Option<&Name> { - if self.is_instance_attribute() { - debug_assert!(self.as_instance_attribute_candidate().is_some()); - self.as_instance_attribute_candidate() + pub const fn as_member(self) -> Option { + if let ScopedPlaceId::Member(id) = self { + Some(id) } else { None } } - - /// Is the place an instance attribute? - pub(crate) fn is_instance_attribute(&self) -> bool { - let is_instance_attribute = self.flags.contains(PlaceFlags::IS_INSTANCE_ATTRIBUTE); - if is_instance_attribute { - debug_assert!(self.is_instance_attribute_candidate()); - } - is_instance_attribute - } - - pub(crate) fn is_name(&self) -> bool { - self.expr.is_name() - } - - pub(crate) fn is_member(&self) -> bool { - self.expr.is_member() - } - - /// Is the place used in its containing scope? - pub fn is_used(&self) -> bool { - self.flags.contains(PlaceFlags::IS_USED) - } - - /// Is the place given a value in its containing scope? - pub fn is_bound(&self) -> bool { - self.flags.contains(PlaceFlags::IS_BOUND) - } - - /// Is the place declared in its containing scope? - pub fn is_declared(&self) -> bool { - self.flags.contains(PlaceFlags::IS_DECLARED) - } - - /// Is the place `global` its containing scope? - pub fn is_marked_global(&self) -> bool { - self.flags.contains(PlaceFlags::MARKED_GLOBAL) - } - - /// Is the place `nonlocal` its containing scope? - pub fn is_marked_nonlocal(&self) -> bool { - self.flags.contains(PlaceFlags::MARKED_NONLOCAL) - } - - pub fn is_reassigned(&self) -> bool { - self.flags.contains(PlaceFlags::IS_REASSIGNED) - } - - pub(crate) fn as_name(&self) -> Option<&Name> { - self.expr.as_name() - } - - pub(crate) fn expect_name(&self) -> &Name { - self.expr.expect_name() - } } -#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)] -pub struct PlaceExprRef<'a> { - pub(crate) root_name: &'a Name, - /// Sub-segments is empty for a simple target (e.g. `foo`). - pub(crate) sub_segments: &'a [PlaceExprSubSegment], -} +impl std::ops::Index for Vec { + type Output = T; -impl PartialEq for PlaceExprRef<'_> { - fn eq(&self, other: &PlaceExpr) -> bool { - self.root_name == &other.root_name && self.sub_segments == &other.sub_segments[..] - } -} - -impl PartialEq> for PlaceExpr { - fn eq(&self, other: &PlaceExprRef<'_>) -> bool { - &self.root_name == other.root_name && &self.sub_segments[..] == other.sub_segments - } -} - -impl<'e> From<&'e PlaceExpr> for PlaceExprRef<'e> { - fn from(expr: &'e PlaceExpr) -> Self { - PlaceExprRef { - root_name: &expr.root_name, - sub_segments: &expr.sub_segments, + fn index(&self, index: ScopedPlaceId) -> &Self::Output { + match index { + ScopedPlaceId::Symbol(id) => &self[id.index()], + ScopedPlaceId::Member(id) => &self[id.index()], } } } -struct RootExprs<'e> { - expr_ref: PlaceExprRef<'e>, - len: usize, -} - -impl<'e> Iterator for RootExprs<'e> { - type Item = PlaceExprRef<'e>; - - fn next(&mut self) -> Option { - if self.len == 0 { - return None; - } - self.len -= 1; - Some(PlaceExprRef { - root_name: self.expr_ref.root_name, - sub_segments: &self.expr_ref.sub_segments[..self.len], - }) +impl From for ScopedPlaceId { + fn from(value: ScopedMemberId) -> Self { + Self::Member(value) } } -bitflags! { - /// Flags that can be queried to obtain information about a place in a given scope. - /// - /// See the doc-comment at the top of [`super::use_def`] for explanations of what it - /// means for a place to be *bound* as opposed to *declared*. - #[derive(Copy, Clone, Debug, Eq, PartialEq)] - struct PlaceFlags: u8 { - const IS_USED = 1 << 0; - const IS_BOUND = 1 << 1; - const IS_DECLARED = 1 << 2; - const MARKED_GLOBAL = 1 << 3; - const MARKED_NONLOCAL = 1 << 4; - const IS_INSTANCE_ATTRIBUTE = 1 << 5; - const IS_REASSIGNED = 1 << 6; +impl From for ScopedPlaceId { + fn from(value: ScopedSymbolId) -> Self { + Self::Symbol(value) } } -impl get_size2::GetSize for PlaceFlags {} - /// ID that uniquely identifies a place in a file. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct FilePlaceId { @@ -454,662 +499,97 @@ impl From for ScopedPlaceId { } } -/// ID that uniquely identifies a place inside a [`Scope`]. -#[newtype_index] -#[derive(salsa::Update, get_size2::GetSize)] -pub struct ScopedPlaceId; - -/// A cross-module identifier of a scope that can be used as a salsa query parameter. -#[salsa::tracked(debug)] -pub struct ScopeId<'db> { - pub file: File, - - pub file_scope_id: FileScopeId, +pub(crate) struct ParentPlaceIter<'a> { + state: Option>, } -// The Salsa heap is tracked separately. -impl get_size2::GetSize for ScopeId<'_> {} - -impl<'db> ScopeId<'db> { - pub(crate) fn is_function_like(self, db: &'db dyn Db) -> bool { - self.node(db).scope_kind().is_function_like() - } - - pub(crate) fn is_type_parameter(self, db: &'db dyn Db) -> bool { - self.node(db).scope_kind().is_type_parameter() - } - - pub(crate) fn node(self, db: &dyn Db) -> &NodeWithScopeKind { - self.scope(db).node() - } - - pub(crate) fn scope(self, db: &dyn Db) -> &Scope { - semantic_index(db, self.file(db)).scope(self.file_scope_id(db)) - } - - #[cfg(test)] - pub(crate) fn name<'ast>(self, db: &'db dyn Db, module: &'ast ParsedModuleRef) -> &'ast str { - match self.node(db) { - NodeWithScopeKind::Module => "", - NodeWithScopeKind::Class(class) | NodeWithScopeKind::ClassTypeParameters(class) => { - class.node(module).name.as_str() - } - NodeWithScopeKind::Function(function) - | NodeWithScopeKind::FunctionTypeParameters(function) => { - function.node(module).name.as_str() - } - NodeWithScopeKind::TypeAlias(type_alias) - | NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => type_alias - .node(module) - .name - .as_name_expr() - .map(|name| name.id.as_str()) - .unwrap_or(""), - NodeWithScopeKind::Lambda(_) => "", - NodeWithScopeKind::ListComprehension(_) => "", - NodeWithScopeKind::SetComprehension(_) => "", - NodeWithScopeKind::DictComprehension(_) => "", - NodeWithScopeKind::GeneratorExpression(_) => "", - } - } +enum ParentPlaceIterState<'a> { + Symbol { + symbol_name: &'a str, + symbols: &'a SymbolTable, + }, + Member { + symbols: &'a SymbolTable, + members: &'a MemberTable, + next_member: MemberExprRef<'a>, + }, } -/// ID that uniquely identifies a scope inside of a module. -#[newtype_index] -#[derive(salsa::Update, get_size2::GetSize)] -pub struct FileScopeId; - -impl FileScopeId { - /// Returns the scope id of the module-global scope. - pub fn global() -> Self { - FileScopeId::from_u32(0) - } - - pub fn is_global(self) -> bool { - self == FileScopeId::global() - } - - pub fn to_scope_id(self, db: &dyn Db, file: File) -> ScopeId<'_> { - let index = semantic_index(db, file); - index.scope_ids_by_scope[self] - } - - pub(crate) fn is_generator_function(self, index: &SemanticIndex) -> bool { - index.generator_functions.contains(&self) - } -} - -#[derive(Debug, salsa::Update, get_size2::GetSize)] -pub struct Scope { - /// The parent scope, if any. - parent: Option, - - /// The node that introduces this scope. - node: NodeWithScopeKind, - - /// The range of [`FileScopeId`]s that are descendants of this scope. - descendants: Range, - - /// The constraint that determines the reachability of this scope. - reachability: ScopedReachabilityConstraintId, - - /// Whether this scope is defined inside an `if TYPE_CHECKING:` block. - in_type_checking_block: bool, -} - -impl Scope { - pub(super) fn new( - parent: Option, - node: NodeWithScopeKind, - descendants: Range, - reachability: ScopedReachabilityConstraintId, - in_type_checking_block: bool, +impl<'a> ParentPlaceIterState<'a> { + fn parent_state( + expression: MemberExprRef<'a>, + symbols: &'a SymbolTable, + members: &'a MemberTable, ) -> Self { - Scope { - parent, - node, - descendants, - reachability, - in_type_checking_block, - } - } + let segments = expression.rev_member_segments(); + let segments = &segments[1..]; - pub fn parent(&self) -> Option { - self.parent - } - - pub fn node(&self) -> &NodeWithScopeKind { - &self.node - } - - pub fn kind(&self) -> ScopeKind { - self.node().scope_kind() - } - - pub(crate) fn visibility(&self) -> ScopeVisibility { - self.kind().visibility() - } - - pub fn descendants(&self) -> Range { - self.descendants.clone() - } - - pub(super) fn extend_descendants(&mut self, children_end: FileScopeId) { - self.descendants = self.descendants.start..children_end; - } - - pub(crate) fn is_eager(&self) -> bool { - self.kind().is_eager() - } - - pub(crate) fn reachability(&self) -> ScopedReachabilityConstraintId { - self.reachability - } - - pub(crate) fn in_type_checking_block(&self) -> bool { - self.in_type_checking_block - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum ScopeKind { - Module, - Annotation, - Class, - Function, - Lambda, - Comprehension, - TypeAlias, -} - -impl ScopeKind { - pub(crate) const fn is_eager(self) -> bool { - self.laziness().is_eager() - } - - pub(crate) const fn laziness(self) -> ScopeLaziness { - match self { - ScopeKind::Module | ScopeKind::Class | ScopeKind::Comprehension => ScopeLaziness::Eager, - ScopeKind::Annotation - | ScopeKind::Function - | ScopeKind::Lambda - | ScopeKind::TypeAlias => ScopeLaziness::Lazy, - } - } - - pub(crate) const fn visibility(self) -> ScopeVisibility { - match self { - ScopeKind::Module | ScopeKind::Class => ScopeVisibility::Public, - ScopeKind::Annotation - | ScopeKind::TypeAlias - | ScopeKind::Function - | ScopeKind::Lambda - | ScopeKind::Comprehension => ScopeVisibility::Private, - } - } - - pub(crate) const fn is_function_like(self) -> bool { - // Type parameter scopes behave like function scopes in terms of name resolution; CPython - // place table also uses the term "function-like" for these scopes. - matches!( - self, - ScopeKind::Annotation - | ScopeKind::Function - | ScopeKind::Lambda - | ScopeKind::TypeAlias - | ScopeKind::Comprehension - ) - } - - pub(crate) const fn is_class(self) -> bool { - matches!(self, ScopeKind::Class) - } - - pub(crate) const fn is_type_parameter(self) -> bool { - matches!(self, ScopeKind::Annotation | ScopeKind::TypeAlias) - } - - pub(crate) const fn is_non_lambda_function(self) -> bool { - matches!(self, ScopeKind::Function) - } -} - -/// [`PlaceExpr`] table for a specific [`Scope`]. -#[derive(Default, get_size2::GetSize)] -pub struct PlaceTable { - /// The place expressions in this scope. - places: IndexVec, - - /// The set of places. - place_set: PlaceSet, -} - -impl PlaceTable { - fn shrink_to_fit(&mut self) { - self.places.shrink_to_fit(); - self.place_set - .shrink_to_fit(|id| PlaceTable::hash_place_expr(&self.places[*id].expr)); - } - - pub(crate) fn place_expr(&self, place_id: impl Into) -> &PlaceExprWithFlags { - &self.places[place_id.into()] - } - - /// Iterate over the "root" expressions of the place (e.g. `x.y.z`, `x.y`, `x` for `x.y.z[0]`). - pub(crate) fn root_place_exprs( - &self, - place_expr: &PlaceExpr, - ) -> impl Iterator { - place_expr - .root_exprs() - .filter_map(|place_expr| self.place_by_expr(place_expr)) - } - - #[expect(unused)] - pub(crate) fn place_ids(&self) -> impl Iterator { - self.places.indices() - } - - pub fn places(&self) -> impl Iterator { - self.places.iter() - } - - pub fn symbols(&self) -> impl Iterator { - self.places().filter(|place_expr| place_expr.is_name()) - } - - pub fn instance_attributes(&self) -> impl Iterator { - self.places() - .filter_map(|place_expr| place_expr.as_instance_attribute()) - } - - /// Returns the place named `name`. - #[cfg(test)] - pub(crate) fn place_by_name(&self, name: &str) -> Option<&PlaceExprWithFlags> { - let id = self.place_id_by_name(name)?; - Some(self.place_expr(id)) - } - - /// Returns the flagged place. - pub(crate) fn place_by_expr<'e>( - &self, - place_expr: impl Into>, - ) -> Option<&PlaceExprWithFlags> { - let id = self.place_id_by_expr(place_expr)?; - Some(self.place_expr(id)) - } - - /// Returns the [`ScopedPlaceId`] of the place named `name`. - pub(crate) fn place_id_by_name(&self, name: &str) -> Option { - self.place_set - .find(Self::hash_name(name), |id| { - self.place_expr(*id).as_name().map(Name::as_str) == Some(name) - }) - .copied() - } - - /// Returns the [`ScopedPlaceId`] of the place expression. - pub(crate) fn place_id_by_expr<'e>( - &self, - place_expr: impl Into>, - ) -> Option { - let place_expr = place_expr.into(); - self.place_set - .find(Self::hash_place_expr(place_expr), |id| { - self.place_expr(*id).expr == place_expr - }) - .copied() - } - - pub(crate) fn place_id_by_instance_attribute_name(&self, name: &str) -> Option { - self.places - .indices() - .find(|id| self.places[*id].is_instance_attribute_named(name)) - } - - fn hash_name(name: &str) -> u64 { - let mut hasher = FxHasher::default(); - name.hash(&mut hasher); - hasher.finish() - } - - fn hash_place_expr<'e>(place_expr: impl Into>) -> u64 { - let place_expr: PlaceExprRef = place_expr.into(); - - let mut hasher = FxHasher::default(); - - // Special case for simple names (e.g. "foo"). Only hash the name so - // that a lookup by name can find it (see `place_by_name`). - if place_expr.sub_segments.is_empty() { - place_expr.root_name.as_str().hash(&mut hasher); + if segments.is_empty() { + Self::Symbol { + symbol_name: expression.symbol_name(), + symbols, + } } else { - place_expr.hash(&mut hasher); - } - hasher.finish() - } -} - -impl PartialEq for PlaceTable { - fn eq(&self, other: &Self) -> bool { - // We don't need to compare the place_set because the place is already captured in `PlaceExpr`. - self.places == other.places - } -} - -impl Eq for PlaceTable {} - -impl std::fmt::Debug for PlaceTable { - /// Exclude the `place_set` field from the debug output. - /// It's very noisy and not useful for debugging. - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("PlaceTable") - .field(&self.places) - .finish_non_exhaustive() - } -} - -#[derive(Debug, Default)] -pub(super) struct PlaceTableBuilder { - table: PlaceTable, - - associated_place_ids: IndexVec>, -} - -impl PlaceTableBuilder { - pub(super) fn add_symbol(&mut self, name: Name) -> (ScopedPlaceId, bool) { - let hash = PlaceTable::hash_name(&name); - let entry = self.table.place_set.entry( - hash, - |id| self.table.places[*id].as_name() == Some(&name), - |id| PlaceTable::hash_place_expr(&self.table.places[*id].expr), - ); - - match entry { - Entry::Occupied(entry) => (*entry.get(), false), - Entry::Vacant(entry) => { - let symbol = PlaceExprWithFlags::name(name); - - let id = self.table.places.push(symbol); - entry.insert(id); - let new_id = self.associated_place_ids.push(SmallVec::new_const()); - debug_assert_eq!(new_id, id); - (id, true) + Self::Member { + next_member: MemberExprRef::from_raw(expression.symbol_name(), segments), + symbols, + members, } } } +} - pub(super) fn add_place( - &mut self, - mut place_expr: PlaceExprWithFlags, - ) -> (ScopedPlaceId, bool) { - let hash = PlaceTable::hash_place_expr(&place_expr.expr); - let entry = self.table.place_set.entry( - hash, - |id| self.table.places[*id].expr == place_expr.expr, - |id| PlaceTable::hash_place_expr(&self.table.places[*id].expr), - ); +impl<'a> ParentPlaceIter<'a> { + pub(super) fn for_symbol() -> Self { + ParentPlaceIter { state: None } + } - match entry { - Entry::Occupied(entry) => (*entry.get(), false), - Entry::Vacant(entry) => { - place_expr.expr.sub_segments.shrink_to_fit(); - let id = self.table.places.push(place_expr); - entry.insert(id); - let new_id = self.associated_place_ids.push(SmallVec::new_const()); - debug_assert_eq!(new_id, id); - for root in self.table.places[id].expr.root_exprs() { - if let Some(root_id) = self.table.place_id_by_expr(root) { - self.associated_place_ids[root_id].push(id); + pub(super) fn for_member( + expression: &'a MemberExpr, + symbol_table: &'a SymbolTable, + member_table: &'a MemberTable, + ) -> Self { + ParentPlaceIter { + state: Some(ParentPlaceIterState::parent_state( + expression.as_ref(), + symbol_table, + member_table, + )), + } + } +} + +impl Iterator for ParentPlaceIter<'_> { + type Item = ScopedPlaceId; + + fn next(&mut self) -> Option { + loop { + match self.state.take()? { + ParentPlaceIterState::Symbol { + symbol_name, + symbols, + } => { + let id = symbols.symbol_id(symbol_name)?; + break Some(id.into()); + } + ParentPlaceIterState::Member { + symbols, + members, + next_member, + } => { + self.state = Some(ParentPlaceIterState::parent_state( + next_member, + symbols, + members, + )); + + if let Some(id) = members.member_id(next_member) { + break Some(id.into()); } } - (id, true) - } - } - } - - pub(super) fn mark_place_bound(&mut self, id: ScopedPlaceId) { - if self.table.places[id].is_bound() { - self.table.places[id].insert_flags(PlaceFlags::IS_REASSIGNED); - } - self.table.places[id].insert_flags(PlaceFlags::IS_BOUND); - } - - pub(super) fn mark_place_declared(&mut self, id: ScopedPlaceId) { - self.table.places[id].insert_flags(PlaceFlags::IS_DECLARED); - } - - pub(super) fn mark_place_used(&mut self, id: ScopedPlaceId) { - self.table.places[id].insert_flags(PlaceFlags::IS_USED); - } - - pub(super) fn mark_place_global(&mut self, id: ScopedPlaceId) { - self.table.places[id].insert_flags(PlaceFlags::MARKED_GLOBAL); - } - - pub(super) fn mark_place_nonlocal(&mut self, id: ScopedPlaceId) { - self.table.places[id].insert_flags(PlaceFlags::MARKED_NONLOCAL); - } - - pub(super) fn places(&self) -> impl Iterator { - self.table.places() - } - - pub(super) fn place_id_by_expr(&self, place_expr: &PlaceExpr) -> Option { - self.table.place_id_by_expr(place_expr) - } - - pub(super) fn place_expr(&self, place_id: impl Into) -> &PlaceExprWithFlags { - self.table.place_expr(place_id) - } - - pub(super) fn is_place_reassigned(&self, place_id: ScopedPlaceId) -> bool { - self.table.places[place_id].is_reassigned() - } - - /// Returns the place IDs associated with the place (e.g. `x.y`, `x.y.z`, `x.y.z[0]` for `x`). - pub(super) fn associated_place_ids( - &self, - place: ScopedPlaceId, - ) -> impl Iterator { - self.associated_place_ids[place].iter().copied() - } - - pub(super) fn finish(mut self) -> PlaceTable { - self.table.shrink_to_fit(); - self.table - } -} - -/// Reference to a node that introduces a new scope. -#[derive(Copy, Clone, Debug)] -pub(crate) enum NodeWithScopeRef<'a> { - Module, - Class(&'a ast::StmtClassDef), - Function(&'a ast::StmtFunctionDef), - Lambda(&'a ast::ExprLambda), - FunctionTypeParameters(&'a ast::StmtFunctionDef), - ClassTypeParameters(&'a ast::StmtClassDef), - TypeAlias(&'a ast::StmtTypeAlias), - TypeAliasTypeParameters(&'a ast::StmtTypeAlias), - ListComprehension(&'a ast::ExprListComp), - SetComprehension(&'a ast::ExprSetComp), - DictComprehension(&'a ast::ExprDictComp), - GeneratorExpression(&'a ast::ExprGenerator), -} - -impl NodeWithScopeRef<'_> { - /// Converts the unowned reference to an owned [`NodeWithScopeKind`]. - /// - /// Note that node wrapped by `self` must be a child of `module`. - pub(super) fn to_kind(self, module: &ParsedModuleRef) -> NodeWithScopeKind { - match self { - NodeWithScopeRef::Module => NodeWithScopeKind::Module, - NodeWithScopeRef::Class(class) => { - NodeWithScopeKind::Class(AstNodeRef::new(module, class)) - } - NodeWithScopeRef::Function(function) => { - NodeWithScopeKind::Function(AstNodeRef::new(module, function)) - } - NodeWithScopeRef::TypeAlias(type_alias) => { - NodeWithScopeKind::TypeAlias(AstNodeRef::new(module, type_alias)) - } - NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => { - NodeWithScopeKind::TypeAliasTypeParameters(AstNodeRef::new(module, type_alias)) - } - NodeWithScopeRef::Lambda(lambda) => { - NodeWithScopeKind::Lambda(AstNodeRef::new(module, lambda)) - } - NodeWithScopeRef::FunctionTypeParameters(function) => { - NodeWithScopeKind::FunctionTypeParameters(AstNodeRef::new(module, function)) - } - NodeWithScopeRef::ClassTypeParameters(class) => { - NodeWithScopeKind::ClassTypeParameters(AstNodeRef::new(module, class)) - } - NodeWithScopeRef::ListComprehension(comprehension) => { - NodeWithScopeKind::ListComprehension(AstNodeRef::new(module, comprehension)) - } - NodeWithScopeRef::SetComprehension(comprehension) => { - NodeWithScopeKind::SetComprehension(AstNodeRef::new(module, comprehension)) - } - NodeWithScopeRef::DictComprehension(comprehension) => { - NodeWithScopeKind::DictComprehension(AstNodeRef::new(module, comprehension)) - } - NodeWithScopeRef::GeneratorExpression(generator) => { - NodeWithScopeKind::GeneratorExpression(AstNodeRef::new(module, generator)) - } - } - } - - pub(crate) fn node_key(self) -> NodeWithScopeKey { - match self { - NodeWithScopeRef::Module => NodeWithScopeKey::Module, - NodeWithScopeRef::Class(class) => NodeWithScopeKey::Class(NodeKey::from_node(class)), - NodeWithScopeRef::Function(function) => { - NodeWithScopeKey::Function(NodeKey::from_node(function)) - } - NodeWithScopeRef::Lambda(lambda) => { - NodeWithScopeKey::Lambda(NodeKey::from_node(lambda)) - } - NodeWithScopeRef::FunctionTypeParameters(function) => { - NodeWithScopeKey::FunctionTypeParameters(NodeKey::from_node(function)) - } - NodeWithScopeRef::ClassTypeParameters(class) => { - NodeWithScopeKey::ClassTypeParameters(NodeKey::from_node(class)) - } - NodeWithScopeRef::TypeAlias(type_alias) => { - NodeWithScopeKey::TypeAlias(NodeKey::from_node(type_alias)) - } - NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => { - NodeWithScopeKey::TypeAliasTypeParameters(NodeKey::from_node(type_alias)) - } - NodeWithScopeRef::ListComprehension(comprehension) => { - NodeWithScopeKey::ListComprehension(NodeKey::from_node(comprehension)) - } - NodeWithScopeRef::SetComprehension(comprehension) => { - NodeWithScopeKey::SetComprehension(NodeKey::from_node(comprehension)) - } - NodeWithScopeRef::DictComprehension(comprehension) => { - NodeWithScopeKey::DictComprehension(NodeKey::from_node(comprehension)) - } - NodeWithScopeRef::GeneratorExpression(generator) => { - NodeWithScopeKey::GeneratorExpression(NodeKey::from_node(generator)) } } } } -/// Node that introduces a new scope. -#[derive(Clone, Debug, salsa::Update, get_size2::GetSize)] -pub enum NodeWithScopeKind { - Module, - Class(AstNodeRef), - ClassTypeParameters(AstNodeRef), - Function(AstNodeRef), - FunctionTypeParameters(AstNodeRef), - TypeAliasTypeParameters(AstNodeRef), - TypeAlias(AstNodeRef), - Lambda(AstNodeRef), - ListComprehension(AstNodeRef), - SetComprehension(AstNodeRef), - DictComprehension(AstNodeRef), - GeneratorExpression(AstNodeRef), -} - -impl NodeWithScopeKind { - pub(crate) const fn scope_kind(&self) -> ScopeKind { - match self { - Self::Module => ScopeKind::Module, - Self::Class(_) => ScopeKind::Class, - Self::Function(_) => ScopeKind::Function, - Self::Lambda(_) => ScopeKind::Lambda, - Self::FunctionTypeParameters(_) - | Self::ClassTypeParameters(_) - | Self::TypeAliasTypeParameters(_) => ScopeKind::Annotation, - Self::TypeAlias(_) => ScopeKind::TypeAlias, - Self::ListComprehension(_) - | Self::SetComprehension(_) - | Self::DictComprehension(_) - | Self::GeneratorExpression(_) => ScopeKind::Comprehension, - } - } - - pub fn expect_class<'ast>(&self, module: &'ast ParsedModuleRef) -> &'ast ast::StmtClassDef { - match self { - Self::Class(class) => class.node(module), - _ => panic!("expected class"), - } - } - - pub(crate) fn as_class<'ast>( - &self, - module: &'ast ParsedModuleRef, - ) -> Option<&'ast ast::StmtClassDef> { - match self { - Self::Class(class) => Some(class.node(module)), - _ => None, - } - } - - pub fn expect_function<'ast>( - &self, - module: &'ast ParsedModuleRef, - ) -> &'ast ast::StmtFunctionDef { - self.as_function(module).expect("expected function") - } - - pub fn expect_type_alias<'ast>( - &self, - module: &'ast ParsedModuleRef, - ) -> &'ast ast::StmtTypeAlias { - match self { - Self::TypeAlias(type_alias) => type_alias.node(module), - _ => panic!("expected type alias"), - } - } - - pub fn as_function<'ast>( - &self, - module: &'ast ParsedModuleRef, - ) -> Option<&'ast ast::StmtFunctionDef> { - match self { - Self::Function(function) => Some(function.node(module)), - _ => None, - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)] -pub(crate) enum NodeWithScopeKey { - Module, - Class(NodeKey), - ClassTypeParameters(NodeKey), - Function(NodeKey), - FunctionTypeParameters(NodeKey), - TypeAlias(NodeKey), - TypeAliasTypeParameters(NodeKey), - Lambda(NodeKey), - ListComprehension(NodeKey), - SetComprehension(NodeKey), - DictComprehension(NodeKey), - GeneratorExpression(NodeKey), -} +impl FusedIterator for ParentPlaceIter<'_> {} diff --git a/crates/ty_python_semantic/src/semantic_index/predicate.rs b/crates/ty_python_semantic/src/semantic_index/predicate.rs index 35d738c6c4..e4955e7122 100644 --- a/crates/ty_python_semantic/src/semantic_index/predicate.rs +++ b/crates/ty_python_semantic/src/semantic_index/predicate.rs @@ -14,7 +14,8 @@ use ruff_python_ast::Singleton; use crate::db::Db; use crate::semantic_index::expression::Expression; use crate::semantic_index::global_scope; -use crate::semantic_index::place::{FileScopeId, ScopeId, ScopedPlaceId}; +use crate::semantic_index::scope::{FileScopeId, ScopeId}; +use crate::semantic_index::symbol::ScopedSymbolId; // A scoped identifier for each `Predicate` in a scope. #[derive(Clone, Debug, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)] @@ -217,7 +218,7 @@ pub(crate) struct StarImportPlaceholderPredicate<'db> { /// for valid `*`-import definitions, and valid `*`-import definitions can only ever /// exist in the global scope; thus, we know that the `symbol_id` here will be relative /// to the global scope of the importing file. - pub(crate) symbol_id: ScopedPlaceId, + pub(crate) symbol_id: ScopedSymbolId, pub(crate) referenced_file: File, } diff --git a/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs b/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs index 03f8ac0ace..16e5ce262b 100644 --- a/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs +++ b/crates/ty_python_semantic/src/semantic_index/reachability_constraints.rs @@ -844,19 +844,17 @@ impl ReachabilityConstraints { PredicateNode::Pattern(inner) => Self::analyze_single_pattern_predicate(db, inner), PredicateNode::StarImportPlaceholder(star_import) => { let place_table = place_table(db, star_import.scope(db)); - let symbol_name = place_table - .place_expr(star_import.symbol_id(db)) - .expect_name(); + let symbol = place_table.symbol(star_import.symbol_id(db)); let referenced_file = star_import.referenced_file(db); let requires_explicit_reexport = match dunder_all_names(db, referenced_file) { Some(all_names) => { - if all_names.contains(symbol_name) { + if all_names.contains(symbol.name()) { Some(RequiresExplicitReExport::No) } else { tracing::trace!( "Symbol `{}` (via star import) not found in `__all__` of `{}`", - symbol_name, + symbol.name(), referenced_file.path(db) ); return Truthiness::AlwaysFalse; @@ -865,8 +863,13 @@ impl ReachabilityConstraints { None => None, }; - match imported_symbol(db, referenced_file, symbol_name, requires_explicit_reexport) - .place + match imported_symbol( + db, + referenced_file, + symbol.name(), + requires_explicit_reexport, + ) + .place { crate::place::Place::Type(_, crate::place::Boundness::Bound) => { Truthiness::AlwaysTrue diff --git a/crates/ty_python_semantic/src/semantic_index/scope.rs b/crates/ty_python_semantic/src/semantic_index/scope.rs new file mode 100644 index 0000000000..89581d9194 --- /dev/null +++ b/crates/ty_python_semantic/src/semantic_index/scope.rs @@ -0,0 +1,458 @@ +use std::ops::Range; + +use ruff_db::{files::File, parsed::ParsedModuleRef}; +use ruff_index::newtype_index; +use ruff_python_ast as ast; + +use crate::{ + Db, + ast_node_ref::AstNodeRef, + node_key::NodeKey, + semantic_index::{ + SemanticIndex, reachability_constraints::ScopedReachabilityConstraintId, semantic_index, + }, +}; + +/// A cross-module identifier of a scope that can be used as a salsa query parameter. +#[salsa::tracked(debug)] +pub struct ScopeId<'db> { + pub file: File, + + pub file_scope_id: FileScopeId, +} + +// The Salsa heap is tracked separately. +impl get_size2::GetSize for ScopeId<'_> {} + +impl<'db> ScopeId<'db> { + pub(crate) fn is_function_like(self, db: &'db dyn Db) -> bool { + self.node(db).scope_kind().is_function_like() + } + + pub(crate) fn is_type_parameter(self, db: &'db dyn Db) -> bool { + self.node(db).scope_kind().is_type_parameter() + } + + pub(crate) fn node(self, db: &dyn Db) -> &NodeWithScopeKind { + self.scope(db).node() + } + + pub(crate) fn scope(self, db: &dyn Db) -> &Scope { + semantic_index(db, self.file(db)).scope(self.file_scope_id(db)) + } + + #[cfg(test)] + pub(crate) fn name<'ast>(self, db: &'db dyn Db, module: &'ast ParsedModuleRef) -> &'ast str { + match self.node(db) { + NodeWithScopeKind::Module => "", + NodeWithScopeKind::Class(class) | NodeWithScopeKind::ClassTypeParameters(class) => { + class.node(module).name.as_str() + } + NodeWithScopeKind::Function(function) + | NodeWithScopeKind::FunctionTypeParameters(function) => { + function.node(module).name.as_str() + } + NodeWithScopeKind::TypeAlias(type_alias) + | NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => type_alias + .node(module) + .name + .as_name_expr() + .map(|name| name.id.as_str()) + .unwrap_or(""), + NodeWithScopeKind::Lambda(_) => "", + NodeWithScopeKind::ListComprehension(_) => "", + NodeWithScopeKind::SetComprehension(_) => "", + NodeWithScopeKind::DictComprehension(_) => "", + NodeWithScopeKind::GeneratorExpression(_) => "", + } + } +} + +/// ID that uniquely identifies a scope inside of a module. +#[newtype_index] +#[derive(salsa::Update, get_size2::GetSize)] +pub struct FileScopeId; + +impl FileScopeId { + /// Returns the scope id of the module-global scope. + pub fn global() -> Self { + FileScopeId::from_u32(0) + } + + pub fn is_global(self) -> bool { + self == FileScopeId::global() + } + + pub fn to_scope_id(self, db: &dyn Db, file: File) -> ScopeId<'_> { + let index = semantic_index(db, file); + index.scope_ids_by_scope[self] + } + + pub(crate) fn is_generator_function(self, index: &SemanticIndex) -> bool { + index.generator_functions.contains(&self) + } +} + +#[derive(Debug, salsa::Update, get_size2::GetSize)] +pub(crate) struct Scope { + /// The parent scope, if any. + parent: Option, + + /// The node that introduces this scope. + node: NodeWithScopeKind, + + /// The range of [`FileScopeId`]s that are descendants of this scope. + descendants: Range, + + /// The constraint that determines the reachability of this scope. + reachability: ScopedReachabilityConstraintId, + + /// Whether this scope is defined inside an `if TYPE_CHECKING:` block. + in_type_checking_block: bool, +} + +impl Scope { + pub(super) fn new( + parent: Option, + node: NodeWithScopeKind, + descendants: Range, + reachability: ScopedReachabilityConstraintId, + in_type_checking_block: bool, + ) -> Self { + Scope { + parent, + node, + descendants, + reachability, + in_type_checking_block, + } + } + + pub(crate) fn parent(&self) -> Option { + self.parent + } + + pub(crate) fn node(&self) -> &NodeWithScopeKind { + &self.node + } + + pub(crate) fn kind(&self) -> ScopeKind { + self.node().scope_kind() + } + + pub(crate) fn visibility(&self) -> ScopeVisibility { + self.kind().visibility() + } + + pub(crate) fn descendants(&self) -> Range { + self.descendants.clone() + } + + pub(super) fn extend_descendants(&mut self, children_end: FileScopeId) { + self.descendants = self.descendants.start..children_end; + } + + pub(crate) fn is_eager(&self) -> bool { + self.kind().is_eager() + } + + pub(crate) fn reachability(&self) -> ScopedReachabilityConstraintId { + self.reachability + } + + pub(crate) fn in_type_checking_block(&self) -> bool { + self.in_type_checking_block + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, get_size2::GetSize)] +pub(crate) enum ScopeVisibility { + /// The scope is private (e.g. function, type alias, comprehension scope). + Private, + /// The scope is public (e.g. module, class scope). + Public, +} + +impl ScopeVisibility { + pub(crate) const fn is_public(self) -> bool { + matches!(self, ScopeVisibility::Public) + } + + pub(crate) const fn is_private(self) -> bool { + matches!(self, ScopeVisibility::Private) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, get_size2::GetSize)] +pub(crate) enum ScopeLaziness { + /// The scope is evaluated lazily (e.g. function, type alias scope). + Lazy, + /// The scope is evaluated eagerly (e.g. module, class, comprehension scope). + Eager, +} + +impl ScopeLaziness { + pub(crate) const fn is_eager(self) -> bool { + matches!(self, ScopeLaziness::Eager) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum ScopeKind { + Module, + Annotation, + Class, + Function, + Lambda, + Comprehension, + TypeAlias, +} + +impl ScopeKind { + pub(crate) const fn is_eager(self) -> bool { + self.laziness().is_eager() + } + + pub(crate) const fn laziness(self) -> ScopeLaziness { + match self { + ScopeKind::Module | ScopeKind::Class | ScopeKind::Comprehension => ScopeLaziness::Eager, + ScopeKind::Annotation + | ScopeKind::Function + | ScopeKind::Lambda + | ScopeKind::TypeAlias => ScopeLaziness::Lazy, + } + } + + pub(crate) const fn visibility(self) -> ScopeVisibility { + match self { + ScopeKind::Module | ScopeKind::Class => ScopeVisibility::Public, + ScopeKind::Annotation + | ScopeKind::TypeAlias + | ScopeKind::Function + | ScopeKind::Lambda + | ScopeKind::Comprehension => ScopeVisibility::Private, + } + } + + pub(crate) const fn is_function_like(self) -> bool { + // Type parameter scopes behave like function scopes in terms of name resolution; CPython + // symbol table also uses the term "function-like" for these scopes. + matches!( + self, + ScopeKind::Annotation + | ScopeKind::Function + | ScopeKind::Lambda + | ScopeKind::TypeAlias + | ScopeKind::Comprehension + ) + } + + pub(crate) const fn is_class(self) -> bool { + matches!(self, ScopeKind::Class) + } + + pub(crate) const fn is_type_parameter(self) -> bool { + matches!(self, ScopeKind::Annotation | ScopeKind::TypeAlias) + } + + pub(crate) const fn is_non_lambda_function(self) -> bool { + matches!(self, ScopeKind::Function) + } +} + +/// Reference to a node that introduces a new scope. +#[derive(Copy, Clone, Debug)] +pub(crate) enum NodeWithScopeRef<'a> { + Module, + Class(&'a ast::StmtClassDef), + Function(&'a ast::StmtFunctionDef), + Lambda(&'a ast::ExprLambda), + FunctionTypeParameters(&'a ast::StmtFunctionDef), + ClassTypeParameters(&'a ast::StmtClassDef), + TypeAlias(&'a ast::StmtTypeAlias), + TypeAliasTypeParameters(&'a ast::StmtTypeAlias), + ListComprehension(&'a ast::ExprListComp), + SetComprehension(&'a ast::ExprSetComp), + DictComprehension(&'a ast::ExprDictComp), + GeneratorExpression(&'a ast::ExprGenerator), +} + +impl NodeWithScopeRef<'_> { + /// Converts the unowned reference to an owned [`NodeWithScopeKind`]. + /// + /// Note that node wrapped by `self` must be a child of `module`. + pub(super) fn to_kind(self, module: &ParsedModuleRef) -> NodeWithScopeKind { + match self { + NodeWithScopeRef::Module => NodeWithScopeKind::Module, + NodeWithScopeRef::Class(class) => { + NodeWithScopeKind::Class(AstNodeRef::new(module, class)) + } + NodeWithScopeRef::Function(function) => { + NodeWithScopeKind::Function(AstNodeRef::new(module, function)) + } + NodeWithScopeRef::TypeAlias(type_alias) => { + NodeWithScopeKind::TypeAlias(AstNodeRef::new(module, type_alias)) + } + NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => { + NodeWithScopeKind::TypeAliasTypeParameters(AstNodeRef::new(module, type_alias)) + } + NodeWithScopeRef::Lambda(lambda) => { + NodeWithScopeKind::Lambda(AstNodeRef::new(module, lambda)) + } + NodeWithScopeRef::FunctionTypeParameters(function) => { + NodeWithScopeKind::FunctionTypeParameters(AstNodeRef::new(module, function)) + } + NodeWithScopeRef::ClassTypeParameters(class) => { + NodeWithScopeKind::ClassTypeParameters(AstNodeRef::new(module, class)) + } + NodeWithScopeRef::ListComprehension(comprehension) => { + NodeWithScopeKind::ListComprehension(AstNodeRef::new(module, comprehension)) + } + NodeWithScopeRef::SetComprehension(comprehension) => { + NodeWithScopeKind::SetComprehension(AstNodeRef::new(module, comprehension)) + } + NodeWithScopeRef::DictComprehension(comprehension) => { + NodeWithScopeKind::DictComprehension(AstNodeRef::new(module, comprehension)) + } + NodeWithScopeRef::GeneratorExpression(generator) => { + NodeWithScopeKind::GeneratorExpression(AstNodeRef::new(module, generator)) + } + } + } + + pub(crate) fn node_key(self) -> NodeWithScopeKey { + match self { + NodeWithScopeRef::Module => NodeWithScopeKey::Module, + NodeWithScopeRef::Class(class) => NodeWithScopeKey::Class(NodeKey::from_node(class)), + NodeWithScopeRef::Function(function) => { + NodeWithScopeKey::Function(NodeKey::from_node(function)) + } + NodeWithScopeRef::Lambda(lambda) => { + NodeWithScopeKey::Lambda(NodeKey::from_node(lambda)) + } + NodeWithScopeRef::FunctionTypeParameters(function) => { + NodeWithScopeKey::FunctionTypeParameters(NodeKey::from_node(function)) + } + NodeWithScopeRef::ClassTypeParameters(class) => { + NodeWithScopeKey::ClassTypeParameters(NodeKey::from_node(class)) + } + NodeWithScopeRef::TypeAlias(type_alias) => { + NodeWithScopeKey::TypeAlias(NodeKey::from_node(type_alias)) + } + NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => { + NodeWithScopeKey::TypeAliasTypeParameters(NodeKey::from_node(type_alias)) + } + NodeWithScopeRef::ListComprehension(comprehension) => { + NodeWithScopeKey::ListComprehension(NodeKey::from_node(comprehension)) + } + NodeWithScopeRef::SetComprehension(comprehension) => { + NodeWithScopeKey::SetComprehension(NodeKey::from_node(comprehension)) + } + NodeWithScopeRef::DictComprehension(comprehension) => { + NodeWithScopeKey::DictComprehension(NodeKey::from_node(comprehension)) + } + NodeWithScopeRef::GeneratorExpression(generator) => { + NodeWithScopeKey::GeneratorExpression(NodeKey::from_node(generator)) + } + } + } +} + +/// Node that introduces a new scope. +#[derive(Clone, Debug, salsa::Update, get_size2::GetSize)] +pub(crate) enum NodeWithScopeKind { + Module, + Class(AstNodeRef), + ClassTypeParameters(AstNodeRef), + Function(AstNodeRef), + FunctionTypeParameters(AstNodeRef), + TypeAliasTypeParameters(AstNodeRef), + TypeAlias(AstNodeRef), + Lambda(AstNodeRef), + ListComprehension(AstNodeRef), + SetComprehension(AstNodeRef), + DictComprehension(AstNodeRef), + GeneratorExpression(AstNodeRef), +} + +impl NodeWithScopeKind { + pub(crate) const fn scope_kind(&self) -> ScopeKind { + match self { + Self::Module => ScopeKind::Module, + Self::Class(_) => ScopeKind::Class, + Self::Function(_) => ScopeKind::Function, + Self::Lambda(_) => ScopeKind::Lambda, + Self::FunctionTypeParameters(_) + | Self::ClassTypeParameters(_) + | Self::TypeAliasTypeParameters(_) => ScopeKind::Annotation, + Self::TypeAlias(_) => ScopeKind::TypeAlias, + Self::ListComprehension(_) + | Self::SetComprehension(_) + | Self::DictComprehension(_) + | Self::GeneratorExpression(_) => ScopeKind::Comprehension, + } + } + + pub(crate) fn expect_class<'ast>( + &self, + module: &'ast ParsedModuleRef, + ) -> &'ast ast::StmtClassDef { + match self { + Self::Class(class) => class.node(module), + _ => panic!("expected class"), + } + } + + pub(crate) fn as_class<'ast>( + &self, + module: &'ast ParsedModuleRef, + ) -> Option<&'ast ast::StmtClassDef> { + match self { + Self::Class(class) => Some(class.node(module)), + _ => None, + } + } + + pub(crate) fn expect_function<'ast>( + &self, + module: &'ast ParsedModuleRef, + ) -> &'ast ast::StmtFunctionDef { + self.as_function(module).expect("expected function") + } + + pub(crate) fn expect_type_alias<'ast>( + &self, + module: &'ast ParsedModuleRef, + ) -> &'ast ast::StmtTypeAlias { + match self { + Self::TypeAlias(type_alias) => type_alias.node(module), + _ => panic!("expected type alias"), + } + } + + pub(crate) fn as_function<'ast>( + &self, + module: &'ast ParsedModuleRef, + ) -> Option<&'ast ast::StmtFunctionDef> { + match self { + Self::Function(function) => Some(function.node(module)), + _ => None, + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)] +pub(crate) enum NodeWithScopeKey { + Module, + Class(NodeKey), + ClassTypeParameters(NodeKey), + Function(NodeKey), + FunctionTypeParameters(NodeKey), + TypeAlias(NodeKey), + TypeAliasTypeParameters(NodeKey), + Lambda(NodeKey), + ListComprehension(NodeKey), + SetComprehension(NodeKey), + DictComprehension(NodeKey), + GeneratorExpression(NodeKey), +} diff --git a/crates/ty_python_semantic/src/semantic_index/symbol.rs b/crates/ty_python_semantic/src/semantic_index/symbol.rs new file mode 100644 index 0000000000..a8564ea950 --- /dev/null +++ b/crates/ty_python_semantic/src/semantic_index/symbol.rs @@ -0,0 +1,237 @@ +use bitflags::bitflags; +use hashbrown::hash_table::Entry; +use ruff_index::{IndexVec, newtype_index}; +use ruff_python_ast::name::Name; +use rustc_hash::FxHasher; +use std::hash::{Hash as _, Hasher as _}; +use std::ops::{Deref, DerefMut}; + +/// Uniquely identifies a symbol in a given scope. +#[newtype_index] +#[derive(get_size2::GetSize)] +pub struct ScopedSymbolId; + +/// A symbol in a given scope. +#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize, salsa::Update)] +pub(crate) struct Symbol { + name: Name, + flags: SymbolFlags, +} + +impl std::fmt::Display for Symbol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.name.fmt(f) + } +} + +bitflags! { + /// Flags that can be queried to obtain information about a symbol in a given scope. + /// + /// See the doc-comment at the top of [`super::use_def`] for explanations of what it + /// means for a symbol to be *bound* as opposed to *declared*. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + struct SymbolFlags: u8 { + const IS_USED = 1 << 0; + const IS_BOUND = 1 << 1; + const IS_DECLARED = 1 << 2; + const MARKED_GLOBAL = 1 << 3; + const MARKED_NONLOCAL = 1 << 4; + const IS_REASSIGNED = 1 << 5; + } +} + +impl get_size2::GetSize for SymbolFlags {} + +impl Symbol { + pub(crate) const fn new(name: Name) -> Self { + Self { + name, + flags: SymbolFlags::empty(), + } + } + + pub(crate) fn name(&self) -> &Name { + &self.name + } + + /// Is the symbol used in its containing scope? + pub(crate) fn is_used(&self) -> bool { + self.flags.contains(SymbolFlags::IS_USED) + } + + /// Is the symbol given a value in its containing scope? + pub(crate) const fn is_bound(&self) -> bool { + self.flags.contains(SymbolFlags::IS_BOUND) + } + + /// Is the symbol declared in its containing scope? + pub(crate) fn is_declared(&self) -> bool { + self.flags.contains(SymbolFlags::IS_DECLARED) + } + + /// Is the symbol `global` its containing scope? + pub(crate) fn is_global(&self) -> bool { + self.flags.contains(SymbolFlags::MARKED_GLOBAL) + } + + /// Is the symbol `nonlocal` its containing scope? + pub(crate) fn is_nonlocal(&self) -> bool { + self.flags.contains(SymbolFlags::MARKED_NONLOCAL) + } + + pub(crate) const fn is_reassigned(&self) -> bool { + self.flags.contains(SymbolFlags::IS_REASSIGNED) + } + + pub(super) fn mark_global(&mut self) { + self.insert_flags(SymbolFlags::MARKED_GLOBAL); + } + + pub(super) fn mark_nonlocal(&mut self) { + self.insert_flags(SymbolFlags::MARKED_NONLOCAL); + } + + pub(super) fn mark_bound(&mut self) { + if self.is_bound() { + self.insert_flags(SymbolFlags::IS_REASSIGNED); + } + + self.insert_flags(SymbolFlags::IS_BOUND); + } + + pub(super) fn mark_used(&mut self) { + self.insert_flags(SymbolFlags::IS_USED); + } + + pub(super) fn mark_declared(&mut self) { + self.insert_flags(SymbolFlags::IS_DECLARED); + } + + fn insert_flags(&mut self, flags: SymbolFlags) { + self.flags.insert(flags); + } +} + +/// The symbols of a given scope. +/// +/// Allows lookup by name and a symbol's ID. +#[derive(Default, get_size2::GetSize)] +pub(super) struct SymbolTable { + symbols: IndexVec, + + /// Map from symbol name to its ID. + /// + /// Uses a hash table to avoid storing the name twice. + map: hashbrown::HashTable, +} + +impl SymbolTable { + /// Look up a symbol by its ID. + /// + /// ## Panics + /// If the ID is not valid for this symbol table. + #[track_caller] + pub(crate) fn symbol(&self, id: ScopedSymbolId) -> &Symbol { + &self.symbols[id] + } + + /// Look up a symbol by its ID, mutably. + /// + /// ## Panics + /// If the ID is not valid for this symbol table. + #[track_caller] + pub(crate) fn symbol_mut(&mut self, id: ScopedSymbolId) -> &mut Symbol { + &mut self.symbols[id] + } + + /// Look up the ID of a symbol by its name. + pub(crate) fn symbol_id(&self, name: &str) -> Option { + self.map + .find(Self::hash_name(name), |id| self.symbols[*id].name == name) + .copied() + } + + /// Iterate over the symbols in this symbol table. + pub(crate) fn iter(&self) -> std::slice::Iter { + self.symbols.iter() + } + + fn hash_name(name: &str) -> u64 { + let mut h = FxHasher::default(); + name.hash(&mut h); + h.finish() + } +} + +impl PartialEq for SymbolTable { + fn eq(&self, other: &Self) -> bool { + // It's sufficient to compare the symbols as the map is only a reverse lookup. + self.symbols == other.symbols + } +} + +impl Eq for SymbolTable {} + +impl std::fmt::Debug for SymbolTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("SymbolTable").field(&self.symbols).finish() + } +} + +#[derive(Debug, Default)] +pub(super) struct SymbolTableBuilder { + table: SymbolTable, +} + +impl SymbolTableBuilder { + /// Add a new symbol to this scope or update the flags if a symbol with the same name already exists. + pub(super) fn add(&mut self, mut symbol: Symbol) -> (ScopedSymbolId, bool) { + let hash = SymbolTable::hash_name(symbol.name()); + let entry = self.table.map.entry( + hash, + |id| &self.table.symbols[*id].name == symbol.name(), + |id| SymbolTable::hash_name(&self.table.symbols[*id].name), + ); + + match entry { + Entry::Occupied(entry) => { + let id = *entry.get(); + + if !symbol.flags.is_empty() { + self.symbols[id].flags.insert(symbol.flags); + } + + (id, false) + } + Entry::Vacant(entry) => { + symbol.name.shrink_to_fit(); + let id = self.table.symbols.push(symbol); + entry.insert(id); + (id, true) + } + } + } + + pub(super) fn build(self) -> SymbolTable { + let mut table = self.table; + table.symbols.shrink_to_fit(); + table + .map + .shrink_to_fit(|id| SymbolTable::hash_name(&table.symbols[*id].name)); + table + } +} + +impl Deref for SymbolTableBuilder { + type Target = SymbolTable; + + fn deref(&self) -> &Self::Target { + &self.table + } +} + +impl DerefMut for SymbolTableBuilder { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.table + } +} diff --git a/crates/ty_python_semantic/src/semantic_index/use_def.rs b/crates/ty_python_semantic/src/semantic_index/use_def.rs index 53701babe3..136287a5c1 100644 --- a/crates/ty_python_semantic/src/semantic_index/use_def.rs +++ b/crates/ty_python_semantic/src/semantic_index/use_def.rs @@ -251,20 +251,21 @@ use crate::node_key::NodeKey; use crate::place::BoundnessAnalysis; use crate::semantic_index::ast_ids::ScopedUseId; use crate::semantic_index::definition::{Definition, DefinitionState}; +use crate::semantic_index::member::ScopedMemberId; use crate::semantic_index::narrowing_constraints::{ ConstraintKey, NarrowingConstraints, NarrowingConstraintsBuilder, NarrowingConstraintsIterator, }; -use crate::semantic_index::place::{ - FileScopeId, PlaceExpr, PlaceExprWithFlags, ScopeKind, ScopedPlaceId, -}; +use crate::semantic_index::place::{PlaceExprRef, ScopedPlaceId}; use crate::semantic_index::predicate::{ Predicate, PredicateOrLiteral, Predicates, PredicatesBuilder, ScopedPredicateId, }; use crate::semantic_index::reachability_constraints::{ ReachabilityConstraints, ReachabilityConstraintsBuilder, ScopedReachabilityConstraintId, }; +use crate::semantic_index::scope::{FileScopeId, ScopeKind, ScopeLaziness}; +use crate::semantic_index::symbol::ScopedSymbolId; use crate::semantic_index::use_def::place_state::PreviousDefinitions; -use crate::semantic_index::{EnclosingSnapshotResult, ScopeLaziness, SemanticIndex}; +use crate::semantic_index::{EnclosingSnapshotResult, SemanticIndex}; use crate::types::{IntersectionBuilder, Truthiness, Type, infer_narrowing_constraint}; mod place_state; @@ -311,11 +312,17 @@ pub(crate) struct UseDefMap<'db> { /// bindings to that symbol. If there are any, the assignment is invalid. bindings_by_definition: FxHashMap, Bindings>, - /// [`PlaceState`] visible at end of scope for each place. - end_of_scope_places: IndexVec, + /// [`PlaceState`] visible at end of scope for each symbol. + end_of_scope_symbols: IndexVec, - /// All potentially reachable bindings and declarations, for each place. - reachable_definitions: IndexVec, + /// [`PlaceState`] visible at end of scope for each member. + end_of_scope_members: IndexVec, + + /// All potentially reachable bindings and declarations, for each symbol. + reachable_definitions_by_symbol: IndexVec, + + /// All potentially reachable bindings and declarations, for each member. + reachable_definitions_by_member: IndexVec, /// Snapshot of bindings in this scope that can be used to resolve a reference in a nested /// scope. @@ -361,7 +368,7 @@ impl<'db> UseDefMap<'db> { &self, constraint_key: ConstraintKey, enclosing_scope: FileScopeId, - expr: &PlaceExpr, + expr: PlaceExprRef, index: &'db SemanticIndex, ) -> ApplicableConstraints<'_, 'db> { match constraint_key { @@ -419,9 +426,29 @@ impl<'db> UseDefMap<'db> { pub(crate) fn end_of_scope_bindings( &self, place: ScopedPlaceId, + ) -> BindingWithConstraintsIterator<'_, 'db> { + match place { + ScopedPlaceId::Symbol(symbol) => self.end_of_scope_symbol_bindings(symbol), + ScopedPlaceId::Member(member) => self.end_of_scope_member_bindings(member), + } + } + + pub(crate) fn end_of_scope_symbol_bindings( + &self, + symbol: ScopedSymbolId, ) -> BindingWithConstraintsIterator<'_, 'db> { self.bindings_iterator( - self.end_of_scope_places[place].bindings(), + self.end_of_scope_symbols[symbol].bindings(), + BoundnessAnalysis::BasedOnUnboundVisibility, + ) + } + + pub(crate) fn end_of_scope_member_bindings( + &self, + member: ScopedMemberId, + ) -> BindingWithConstraintsIterator<'_, 'db> { + self.bindings_iterator( + self.end_of_scope_members[member].bindings(), BoundnessAnalysis::BasedOnUnboundVisibility, ) } @@ -430,10 +457,26 @@ impl<'db> UseDefMap<'db> { &self, place: ScopedPlaceId, ) -> BindingWithConstraintsIterator<'_, 'db> { - self.bindings_iterator( - &self.reachable_definitions[place].bindings, - BoundnessAnalysis::AssumeBound, - ) + match place { + ScopedPlaceId::Symbol(symbol) => self.all_reachable_symbol_bindings(symbol), + ScopedPlaceId::Member(member) => self.all_reachable_member_bindings(member), + } + } + + pub(crate) fn all_reachable_symbol_bindings( + &self, + symbol: ScopedSymbolId, + ) -> BindingWithConstraintsIterator<'_, 'db> { + let bindings = &self.reachable_definitions_by_symbol[symbol].bindings; + self.bindings_iterator(bindings, BoundnessAnalysis::AssumeBound) + } + + pub(crate) fn all_reachable_member_bindings( + &self, + symbol: ScopedMemberId, + ) -> BindingWithConstraintsIterator<'_, 'db> { + let bindings = &self.reachable_definitions_by_member[symbol].bindings; + self.bindings_iterator(bindings, BoundnessAnalysis::AssumeBound) } pub(crate) fn enclosing_snapshot( @@ -482,33 +525,69 @@ impl<'db> UseDefMap<'db> { &'map self, place: ScopedPlaceId, ) -> DeclarationsIterator<'map, 'db> { - let declarations = self.end_of_scope_places[place].declarations(); + match place { + ScopedPlaceId::Symbol(symbol) => self.end_of_scope_symbol_declarations(symbol), + ScopedPlaceId::Member(member) => self.end_of_scope_member_declarations(member), + } + } + + pub(crate) fn end_of_scope_symbol_declarations<'map>( + &'map self, + symbol: ScopedSymbolId, + ) -> DeclarationsIterator<'map, 'db> { + let declarations = self.end_of_scope_symbols[symbol].declarations(); self.declarations_iterator(declarations, BoundnessAnalysis::BasedOnUnboundVisibility) } + pub(crate) fn end_of_scope_member_declarations<'map>( + &'map self, + member: ScopedMemberId, + ) -> DeclarationsIterator<'map, 'db> { + let declarations = self.end_of_scope_members[member].declarations(); + self.declarations_iterator(declarations, BoundnessAnalysis::BasedOnUnboundVisibility) + } + + pub(crate) fn all_reachable_symbol_declarations( + &self, + symbol: ScopedSymbolId, + ) -> DeclarationsIterator<'_, 'db> { + let declarations = &self.reachable_definitions_by_symbol[symbol].declarations; + self.declarations_iterator(declarations, BoundnessAnalysis::AssumeBound) + } + + pub(crate) fn all_reachable_member_declarations( + &self, + member: ScopedMemberId, + ) -> DeclarationsIterator<'_, 'db> { + let declarations = &self.reachable_definitions_by_member[member].declarations; + self.declarations_iterator(declarations, BoundnessAnalysis::AssumeBound) + } + pub(crate) fn all_reachable_declarations( &self, place: ScopedPlaceId, ) -> DeclarationsIterator<'_, 'db> { - let declarations = &self.reachable_definitions[place].declarations; - self.declarations_iterator(declarations, BoundnessAnalysis::AssumeBound) + match place { + ScopedPlaceId::Symbol(symbol) => self.all_reachable_symbol_declarations(symbol), + ScopedPlaceId::Member(member) => self.all_reachable_member_declarations(member), + } } - pub(crate) fn all_end_of_scope_declarations<'map>( + pub(crate) fn all_end_of_scope_symbol_declarations<'map>( &'map self, - ) -> impl Iterator)> + 'map { - (0..self.end_of_scope_places.len()) - .map(ScopedPlaceId::from_usize) - .map(|place_id| (place_id, self.end_of_scope_declarations(place_id))) + ) -> impl Iterator)> + 'map { + self.end_of_scope_symbols + .indices() + .map(|symbol_id| (symbol_id, self.end_of_scope_symbol_declarations(symbol_id))) } - pub(crate) fn all_end_of_scope_bindings<'map>( + pub(crate) fn all_end_of_scope_symbol_bindings<'map>( &'map self, - ) -> impl Iterator)> + 'map + ) -> impl Iterator)> + 'map { - (0..self.end_of_scope_places.len()) - .map(ScopedPlaceId::from_usize) - .map(|place_id| (place_id, self.end_of_scope_bindings(place_id))) + self.end_of_scope_symbols + .indices() + .map(|symbol_id| (symbol_id, self.end_of_scope_symbol_bindings(symbol_id))) } /// This function is intended to be called only once inside `TypeInferenceBuilder::infer_function_body`. @@ -730,7 +809,8 @@ struct ReachableDefinitions { /// A snapshot of the definitions and constraints state at a particular point in control flow. #[derive(Clone, Debug)] pub(super) struct FlowSnapshot { - place_states: IndexVec, + symbol_states: IndexVec, + member_states: IndexVec, reachability: ScopedReachabilityConstraintId, } @@ -765,10 +845,14 @@ pub(super) struct UseDefMapBuilder<'db> { bindings_by_definition: FxHashMap, Bindings>, /// Currently live bindings and declarations for each place. - place_states: IndexVec, + symbol_states: IndexVec, + + member_states: IndexVec, /// All potentially reachable bindings and declarations, for each place. - reachable_definitions: IndexVec, + reachable_symbol_definitions: IndexVec, + + reachable_member_definitions: IndexVec, /// Snapshots of place states in this scope that can be used to resolve a reference in a /// nested scope. @@ -790,8 +874,10 @@ impl<'db> UseDefMapBuilder<'db> { node_reachability: FxHashMap::default(), declarations_by_binding: FxHashMap::default(), bindings_by_definition: FxHashMap::default(), - place_states: IndexVec::new(), - reachable_definitions: IndexVec::new(), + symbol_states: IndexVec::new(), + member_states: IndexVec::new(), + reachable_member_definitions: IndexVec::new(), + reachable_symbol_definitions: IndexVec::new(), enclosing_snapshots: EnclosingSnapshots::default(), is_class_scope, } @@ -800,7 +886,14 @@ impl<'db> UseDefMapBuilder<'db> { pub(super) fn mark_unreachable(&mut self) { self.reachability = ScopedReachabilityConstraintId::ALWAYS_FALSE; - for state in &mut self.place_states { + for state in &mut self.symbol_states { + state.record_reachability_constraint( + &mut self.reachability_constraints, + ScopedReachabilityConstraintId::ALWAYS_FALSE, + ); + } + + for state in &mut self.member_states { state.record_reachability_constraint( &mut self.reachability_constraints, ScopedReachabilityConstraintId::ALWAYS_FALSE, @@ -809,42 +902,73 @@ impl<'db> UseDefMapBuilder<'db> { } pub(super) fn add_place(&mut self, place: ScopedPlaceId) { - let new_place = self - .place_states - .push(PlaceState::undefined(self.reachability)); - debug_assert_eq!(place, new_place); - let new_place = self.reachable_definitions.push(ReachableDefinitions { - bindings: Bindings::unbound(self.reachability), - declarations: Declarations::undeclared(self.reachability), - }); - debug_assert_eq!(place, new_place); + match place { + ScopedPlaceId::Symbol(symbol) => { + let new_place = self + .symbol_states + .push(PlaceState::undefined(self.reachability)); + debug_assert_eq!(symbol, new_place); + let new_place = self + .reachable_symbol_definitions + .push(ReachableDefinitions { + bindings: Bindings::unbound(self.reachability), + declarations: Declarations::undeclared(self.reachability), + }); + debug_assert_eq!(symbol, new_place); + } + ScopedPlaceId::Member(member) => { + let new_place = self + .member_states + .push(PlaceState::undefined(self.reachability)); + debug_assert_eq!(member, new_place); + let new_place = self + .reachable_member_definitions + .push(ReachableDefinitions { + bindings: Bindings::unbound(self.reachability), + declarations: Declarations::undeclared(self.reachability), + }); + debug_assert_eq!(member, new_place); + } + } } - pub(super) fn record_binding( - &mut self, - place: ScopedPlaceId, - binding: Definition<'db>, - is_place_name: bool, - ) { + pub(super) fn record_binding(&mut self, place: ScopedPlaceId, binding: Definition<'db>) { + let bindings = match place { + ScopedPlaceId::Symbol(symbol) => self.symbol_states[symbol].bindings(), + ScopedPlaceId::Member(member) => self.member_states[member].bindings(), + }; + self.bindings_by_definition - .insert(binding, self.place_states[place].bindings().clone()); + .insert(binding, bindings.clone()); let def_id = self.all_definitions.push(DefinitionState::Defined(binding)); - let place_state = &mut self.place_states[place]; + let place_state = match place { + ScopedPlaceId::Symbol(symbol) => &mut self.symbol_states[symbol], + ScopedPlaceId::Member(member) => &mut self.member_states[member], + }; self.declarations_by_binding .insert(binding, place_state.declarations().clone()); place_state.record_binding( def_id, self.reachability, self.is_class_scope, - is_place_name, + place.is_symbol(), ); - self.reachable_definitions[place].bindings.record_binding( + let bindings = match place { + ScopedPlaceId::Symbol(symbol) => { + &mut self.reachable_symbol_definitions[symbol].bindings + } + ScopedPlaceId::Member(member) => { + &mut self.reachable_member_definitions[member].bindings + } + }; + + bindings.record_binding( def_id, self.reachability, self.is_class_scope, - is_place_name, + place.is_symbol(), PreviousDefinitions::AreKept, ); } @@ -869,7 +993,12 @@ impl<'db> UseDefMapBuilder<'db> { } let narrowing_constraint = predicate.into(); - for state in &mut self.place_states { + for state in &mut self.symbol_states { + state + .record_narrowing_constraint(&mut self.narrowing_constraints, narrowing_constraint); + } + + for state in &mut self.member_states { state .record_narrowing_constraint(&mut self.narrowing_constraints, narrowing_constraint); } @@ -880,8 +1009,8 @@ impl<'db> UseDefMapBuilder<'db> { /// This is only used for `*`-import reachability constraints, which are handled differently /// to most other reachability constraints. See the doc-comment for /// [`Self::record_and_negate_star_import_reachability_constraint`] for more details. - pub(super) fn single_place_snapshot(&self, place: ScopedPlaceId) -> PlaceState { - self.place_states[place].clone() + pub(super) fn single_symbol_place_snapshot(&self, symbol: ScopedSymbolId) -> PlaceState { + self.symbol_states[symbol].clone() } /// This method exists solely for handling `*`-import reachability constraints. @@ -916,7 +1045,7 @@ impl<'db> UseDefMapBuilder<'db> { pub(super) fn record_and_negate_star_import_reachability_constraint( &mut self, reachability_id: ScopedReachabilityConstraintId, - symbol: ScopedPlaceId, + symbol: ScopedSymbolId, pre_definition_state: PlaceState, ) { let negated_reachability_id = self @@ -924,17 +1053,17 @@ impl<'db> UseDefMapBuilder<'db> { .add_not_constraint(reachability_id); let mut post_definition_state = - std::mem::replace(&mut self.place_states[symbol], pre_definition_state); + std::mem::replace(&mut self.symbol_states[symbol], pre_definition_state); post_definition_state .record_reachability_constraint(&mut self.reachability_constraints, reachability_id); - self.place_states[symbol].record_reachability_constraint( + self.symbol_states[symbol].record_reachability_constraint( &mut self.reachability_constraints, negated_reachability_id, ); - self.place_states[symbol].merge( + self.symbol_states[symbol].merge( post_definition_state, &mut self.narrowing_constraints, &mut self.reachability_constraints, @@ -949,7 +1078,11 @@ impl<'db> UseDefMapBuilder<'db> { .reachability_constraints .add_and_constraint(self.reachability, constraint); - for state in &mut self.place_states { + for state in &mut self.symbol_states { + state.record_reachability_constraint(&mut self.reachability_constraints, constraint); + } + + for state in &mut self.member_states { state.record_reachability_constraint(&mut self.reachability_constraints, constraint); } } @@ -962,56 +1095,81 @@ impl<'db> UseDefMapBuilder<'db> { let def_id = self .all_definitions .push(DefinitionState::Defined(declaration)); - let place_state = &mut self.place_states[place]; + + let place_state = match place { + ScopedPlaceId::Symbol(symbol) => &mut self.symbol_states[symbol], + ScopedPlaceId::Member(member) => &mut self.member_states[member], + }; + self.bindings_by_definition .insert(declaration, place_state.bindings().clone()); place_state.record_declaration(def_id, self.reachability); - self.reachable_definitions[place] - .declarations - .record_declaration(def_id, self.reachability, PreviousDefinitions::AreKept); + let definitions = match place { + ScopedPlaceId::Symbol(symbol) => &mut self.reachable_symbol_definitions[symbol], + ScopedPlaceId::Member(member) => &mut self.reachable_member_definitions[member], + }; + + definitions.declarations.record_declaration( + def_id, + self.reachability, + PreviousDefinitions::AreKept, + ); } pub(super) fn record_declaration_and_binding( &mut self, place: ScopedPlaceId, definition: Definition<'db>, - is_place_name: bool, ) { // We don't need to store anything in self.bindings_by_declaration or // self.declarations_by_binding. let def_id = self .all_definitions .push(DefinitionState::Defined(definition)); - let place_state = &mut self.place_states[place]; + let place_state = match place { + ScopedPlaceId::Symbol(symbol) => &mut self.symbol_states[symbol], + ScopedPlaceId::Member(member) => &mut self.member_states[member], + }; place_state.record_declaration(def_id, self.reachability); place_state.record_binding( def_id, self.reachability, self.is_class_scope, - is_place_name, + place.is_symbol(), ); - self.reachable_definitions[place] - .declarations - .record_declaration(def_id, self.reachability, PreviousDefinitions::AreKept); - self.reachable_definitions[place].bindings.record_binding( + let reachable_definitions = match place { + ScopedPlaceId::Symbol(symbol) => &mut self.reachable_symbol_definitions[symbol], + ScopedPlaceId::Member(member) => &mut self.reachable_member_definitions[member], + }; + + reachable_definitions.declarations.record_declaration( + def_id, + self.reachability, + PreviousDefinitions::AreKept, + ); + reachable_definitions.bindings.record_binding( def_id, self.reachability, self.is_class_scope, - is_place_name, + place.is_symbol(), PreviousDefinitions::AreKept, ); } - pub(super) fn delete_binding(&mut self, place: ScopedPlaceId, is_place_name: bool) { + pub(super) fn delete_binding(&mut self, place: ScopedPlaceId) { let def_id = self.all_definitions.push(DefinitionState::Deleted); - let place_state = &mut self.place_states[place]; + let place_state = match place { + ScopedPlaceId::Symbol(symbol) => &mut self.symbol_states[symbol], + ScopedPlaceId::Member(member) => &mut self.member_states[member], + }; + place_state.record_binding( def_id, self.reachability, self.is_class_scope, - is_place_name, + place.is_symbol(), ); } @@ -1021,11 +1179,13 @@ impl<'db> UseDefMapBuilder<'db> { use_id: ScopedUseId, node_key: NodeKey, ) { + let bindings = match place { + ScopedPlaceId::Symbol(symbol) => &mut self.symbol_states[symbol].bindings(), + ScopedPlaceId::Member(member) => &mut self.member_states[member].bindings(), + }; // We have a use of a place; clone the current bindings for that place, and record them // as the live bindings for this use. - let new_use = self - .bindings_by_use - .push(self.place_states[place].bindings().clone()); + let new_use = self.bindings_by_use.push(bindings.clone()); debug_assert_eq!(use_id, new_use); // Track reachability of all uses of places to silence `unresolved-reference` @@ -1041,28 +1201,30 @@ impl<'db> UseDefMapBuilder<'db> { &mut self, enclosing_place: ScopedPlaceId, scope: ScopeKind, - enclosing_place_expr: &PlaceExprWithFlags, + enclosing_place_expr: PlaceExprRef, ) -> ScopedEnclosingSnapshotId { + let bindings = match enclosing_place { + ScopedPlaceId::Symbol(symbol) => self.symbol_states[symbol].bindings(), + ScopedPlaceId::Member(member) => self.member_states[member].bindings(), + }; + // Names bound in class scopes are never visible to nested scopes (but attributes/subscripts are visible), // so we never need to save eager scope bindings in a class scope. - if (scope.is_class() && enclosing_place_expr.is_name()) || !enclosing_place_expr.is_bound() - { + if (scope.is_class() && enclosing_place.is_symbol()) || !enclosing_place_expr.is_bound() { self.enclosing_snapshots.push(EnclosingSnapshot::Constraint( - self.place_states[enclosing_place] - .bindings() - .unbound_narrowing_constraint(), + bindings.unbound_narrowing_constraint(), )) } else { - self.enclosing_snapshots.push(EnclosingSnapshot::Bindings( - self.place_states[enclosing_place].bindings().clone(), - )) + self.enclosing_snapshots + .push(EnclosingSnapshot::Bindings(bindings.clone())) } } /// Take a snapshot of the current visible-places state. pub(super) fn snapshot(&self) -> FlowSnapshot { FlowSnapshot { - place_states: self.place_states.clone(), + symbol_states: self.symbol_states.clone(), + member_states: self.member_states.clone(), reachability: self.reachability, } } @@ -1072,18 +1234,23 @@ impl<'db> UseDefMapBuilder<'db> { // We never remove places from `place_states` (it's an IndexVec, and the place // IDs must line up), so the current number of known places must always be equal to or // greater than the number of known places in a previously-taken snapshot. - let num_places = self.place_states.len(); - debug_assert!(num_places >= snapshot.place_states.len()); + let num_symbols = self.symbol_states.len(); + let num_members = self.member_states.len(); + debug_assert!(num_symbols >= snapshot.symbol_states.len()); // Restore the current visible-definitions state to the given snapshot. - self.place_states = snapshot.place_states; + self.symbol_states = snapshot.symbol_states; + self.member_states = snapshot.member_states; self.reachability = snapshot.reachability; // If the snapshot we are restoring is missing some places we've recorded since, we need // to fill them in so the place IDs continue to line up. Since they don't exist in the // snapshot, the correct state to fill them in with is "undefined". - self.place_states - .resize(num_places, PlaceState::undefined(self.reachability)); + self.symbol_states + .resize(num_symbols, PlaceState::undefined(self.reachability)); + + self.member_states + .resize(num_members, PlaceState::undefined(self.reachability)); } /// Merge the given snapshot into the current state, reflecting that we might have taken either @@ -1108,10 +1275,29 @@ impl<'db> UseDefMapBuilder<'db> { // We never remove places from `place_states` (it's an IndexVec, and the place // IDs must line up), so the current number of known places must always be equal to or // greater than the number of known places in a previously-taken snapshot. - debug_assert!(self.place_states.len() >= snapshot.place_states.len()); + debug_assert!(self.symbol_states.len() >= snapshot.symbol_states.len()); + debug_assert!(self.member_states.len() >= snapshot.member_states.len()); - let mut snapshot_definitions_iter = snapshot.place_states.into_iter(); - for current in &mut self.place_states { + let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter(); + for current in &mut self.symbol_states { + if let Some(snapshot) = snapshot_definitions_iter.next() { + current.merge( + snapshot, + &mut self.narrowing_constraints, + &mut self.reachability_constraints, + ); + } else { + current.merge( + PlaceState::undefined(snapshot.reachability), + &mut self.narrowing_constraints, + &mut self.reachability_constraints, + ); + // Place not present in snapshot, so it's unbound/undeclared from that path. + } + } + + let mut snapshot_definitions_iter = snapshot.member_states.into_iter(); + for current in &mut self.member_states { if let Some(snapshot) = snapshot_definitions_iter.next() { current.merge( snapshot, @@ -1142,10 +1328,21 @@ impl<'db> UseDefMapBuilder<'db> { for constraint in self.node_reachability.values() { self.reachability_constraints.mark_used(*constraint); } - for place_state in &mut self.place_states { - place_state.finish(&mut self.reachability_constraints); + for symbol_state in &mut self.symbol_states { + symbol_state.finish(&mut self.reachability_constraints); } - for reachable_definition in &mut self.reachable_definitions { + for member_state in &mut self.member_states { + member_state.finish(&mut self.reachability_constraints); + } + for reachable_definition in &mut self.reachable_symbol_definitions { + reachable_definition + .bindings + .finish(&mut self.reachability_constraints); + reachable_definition + .declarations + .finish(&mut self.reachability_constraints); + } + for reachable_definition in &mut self.reachable_member_definitions { reachable_definition .bindings .finish(&mut self.reachability_constraints); @@ -1169,8 +1366,10 @@ impl<'db> UseDefMapBuilder<'db> { self.mark_reachability_constraints(); self.all_definitions.shrink_to_fit(); - self.place_states.shrink_to_fit(); - self.reachable_definitions.shrink_to_fit(); + self.symbol_states.shrink_to_fit(); + self.member_states.shrink_to_fit(); + self.reachable_symbol_definitions.shrink_to_fit(); + self.reachable_member_definitions.shrink_to_fit(); self.bindings_by_use.shrink_to_fit(); self.node_reachability.shrink_to_fit(); self.declarations_by_binding.shrink_to_fit(); @@ -1184,8 +1383,10 @@ impl<'db> UseDefMapBuilder<'db> { reachability_constraints: self.reachability_constraints.build(), bindings_by_use: self.bindings_by_use, node_reachability: self.node_reachability, - end_of_scope_places: self.place_states, - reachable_definitions: self.reachable_definitions, + end_of_scope_symbols: self.symbol_states, + end_of_scope_members: self.member_states, + reachable_definitions_by_symbol: self.reachable_symbol_definitions, + reachable_definitions_by_member: self.reachable_member_definitions, declarations_by_binding: self.declarations_by_binding, bindings_by_definition: self.bindings_by_definition, enclosing_snapshots: self.enclosing_snapshots, diff --git a/crates/ty_python_semantic/src/semantic_model.rs b/crates/ty_python_semantic/src/semantic_model.rs index 06afb0b795..8816e1bf87 100644 --- a/crates/ty_python_semantic/src/semantic_model.rs +++ b/crates/ty_python_semantic/src/semantic_model.rs @@ -7,7 +7,7 @@ use ruff_source_file::LineIndex; use crate::Db; use crate::module_name::ModuleName; use crate::module_resolver::{KnownModule, Module, resolve_module}; -use crate::semantic_index::place::FileScopeId; +use crate::semantic_index::scope::FileScopeId; use crate::semantic_index::semantic_index; use crate::types::ide_support::all_declarations_and_bindings; use crate::types::{Type, binding_type, infer_scope_types}; diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index f3e9e18578..fd334ae74e 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -33,7 +33,8 @@ use crate::module_name::ModuleName; use crate::module_resolver::{KnownModule, resolve_module}; use crate::place::{Boundness, Place, PlaceAndQualifiers, imported_symbol}; use crate::semantic_index::definition::Definition; -use crate::semantic_index::place::{ScopeId, ScopedPlaceId}; +use crate::semantic_index::place::ScopedPlaceId; +use crate::semantic_index::scope::ScopeId; use crate::semantic_index::{imported_modules, place_table, semantic_index}; use crate::suppression::check_suppressions; use crate::types::call::{Binding, Bindings, CallArguments, CallableBinding}; @@ -8840,7 +8841,7 @@ impl<'db> TypeIsType<'db> { let (scope, place) = self.place_info(db)?; let table = place_table(db, scope); - Some(format!("{}", table.place_expr(place))) + Some(format!("{}", table.place(place))) } pub fn unbound(db: &'db dyn Db, ty: Type<'db>) -> Type<'db> { diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index 9a52fed994..7c201bf4e3 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -10,7 +10,7 @@ use super::{ infer_expression_type, infer_unpack_types, }; use crate::semantic_index::definition::{Definition, DefinitionState}; -use crate::semantic_index::place::NodeWithScopeKind; +use crate::semantic_index::scope::NodeWithScopeKind; use crate::semantic_index::{DeclarationWithConstraint, SemanticIndex, attribute_declarations}; use crate::types::context::InferContext; use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE}; @@ -36,8 +36,9 @@ use crate::{ semantic_index::{ attribute_assignments, definition::{DefinitionKind, TargetKind}, - place::ScopeId, - place_table, semantic_index, use_def_map, + place_table, + scope::ScopeId, + semantic_index, use_def_map, }, types::{ CallArguments, CallError, CallErrorKind, MetaclassCandidate, UnionBuilder, UnionType, @@ -1820,7 +1821,7 @@ impl<'db> ClassLiteral<'db> { let table = place_table(db, class_body_scope); let use_def = use_def_map(db, class_body_scope); - for (place_id, declarations) in use_def.all_end_of_scope_declarations() { + for (symbol_id, declarations) in use_def.all_end_of_scope_symbol_declarations() { // Here, we exclude all declarations that are not annotated assignments. We need this because // things like function definitions and nested classes would otherwise be considered dataclass // fields. The check is too broad in the sense that it also excludes (weird) constructs where @@ -1842,7 +1843,7 @@ impl<'db> ClassLiteral<'db> { continue; } - let place_expr = table.place_expr(place_id); + let symbol = table.symbol(symbol_id); if let Ok(attr) = place_from_declarations(db, declarations) { if attr.is_class_var() { @@ -1850,11 +1851,11 @@ impl<'db> ClassLiteral<'db> { } if let Some(attr_ty) = attr.place.ignore_possibly_unbound() { - let bindings = use_def.end_of_scope_bindings(place_id); + let bindings = use_def.end_of_scope_symbol_bindings(symbol_id); let default_ty = place_from_bindings(db, bindings).ignore_possibly_unbound(); attributes.insert( - place_expr.expect_name().clone(), + symbol.name().clone(), DataclassField { field_ty: attr_ty.apply_optional_specialization(db, specialization), default_ty: default_ty @@ -2041,9 +2042,9 @@ impl<'db> ClassLiteral<'db> { let is_method_reachable = if let Some(method_def) = method_scope.node(db).as_function(&module) { let method = index.expect_single_definition(method_def); - let method_place = class_table.place_id_by_name(&method_def.name).unwrap(); + let method_place = class_table.symbol_id(&method_def.name).unwrap(); class_map - .all_reachable_bindings(method_place) + .all_reachable_symbol_bindings(method_place) .find_map(|bind| { (bind.binding.is_defined_and(|def| def == method)) .then(|| class_map.is_binding_reachable(db, &bind)) @@ -2258,10 +2259,10 @@ impl<'db> ClassLiteral<'db> { let body_scope = self.body_scope(db); let table = place_table(db, body_scope); - if let Some(place_id) = table.place_id_by_name(name) { + if let Some(symbol_id) = table.symbol_id(name) { let use_def = use_def_map(db, body_scope); - let declarations = use_def.end_of_scope_declarations(place_id); + let declarations = use_def.end_of_scope_symbol_declarations(symbol_id); let declared_and_qualifiers = place_from_declarations(db, declarations); match declared_and_qualifiers { @@ -2288,7 +2289,7 @@ impl<'db> ClassLiteral<'db> { // The attribute is declared in the class body. - let bindings = use_def.end_of_scope_bindings(place_id); + let bindings = use_def.end_of_scope_symbol_bindings(symbol_id); let inferred = place_from_bindings(db, bindings); let has_binding = !inferred.is_unbound(); diff --git a/crates/ty_python_semantic/src/types/context.rs b/crates/ty_python_semantic/src/types/context.rs index bbf6b59b66..2230603d82 100644 --- a/crates/ty_python_semantic/src/types/context.rs +++ b/crates/ty_python_semantic/src/types/context.rs @@ -12,7 +12,7 @@ use ruff_text_size::{Ranged, TextRange}; use super::{Type, TypeCheckDiagnostics, binding_type}; use crate::lint::LintSource; -use crate::semantic_index::place::ScopeId; +use crate::semantic_index::scope::ScopeId; use crate::semantic_index::semantic_index; use crate::types::function::FunctionDecorators; use crate::{ diff --git a/crates/ty_python_semantic/src/types/enums.rs b/crates/ty_python_semantic/src/types/enums.rs index c10fcdce39..2195e73a92 100644 --- a/crates/ty_python_semantic/src/types/enums.rs +++ b/crates/ty_python_semantic/src/types/enums.rs @@ -82,9 +82,8 @@ pub(crate) fn enum_metadata<'db>( // TODO: handle `StrEnum` which uses lowercase names as values when using `auto()`. let mut auto_counter = 0; - let ignored_names: Option> = if let Some(ignore) = table.place_id_by_name("_ignore_") - { - let ignore_bindings = use_def_map.all_reachable_bindings(ignore); + let ignored_names: Option> = if let Some(ignore) = table.symbol_id("_ignore_") { + let ignore_bindings = use_def_map.all_reachable_symbol_bindings(ignore); let ignore_place = place_from_bindings(db, ignore_bindings); match ignore_place { @@ -101,9 +100,9 @@ pub(crate) fn enum_metadata<'db>( let mut aliases = FxHashMap::default(); let members = use_def_map - .all_end_of_scope_bindings() - .filter_map(|(place_id, bindings)| { - let name = table.place_expr(place_id).as_name()?; + .all_end_of_scope_symbol_bindings() + .filter_map(|(symbol_id, bindings)| { + let name = table.symbol(symbol_id).name(); if name.starts_with("__") && !name.ends_with("__") { // Skip private attributes @@ -187,7 +186,7 @@ pub(crate) fn enum_metadata<'db>( } } - let declarations = use_def_map.end_of_scope_declarations(place_id); + let declarations = use_def_map.end_of_scope_symbol_declarations(symbol_id); let declared = place_from_declarations(db, declarations); match declared { @@ -213,9 +212,8 @@ pub(crate) fn enum_metadata<'db>( } } - Some(name) + Some(name.clone()) }) - .cloned() .collect::>(); if members.is_empty() { diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 4d96b61731..6478b346d0 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -62,7 +62,7 @@ use crate::module_resolver::{KnownModule, file_to_module}; use crate::place::{Boundness, Place, place_from_bindings}; use crate::semantic_index::ast_ids::HasScopedUseId; use crate::semantic_index::definition::Definition; -use crate::semantic_index::place::ScopeId; +use crate::semantic_index::scope::ScopeId; use crate::semantic_index::semantic_index; use crate::types::call::{Binding, CallArguments}; use crate::types::context::InferContext; diff --git a/crates/ty_python_semantic/src/types/ide_support.rs b/crates/ty_python_semantic/src/types/ide_support.rs index 1178076aa2..efbe151702 100644 --- a/crates/ty_python_semantic/src/types/ide_support.rs +++ b/crates/ty_python_semantic/src/types/ide_support.rs @@ -5,7 +5,7 @@ use crate::place::{ }; use crate::semantic_index::definition::Definition; use crate::semantic_index::definition::DefinitionKind; -use crate::semantic_index::place::ScopeId; +use crate::semantic_index::scope::ScopeId; use crate::semantic_index::{ attribute_scopes, global_scope, place_table, semantic_index, use_def_map, }; @@ -31,35 +31,33 @@ pub(crate) fn all_declarations_and_bindings<'db>( let table = place_table(db, scope_id); use_def_map - .all_end_of_scope_declarations() + .all_end_of_scope_symbol_declarations() .filter_map(move |(symbol_id, declarations)| { place_from_declarations(db, declarations) .ok() .and_then(|result| { - result.place.ignore_possibly_unbound().and_then(|ty| { - table - .place_expr(symbol_id) - .as_name() - .cloned() - .map(|name| Member { name, ty }) + result.place.ignore_possibly_unbound().map(|ty| { + let symbol = table.symbol(symbol_id); + Member { + name: symbol.name().clone(), + ty, + } }) }) }) - .chain( - use_def_map - .all_end_of_scope_bindings() - .filter_map(move |(symbol_id, bindings)| { - place_from_bindings(db, bindings) - .ignore_possibly_unbound() - .and_then(|ty| { - table - .place_expr(symbol_id) - .as_name() - .cloned() - .map(|name| Member { name, ty }) - }) - }), - ) + .chain(use_def_map.all_end_of_scope_symbol_bindings().filter_map( + move |(symbol_id, bindings)| { + place_from_bindings(db, bindings) + .ignore_possibly_unbound() + .map(|ty| { + let symbol = table.symbol(symbol_id); + Member { + name: symbol.name().clone(), + ty, + } + }) + }, + )) } struct AllMembers<'db> { @@ -164,10 +162,8 @@ impl<'db> AllMembers<'db> { let use_def_map = use_def_map(db, module_scope); let place_table = place_table(db, module_scope); - for (symbol_id, _) in use_def_map.all_end_of_scope_declarations() { - let Some(symbol_name) = place_table.place_expr(symbol_id).as_name() else { - continue; - }; + for (symbol_id, _) in use_def_map.all_end_of_scope_symbol_declarations() { + let symbol_name = place_table.symbol(symbol_id).name(); let Place::Type(ty, _) = imported_symbol(db, file, symbol_name, None).place else { continue; @@ -204,7 +200,7 @@ impl<'db> AllMembers<'db> { } self.members.insert(Member { - name: place_table.place_expr(symbol_id).expect_name().clone(), + name: symbol_name.clone(), ty, }); } @@ -276,7 +272,7 @@ impl<'db> AllMembers<'db> { let index = semantic_index(db, file); for function_scope_id in attribute_scopes(db, class_body_scope) { let place_table = index.place_table(function_scope_id); - for place_expr in place_table.places() { + for place_expr in place_table.members() { let Some(name) = place_expr.as_instance_attribute() else { continue; }; @@ -376,11 +372,11 @@ pub fn definition_kind_for_name<'db>( let place_table = index.place_table(file_scope); // Look up the place by name - let place_id = place_table.place_id_by_name(name_str)?; + let symbol_id = place_table.symbol_id(name_str)?; // Get the use-def map and look up definitions for this place let use_def_map = index.use_def_map(file_scope); - let declarations = use_def_map.all_reachable_declarations(place_id); + let declarations = use_def_map.all_reachable_symbol_declarations(symbol_id); // Find the first valid definition and return its kind for declaration in declarations { @@ -412,14 +408,14 @@ pub fn definitions_for_name<'db>( for (scope_id, _scope) in index.visible_ancestor_scopes(file_scope) { let place_table = index.place_table(scope_id); - let Some(place_id) = place_table.place_id_by_name(name_str) else { + let Some(symbol_id) = place_table.symbol_id(name_str) else { continue; // Name not found in this scope, try parent scope }; // Check if this place is marked as global or nonlocal - let place_expr = place_table.place_expr(place_id); - let is_global = place_expr.is_marked_global(); - let is_nonlocal = place_expr.is_marked_nonlocal(); + let place_expr = place_table.symbol(symbol_id); + let is_global = place_expr.is_global(); + let is_nonlocal = place_expr.is_nonlocal(); // TODO: The current algorithm doesn't return definintions or bindings // for other scopes that are outside of this scope hierarchy that target @@ -432,11 +428,12 @@ pub fn definitions_for_name<'db>( let global_scope_id = global_scope(db, file); let global_place_table = crate::semantic_index::place_table(db, global_scope_id); - if let Some(global_place_id) = global_place_table.place_id_by_name(name_str) { + if let Some(global_symbol_id) = global_place_table.symbol_id(name_str) { let global_use_def_map = crate::semantic_index::use_def_map(db, global_scope_id); - let global_bindings = global_use_def_map.all_reachable_bindings(global_place_id); + let global_bindings = + global_use_def_map.all_reachable_symbol_bindings(global_symbol_id); let global_declarations = - global_use_def_map.all_reachable_declarations(global_place_id); + global_use_def_map.all_reachable_symbol_declarations(global_symbol_id); for binding in global_bindings { if let Some(def) = binding.binding.definition() { @@ -462,8 +459,8 @@ pub fn definitions_for_name<'db>( let use_def_map = index.use_def_map(scope_id); // Get all definitions (both bindings and declarations) for this place - let bindings = use_def_map.all_reachable_bindings(place_id); - let declarations = use_def_map.all_reachable_declarations(place_id); + let bindings = use_def_map.all_reachable_symbol_bindings(symbol_id); + let declarations = use_def_map.all_reachable_symbol_declarations(symbol_id); for binding in bindings { if let Some(def) = binding.binding.definition() { @@ -577,11 +574,11 @@ pub fn definitions_for_attribute<'db>( let class_place_table = crate::semantic_index::place_table(db, class_scope); // Look for class-level declarations and bindings - if let Some(place_id) = class_place_table.place_id_by_name(name_str) { + if let Some(place_id) = class_place_table.symbol_id(name_str) { let use_def = use_def_map(db, class_scope); // Check declarations first - for decl in use_def.all_reachable_declarations(place_id) { + for decl in use_def.all_reachable_symbol_declarations(place_id) { if let Some(def) = decl.declaration.definition() { resolved.extend(resolve_definition(db, def, Some(name_str))); break 'scopes; @@ -589,7 +586,7 @@ pub fn definitions_for_attribute<'db>( } // If no declarations found, check bindings - for binding in use_def.all_reachable_bindings(place_id) { + for binding in use_def.all_reachable_symbol_bindings(place_id) { if let Some(def) = binding.binding.definition() { resolved.extend(resolve_definition(db, def, Some(name_str))); break 'scopes; @@ -604,11 +601,11 @@ pub fn definitions_for_attribute<'db>( for function_scope_id in attribute_scopes(db, class_scope) { let place_table = index.place_table(function_scope_id); - if let Some(place_id) = place_table.place_id_by_instance_attribute_name(name_str) { + if let Some(place_id) = place_table.member_id_by_instance_attribute_name(name_str) { let use_def = index.use_def_map(function_scope_id); // Check declarations first - for decl in use_def.all_reachable_declarations(place_id) { + for decl in use_def.all_reachable_member_declarations(place_id) { if let Some(def) = decl.declaration.definition() { resolved.extend(resolve_definition(db, def, Some(name_str))); break 'scopes; @@ -616,7 +613,7 @@ pub fn definitions_for_attribute<'db>( } // If no declarations found, check bindings - for binding in use_def.all_reachable_bindings(place_id) { + for binding in use_def.all_reachable_member_bindings(place_id) { if let Some(def) = binding.binding.definition() { resolved.extend(resolve_definition(db, def, Some(name_str))); break 'scopes; @@ -801,7 +798,7 @@ mod resolve_definition { use crate::module_resolver::file_to_module; use crate::semantic_index::definition::{Definition, DefinitionKind}; - use crate::semantic_index::place::{NodeWithScopeKind, ScopeId}; + use crate::semantic_index::scope::{NodeWithScopeKind, ScopeId}; use crate::semantic_index::{global_scope, place_table, semantic_index, use_def_map}; use crate::{Db, ModuleName, resolve_module, resolve_real_module}; @@ -971,7 +968,7 @@ mod resolve_definition { symbol_name: &str, ) -> IndexSet> { let place_table = place_table(db, scope); - let Some(place_id) = place_table.place_id_by_name(symbol_name) else { + let Some(symbol_id) = place_table.symbol_id(symbol_name) else { return IndexSet::new(); }; @@ -979,8 +976,8 @@ mod resolve_definition { let mut definitions = IndexSet::new(); // Get all definitions (both bindings and declarations) for this place - let bindings = use_def_map.all_reachable_bindings(place_id); - let declarations = use_def_map.all_reachable_declarations(place_id); + let bindings = use_def_map.all_reachable_symbol_bindings(symbol_id); + let declarations = use_def_map.all_reachable_symbol_declarations(symbol_id); for binding in bindings { if let Some(def) = binding.binding.definition() { diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index ea8c0381c4..3d7c128106 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -81,9 +81,11 @@ use crate::semantic_index::definition::{ }; use crate::semantic_index::expression::{Expression, ExpressionKind}; use crate::semantic_index::narrowing_constraints::ConstraintKey; -use crate::semantic_index::place::{ - FileScopeId, NodeWithScopeKind, NodeWithScopeRef, PlaceExpr, ScopeId, ScopeKind, ScopedPlaceId, +use crate::semantic_index::place::{PlaceExpr, PlaceExprRef}; +use crate::semantic_index::scope::{ + FileScopeId, NodeWithScopeKind, NodeWithScopeRef, ScopeId, ScopeKind, }; +use crate::semantic_index::symbol::ScopedSymbolId; use crate::semantic_index::{ ApplicableConstraints, EnclosingSnapshotResult, SemanticIndex, place_table, semantic_index, }; @@ -1415,7 +1417,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { for place in overloaded_function_places { if let Place::Type(Type::FunctionLiteral(function), Boundness::Bound) = - place_from_bindings(self.db(), use_def.end_of_scope_bindings(place)) + place_from_bindings( + self.db(), + use_def.end_of_scope_symbol_bindings(place.as_symbol().unwrap()), + ) { if function.file(self.db()) != self.file() { // If the function is not in this file, we don't need to check it. @@ -1779,67 +1784,74 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let global_use_def_map = self.index.use_def_map(FileScopeId::global()); let nonlocal_use_def_map; let place_id = binding.place(self.db()); - let place = place_table.place_expr(place_id); - let skip_non_global_scopes = self.skip_non_global_scopes(file_scope_id, place_id); - let (declarations, is_local) = if skip_non_global_scopes { - match self + let place = place_table.place(place_id); + + let (declarations, is_local) = if let Some(symbol) = place.as_symbol() { + let symbol_id = place_id.expect_symbol(); + let skip_non_global_scopes = self.skip_non_global_scopes(file_scope_id, symbol_id); + + if skip_non_global_scopes { + match self + .index + .place_table(FileScopeId::global()) + .symbol_id(symbol.name()) + { + Some(id) => ( + global_use_def_map.end_of_scope_symbol_declarations(id), + false, + ), + // This variable shows up in `global` declarations but doesn't have an explicit + // binding in the global scope. + None => (use_def.declarations_at_binding(binding), true), + } + } else if self .index - .place_table(FileScopeId::global()) - .place_id_by_expr(&place.expr) + .symbol_is_nonlocal_in_scope(symbol_id, file_scope_id) { - Some(id) => (global_use_def_map.end_of_scope_declarations(id), false), - // This variable shows up in `global` declarations but doesn't have an explicit - // binding in the global scope. - None => (use_def.declarations_at_binding(binding), true), - } - } else if self - .index - .symbol_is_nonlocal_in_scope(place_id, file_scope_id) - { - // If we run out of ancestor scopes without finding a definition, we'll fall back to - // the local scope. This will also be a syntax error in `infer_nonlocal_statement` (no - // binding for `nonlocal` found), but ignore that here. - let mut declarations = use_def.declarations_at_binding(binding); - let mut is_local = true; - // Walk up parent scopes looking for the enclosing scope that has definition of this - // name. `ancestor_scopes` includes the current scope, so skip that one. - for (enclosing_scope_file_id, enclosing_scope) in - self.index.ancestor_scopes(file_scope_id).skip(1) - { - // Ignore class scopes and the global scope. - if !enclosing_scope.kind().is_function_like() { - continue; - } - let enclosing_place_table = self.index.place_table(enclosing_scope_file_id); - let Some(enclosing_place_id) = enclosing_place_table.place_id_by_expr(&place.expr) - else { - // This ancestor scope doesn't have a binding. Keep going. - continue; - }; - if self - .index - .symbol_is_nonlocal_in_scope(enclosing_place_id, enclosing_scope_file_id) + // If we run out of ancestor scopes without finding a definition, we'll fall back to + // the local scope. This will also be a syntax error in `infer_nonlocal_statement` (no + // binding for `nonlocal` found), but ignore that here. + let mut declarations = use_def.declarations_at_binding(binding); + let mut is_local = true; + // Walk up parent scopes looking for the enclosing scope that has definition of this + // name. `ancestor_scopes` includes the current scope, so skip that one. + for (enclosing_scope_file_id, enclosing_scope) in + self.index.ancestor_scopes(file_scope_id).skip(1) { - // The variable is `nonlocal` in this ancestor scope. Keep going. - continue; - } - if self - .index - .symbol_is_global_in_scope(enclosing_place_id, enclosing_scope_file_id) - { - // The variable is `global` in this ancestor scope. This breaks the `nonlocal` - // chain, and it's a syntax error in `infer_nonlocal_statement`. Ignore that - // here and just bail out of this loop. + // Ignore class scopes and the global scope. + if !enclosing_scope.kind().is_function_like() { + continue; + } + let enclosing_place_table = self.index.place_table(enclosing_scope_file_id); + let Some(enclosing_symbol_id) = enclosing_place_table.symbol_id(symbol.name()) + else { + // This ancestor scope doesn't have a binding. Keep going. + continue; + }; + + let enclosing_symbol = enclosing_place_table.symbol(enclosing_symbol_id); + if enclosing_symbol.is_nonlocal() { + // The variable is `nonlocal` in this ancestor scope. Keep going. + continue; + } + if enclosing_symbol.is_global() { + // The variable is `global` in this ancestor scope. This breaks the `nonlocal` + // chain, and it's a syntax error in `infer_nonlocal_statement`. Ignore that + // here and just bail out of this loop. + break; + } + // 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_symbol_declarations(enclosing_symbol_id); + is_local = false; break; } - // 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; - break; + (declarations, is_local) + } else { + (use_def.declarations_at_binding(binding), true) } - (declarations, is_local) } else { (use_def.declarations_at_binding(binding), true) }; @@ -1849,12 +1861,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { Ok( if matches!(place_and_quals.place, Place::Type(_, Boundness::Bound)) { place_and_quals - } else if skip_non_global_scopes - || self.scope().file_scope_id(self.db()).is_global() - { - let module_type_declarations = - module_type_implicit_global_declaration(self.db(), &place.expr)?; - place_and_quals.or_fall_back_to(self.db(), || module_type_declarations) + } else if let PlaceExprRef::Symbol(symbol) = place { + let symbol_id = place_id.expect_symbol(); + + if self.skip_non_global_scopes(file_scope_id, symbol_id) + || self.scope.file_scope_id(self.db()).is_global() + { + let module_type_declarations = + module_type_implicit_global_declaration(self.db(), symbol.name())?; + place_and_quals.or_fall_back_to(self.db(), || module_type_declarations) + } else { + place_and_quals + } } else { place_and_quals }, @@ -1867,7 +1885,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { }| { let is_modifiable = !qualifiers.contains(TypeQualifiers::FINAL); - if resolved_place.is_unbound() && !place_table.place_expr(place_id).is_name() { + if resolved_place.is_unbound() && !place_table.place(place_id).is_symbol() { if let AnyNodeRef::ExprAttribute(ast::ExprAttribute { value, attr, .. }) = node @@ -1902,7 +1920,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ) .unwrap_or_else(|(ty, conflicting)| { // TODO point out the conflicting declarations in the diagnostic? - let place = place_table.place_expr(binding.place(db)); + let place = place_table.place(binding.place(db)); if let Some(builder) = self.context.report_lint(&CONFLICTING_DECLARATIONS, node) { builder.into_diagnostic(format_args!( "Conflicting declared types for `{place}`: {}", @@ -1925,7 +1943,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .and_then(|r| r.binding.definition()); if !is_local || previous_definition.is_some() { - let place = place_table.place_expr(binding.place(db)); + let place = place_table.place(binding.place(db)); if let Some(builder) = self.context.report_lint( &INVALID_ASSIGNMENT, binding.full_range(self.db(), self.module()), @@ -2026,7 +2044,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { /// Returns `true` if `symbol_id` should be looked up in the global scope, skipping intervening /// local scopes. - fn skip_non_global_scopes(&self, file_scope_id: FileScopeId, symbol_id: ScopedPlaceId) -> bool { + fn skip_non_global_scopes( + &self, + file_scope_id: FileScopeId, + symbol_id: ScopedSymbolId, + ) -> bool { !file_scope_id.is_global() && self .index @@ -2054,9 +2076,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // Fallback to bindings declared on `types.ModuleType` if it's a global symbol let scope = self.scope().file_scope_id(self.db()); let place_table = self.index.place_table(scope); - let place = place_table.place_expr(declaration.place(self.db())); - if scope.is_global() && place.is_name() { - module_type_implicit_global_symbol(self.db(), place.expect_name()) + let place = place_table.place(declaration.place(self.db())); + if let PlaceExprRef::Symbol(symbol) = &place { + if scope.is_global() { + module_type_implicit_global_symbol(self.db(), symbol.name()) + } else { + Place::Unbound.into() + } } else { Place::Unbound.into() } @@ -2107,11 +2133,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let file_scope_id = self.scope().file_scope_id(self.db()); if file_scope_id.is_global() { let place_table = self.index.place_table(file_scope_id); - let place = place_table.place_expr(definition.place(self.db())); - if let Some(module_type_implicit_declaration) = - module_type_implicit_global_declaration(self.db(), &place.expr) - .ok() - .and_then(|place| place.place.ignore_possibly_unbound()) + let place = place_table.place(definition.place(self.db())); + if let Some(module_type_implicit_declaration) = place + .as_symbol() + .map(|symbol| module_type_implicit_global_symbol(self.db(), symbol.name())) + .and_then(|place| place.place.ignore_possibly_unbound()) { let declared_type = declared_ty.inner_type(); if !declared_type @@ -4360,7 +4386,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // If the target of an assignment is not one of the place expressions we support, // then they are not definitions, so we can only be here if the target is in a form supported as a place expression. // In this case, we can simply store types in `target` below, instead of calling `infer_expression` (which would return `Never`). - debug_assert!(PlaceExpr::try_from(target).is_ok()); + debug_assert!(PlaceExpr::try_from_expr(target).is_some()); if let Some(value) = value { let inferred_ty = self.infer_maybe_standalone_expression(value); @@ -4874,16 +4900,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .kind(self.db()) .as_star_import() .map(|star_import| { - let symbol_table = self + let place_table = self .index .place_table(self.scope().file_scope_id(self.db())); - (star_import, symbol_table) + (star_import, place_table) }); let name = if let Some((star_import, symbol_table)) = star_import_info.as_ref() { - symbol_table - .place_expr(star_import.place_id()) - .expect_name() + symbol_table.symbol(star_import.symbol_id()).name() } else { &alias.name.id }; @@ -5041,9 +5065,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } = global; let global_place_table = self.index.place_table(FileScopeId::global()); for name in names { - if let Some(place_id) = global_place_table.place_id_by_name(name) { - let place = global_place_table.place_expr(place_id); - if place.is_bound() || place.is_declared() { + if let Some(symbol_id) = global_place_table.symbol_id(name) { + let symbol = global_place_table.symbol(symbol_id); + if symbol.is_bound() || symbol.is_declared() { // This name is explicitly defined in the global scope (not just in function // bodies that mark it `global`). continue; @@ -5095,11 +5119,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { continue; } let enclosing_place_table = self.index.place_table(enclosing_scope_file_id); - let Some(enclosing_place_id) = enclosing_place_table.place_id_by_name(name) else { + let Some(enclosing_symbol_id) = enclosing_place_table.symbol_id(name) else { // This scope doesn't define this name. Keep going. continue; }; - let enclosing_place = enclosing_place_table.place_expr(enclosing_place_id); + let enclosing_symbol = enclosing_place_table.symbol(enclosing_symbol_id); // We've found a definition for this name in an enclosing function-like scope. // Either this definition is the valid place this name refers to, or else we'll // emit a syntax error. Either way, we won't walk any more enclosing scopes. Note @@ -5110,16 +5134,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // `nonlocal` keyword can't refer to global variables (that's a `SyntaxError`), and // it also can't refer to local variables in enclosing functions that are declared // `global` (also a `SyntaxError`). - if enclosing_place.is_marked_global() { + if enclosing_symbol.is_global() { // A "chain" of `nonlocal` statements is "broken" by a `global` statement. Stop // looping and report that this `nonlocal` statement is invalid. break; } - if !enclosing_place.is_bound() - && !enclosing_place.is_declared() - && !enclosing_place.is_marked_nonlocal() + if !enclosing_symbol.is_bound() + && !enclosing_symbol.is_declared() + && !enclosing_symbol.is_nonlocal() { - debug_assert!(enclosing_place.is_used()); + debug_assert!(enclosing_symbol.is_used()); // The name is only referenced here, not defined. Keep going. continue; } @@ -6041,9 +6065,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } None } - Some(expr) => match PlaceExpr::try_from(expr) { - Ok(place_expr) => place_table(db, scope).place_id_by_expr(&place_expr), - Err(()) => None, + Some(expr) => match PlaceExpr::try_from_expr(expr) { + Some(place_expr) => place_table(db, scope).place_id(&place_expr), + None => None, }, }; @@ -6121,7 +6145,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // Perform narrowing with applicable constraints between the current scope and the enclosing scope. fn narrow_place_with_applicable_constraints( &self, - expr: &PlaceExpr, + expr: PlaceExprRef, mut ty: Type<'db>, constraint_keys: &[(FileScopeId, ConstraintKey)], ) -> Type<'db> { @@ -6129,7 +6153,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { for (enclosing_scope_file_id, constraint_key) in constraint_keys { let use_def = self.index.use_def_map(*enclosing_scope_file_id); let place_table = self.index.place_table(*enclosing_scope_file_id); - let place = place_table.place_id_by_expr(expr).unwrap(); + let place = place_table.place_id(expr).unwrap(); match use_def.applicable_constraints( *constraint_key, @@ -6258,11 +6282,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { id: symbol_name, ctx: _, } = name_node; - let Ok(expr) = PlaceExpr::try_from(symbol_name); + let expr = PlaceExpr::from_expr_name(name_node); let db = self.db(); let (resolved, constraint_keys) = - self.infer_place_load(&expr, ast::ExprRef::Name(name_node)); + self.infer_place_load(PlaceExprRef::from(&expr), ast::ExprRef::Name(name_node)); resolved // Not found in the module's explicitly declared global symbols? @@ -6270,7 +6294,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // These are looked up as attributes on `types.ModuleType`. .or_fall_back_to(db, || { module_type_implicit_global_symbol(db, symbol_name).map_type(|ty| { - self.narrow_place_with_applicable_constraints(&expr, ty, &constraint_keys) + self.narrow_place_with_applicable_constraints( + PlaceExprRef::from(&expr), + ty, + &constraint_keys, + ) }) }) // Not found in globals? Fallback to builtins @@ -6314,7 +6342,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { fn infer_local_place_load( &self, - expr: &PlaceExpr, + expr: PlaceExprRef, expr_ref: ast::ExprRef, ) -> (Place<'db>, Option) { let db = self.db(); @@ -6325,7 +6353,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // If we're inferring types of deferred expressions, always treat them as public symbols if self.is_deferred() { - let place = if let Some(place_id) = place_table.place_id_by_expr(expr) { + let place = if let Some(place_id) = place_table.place_id(expr) { place_from_bindings(db, use_def.all_reachable_bindings(place_id)) } else { assert!( @@ -6355,7 +6383,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { /// which is used to narrow by condition rather than by assignment. fn infer_place_load( &self, - expr: &PlaceExpr, + place_expr: PlaceExprRef, expr_ref: ast::ExprRef, ) -> (PlaceAndQualifiers<'db>, Vec<(FileScopeId, ConstraintKey)>) { let db = self.db(); @@ -6364,14 +6392,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let place_table = self.index.place_table(file_scope_id); let mut constraint_keys = vec![]; - let (local_scope_place, use_id) = self.infer_local_place_load(expr, expr_ref); + let (local_scope_place, use_id) = self.infer_local_place_load(place_expr, expr_ref); if let Some(use_id) = use_id { constraint_keys.push((file_scope_id, ConstraintKey::UseId(use_id))); } let place = PlaceAndQualifiers::from(local_scope_place).or_fall_back_to(db, || { - let has_bindings_in_this_scope = match place_table.place_by_expr(expr) { - Some(place_expr) => place_expr.is_bound(), + let has_bindings_in_this_scope = match place_table.place_id(place_expr) { + Some(place_id) => place_table.place(place_id).is_bound(), None => { assert!( self.deferred_state.in_string_annotation(), @@ -6384,16 +6412,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let current_file = self.file(); let mut is_nonlocal_binding = false; - if let Some(name) = expr.as_name() { - if let Some(symbol_id) = place_table.place_id_by_name(name) { + if let Some(symbol) = place_expr.as_symbol() { + if let Some(symbol_id) = place_table.symbol_id(symbol.name()) { if self.skip_non_global_scopes(file_scope_id, symbol_id) { - return global_symbol(self.db(), self.file(), name).map_type(|ty| { - self.narrow_place_with_applicable_constraints( - expr, - ty, - &constraint_keys, - ) - }); + return global_symbol(self.db(), self.file(), symbol.name()).map_type( + |ty| { + self.narrow_place_with_applicable_constraints( + place_expr, + ty, + &constraint_keys, + ) + }, + ); } is_nonlocal_binding = self .index @@ -6414,9 +6444,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { return Place::Unbound.into(); } - for root_expr in place_table.root_place_exprs(expr) { + for parent_id in place_table.parents(place_expr) { + let parent_expr = place_table.place(parent_id); let mut expr_ref = expr_ref; - for _ in 0..(expr.sub_segments().len() - root_expr.expr.sub_segments().len()) { + for _ in 0..(place_expr.num_member_segments() - parent_expr.num_member_segments()) { match expr_ref { ast::ExprRef::Attribute(attribute) => { expr_ref = ast::ExprRef::from(&attribute.value); @@ -6427,8 +6458,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { _ => unreachable!(), } } - let (parent_place, _use_id) = - self.infer_local_place_load(&root_expr.expr, expr_ref); + let (parent_place, _use_id) = self.infer_local_place_load(parent_expr, expr_ref); if let Place::Type(_, _) = parent_place { return Place::Unbound.into(); } @@ -6463,7 +6493,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if !self.is_deferred() { match self.index.enclosing_snapshot( enclosing_scope_file_id, - expr, + place_expr, file_scope_id, ) { EnclosingSnapshotResult::FoundConstraint(constraint) => { @@ -6473,7 +6503,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { )); } EnclosingSnapshotResult::FoundBindings(bindings) => { - if expr.is_name() + if place_expr.is_symbol() && !enclosing_scope_id.is_function_like(db) && !is_immediately_enclosing_scope { @@ -6481,7 +6511,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } let place = place_from_bindings(db, bindings).map_type(|ty| { self.narrow_place_with_applicable_constraints( - expr, + place_expr, ty, &constraint_keys, ) @@ -6497,13 +6527,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { EnclosingSnapshotResult::NotFound => { let enclosing_place_table = self.index.place_table(enclosing_scope_file_id); - for enclosing_root_place in enclosing_place_table.root_place_exprs(expr) + for enclosing_root_place_id in enclosing_place_table.parents(place_expr) { + let enclosing_root_place = + enclosing_place_table.place(enclosing_root_place_id); if enclosing_root_place.is_bound() { if let Place::Type(_, _) = place( db, enclosing_scope_id, - &enclosing_root_place.expr, + enclosing_root_place, ConsideredDefinitions::AllReachable, ) .place @@ -6523,17 +6555,22 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } let enclosing_place_table = self.index.place_table(enclosing_scope_file_id); - let Some(enclosing_place) = enclosing_place_table.place_by_expr(expr) else { + let Some(enclosing_place_id) = enclosing_place_table.place_id(place_expr) else { continue; }; + let enclosing_place = enclosing_place_table.place(enclosing_place_id); + // Reads of "free" variables terminate at any enclosing scope that marks the // variable `global`, whether or not that scope actually binds the variable. If we // see a `global` declaration, stop walking scopes and proceed to the global // handling below. (If we're walking from a prior/inner scope where this variable // is `nonlocal`, then this is a semantic syntax error, but we don't enforce that // here. See `infer_nonlocal_statement`.) - if enclosing_place.is_marked_global() { + if enclosing_place + .as_symbol() + .is_some_and(super::super::semantic_index::symbol::Symbol::is_global) + { break; } @@ -6547,11 +6584,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let local_place_and_qualifiers = place( db, enclosing_scope_id, - expr, + place_expr, ConsideredDefinitions::AllReachable, ) .map_type(|ty| { - self.narrow_place_with_applicable_constraints(expr, ty, &constraint_keys) + self.narrow_place_with_applicable_constraints( + place_expr, + ty, + &constraint_keys, + ) }); // We could have Place::Unbound here, despite the checks above, for example if // this scope contains a `del` statement but no binding or declaration. @@ -6562,7 +6603,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { found_some_definition = true; } - if !enclosing_place.is_marked_nonlocal() { + if !enclosing_place + .as_symbol() + .is_some_and(super::super::semantic_index::symbol::Symbol::is_nonlocal) + { // We've reached a function-like scope that marks this name bound or // declared but doesn't mark it `nonlocal`. The name is therefore resolved, // and we won't consider any scopes outside of this one. @@ -6586,7 +6630,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if !self.is_deferred() { match self.index.enclosing_snapshot( FileScopeId::global(), - expr, + place_expr, file_scope_id, ) { EnclosingSnapshotResult::FoundConstraint(constraint) => { @@ -6598,7 +6642,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { EnclosingSnapshotResult::FoundBindings(bindings) => { let place = place_from_bindings(db, bindings).map_type(|ty| { self.narrow_place_with_applicable_constraints( - expr, + place_expr, ty, &constraint_keys, ) @@ -6617,12 +6661,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } - let Some(name) = expr.as_name() else { + let Some(symbol) = place_expr.as_symbol() else { return Place::Unbound.into(); }; - explicit_global_symbol(db, self.file(), name).map_type(|ty| { - self.narrow_place_with_applicable_constraints(expr, ty, &constraint_keys) + explicit_global_symbol(db, self.file(), symbol.name()).map_type(|ty| { + self.narrow_place_with_applicable_constraints( + place_expr, + ty, + &constraint_keys, + ) }) }) }); @@ -6739,8 +6787,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ) -> Type<'db> { let target = target.into(); - if let Ok(place_expr) = PlaceExpr::try_from(target) { - self.narrow_place_with_applicable_constraints(&place_expr, target_ty, constraint_keys) + if let Some(place_expr) = PlaceExpr::try_from_expr(target) { + self.narrow_place_with_applicable_constraints( + PlaceExprRef::from(&place_expr), + target_ty, + constraint_keys, + ) } else { target_ty } @@ -6761,9 +6813,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let mut constraint_keys = vec![]; let mut assigned_type = None; - if let Ok(place_expr) = PlaceExpr::try_from(attribute) { - let (resolved, keys) = - self.infer_place_load(&place_expr, ast::ExprRef::Attribute(attribute)); + if let Some(place_expr) = PlaceExpr::try_from_expr(attribute) { + let (resolved, keys) = self.infer_place_load( + PlaceExprRef::from(&place_expr), + ast::ExprRef::Attribute(attribute), + ); constraint_keys.extend(keys); if let Place::Type(ty, Boundness::Bound) = resolved.place { assigned_type = Some(ty); @@ -8309,9 +8363,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // If `value` is a valid reference, we attempt type narrowing by assignment. if !value_ty.is_unknown() { - if let Ok(expr) = PlaceExpr::try_from(subscript) { - let (place, keys) = - self.infer_place_load(&expr, ast::ExprRef::Subscript(subscript)); + if let Some(expr) = PlaceExpr::try_from_expr(subscript) { + let (place, keys) = self.infer_place_load( + PlaceExprRef::from(&expr), + ast::ExprRef::Subscript(subscript), + ); constraint_keys.extend(keys); if let Place::Type(ty, Boundness::Bound) = place.place { // Even if we can obtain the subscript type based on the assignments, we still perform default type inference @@ -11001,7 +11057,7 @@ mod tests { use crate::db::tests::{TestDb, setup_db}; use crate::place::{global_symbol, symbol}; use crate::semantic_index::definition::Definition; - use crate::semantic_index::place::FileScopeId; + use crate::semantic_index::scope::FileScopeId; use crate::semantic_index::{global_scope, place_table, semantic_index, use_def_map}; use crate::types::check_types; use ruff_db::diagnostic::Diagnostic; @@ -11278,7 +11334,7 @@ mod tests { fn first_public_binding<'db>(db: &'db TestDb, file: File, name: &str) -> Definition<'db> { let scope = global_scope(db, file); use_def_map(db, scope) - .end_of_scope_bindings(place_table(db, scope).place_id_by_name(name).unwrap()) + .end_of_scope_symbol_bindings(place_table(db, scope).symbol_id(name).unwrap()) .find_map(|b| b.binding.definition()) .expect("no binding found") } diff --git a/crates/ty_python_semantic/src/types/narrow.rs b/crates/ty_python_semantic/src/types/narrow.rs index e08cb4639c..24be9f1e88 100644 --- a/crates/ty_python_semantic/src/types/narrow.rs +++ b/crates/ty_python_semantic/src/types/narrow.rs @@ -1,11 +1,12 @@ use crate::Db; use crate::semantic_index::expression::Expression; -use crate::semantic_index::place::{PlaceExpr, PlaceTable, ScopeId, ScopedPlaceId}; +use crate::semantic_index::place::{PlaceExpr, PlaceTable, ScopedPlaceId}; use crate::semantic_index::place_table; use crate::semantic_index::predicate::{ CallableAndCallExpr, ClassPatternKind, PatternPredicate, PatternPredicateKind, Predicate, PredicateNode, }; +use crate::semantic_index::scope::ScopeId; use crate::types::enums::{enum_member_literals, enum_metadata}; use crate::types::function::KnownFunction; use crate::types::infer::infer_same_file_expression_type; @@ -312,11 +313,8 @@ fn negate_if<'db>(constraints: &mut NarrowingConstraints<'db>, db: &'db dyn Db, fn place_expr(expr: &ast::Expr) -> Option { match expr { - ast::Expr::Name(name) => Some(PlaceExpr::name(name.id.clone())), - ast::Expr::Attribute(attr) => PlaceExpr::try_from(attr).ok(), - ast::Expr::Subscript(subscript) => PlaceExpr::try_from(subscript).ok(), - ast::Expr::Named(named) => PlaceExpr::try_from(named.target.as_ref()).ok(), - _ => None, + ast::Expr::Named(named) => PlaceExpr::try_from_expr(named.target.as_ref()), + _ => PlaceExpr::try_from_expr(expr), } } @@ -447,7 +445,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { #[track_caller] fn expect_place(&self, place_expr: &PlaceExpr) -> ScopedPlaceId { self.places() - .place_id_by_expr(place_expr) + .place_id(place_expr) .expect("We should always have a place for every `PlaceExpr`") } diff --git a/crates/ty_python_semantic/src/types/protocol_class.rs b/crates/ty_python_semantic/src/types/protocol_class.rs index 1a06f693c8..4b01ce1670 100644 --- a/crates/ty_python_semantic/src/types/protocol_class.rs +++ b/crates/ty_python_semantic/src/types/protocol_class.rs @@ -4,10 +4,12 @@ use itertools::Itertools; use ruff_python_ast::name::Name; +use super::TypeVarVariance; +use crate::semantic_index::place_table; use crate::{ Db, FxOrderSet, place::{Boundness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations}, - semantic_index::{place_table, use_def_map}, + semantic_index::use_def_map, types::{ CallableType, ClassBase, ClassLiteral, KnownFunction, PropertyInstanceType, Signature, Type, TypeMapping, TypeQualifiers, TypeRelation, TypeTransformer, TypeVarInstance, @@ -16,8 +18,6 @@ use crate::{ }, }; -use super::TypeVarVariance; - impl<'db> ClassLiteral<'db> { /// Returns `Some` if this is a protocol class, `None` otherwise. pub(super) fn into_protocol_class(self, db: &'db dyn Db) -> Option> { @@ -471,15 +471,15 @@ fn cached_protocol_interface<'db>( members.extend( use_def_map - .all_end_of_scope_declarations() - .flat_map(|(place_id, declarations)| { - place_from_declarations(db, declarations).map(|place| (place_id, place)) + .all_end_of_scope_symbol_declarations() + .flat_map(|(symbol_id, declarations)| { + place_from_declarations(db, declarations).map(|place| (symbol_id, place)) }) - .filter_map(|(place_id, place)| { + .filter_map(|(symbol_id, place)| { place .place .ignore_possibly_unbound() - .map(|ty| (place_id, ty, place.qualifiers, BoundOnClass::No)) + .map(|ty| (symbol_id, ty, place.qualifiers, BoundOnClass::No)) }) // Bindings in the class body that are not declared in the class body // are not valid protocol members, and we plan to emit diagnostics for them @@ -489,20 +489,20 @@ fn cached_protocol_interface<'db>( // members at runtime, and it's important that we accurately understand // type narrowing that uses `isinstance()` or `issubclass()` with // runtime-checkable protocols. - .chain(use_def_map.all_end_of_scope_bindings().filter_map( - |(place_id, bindings)| { + .chain(use_def_map.all_end_of_scope_symbol_bindings().filter_map( + |(symbol_id, bindings)| { place_from_bindings(db, bindings) .ignore_possibly_unbound() - .map(|ty| (place_id, ty, TypeQualifiers::default(), BoundOnClass::Yes)) + .map(|ty| (symbol_id, ty, TypeQualifiers::default(), BoundOnClass::Yes)) }, )) - .filter_map(|(place_id, member, qualifiers, bound_on_class)| { - Some(( - place_table.place_expr(place_id).as_name()?, + .map(|(symbol_id, member, qualifiers, bound_on_class)| { + ( + place_table.symbol(symbol_id).name(), member, qualifiers, bound_on_class, - )) + ) }) .filter(|(name, _, _, _)| !excluded_from_proto_members(name)) .map(|(name, ty, qualifiers, bound_on_class)| { diff --git a/crates/ty_python_semantic/src/types/unpacker.rs b/crates/ty_python_semantic/src/types/unpacker.rs index a85f2189b2..a02475ab7a 100644 --- a/crates/ty_python_semantic/src/types/unpacker.rs +++ b/crates/ty_python_semantic/src/types/unpacker.rs @@ -7,7 +7,7 @@ use ruff_python_ast::{self as ast, AnyNodeRef}; use crate::Db; use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey; -use crate::semantic_index::place::ScopeId; +use crate::semantic_index::scope::ScopeId; use crate::types::tuple::{ResizeTupleError, Tuple, TupleLength, TupleSpec, TupleUnpacker}; use crate::types::{Type, TypeCheckDiagnostics, infer_expression_types}; use crate::unpack::{UnpackKind, UnpackValue}; diff --git a/crates/ty_python_semantic/src/unpack.rs b/crates/ty_python_semantic/src/unpack.rs index 42823aa628..b23dfa9b56 100644 --- a/crates/ty_python_semantic/src/unpack.rs +++ b/crates/ty_python_semantic/src/unpack.rs @@ -6,7 +6,7 @@ use ruff_text_size::{Ranged, TextRange}; use crate::Db; use crate::ast_node_ref::AstNodeRef; use crate::semantic_index::expression::Expression; -use crate::semantic_index::place::{FileScopeId, ScopeId}; +use crate::semantic_index::scope::{FileScopeId, ScopeId}; /// This ingredient represents a single unpacking. ///