[ty] Split ScopedPlaceId into ScopedSymbolId and ScopedMemberId (#19497)

This commit is contained in:
Micha Reiser 2025-07-25 13:54:33 +02:00 committed by GitHub
parent f722bfa9e6
commit b033fb6bfd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 2454 additions and 1647 deletions

1
Cargo.lock generated
View file

@ -4305,7 +4305,6 @@ dependencies = [
"strum_macros", "strum_macros",
"tempfile", "tempfile",
"test-case", "test-case",
"thin-vec",
"thiserror 2.0.12", "thiserror 2.0.12",
"tracing", "tracing",
"ty_python_semantic", "ty_python_semantic",

View file

@ -166,7 +166,6 @@ strum_macros = { version = "0.27.0" }
syn = { version = "2.0.55" } syn = { version = "2.0.55" }
tempfile = { version = "3.9.0" } tempfile = { version = "3.9.0" }
test-case = { version = "3.3.1" } test-case = { version = "3.3.1" }
thin-vec = { version = "0.2.14" }
thiserror = { version = "2.0.0" } thiserror = { version = "2.0.0" }
tikv-jemallocator = { version = "0.6.0" } tikv-jemallocator = { version = "0.6.0" }
toml = { version = "0.9.0" } toml = { version = "0.9.0" }

View file

@ -29,6 +29,10 @@ impl Name {
Self(compact_str::CompactString::const_new(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 { pub fn as_str(&self) -> &str {
self.0.as_str() self.0.as_str()
} }

View file

@ -36,7 +36,6 @@ indexmap = { workspace = true }
itertools = { workspace = true } itertools = { workspace = true }
ordermap = { workspace = true } ordermap = { workspace = true }
salsa = { workspace = true, features = ["compact_str"] } salsa = { workspace = true, features = ["compact_str"] }
thin-vec = { workspace = true }
thiserror = { workspace = true } thiserror = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
rustc-hash = { workspace = true } rustc-hash = { workspace = true }

View file

@ -3,7 +3,8 @@ use ruff_db::files::File;
use crate::dunder_all::dunder_all_names; use crate::dunder_all::dunder_all_names;
use crate::module_resolver::file_to_module; use crate::module_resolver::file_to_module;
use crate::semantic_index::definition::{Definition, DefinitionState}; 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::{ use crate::semantic_index::{
BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, place_table, BindingWithConstraints, BindingWithConstraintsIterator, DeclarationsIterator, place_table,
}; };
@ -214,13 +215,13 @@ pub(crate) fn symbol<'db>(
pub(crate) fn place<'db>( pub(crate) fn place<'db>(
db: &'db dyn Db, db: &'db dyn Db,
scope: ScopeId<'db>, scope: ScopeId<'db>,
expr: &PlaceExpr, member: PlaceExprRef,
considered_definitions: ConsideredDefinitions, considered_definitions: ConsideredDefinitions,
) -> PlaceAndQualifiers<'db> { ) -> PlaceAndQualifiers<'db> {
place_impl( place_impl(
db, db,
scope, scope,
expr, member,
RequiresExplicitReExport::No, RequiresExplicitReExport::No,
considered_definitions, considered_definitions,
) )
@ -234,12 +235,12 @@ pub(crate) fn class_symbol<'db>(
name: &str, name: &str,
) -> PlaceAndQualifiers<'db> { ) -> PlaceAndQualifiers<'db> {
place_table(db, scope) place_table(db, scope)
.place_id_by_name(name) .symbol_id(name)
.map(|place| { .map(|symbol_id| {
let place_and_quals = place_by_id( let place_and_quals = place_by_id(
db, db,
scope, scope,
place, symbol_id.into(),
RequiresExplicitReExport::No, RequiresExplicitReExport::No,
ConsideredDefinitions::EndOfScope, ConsideredDefinitions::EndOfScope,
); );
@ -256,7 +257,7 @@ pub(crate) fn class_symbol<'db>(
{ {
// Otherwise, we need to check if the symbol has bindings // Otherwise, we need to check if the symbol has bindings
let use_def = use_def_map(db, scope); 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); 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 // 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` // `TYPE_CHECKING` is a special variable that should only be assigned `False`
// at runtime, but is always considered `True` in type checking. // at runtime, but is always considered `True` in type checking.
// See mdtest/known_constants.md#user-defined-type_checking for details. // See mdtest/known_constants.md#user-defined-type_checking for details.
let is_considered_non_modifiable = place_table(db, scope) let is_considered_non_modifiable = place_id.as_symbol().is_some_and(|symbol_id| {
.place_expr(place_id) matches!(
.expr place_table(db, scope).symbol(symbol_id).name().as_str(),
.is_name_and(|name| matches!(name, "__slots__" | "TYPE_CHECKING")); "__slots__" | "TYPE_CHECKING"
)
});
if scope.file(db).is_stub(db) || scope.scope(db).visibility().is_private() { 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 // 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_table(db, scope)
.place_id_by_name(name) .symbol_id(name)
.map(|symbol| { .map(|symbol| {
place_by_id( place_by_id(
db, db,
scope, scope,
symbol, symbol.into(),
requires_explicit_reexport, requires_explicit_reexport,
considered_definitions, considered_definitions,
) )
@ -845,18 +848,17 @@ fn symbol_impl<'db>(
.unwrap_or_default() .unwrap_or_default()
} }
/// Implementation of [`place`].
fn place_impl<'db>( fn place_impl<'db>(
db: &'db dyn Db, db: &'db dyn Db,
scope: ScopeId<'db>, scope: ScopeId<'db>,
expr: &PlaceExpr, place: PlaceExprRef,
requires_explicit_reexport: RequiresExplicitReExport, requires_explicit_reexport: RequiresExplicitReExport,
considered_definitions: ConsideredDefinitions, considered_definitions: ConsideredDefinitions,
) -> PlaceAndQualifiers<'db> { ) -> PlaceAndQualifiers<'db> {
let _span = tracing::trace_span!("place", ?expr).entered(); let _span = tracing::trace_span!("place_impl", ?place).entered();
place_table(db, scope) place_table(db, scope)
.place_id_by_expr(expr) .place_id(place)
.map(|place| { .map(|place| {
place_by_id( place_by_id(
db, db,
@ -1265,7 +1267,8 @@ fn is_reexported(db: &dyn Db, definition: Definition<'_>) -> bool {
return false; return false;
}; };
let table = place_table(db, definition.scope(db)); 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) all_names.contains(symbol_name)
} }
@ -1274,19 +1277,19 @@ mod implicit_globals {
use crate::db::Db; use crate::db::Db;
use crate::place::PlaceAndQualifiers; use crate::place::PlaceAndQualifiers;
use crate::semantic_index::place::PlaceExpr; use crate::semantic_index::symbol::Symbol;
use crate::semantic_index::{self, place_table, use_def_map}; use crate::semantic_index::{place_table, use_def_map};
use crate::types::{KnownClass, Type}; use crate::types::{KnownClass, Type};
use super::{Place, PlaceFromDeclarationsResult, place_from_declarations}; use super::{Place, PlaceFromDeclarationsResult, place_from_declarations};
pub(crate) fn module_type_implicit_global_declaration<'db>( pub(crate) fn module_type_implicit_global_declaration<'db>(
db: &'db dyn Db, db: &'db dyn Db,
expr: &PlaceExpr, name: &str,
) -> PlaceFromDeclarationsResult<'db> { ) -> PlaceFromDeclarationsResult<'db> {
if !module_type_symbols(db) if !module_type_symbols(db)
.iter() .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()); return Ok(Place::Unbound.into());
} }
@ -1296,12 +1299,12 @@ mod implicit_globals {
}; };
let module_type_scope = module_type_class.body_scope(db); let module_type_scope = module_type_class.body_scope(db);
let place_table = place_table(db, module_type_scope); 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()); return Ok(Place::Unbound.into());
}; };
place_from_declarations( place_from_declarations(
db, 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); let module_type_symbol_table = place_table(db, module_type_scope);
module_type_symbol_table module_type_symbol_table
.places() .symbols()
.filter(|place| place.is_declared() && place.is_name()) .filter(|symbol| symbol.is_declared())
.map(semantic_index::place::PlaceExprWithFlags::expect_name) .map(Symbol::name)
.filter(|symbol_name| { .filter(|symbol_name| {
!matches!(&***symbol_name, "__dict__" | "__getattr__" | "__init__") !matches!(
symbol_name.as_str(),
"__dict__" | "__getattr__" | "__init__"
)
}) })
.cloned() .cloned()
.collect() .collect()

View file

@ -20,10 +20,12 @@ use crate::semantic_index::builder::SemanticIndexBuilder;
use crate::semantic_index::definition::{Definition, DefinitionNodeKey, Definitions}; use crate::semantic_index::definition::{Definition, DefinitionNodeKey, Definitions};
use crate::semantic_index::expression::Expression; use crate::semantic_index::expression::Expression;
use crate::semantic_index::narrowing_constraints::ScopedNarrowingConstraint; use crate::semantic_index::narrowing_constraints::ScopedNarrowingConstraint;
use crate::semantic_index::place::{ use crate::semantic_index::place::{PlaceExprRef, PlaceTable};
FileScopeId, NodeWithScopeKey, NodeWithScopeRef, PlaceExpr, PlaceTable, Scope, ScopeId, pub use crate::semantic_index::scope::FileScopeId;
ScopeKind, ScopedPlaceId, 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_index::use_def::{EnclosingSnapshotKey, ScopedEnclosingSnapshotId, UseDefMap};
use crate::semantic_model::HasTrackedScope; use crate::semantic_model::HasTrackedScope;
use crate::util::get_size::untracked_arc_size; use crate::util::get_size::untracked_arc_size;
@ -32,11 +34,14 @@ pub mod ast_ids;
mod builder; mod builder;
pub mod definition; pub mod definition;
pub mod expression; pub mod expression;
pub(crate) mod member;
pub(crate) mod narrowing_constraints; pub(crate) mod narrowing_constraints;
pub mod place; pub mod place;
pub(crate) mod predicate; pub(crate) mod predicate;
mod re_exports; mod re_exports;
mod reachability_constraints; mod reachability_constraints;
pub(crate) mod scope;
pub(crate) mod symbol;
mod use_def; mod use_def;
pub(crate) use self::use_def::{ pub(crate) use self::use_def::{
@ -44,8 +49,6 @@ pub(crate) use self::use_def::{
DeclarationWithConstraint, DeclarationsIterator, DeclarationWithConstraint, DeclarationsIterator,
}; };
type PlaceSet = hashbrown::HashTable<ScopedPlaceId>;
/// Returns the semantic index for `file`. /// Returns the semantic index for `file`.
/// ///
/// Prefer using [`symbol_table`] when working with symbols from a single scope. /// 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| { attribute_scopes(db, class_body_scope).filter_map(|function_scope_id| {
let place_table = index.place_table(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]; let use_def = &index.use_def_maps[function_scope_id];
Some(( Some((
use_def.inner.all_reachable_bindings(place), use_def.inner.all_reachable_member_bindings(member),
function_scope_id, 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| { attribute_scopes(db, class_body_scope).filter_map(|function_scope_id| {
let place_table = index.place_table(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]; let use_def = &index.use_def_maps[function_scope_id];
Some(( Some((
use_def.inner.all_reachable_declarations(place), use_def.inner.all_reachable_member_declarations(member),
function_scope_id, 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) 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> { pub(crate) enum EnclosingSnapshotResult<'map, 'db> {
FoundConstraint(ScopedNarrowingConstraint), FoundConstraint(ScopedNarrowingConstraint),
FoundBindings(BindingWithConstraintsIterator<'map, 'db>), FoundBindings(BindingWithConstraintsIterator<'map, 'db>),
@ -337,22 +308,18 @@ impl<'db> SemanticIndex<'db> {
pub(crate) fn symbol_is_global_in_scope( pub(crate) fn symbol_is_global_in_scope(
&self, &self,
symbol: ScopedPlaceId, symbol: ScopedSymbolId,
scope: FileScopeId, scope: FileScopeId,
) -> bool { ) -> bool {
self.place_table(scope) self.place_table(scope).symbol(symbol).is_global()
.place_expr(symbol)
.is_marked_global()
} }
pub(crate) fn symbol_is_nonlocal_in_scope( pub(crate) fn symbol_is_nonlocal_in_scope(
&self, &self,
symbol: ScopedPlaceId, symbol: ScopedSymbolId,
scope: FileScopeId, scope: FileScopeId,
) -> bool { ) -> bool {
self.place_table(scope) self.place_table(scope).symbol(symbol).is_nonlocal()
.place_expr(symbol)
.is_marked_nonlocal()
} }
/// Returns the id of the parent scope. /// Returns the id of the parent scope.
@ -523,7 +490,7 @@ impl<'db> SemanticIndex<'db> {
pub(crate) fn enclosing_snapshot( pub(crate) fn enclosing_snapshot(
&self, &self,
enclosing_scope: FileScopeId, enclosing_scope: FileScopeId,
expr: &PlaceExpr, expr: PlaceExprRef,
nested_scope: FileScopeId, nested_scope: FileScopeId,
) -> EnclosingSnapshotResult<'_, 'db> { ) -> EnclosingSnapshotResult<'_, 'db> {
for (ancestor_scope_id, ancestor_scope) in self.ancestor_scopes(nested_scope) { for (ancestor_scope_id, ancestor_scope) in self.ancestor_scopes(nested_scope) {
@ -531,13 +498,13 @@ impl<'db> SemanticIndex<'db> {
break; break;
} }
if !ancestor_scope.is_eager() { if !ancestor_scope.is_eager() {
if expr.is_name() { if let PlaceExprRef::Symbol(symbol) = expr {
if let Some(place_id) = 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 { let key = EnclosingSnapshotKey {
enclosing_scope, enclosing_scope,
enclosing_place: place_id, enclosing_place: place_id.into(),
nested_scope, nested_scope,
nested_laziness: ScopeLaziness::Lazy, nested_laziness: ScopeLaziness::Lazy,
}; };
@ -551,7 +518,7 @@ impl<'db> SemanticIndex<'db> {
return EnclosingSnapshotResult::NoLongerInEagerContext; 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; return EnclosingSnapshotResult::NotFound;
}; };
let key = EnclosingSnapshotKey { let key = EnclosingSnapshotKey {
@ -595,7 +562,7 @@ impl<'db> ArcUseDefMap<'db> {
} }
} }
pub struct AncestorsIter<'a> { pub(crate) struct AncestorsIter<'a> {
scopes: &'a IndexSlice<FileScopeId, Scope>, scopes: &'a IndexSlice<FileScopeId, Scope>,
next_id: Option<FileScopeId>, next_id: Option<FileScopeId>,
} }
@ -623,7 +590,7 @@ impl<'a> Iterator for AncestorsIter<'a> {
impl FusedIterator for AncestorsIter<'_> {} impl FusedIterator for AncestorsIter<'_> {}
pub struct VisibleAncestorsIter<'a> { pub(crate) struct VisibleAncestorsIter<'a> {
inner: AncestorsIter<'a>, inner: AncestorsIter<'a>,
starting_scope_kind: ScopeKind, starting_scope_kind: ScopeKind,
yielded_count: usize, yielded_count: usize,
@ -670,7 +637,7 @@ impl<'a> Iterator for VisibleAncestorsIter<'a> {
impl FusedIterator for VisibleAncestorsIter<'_> {} impl FusedIterator for VisibleAncestorsIter<'_> {}
pub struct DescendantsIter<'a> { pub(crate) struct DescendantsIter<'a> {
next_id: FileScopeId, next_id: FileScopeId,
descendants: std::slice::Iter<'a, Scope>, descendants: std::slice::Iter<'a, Scope>,
} }
@ -707,7 +674,7 @@ impl FusedIterator for DescendantsIter<'_> {}
impl ExactSizeIterator for DescendantsIter<'_> {} impl ExactSizeIterator for DescendantsIter<'_> {}
pub struct ChildrenIter<'a> { pub(crate) struct ChildrenIter<'a> {
parent: FileScopeId, parent: FileScopeId,
descendants: DescendantsIter<'a>, descendants: DescendantsIter<'a>,
} }
@ -777,13 +744,15 @@ mod tests {
use crate::db::tests::{TestDb, TestDbBuilder}; use crate::db::tests::{TestDb, TestDbBuilder};
use crate::semantic_index::ast_ids::{HasScopedUseId, ScopedUseId}; use crate::semantic_index::ast_ids::{HasScopedUseId, ScopedUseId};
use crate::semantic_index::definition::{Definition, DefinitionKind}; 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::use_def::UseDefMap;
use crate::semantic_index::{global_scope, place_table, semantic_index, use_def_map}; use crate::semantic_index::{global_scope, place_table, semantic_index, use_def_map};
impl UseDefMap<'_> { impl UseDefMap<'_> {
fn first_public_binding(&self, symbol: ScopedPlaceId) -> Option<Definition<'_>> { fn first_public_binding(&self, symbol: ScopedSymbolId) -> Option<Definition<'_>> {
self.end_of_scope_bindings(symbol) self.end_of_scope_symbol_bindings(symbol)
.find_map(|constrained_binding| constrained_binding.binding.definition()) .find_map(|constrained_binding| constrained_binding.binding.definition())
} }
@ -813,8 +782,8 @@ mod tests {
fn names(table: &PlaceTable) -> Vec<String> { fn names(table: &PlaceTable) -> Vec<String> {
table table
.places() .symbols()
.filter_map(|expr| Some(expr.as_name()?.to_string())) .map(|expr| expr.name().to_string())
.collect() .collect()
} }
@ -852,7 +821,7 @@ mod tests {
let global_table = place_table(&db, scope); let global_table = place_table(&db, scope);
assert_eq!(names(global_table), vec!["foo"]); 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 use_def = use_def_map(&db, scope);
let binding = use_def.first_public_binding(foo).unwrap(); let binding = use_def.first_public_binding(foo).unwrap();
@ -884,18 +853,14 @@ mod tests {
assert_eq!(names(global_table), vec!["foo"]); assert_eq!(names(global_table), vec!["foo"]);
assert!( assert!(
global_table global_table
.place_by_name("foo") .symbol_by_name("foo")
.is_some_and(|symbol| { symbol.is_bound() && !symbol.is_used() }), .is_some_and(|symbol| { symbol.is_bound() && !symbol.is_used() }),
"symbols that are defined get the defined flag" "symbols that are defined get the defined flag"
); );
let use_def = use_def_map(&db, scope); let use_def = use_def_map(&db, scope);
let binding = use_def let binding = use_def
.first_public_binding( .first_public_binding(global_table.symbol_id("foo").expect("symbol to exist"))
global_table
.place_id_by_name("foo")
.expect("symbol to exist"),
)
.unwrap(); .unwrap();
assert!(matches!(binding.kind(&db), DefinitionKind::ImportFrom(_))); assert!(matches!(binding.kind(&db), DefinitionKind::ImportFrom(_)));
} }
@ -909,13 +874,13 @@ mod tests {
assert_eq!(names(global_table), vec!["foo", "x"]); assert_eq!(names(global_table), vec!["foo", "x"]);
assert!( assert!(
global_table global_table
.place_by_name("foo") .symbol_by_name("foo")
.is_some_and(|symbol| { !symbol.is_bound() && symbol.is_used() }), .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" "a symbol used but not bound in a scope should have only the used flag"
); );
let use_def = use_def_map(&db, scope); let use_def = use_def_map(&db, scope);
let binding = use_def 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(); .unwrap();
assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_))); assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_)));
} }
@ -930,7 +895,7 @@ mod tests {
let use_def = use_def_map(&db, scope); let use_def = use_def_map(&db, scope);
let binding = use_def 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(); .unwrap();
assert!(matches!( assert!(matches!(
@ -972,7 +937,7 @@ y = 2
let use_def = index.use_def_map(class_scope_id); let use_def = index.use_def_map(class_scope_id);
let binding = use_def 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(); .unwrap();
assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_))); assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_)));
} }
@ -1009,7 +974,7 @@ y = 2
let use_def = index.use_def_map(function_scope_id); let use_def = index.use_def_map(function_scope_id);
let binding = use_def 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(); .unwrap();
assert!(matches!(binding.kind(&db), DefinitionKind::Assignment(_))); 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); let use_def = index.use_def_map(function_scope_id);
for name in ["a", "b", "c", "d"] { for name in ["a", "b", "c", "d"] {
let binding = use_def let binding = use_def
.first_public_binding( .first_public_binding(function_table.symbol_id(name).expect("symbol exists"))
function_table
.place_id_by_name(name)
.expect("symbol exists"),
)
.unwrap(); .unwrap();
assert!(matches!(binding.kind(&db), DefinitionKind::Parameter(_))); assert!(matches!(binding.kind(&db), DefinitionKind::Parameter(_)));
} }
let args_binding = use_def let args_binding = use_def
.first_public_binding( .first_public_binding(function_table.symbol_id("args").expect("symbol exists"))
function_table
.place_id_by_name("args")
.expect("symbol exists"),
)
.unwrap(); .unwrap();
assert!(matches!( assert!(matches!(
args_binding.kind(&db), args_binding.kind(&db),
DefinitionKind::VariadicPositionalParameter(_) DefinitionKind::VariadicPositionalParameter(_)
)); ));
let kwargs_binding = use_def let kwargs_binding = use_def
.first_public_binding( .first_public_binding(function_table.symbol_id("kwargs").expect("symbol exists"))
function_table
.place_id_by_name("kwargs")
.expect("symbol exists"),
)
.unwrap(); .unwrap();
assert!(matches!( assert!(matches!(
kwargs_binding.kind(&db), 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); let use_def = index.use_def_map(lambda_scope_id);
for name in ["a", "b", "c", "d"] { for name in ["a", "b", "c", "d"] {
let binding = use_def 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(); .unwrap();
assert!(matches!(binding.kind(&db), DefinitionKind::Parameter(_))); assert!(matches!(binding.kind(&db), DefinitionKind::Parameter(_)));
} }
let args_binding = use_def let args_binding = use_def
.first_public_binding( .first_public_binding(lambda_table.symbol_id("args").expect("symbol exists"))
lambda_table
.place_id_by_name("args")
.expect("symbol exists"),
)
.unwrap(); .unwrap();
assert!(matches!( assert!(matches!(
args_binding.kind(&db), args_binding.kind(&db),
DefinitionKind::VariadicPositionalParameter(_) DefinitionKind::VariadicPositionalParameter(_)
)); ));
let kwargs_binding = use_def let kwargs_binding = use_def
.first_public_binding( .first_public_binding(lambda_table.symbol_id("kwargs").expect("symbol exists"))
lambda_table
.place_id_by_name("kwargs")
.expect("symbol exists"),
)
.unwrap(); .unwrap();
assert!(matches!( assert!(matches!(
kwargs_binding.kind(&db), 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 let binding = use_def
.first_public_binding( .first_public_binding(
comprehension_symbol_table comprehension_symbol_table
.place_id_by_name(name) .symbol_id(name)
.expect("symbol exists"), .expect("symbol exists"),
) )
.unwrap(); .unwrap();
@ -1298,7 +1243,7 @@ with item1 as x, item2 as y:
let use_def = index.use_def_map(FileScopeId::global()); let use_def = index.use_def_map(FileScopeId::global());
for name in ["x", "y"] { for name in ["x", "y"] {
let binding = use_def 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}"); .expect("Expected with item definition for {name}");
assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_))); 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()); let use_def = index.use_def_map(FileScopeId::global());
for name in ["x", "y"] { for name in ["x", "y"] {
let binding = use_def 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}"); .expect("Expected with item definition for {name}");
assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_))); assert!(matches!(binding.kind(&db), DefinitionKind::WithItem(_)));
} }
@ -1371,11 +1316,7 @@ def func():
let use_def = index.use_def_map(FileScopeId::global()); let use_def = index.use_def_map(FileScopeId::global());
let binding = use_def let binding = use_def
.first_public_binding( .first_public_binding(global_table.symbol_id("func").expect("symbol exists"))
global_table
.place_id_by_name("func")
.expect("symbol exists"),
)
.unwrap(); .unwrap();
assert!(matches!(binding.kind(&db), DefinitionKind::Function(_))); assert!(matches!(binding.kind(&db), DefinitionKind::Function(_)));
} }
@ -1452,7 +1393,7 @@ class C[T]:
assert_eq!(names(&ann_table), vec!["T"]); assert_eq!(names(&ann_table), vec!["T"]);
assert!( assert!(
ann_table ann_table
.place_by_name("T") .symbol_by_name("T")
.is_some_and(|s| s.is_bound() && !s.is_used()), .is_some_and(|s| s.is_bound() && !s.is_used()),
"type parameters are defined by the scope that introduces them" "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_scope_id = global_scope(&db, file);
let global_table = place_table(&db, global_scope_id); 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!( assert_eq!(
names(global_table), names(global_table),
vec![ vec![
@ -1624,7 +1565,7 @@ match subject:
("l", 1), ("l", 1),
] { ] {
let binding = use_def 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}"); .expect("Expected with item definition for {name}");
if let DefinitionKind::MatchPattern(pattern) = binding.kind(&db) { if let DefinitionKind::MatchPattern(pattern) = binding.kind(&db) {
assert_eq!(pattern.index(), expected_index); assert_eq!(pattern.index(), expected_index);
@ -1654,7 +1595,7 @@ match 1:
let use_def = use_def_map(&db, global_scope_id); let use_def = use_def_map(&db, global_scope_id);
for (name, expected_index) in [("first", 0), ("second", 0)] { for (name, expected_index) in [("first", 0), ("second", 0)] {
let binding = use_def 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}"); .expect("Expected with item definition for {name}");
if let DefinitionKind::MatchPattern(pattern) = binding.kind(&db) { if let DefinitionKind::MatchPattern(pattern) = binding.kind(&db) {
assert_eq!(pattern.index(), expected_index); assert_eq!(pattern.index(), expected_index);
@ -1674,7 +1615,7 @@ match 1:
let use_def = use_def_map(&db, scope); let use_def = use_def_map(&db, scope);
let binding = use_def 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(); .unwrap();
assert!(matches!(binding.kind(&db), DefinitionKind::For(_))); assert!(matches!(binding.kind(&db), DefinitionKind::For(_)));
@ -1690,10 +1631,10 @@ match 1:
let use_def = use_def_map(&db, scope); let use_def = use_def_map(&db, scope);
let x_binding = use_def 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(); .unwrap();
let y_binding = use_def 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(); .unwrap();
assert!(matches!(x_binding.kind(&db), DefinitionKind::For(_))); assert!(matches!(x_binding.kind(&db), DefinitionKind::For(_)));
@ -1710,7 +1651,7 @@ match 1:
let use_def = use_def_map(&db, scope); let use_def = use_def_map(&db, scope);
let binding = use_def 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(); .unwrap();
assert!(matches!(binding.kind(&db), DefinitionKind::For(_))); assert!(matches!(binding.kind(&db), DefinitionKind::For(_)));

View file

@ -6,7 +6,7 @@ use ruff_python_ast::ExprRef;
use crate::Db; use crate::Db;
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey; 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; use crate::semantic_index::semantic_index;
/// AST ids for a single scope. /// 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)) 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] #[newtype_index]
#[derive(get_size2::GetSize)] #[derive(get_size2::GetSize)]
pub struct ScopedUseId; pub struct ScopedUseId;

View file

@ -30,10 +30,7 @@ use crate::semantic_index::definition::{
StarImportDefinitionNodeRef, WithItemDefinitionNodeRef, StarImportDefinitionNodeRef, WithItemDefinitionNodeRef,
}; };
use crate::semantic_index::expression::{Expression, ExpressionKind}; use crate::semantic_index::expression::{Expression, ExpressionKind};
use crate::semantic_index::place::{ use crate::semantic_index::place::{PlaceExpr, PlaceTableBuilder, ScopedPlaceId};
FileScopeId, NodeWithScopeKey, NodeWithScopeKind, NodeWithScopeRef, PlaceExpr,
PlaceExprWithFlags, PlaceTableBuilder, Scope, ScopeId, ScopeKind, ScopedPlaceId,
};
use crate::semantic_index::predicate::{ use crate::semantic_index::predicate::{
CallableAndCallExpr, ClassPatternKind, PatternPredicate, PatternPredicateKind, Predicate, CallableAndCallExpr, ClassPatternKind, PatternPredicate, PatternPredicateKind, Predicate,
PredicateNode, PredicateOrLiteral, ScopedPredicateId, StarImportPlaceholderPredicate, PredicateNode, PredicateOrLiteral, ScopedPredicateId, StarImportPlaceholderPredicate,
@ -42,10 +39,15 @@ use crate::semantic_index::re_exports::exported_names;
use crate::semantic_index::reachability_constraints::{ use crate::semantic_index::reachability_constraints::{
ReachabilityConstraintsBuilder, ScopedReachabilityConstraintId, 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::{ use crate::semantic_index::use_def::{
EnclosingSnapshotKey, FlowSnapshot, ScopedEnclosingSnapshotId, UseDefMapBuilder, 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::semantic_model::HasTrackedScope;
use crate::unpack::{Unpack, UnpackKind, UnpackPosition, UnpackValue}; use crate::unpack::{Unpack, UnpackKind, UnpackPosition, UnpackValue};
use crate::{Db, Program}; 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_scope_kind = self.scopes[enclosing_scope_id].kind();
let enclosing_place_table = &self.place_tables[enclosing_scope_id]; 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. // 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, // Note that even if this place is bound in the popped scope,
// it may refer to the enclosing scope bindings // it may refer to the enclosing scope bindings
// so we also need to snapshot the bindings of the enclosing scope. // so we also need to snapshot the bindings of the enclosing scope.
let Some(enclosing_place_id) = let Some(enclosing_place_id) = enclosing_place_table.place_id(nested_place) else {
enclosing_place_table.place_id_by_expr(&nested_place.expr)
else {
continue; 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 // Snapshot the state of this place that are visible at this point in this
// enclosing scope. // enclosing scope.
@ -332,11 +332,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
} }
} }
fn bound_scope( fn bound_scope(&self, enclosing_scope: FileScopeId, symbol: &Symbol) -> Option<FileScopeId> {
&self,
enclosing_scope: FileScopeId,
place_expr: &PlaceExpr,
) -> Option<FileScopeId> {
self.scope_stack self.scope_stack
.iter() .iter()
.rev() .rev()
@ -344,12 +340,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
.find_map(|scope_info| { .find_map(|scope_info| {
let scope_id = scope_info.file_scope_id; let scope_id = scope_info.file_scope_id;
let place_table = &self.place_tables[scope_id]; let place_table = &self.place_tables[scope_id];
let place_id = place_table.place_id_by_expr(place_expr)?; let place_id = place_table.symbol_id(symbol.name())?;
if place_table.place_expr(place_id).is_bound() { place_table.place(place_id).is_bound().then_some(scope_id)
Some(scope_id)
} else {
None
}
}) })
} }
@ -360,13 +352,11 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
let enclosing_scope_kind = self.scopes[enclosing_scope_id].kind(); let enclosing_scope_kind = self.scopes[enclosing_scope_id].kind();
let enclosing_place_table = &self.place_tables[enclosing_scope_id]; 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. // 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. // Also, if the enclosing scope allows its members to be modified from elsewhere, the snapshot will not be recorded.
if !nested_place.is_name() if self.scopes[enclosing_scope_id].visibility().is_public() {
|| self.scopes[enclosing_scope_id].visibility().is_public()
{
continue; continue;
} }
@ -374,17 +364,16 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
// Note that even if this place is bound in the popped scope, // Note that even if this place is bound in the popped scope,
// it may refer to the enclosing scope bindings // it may refer to the enclosing scope bindings
// so we also need to snapshot the bindings of the enclosing scope. // so we also need to snapshot the bindings of the enclosing scope.
let Some(enclosed_symbol_id) =
let Some(enclosing_place_id) = enclosing_place_table.symbol_id(nested_symbol.name())
enclosing_place_table.place_id_by_expr(&nested_place.expr)
else { else {
continue; 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 !enclosing_place.is_bound() {
// If the bound scope of a place can be modified from elsewhere, the snapshot will not be recorded. // If the bound scope of a place can be modified from elsewhere, the snapshot will not be recorded.
if self 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()) .is_none_or(|scope| self.scopes[scope].visibility().is_public())
{ {
continue; continue;
@ -395,14 +384,14 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
// enclosing scope (this may later be invalidated and swept away). // enclosing scope (this may later be invalidated and swept away).
let key = EnclosingSnapshotKey { let key = EnclosingSnapshotKey {
enclosing_scope: enclosing_scope_id, enclosing_scope: enclosing_scope_id,
enclosing_place: enclosing_place_id, enclosing_place: enclosed_symbol_id.into(),
nested_scope: popped_scope_id, nested_scope: popped_scope_id,
nested_laziness: ScopeLaziness::Lazy, nested_laziness: ScopeLaziness::Lazy,
}; };
let lazy_snapshot = self.use_def_maps[enclosing_scope_id].snapshot_outer_state( let lazy_snapshot = self.use_def_maps[enclosing_scope_id].snapshot_outer_state(
enclosing_place_id, enclosed_symbol_id.into(),
enclosing_scope_kind, enclosing_scope_kind,
enclosing_place, enclosing_place.into(),
); );
self.enclosing_snapshots.insert(key, lazy_snapshot); 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]; let place_table = &self.place_tables[key.enclosing_scope];
key.nested_laziness.is_eager() key.nested_laziness.is_eager()
|| key.enclosing_scope != popped_scope_id || 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, _| { self.enclosing_snapshots.retain(|key, _| {
let place_table = &self.place_tables[key.enclosing_scope]; let place_table = &self.place_tables[key.enclosing_scope];
let is_place_bound_and_nonlocal = || -> bool { let is_bound_and_non_local = || -> bool {
let place_expr = place_table.place_expr(key.enclosing_place); let ScopedPlaceId::Symbol(symbol_id) = key.enclosing_place else {
return false;
};
let symbol = place_table.symbol(symbol_id);
self.scopes self.scopes
.iter_enumerated() .iter_enumerated()
.skip_while(|(scope_id, _)| *scope_id != key.enclosing_scope) .skip_while(|(scope_id, _)| *scope_id != key.enclosing_scope)
.any(|(scope_id, _)| { .any(|(scope_id, _)| {
let other_scope_place_table = &self.place_tables[scope_id]; let other_scope_place_table = &self.place_tables[scope_id];
let Some(place_id) = let Some(symbol_id) = other_scope_place_table.symbol_id(symbol.name())
other_scope_place_table.place_id_by_expr(&place_expr.expr)
else { else {
return false; return false;
}; };
let place = other_scope_place_table.place_expr(place_id); let symbol = other_scope_place_table.symbol(symbol_id);
place.is_marked_nonlocal() && place.is_bound() 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. /// Add a symbol to the place table and the use-def map.
/// Return the [`ScopedPlaceId`] that uniquely identifies the symbol in both. /// Return the [`ScopedPlaceId`] that uniquely identifies the symbol in both.
fn add_symbol(&mut self, name: Name) -> ScopedPlaceId { fn add_symbol(&mut self, name: Name) -> ScopedSymbolId {
let (place_id, added) = self.current_place_table_mut().add_symbol(name); let (symbol_id, added) = self.current_place_table_mut().add_symbol(Symbol::new(name));
if added { 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. /// Add a place to the place table and the use-def map.
/// Return the [`ScopedPlaceId`] that uniquely identifies the place in both. /// 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); let (place_id, added) = self.current_place_table_mut().add_place(place_expr);
if added { if added {
self.current_use_def_map_mut().add_place(place_id); self.current_use_def_map_mut().add_place(place_id);
@ -533,16 +528,19 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
place_id place_id
} }
#[track_caller]
fn mark_place_bound(&mut self, id: ScopedPlaceId) { 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) { 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) { #[track_caller]
self.current_place_table_mut().mark_place_used(id); 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> { 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) { fn delete_associated_bindings(&mut self, place: ScopedPlaceId) {
let scope = self.current_scope(); 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) // 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 if self.scopes[scope].kind() == ScopeKind::Class && place.is_symbol() {
&& self.place_tables[scope].place_expr(place).is_name()
{
return; return;
} }
for associated_place in self.place_tables[scope].associated_place_ids(place) { for associated_place in self.place_tables[scope]
let is_place_name = self.place_tables[scope] .associated_place_ids(place)
.place_expr(associated_place) .iter()
.is_name(); .copied()
self.use_def_maps[scope].delete_binding(associated_place, is_place_name); {
self.use_def_maps[scope].delete_binding(associated_place.into());
} }
} }
fn delete_binding(&mut self, place: ScopedPlaceId) { 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);
self.current_use_def_map_mut()
.delete_binding(place, is_place_name);
} }
/// Push a new [`Definition`] onto the list of definitions /// Push a new [`Definition`] onto the list of definitions
@ -637,16 +632,15 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
self.mark_place_declared(place); 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(); let use_def = self.current_use_def_map_mut();
match category { match category {
DefinitionCategory::DeclarationAndBinding => { 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); self.delete_associated_bindings(place);
} }
DefinitionCategory::Declaration => use_def.record_declaration(place, definition), DefinitionCategory::Declaration => use_def.record_declaration(place, definition),
DefinitionCategory::Binding => { DefinitionCategory::Binding => {
use_def.record_binding(place, definition, is_place_name); use_def.record_binding(place, definition);
self.delete_associated_bindings(place); self.delete_associated_bindings(place);
} }
} }
@ -963,8 +957,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
// TODO create Definition for PEP 695 typevars // TODO create Definition for PEP 695 typevars
// note that the "bound" on the typevar is a totally different thing than whether // 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. // or not a name is "bound" by a typevar declaration; the latter is always true.
self.mark_place_bound(symbol); self.mark_place_bound(symbol.into());
self.mark_place_declared(symbol); self.mark_place_declared(symbol.into());
if let Some(bounds) = bound { if let Some(bounds) = bound {
self.visit_expr(bounds); self.visit_expr(bounds);
} }
@ -972,9 +966,9 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
self.visit_expr(default); self.visit_expr(default);
} }
match type_param { match type_param {
ast::TypeParam::TypeVar(node) => self.add_definition(symbol, node), ast::TypeParam::TypeVar(node) => self.add_definition(symbol.into(), node),
ast::TypeParam::ParamSpec(node) => self.add_definition(symbol, node), ast::TypeParam::ParamSpec(node) => self.add_definition(symbol.into(), node),
ast::TypeParam::TypeVarTuple(node) => self.add_definition(symbol, 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() { if let Some(vararg) = parameters.vararg.as_ref() {
let symbol = self.add_symbol(vararg.name.id().clone()); let symbol = self.add_symbol(vararg.name.id().clone());
self.add_definition( self.add_definition(
symbol, symbol.into(),
DefinitionNodeRef::VariadicPositionalParameter(vararg), DefinitionNodeRef::VariadicPositionalParameter(vararg),
); );
} }
if let Some(kwarg) = parameters.kwarg.as_ref() { if let Some(kwarg) = parameters.kwarg.as_ref() {
let symbol = self.add_symbol(kwarg.name.id().clone()); 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) { fn declare_parameter(&mut self, parameter: &'ast ast::ParameterWithDefault) {
let symbol = self.add_symbol(parameter.name().id().clone()); 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 // Insert a mapping from the inner Parameter node to the same definition. This
// ensures that calling `HasType::inferred_type` on the inner parameter returns // 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 // 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 // done on the `Identifier` node as opposed to `ExprName` because that's what the
// AST uses. // AST uses.
self.mark_place_used(symbol); self.mark_symbol_used(symbol);
let use_id = self.current_ast_ids().record_use(name); let use_id = self.current_ast_ids().record_use(name);
self.current_use_def_map_mut() self.current_use_def_map_mut().record_use(
.record_use(symbol, use_id, NodeKey::from_node(name)); 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) => { ast::Stmt::ClassDef(class) => {
for decorator in &class.decorator_list { 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. // In Python runtime semantics, a class is registered after its scope is evaluated.
let symbol = self.add_symbol(class.name.id.clone()); 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) => { ast::Stmt::TypeAlias(type_alias) => {
let symbol = self.add_symbol( let symbol = self.add_symbol(
@ -1334,7 +1334,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
.map(|name| name.id.clone()) .map(|name| name.id.clone())
.unwrap_or("<unknown>".into()), .unwrap_or("<unknown>".into()),
); );
self.add_definition(symbol, type_alias); self.add_definition(symbol.into(), type_alias);
self.visit_expr(&type_alias.name); self.visit_expr(&type_alias.name);
self.with_type_params( self.with_type_params(
@ -1366,7 +1366,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
let symbol = self.add_symbol(symbol_name); let symbol = self.add_symbol(symbol_name);
self.add_definition( self.add_definition(
symbol, symbol.into(),
ImportDefinitionNodeRef { ImportDefinitionNodeRef {
node, node,
alias_index, alias_index,
@ -1438,10 +1438,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
// For more details, see the doc-comment on `StarImportPlaceholderPredicate`. // For more details, see the doc-comment on `StarImportPlaceholderPredicate`.
for export in exported_names(self.db, referenced_module) { for export in exported_names(self.db, referenced_module) {
let symbol_id = self.add_symbol(export.clone()); let symbol_id = self.add_symbol(export.clone());
let node_ref = StarImportDefinitionNodeRef { let node_ref = StarImportDefinitionNodeRef { node, symbol_id };
node,
place_id: symbol_id,
};
let star_import = StarImportPlaceholderPredicate::new( let star_import = StarImportPlaceholderPredicate::new(
self.db, self.db,
self.file, self.file,
@ -1451,8 +1448,9 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
let star_import_predicate = self.add_predicate(star_import.into()); let star_import_predicate = self.add_predicate(star_import.into());
let pre_definition = let pre_definition = self
self.current_use_def_map().single_place_snapshot(symbol_id); .current_use_def_map()
.single_symbol_place_snapshot(symbol_id);
let pre_definition_reachability = let pre_definition_reachability =
self.current_use_def_map().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.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() self.current_use_def_map_mut()
.record_and_negate_star_import_reachability_constraint( .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()); let symbol = self.add_symbol(symbol_name.clone());
self.add_definition( self.add_definition(
symbol, symbol.into(),
ImportFromDefinitionNodeRef { ImportFromDefinitionNodeRef {
node, node,
alias_index, alias_index,
@ -1580,9 +1578,9 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
if let ast::Expr::Name(name) = &*node.target { if let ast::Expr::Name(name) = &*node.target {
let symbol_id = self.add_symbol(name.id.clone()); 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. // Check whether the variable has been declared global.
if symbol.is_marked_global() { if symbol.is_global() {
self.report_semantic_error(SemanticSyntaxError { self.report_semantic_error(SemanticSyntaxError {
kind: SemanticSyntaxErrorKind::AnnotatedGlobal(name.id.as_str().into()), kind: SemanticSyntaxErrorKind::AnnotatedGlobal(name.id.as_str().into()),
range: name.range, range: name.range,
@ -1590,7 +1588,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
}); });
} }
// Check whether the variable has been declared nonlocal. // Check whether the variable has been declared nonlocal.
if symbol.is_marked_nonlocal() { if symbol.is_nonlocal() {
self.report_semantic_error(SemanticSyntaxError { self.report_semantic_error(SemanticSyntaxError {
kind: SemanticSyntaxErrorKind::AnnotatedNonlocal( kind: SemanticSyntaxErrorKind::AnnotatedNonlocal(
name.id.as_str().into(), 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()); let symbol = self.add_symbol(symbol_name.id.clone());
self.add_definition( self.add_definition(
symbol, symbol.into(),
DefinitionNodeRef::ExceptHandler(ExceptHandlerDefinitionNodeRef { DefinitionNodeRef::ExceptHandler(ExceptHandlerDefinitionNodeRef {
handler: except_handler, handler: except_handler,
is_star: *is_star, is_star: *is_star,
@ -2020,7 +2018,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
self.visit_body(handler_body); self.visit_body(handler_body);
// The caught exception is cleared at the end of the except clause // The caught exception is cleared at the end of the except clause
if let Some(symbol) = symbol { 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. // Each `except` block is mutually exclusive with all other `except` blocks.
post_except_states.push(self.flow_snapshot()); post_except_states.push(self.flow_snapshot());
@ -2078,7 +2076,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
}) => { }) => {
for name in names { for name in names {
let symbol_id = self.add_symbol(name.id.clone()); 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. // Check whether the variable has already been accessed in this scope.
if symbol.is_bound() || symbol.is_declared() || symbol.is_used() { if symbol.is_bound() || symbol.is_declared() || symbol.is_used() {
self.report_semantic_error(SemanticSyntaxError { 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. // Check whether the variable has also been declared nonlocal.
if symbol.is_marked_nonlocal() { if symbol.is_nonlocal() {
self.report_semantic_error(SemanticSyntaxError { self.report_semantic_error(SemanticSyntaxError {
kind: SemanticSyntaxErrorKind::NonlocalAndGlobal(name.to_string()), kind: SemanticSyntaxErrorKind::NonlocalAndGlobal(name.to_string()),
range: name.range, range: name.range,
python_version: self.python_version, 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); walk_stmt(self, stmt);
} }
@ -2109,7 +2109,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
}) => { }) => {
for name in names { for name in names {
let symbol_id = self.add_symbol(name.id.clone()); 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. // Check whether the variable has already been accessed in this scope.
if symbol.is_bound() || symbol.is_declared() || symbol.is_used() { if symbol.is_bound() || symbol.is_declared() || symbol.is_used() {
self.report_semantic_error(SemanticSyntaxError { 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. // Check whether the variable has also been declared global.
if symbol.is_marked_global() { if symbol.is_global() {
self.report_semantic_error(SemanticSyntaxError { self.report_semantic_error(SemanticSyntaxError {
kind: SemanticSyntaxErrorKind::NonlocalAndGlobal(name.to_string()), kind: SemanticSyntaxErrorKind::NonlocalAndGlobal(name.to_string()),
range: name.range, range: name.range,
@ -2139,7 +2139,8 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
// x = 1 // x = 1
// ``` // ```
self.current_place_table_mut() self.current_place_table_mut()
.mark_place_nonlocal(symbol_id); .symbol_mut(symbol_id)
.mark_nonlocal();
} }
walk_stmt(self, stmt); 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. // We will check the target expressions and then delete them.
walk_stmt(self, stmt); walk_stmt(self, stmt);
for target in targets { for target in targets {
if let Ok(target) = PlaceExpr::try_from(target) { if let Some(mut target) = PlaceExpr::try_from_expr(target) {
let is_name = target.is_name(); if let PlaceExpr::Symbol(symbol) = &mut target {
let place_id = self.add_place(PlaceExprWithFlags::new(target));
let place_table = self.current_place_table_mut();
if is_name {
// `del x` behaves like an assignment in that it forces all references // `del x` behaves like an assignment in that it forces all references
// to `x` in the current scope (including *prior* references) to refer // to `x` in the current scope (including *prior* references) to refer
// to the current scope's binding (unless `x` is declared `global` or // 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 // del x
// foo() // 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); self.delete_binding(place_id);
} }
} }
@ -2238,19 +2238,20 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
ast::Expr::Name(ast::ExprName { ctx, .. }) ast::Expr::Name(ast::ExprName { ctx, .. })
| ast::Expr::Attribute(ast::ExprAttribute { ctx, .. }) | ast::Expr::Attribute(ast::ExprAttribute { ctx, .. })
| ast::Expr::Subscript(ast::ExprSubscript { ctx, .. }) => { | ast::Expr::Subscript(ast::ExprSubscript { ctx, .. }) => {
if let Ok(place_expr) = PlaceExpr::try_from(expr) { if let Some(mut place_expr) = PlaceExpr::try_from_expr(expr) {
let mut place_expr = PlaceExprWithFlags::new(place_expr); if self.is_method_of_class().is_some() {
if self.is_method_of_class().is_some() if let PlaceExpr::Member(member) = &mut place_expr {
&& place_expr.is_instance_attribute_candidate() if member.is_instance_attribute_candidate() {
{
// We specifically mark attribute assignments to the first parameter of a method, // We specifically mark attribute assignments to the first parameter of a method,
// i.e. typically `self` or `cls`. // i.e. typically `self` or `cls`.
let accessed_object_refers_to_first_parameter = self let accessed_object_refers_to_first_parameter = self
.current_first_parameter_name .current_first_parameter_name
.is_some_and(|fst| place_expr.expr.root_name() == fst); .is_some_and(|first| member.symbol_name() == first);
if accessed_object_refers_to_first_parameter && place_expr.is_member() { if accessed_object_refers_to_first_parameter {
place_expr.mark_instance_attribute(); member.mark_instance_attribute();
}
}
} }
} }
@ -2267,7 +2268,9 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
let place_id = self.add_place(place_expr); let place_id = self.add_place(place_expr);
if is_use { 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); let use_id = self.current_ast_ids().record_use(expr);
self.current_use_def_map_mut() self.current_use_def_map_mut()
.record_use(place_id, use_id, node_key); .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 symbol = self.add_symbol(name.id().clone());
let state = self.current_match_case.as_ref().unwrap(); let state = self.current_match_case.as_ref().unwrap();
self.add_definition( self.add_definition(
symbol, symbol.into(),
MatchPatternDefinitionNodeRef { MatchPatternDefinitionNodeRef {
pattern: state.pattern, pattern: state.pattern,
identifier: name, identifier: name,
@ -2582,7 +2585,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
let symbol = self.add_symbol(name.id().clone()); let symbol = self.add_symbol(name.id().clone());
let state = self.current_match_case.as_ref().unwrap(); let state = self.current_match_case.as_ref().unwrap();
self.add_definition( self.add_definition(
symbol, symbol.into(),
MatchPatternDefinitionNodeRef { MatchPatternDefinitionNodeRef {
pattern: state.pattern, pattern: state.pattern,
identifier: name, identifier: name,

View file

@ -8,7 +8,9 @@ use ruff_text_size::{Ranged, TextRange};
use crate::Db; use crate::Db;
use crate::ast_node_ref::AstNodeRef; use crate::ast_node_ref::AstNodeRef;
use crate::node_key::NodeKey; 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}; use crate::unpack::{Unpack, UnpackPosition};
/// A definition of a place. /// A definition of a place.
@ -305,7 +307,7 @@ pub(crate) struct ImportDefinitionNodeRef<'ast> {
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub(crate) struct StarImportDefinitionNodeRef<'ast> { pub(crate) struct StarImportDefinitionNodeRef<'ast> {
pub(crate) node: &'ast ast::StmtImportFrom, pub(crate) node: &'ast ast::StmtImportFrom,
pub(crate) place_id: ScopedPlaceId, pub(crate) symbol_id: ScopedSymbolId,
} }
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -395,10 +397,10 @@ impl<'db> DefinitionNodeRef<'_, 'db> {
is_reexported, is_reexported,
}), }),
DefinitionNodeRef::ImportStar(star_import) => { DefinitionNodeRef::ImportStar(star_import) => {
let StarImportDefinitionNodeRef { node, place_id } = star_import; let StarImportDefinitionNodeRef { node, symbol_id } = star_import;
DefinitionKind::StarImport(StarImportDefinitionKind { DefinitionKind::StarImport(StarImportDefinitionKind {
node: AstNodeRef::new(parsed, node), node: AstNodeRef::new(parsed, node),
place_id, symbol_id,
}) })
} }
DefinitionNodeRef::Function(function) => { DefinitionNodeRef::Function(function) => {
@ -522,7 +524,7 @@ impl<'db> DefinitionNodeRef<'_, 'db> {
// INVARIANT: for an invalid-syntax statement such as `from foo import *, bar, *`, // 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. // 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 .names
.iter() .iter()
.find(|alias| &alias.name == "*") .find(|alias| &alias.name == "*")
@ -822,7 +824,7 @@ impl<'db> From<Option<(UnpackPosition, Unpack<'db>)>> for TargetKind<'db> {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct StarImportDefinitionKind { pub struct StarImportDefinitionKind {
node: AstNodeRef<ast::StmtImportFrom>, node: AstNodeRef<ast::StmtImportFrom>,
place_id: ScopedPlaceId, symbol_id: ScopedSymbolId,
} }
impl StarImportDefinitionKind { impl StarImportDefinitionKind {
@ -844,8 +846,8 @@ impl StarImportDefinitionKind {
) )
} }
pub(crate) fn place_id(&self) -> ScopedPlaceId { pub(crate) fn symbol_id(&self) -> ScopedSymbolId {
self.place_id self.symbol_id
} }
} }

View file

@ -1,6 +1,6 @@
use crate::ast_node_ref::AstNodeRef; use crate::ast_node_ref::AstNodeRef;
use crate::db::Db; 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::files::File;
use ruff_db::parsed::ParsedModuleRef; use ruff_db::parsed::ParsedModuleRef;
use ruff_python_ast as ast; use ruff_python_ast as ast;

View file

@ -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 `<NAME>.<MEMBER>`
/// (meaning it *may* be an instance attribute),
/// return `Some(<MEMBER>)`. 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 `<NAME>` 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 `<NAME>.<MEMBER>`,
/// 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 `<NAME>` 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(<ATTRIBUTE>)` 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<Item = &MemberSegment> + 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<MemberExprRef<'_>> for MemberExpr {
fn eq(&self, other: &MemberExprRef) -> bool {
self.as_ref() == *other
}
}
impl PartialEq<MemberExprRef<'_>> for &MemberExpr {
fn eq(&self, other: &MemberExprRef) -> bool {
self.as_ref() == *other
}
}
impl PartialEq<MemberExpr> 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<ScopedMemberId, Member>,
/// Map from member path to its ID.
///
/// Uses a hash table to avoid storing the path twice.
map: hashbrown::HashTable<ScopedMemberId>,
}
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<Member> {
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<MemberExprRef<'a>>,
) -> Option<ScopedMemberId> {
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<ScopedMemberId> {
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
}
}

View file

@ -30,8 +30,8 @@
use crate::list::{List, ListBuilder, ListSetReverseIterator, ListStorage}; use crate::list::{List, ListBuilder, ListSetReverseIterator, ListStorage};
use crate::semantic_index::ast_ids::ScopedUseId; use crate::semantic_index::ast_ids::ScopedUseId;
use crate::semantic_index::place::FileScopeId;
use crate::semantic_index::predicate::ScopedPredicateId; use crate::semantic_index::predicate::ScopedPredicateId;
use crate::semantic_index::scope::FileScopeId;
/// A narrowing constraint associated with a live binding. /// A narrowing constraint associated with a live binding.
/// ///

File diff suppressed because it is too large Load diff

View file

@ -14,7 +14,8 @@ use ruff_python_ast::Singleton;
use crate::db::Db; use crate::db::Db;
use crate::semantic_index::expression::Expression; use crate::semantic_index::expression::Expression;
use crate::semantic_index::global_scope; 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. // A scoped identifier for each `Predicate` in a scope.
#[derive(Clone, Debug, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)] #[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 /// 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 /// exist in the global scope; thus, we know that the `symbol_id` here will be relative
/// to the global scope of the importing file. /// to the global scope of the importing file.
pub(crate) symbol_id: ScopedPlaceId, pub(crate) symbol_id: ScopedSymbolId,
pub(crate) referenced_file: File, pub(crate) referenced_file: File,
} }

View file

@ -844,19 +844,17 @@ impl ReachabilityConstraints {
PredicateNode::Pattern(inner) => Self::analyze_single_pattern_predicate(db, inner), PredicateNode::Pattern(inner) => Self::analyze_single_pattern_predicate(db, inner),
PredicateNode::StarImportPlaceholder(star_import) => { PredicateNode::StarImportPlaceholder(star_import) => {
let place_table = place_table(db, star_import.scope(db)); let place_table = place_table(db, star_import.scope(db));
let symbol_name = place_table let symbol = place_table.symbol(star_import.symbol_id(db));
.place_expr(star_import.symbol_id(db))
.expect_name();
let referenced_file = star_import.referenced_file(db); let referenced_file = star_import.referenced_file(db);
let requires_explicit_reexport = match dunder_all_names(db, referenced_file) { let requires_explicit_reexport = match dunder_all_names(db, referenced_file) {
Some(all_names) => { Some(all_names) => {
if all_names.contains(symbol_name) { if all_names.contains(symbol.name()) {
Some(RequiresExplicitReExport::No) Some(RequiresExplicitReExport::No)
} else { } else {
tracing::trace!( tracing::trace!(
"Symbol `{}` (via star import) not found in `__all__` of `{}`", "Symbol `{}` (via star import) not found in `__all__` of `{}`",
symbol_name, symbol.name(),
referenced_file.path(db) referenced_file.path(db)
); );
return Truthiness::AlwaysFalse; return Truthiness::AlwaysFalse;
@ -865,7 +863,12 @@ impl ReachabilityConstraints {
None => None, None => None,
}; };
match imported_symbol(db, referenced_file, symbol_name, requires_explicit_reexport) match imported_symbol(
db,
referenced_file,
symbol.name(),
requires_explicit_reexport,
)
.place .place
{ {
crate::place::Place::Type(_, crate::place::Boundness::Bound) => { crate::place::Place::Type(_, crate::place::Boundness::Bound) => {

View file

@ -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 => "<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("<type alias>"),
NodeWithScopeKind::Lambda(_) => "<lambda>",
NodeWithScopeKind::ListComprehension(_) => "<listcomp>",
NodeWithScopeKind::SetComprehension(_) => "<setcomp>",
NodeWithScopeKind::DictComprehension(_) => "<dictcomp>",
NodeWithScopeKind::GeneratorExpression(_) => "<generator>",
}
}
}
/// 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<FileScopeId>,
/// The node that introduces this scope.
node: NodeWithScopeKind,
/// The range of [`FileScopeId`]s that are descendants of this scope.
descendants: Range<FileScopeId>,
/// 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<FileScopeId>,
node: NodeWithScopeKind,
descendants: Range<FileScopeId>,
reachability: ScopedReachabilityConstraintId,
in_type_checking_block: bool,
) -> Self {
Scope {
parent,
node,
descendants,
reachability,
in_type_checking_block,
}
}
pub(crate) fn parent(&self) -> Option<FileScopeId> {
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<FileScopeId> {
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<ast::StmtClassDef>),
ClassTypeParameters(AstNodeRef<ast::StmtClassDef>),
Function(AstNodeRef<ast::StmtFunctionDef>),
FunctionTypeParameters(AstNodeRef<ast::StmtFunctionDef>),
TypeAliasTypeParameters(AstNodeRef<ast::StmtTypeAlias>),
TypeAlias(AstNodeRef<ast::StmtTypeAlias>),
Lambda(AstNodeRef<ast::ExprLambda>),
ListComprehension(AstNodeRef<ast::ExprListComp>),
SetComprehension(AstNodeRef<ast::ExprSetComp>),
DictComprehension(AstNodeRef<ast::ExprDictComp>),
GeneratorExpression(AstNodeRef<ast::ExprGenerator>),
}
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),
}

View file

@ -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<ScopedSymbolId, Symbol>,
/// Map from symbol name to its ID.
///
/// Uses a hash table to avoid storing the name twice.
map: hashbrown::HashTable<ScopedSymbolId>,
}
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<ScopedSymbolId> {
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<Symbol> {
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
}
}

View file

@ -251,20 +251,21 @@ use crate::node_key::NodeKey;
use crate::place::BoundnessAnalysis; use crate::place::BoundnessAnalysis;
use crate::semantic_index::ast_ids::ScopedUseId; use crate::semantic_index::ast_ids::ScopedUseId;
use crate::semantic_index::definition::{Definition, DefinitionState}; use crate::semantic_index::definition::{Definition, DefinitionState};
use crate::semantic_index::member::ScopedMemberId;
use crate::semantic_index::narrowing_constraints::{ use crate::semantic_index::narrowing_constraints::{
ConstraintKey, NarrowingConstraints, NarrowingConstraintsBuilder, NarrowingConstraintsIterator, ConstraintKey, NarrowingConstraints, NarrowingConstraintsBuilder, NarrowingConstraintsIterator,
}; };
use crate::semantic_index::place::{ use crate::semantic_index::place::{PlaceExprRef, ScopedPlaceId};
FileScopeId, PlaceExpr, PlaceExprWithFlags, ScopeKind, ScopedPlaceId,
};
use crate::semantic_index::predicate::{ use crate::semantic_index::predicate::{
Predicate, PredicateOrLiteral, Predicates, PredicatesBuilder, ScopedPredicateId, Predicate, PredicateOrLiteral, Predicates, PredicatesBuilder, ScopedPredicateId,
}; };
use crate::semantic_index::reachability_constraints::{ use crate::semantic_index::reachability_constraints::{
ReachabilityConstraints, ReachabilityConstraintsBuilder, ScopedReachabilityConstraintId, 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::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}; use crate::types::{IntersectionBuilder, Truthiness, Type, infer_narrowing_constraint};
mod place_state; 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 to that symbol. If there are any, the assignment is invalid.
bindings_by_definition: FxHashMap<Definition<'db>, Bindings>, bindings_by_definition: FxHashMap<Definition<'db>, Bindings>,
/// [`PlaceState`] visible at end of scope for each place. /// [`PlaceState`] visible at end of scope for each symbol.
end_of_scope_places: IndexVec<ScopedPlaceId, PlaceState>, end_of_scope_symbols: IndexVec<ScopedSymbolId, PlaceState>,
/// All potentially reachable bindings and declarations, for each place. /// [`PlaceState`] visible at end of scope for each member.
reachable_definitions: IndexVec<ScopedPlaceId, ReachableDefinitions>, end_of_scope_members: IndexVec<ScopedMemberId, PlaceState>,
/// All potentially reachable bindings and declarations, for each symbol.
reachable_definitions_by_symbol: IndexVec<ScopedSymbolId, ReachableDefinitions>,
/// All potentially reachable bindings and declarations, for each member.
reachable_definitions_by_member: IndexVec<ScopedMemberId, ReachableDefinitions>,
/// Snapshot of bindings in this scope that can be used to resolve a reference in a nested /// Snapshot of bindings in this scope that can be used to resolve a reference in a nested
/// scope. /// scope.
@ -361,7 +368,7 @@ impl<'db> UseDefMap<'db> {
&self, &self,
constraint_key: ConstraintKey, constraint_key: ConstraintKey,
enclosing_scope: FileScopeId, enclosing_scope: FileScopeId,
expr: &PlaceExpr, expr: PlaceExprRef,
index: &'db SemanticIndex, index: &'db SemanticIndex,
) -> ApplicableConstraints<'_, 'db> { ) -> ApplicableConstraints<'_, 'db> {
match constraint_key { match constraint_key {
@ -419,9 +426,29 @@ impl<'db> UseDefMap<'db> {
pub(crate) fn end_of_scope_bindings( pub(crate) fn end_of_scope_bindings(
&self, &self,
place: ScopedPlaceId, 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> { ) -> BindingWithConstraintsIterator<'_, 'db> {
self.bindings_iterator( 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, BoundnessAnalysis::BasedOnUnboundVisibility,
) )
} }
@ -430,10 +457,26 @@ impl<'db> UseDefMap<'db> {
&self, &self,
place: ScopedPlaceId, place: ScopedPlaceId,
) -> BindingWithConstraintsIterator<'_, 'db> { ) -> BindingWithConstraintsIterator<'_, 'db> {
self.bindings_iterator( match place {
&self.reachable_definitions[place].bindings, ScopedPlaceId::Symbol(symbol) => self.all_reachable_symbol_bindings(symbol),
BoundnessAnalysis::AssumeBound, 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( pub(crate) fn enclosing_snapshot(
@ -482,33 +525,69 @@ impl<'db> UseDefMap<'db> {
&'map self, &'map self,
place: ScopedPlaceId, place: ScopedPlaceId,
) -> DeclarationsIterator<'map, 'db> { ) -> 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) 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( pub(crate) fn all_reachable_declarations(
&self, &self,
place: ScopedPlaceId, place: ScopedPlaceId,
) -> DeclarationsIterator<'_, 'db> { ) -> DeclarationsIterator<'_, 'db> {
let declarations = &self.reachable_definitions[place].declarations; match place {
self.declarations_iterator(declarations, BoundnessAnalysis::AssumeBound) 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, &'map self,
) -> impl Iterator<Item = (ScopedPlaceId, DeclarationsIterator<'map, 'db>)> + 'map { ) -> impl Iterator<Item = (ScopedSymbolId, DeclarationsIterator<'map, 'db>)> + 'map {
(0..self.end_of_scope_places.len()) self.end_of_scope_symbols
.map(ScopedPlaceId::from_usize) .indices()
.map(|place_id| (place_id, self.end_of_scope_declarations(place_id))) .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, &'map self,
) -> impl Iterator<Item = (ScopedPlaceId, BindingWithConstraintsIterator<'map, 'db>)> + 'map ) -> impl Iterator<Item = (ScopedSymbolId, BindingWithConstraintsIterator<'map, 'db>)> + 'map
{ {
(0..self.end_of_scope_places.len()) self.end_of_scope_symbols
.map(ScopedPlaceId::from_usize) .indices()
.map(|place_id| (place_id, self.end_of_scope_bindings(place_id))) .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`. /// 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. /// A snapshot of the definitions and constraints state at a particular point in control flow.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(super) struct FlowSnapshot { pub(super) struct FlowSnapshot {
place_states: IndexVec<ScopedPlaceId, PlaceState>, symbol_states: IndexVec<ScopedSymbolId, PlaceState>,
member_states: IndexVec<ScopedMemberId, PlaceState>,
reachability: ScopedReachabilityConstraintId, reachability: ScopedReachabilityConstraintId,
} }
@ -765,10 +845,14 @@ pub(super) struct UseDefMapBuilder<'db> {
bindings_by_definition: FxHashMap<Definition<'db>, Bindings>, bindings_by_definition: FxHashMap<Definition<'db>, Bindings>,
/// Currently live bindings and declarations for each place. /// Currently live bindings and declarations for each place.
place_states: IndexVec<ScopedPlaceId, PlaceState>, symbol_states: IndexVec<ScopedSymbolId, PlaceState>,
member_states: IndexVec<ScopedMemberId, PlaceState>,
/// All potentially reachable bindings and declarations, for each place. /// All potentially reachable bindings and declarations, for each place.
reachable_definitions: IndexVec<ScopedPlaceId, ReachableDefinitions>, reachable_symbol_definitions: IndexVec<ScopedSymbolId, ReachableDefinitions>,
reachable_member_definitions: IndexVec<ScopedMemberId, ReachableDefinitions>,
/// Snapshots of place states in this scope that can be used to resolve a reference in a /// Snapshots of place states in this scope that can be used to resolve a reference in a
/// nested scope. /// nested scope.
@ -790,8 +874,10 @@ impl<'db> UseDefMapBuilder<'db> {
node_reachability: FxHashMap::default(), node_reachability: FxHashMap::default(),
declarations_by_binding: FxHashMap::default(), declarations_by_binding: FxHashMap::default(),
bindings_by_definition: FxHashMap::default(), bindings_by_definition: FxHashMap::default(),
place_states: IndexVec::new(), symbol_states: IndexVec::new(),
reachable_definitions: IndexVec::new(), member_states: IndexVec::new(),
reachable_member_definitions: IndexVec::new(),
reachable_symbol_definitions: IndexVec::new(),
enclosing_snapshots: EnclosingSnapshots::default(), enclosing_snapshots: EnclosingSnapshots::default(),
is_class_scope, is_class_scope,
} }
@ -800,7 +886,14 @@ impl<'db> UseDefMapBuilder<'db> {
pub(super) fn mark_unreachable(&mut self) { pub(super) fn mark_unreachable(&mut self) {
self.reachability = ScopedReachabilityConstraintId::ALWAYS_FALSE; 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( state.record_reachability_constraint(
&mut self.reachability_constraints, &mut self.reachability_constraints,
ScopedReachabilityConstraintId::ALWAYS_FALSE, ScopedReachabilityConstraintId::ALWAYS_FALSE,
@ -809,42 +902,73 @@ impl<'db> UseDefMapBuilder<'db> {
} }
pub(super) fn add_place(&mut self, place: ScopedPlaceId) { pub(super) fn add_place(&mut self, place: ScopedPlaceId) {
match place {
ScopedPlaceId::Symbol(symbol) => {
let new_place = self let new_place = self
.place_states .symbol_states
.push(PlaceState::undefined(self.reachability)); .push(PlaceState::undefined(self.reachability));
debug_assert_eq!(place, new_place); debug_assert_eq!(symbol, new_place);
let new_place = self.reachable_definitions.push(ReachableDefinitions { let new_place = self
.reachable_symbol_definitions
.push(ReachableDefinitions {
bindings: Bindings::unbound(self.reachability), bindings: Bindings::unbound(self.reachability),
declarations: Declarations::undeclared(self.reachability), declarations: Declarations::undeclared(self.reachability),
}); });
debug_assert_eq!(place, new_place); 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( pub(super) fn record_binding(&mut self, place: ScopedPlaceId, binding: Definition<'db>) {
&mut self, let bindings = match place {
place: ScopedPlaceId, ScopedPlaceId::Symbol(symbol) => self.symbol_states[symbol].bindings(),
binding: Definition<'db>, ScopedPlaceId::Member(member) => self.member_states[member].bindings(),
is_place_name: bool, };
) {
self.bindings_by_definition 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 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 self.declarations_by_binding
.insert(binding, place_state.declarations().clone()); .insert(binding, place_state.declarations().clone());
place_state.record_binding( place_state.record_binding(
def_id, def_id,
self.reachability, self.reachability,
self.is_class_scope, 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, def_id,
self.reachability, self.reachability,
self.is_class_scope, self.is_class_scope,
is_place_name, place.is_symbol(),
PreviousDefinitions::AreKept, PreviousDefinitions::AreKept,
); );
} }
@ -869,7 +993,12 @@ impl<'db> UseDefMapBuilder<'db> {
} }
let narrowing_constraint = predicate.into(); 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 state
.record_narrowing_constraint(&mut self.narrowing_constraints, narrowing_constraint); .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 /// This is only used for `*`-import reachability constraints, which are handled differently
/// to most other reachability constraints. See the doc-comment for /// to most other reachability constraints. See the doc-comment for
/// [`Self::record_and_negate_star_import_reachability_constraint`] for more details. /// [`Self::record_and_negate_star_import_reachability_constraint`] for more details.
pub(super) fn single_place_snapshot(&self, place: ScopedPlaceId) -> PlaceState { pub(super) fn single_symbol_place_snapshot(&self, symbol: ScopedSymbolId) -> PlaceState {
self.place_states[place].clone() self.symbol_states[symbol].clone()
} }
/// This method exists solely for handling `*`-import reachability constraints. /// 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( pub(super) fn record_and_negate_star_import_reachability_constraint(
&mut self, &mut self,
reachability_id: ScopedReachabilityConstraintId, reachability_id: ScopedReachabilityConstraintId,
symbol: ScopedPlaceId, symbol: ScopedSymbolId,
pre_definition_state: PlaceState, pre_definition_state: PlaceState,
) { ) {
let negated_reachability_id = self let negated_reachability_id = self
@ -924,17 +1053,17 @@ impl<'db> UseDefMapBuilder<'db> {
.add_not_constraint(reachability_id); .add_not_constraint(reachability_id);
let mut post_definition_state = 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 post_definition_state
.record_reachability_constraint(&mut self.reachability_constraints, reachability_id); .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, &mut self.reachability_constraints,
negated_reachability_id, negated_reachability_id,
); );
self.place_states[symbol].merge( self.symbol_states[symbol].merge(
post_definition_state, post_definition_state,
&mut self.narrowing_constraints, &mut self.narrowing_constraints,
&mut self.reachability_constraints, &mut self.reachability_constraints,
@ -949,7 +1078,11 @@ impl<'db> UseDefMapBuilder<'db> {
.reachability_constraints .reachability_constraints
.add_and_constraint(self.reachability, constraint); .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); state.record_reachability_constraint(&mut self.reachability_constraints, constraint);
} }
} }
@ -962,56 +1095,81 @@ impl<'db> UseDefMapBuilder<'db> {
let def_id = self let def_id = self
.all_definitions .all_definitions
.push(DefinitionState::Defined(declaration)); .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 self.bindings_by_definition
.insert(declaration, place_state.bindings().clone()); .insert(declaration, place_state.bindings().clone());
place_state.record_declaration(def_id, self.reachability); place_state.record_declaration(def_id, self.reachability);
self.reachable_definitions[place] let definitions = match place {
.declarations ScopedPlaceId::Symbol(symbol) => &mut self.reachable_symbol_definitions[symbol],
.record_declaration(def_id, self.reachability, PreviousDefinitions::AreKept); 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( pub(super) fn record_declaration_and_binding(
&mut self, &mut self,
place: ScopedPlaceId, place: ScopedPlaceId,
definition: Definition<'db>, definition: Definition<'db>,
is_place_name: bool,
) { ) {
// We don't need to store anything in self.bindings_by_declaration or // We don't need to store anything in self.bindings_by_declaration or
// self.declarations_by_binding. // self.declarations_by_binding.
let def_id = self let def_id = self
.all_definitions .all_definitions
.push(DefinitionState::Defined(definition)); .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_declaration(def_id, self.reachability);
place_state.record_binding( place_state.record_binding(
def_id, def_id,
self.reachability, self.reachability,
self.is_class_scope, self.is_class_scope,
is_place_name, place.is_symbol(),
); );
self.reachable_definitions[place] let reachable_definitions = match place {
.declarations ScopedPlaceId::Symbol(symbol) => &mut self.reachable_symbol_definitions[symbol],
.record_declaration(def_id, self.reachability, PreviousDefinitions::AreKept); ScopedPlaceId::Member(member) => &mut self.reachable_member_definitions[member],
self.reachable_definitions[place].bindings.record_binding( };
reachable_definitions.declarations.record_declaration(
def_id,
self.reachability,
PreviousDefinitions::AreKept,
);
reachable_definitions.bindings.record_binding(
def_id, def_id,
self.reachability, self.reachability,
self.is_class_scope, self.is_class_scope,
is_place_name, place.is_symbol(),
PreviousDefinitions::AreKept, 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 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( place_state.record_binding(
def_id, def_id,
self.reachability, self.reachability,
self.is_class_scope, self.is_class_scope,
is_place_name, place.is_symbol(),
); );
} }
@ -1021,11 +1179,13 @@ impl<'db> UseDefMapBuilder<'db> {
use_id: ScopedUseId, use_id: ScopedUseId,
node_key: NodeKey, 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 // We have a use of a place; clone the current bindings for that place, and record them
// as the live bindings for this use. // as the live bindings for this use.
let new_use = self let new_use = self.bindings_by_use.push(bindings.clone());
.bindings_by_use
.push(self.place_states[place].bindings().clone());
debug_assert_eq!(use_id, new_use); debug_assert_eq!(use_id, new_use);
// Track reachability of all uses of places to silence `unresolved-reference` // Track reachability of all uses of places to silence `unresolved-reference`
@ -1041,28 +1201,30 @@ impl<'db> UseDefMapBuilder<'db> {
&mut self, &mut self,
enclosing_place: ScopedPlaceId, enclosing_place: ScopedPlaceId,
scope: ScopeKind, scope: ScopeKind,
enclosing_place_expr: &PlaceExprWithFlags, enclosing_place_expr: PlaceExprRef,
) -> ScopedEnclosingSnapshotId { ) -> 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), // 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. // 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.enclosing_snapshots.push(EnclosingSnapshot::Constraint(
self.place_states[enclosing_place] bindings.unbound_narrowing_constraint(),
.bindings()
.unbound_narrowing_constraint(),
)) ))
} else { } else {
self.enclosing_snapshots.push(EnclosingSnapshot::Bindings( self.enclosing_snapshots
self.place_states[enclosing_place].bindings().clone(), .push(EnclosingSnapshot::Bindings(bindings.clone()))
))
} }
} }
/// Take a snapshot of the current visible-places state. /// Take a snapshot of the current visible-places state.
pub(super) fn snapshot(&self) -> FlowSnapshot { pub(super) fn snapshot(&self) -> FlowSnapshot {
FlowSnapshot { FlowSnapshot {
place_states: self.place_states.clone(), symbol_states: self.symbol_states.clone(),
member_states: self.member_states.clone(),
reachability: self.reachability, 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 // 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 // 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. // greater than the number of known places in a previously-taken snapshot.
let num_places = self.place_states.len(); let num_symbols = self.symbol_states.len();
debug_assert!(num_places >= snapshot.place_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. // 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; self.reachability = snapshot.reachability;
// If the snapshot we are restoring is missing some places we've recorded since, we need // 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 // 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". // snapshot, the correct state to fill them in with is "undefined".
self.place_states self.symbol_states
.resize(num_places, PlaceState::undefined(self.reachability)); .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 /// 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 // 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 // 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. // 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(); let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter();
for current in &mut self.place_states { 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() { if let Some(snapshot) = snapshot_definitions_iter.next() {
current.merge( current.merge(
snapshot, snapshot,
@ -1142,10 +1328,21 @@ impl<'db> UseDefMapBuilder<'db> {
for constraint in self.node_reachability.values() { for constraint in self.node_reachability.values() {
self.reachability_constraints.mark_used(*constraint); self.reachability_constraints.mark_used(*constraint);
} }
for place_state in &mut self.place_states { for symbol_state in &mut self.symbol_states {
place_state.finish(&mut self.reachability_constraints); 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 reachable_definition
.bindings .bindings
.finish(&mut self.reachability_constraints); .finish(&mut self.reachability_constraints);
@ -1169,8 +1366,10 @@ impl<'db> UseDefMapBuilder<'db> {
self.mark_reachability_constraints(); self.mark_reachability_constraints();
self.all_definitions.shrink_to_fit(); self.all_definitions.shrink_to_fit();
self.place_states.shrink_to_fit(); self.symbol_states.shrink_to_fit();
self.reachable_definitions.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.bindings_by_use.shrink_to_fit();
self.node_reachability.shrink_to_fit(); self.node_reachability.shrink_to_fit();
self.declarations_by_binding.shrink_to_fit(); self.declarations_by_binding.shrink_to_fit();
@ -1184,8 +1383,10 @@ impl<'db> UseDefMapBuilder<'db> {
reachability_constraints: self.reachability_constraints.build(), reachability_constraints: self.reachability_constraints.build(),
bindings_by_use: self.bindings_by_use, bindings_by_use: self.bindings_by_use,
node_reachability: self.node_reachability, node_reachability: self.node_reachability,
end_of_scope_places: self.place_states, end_of_scope_symbols: self.symbol_states,
reachable_definitions: self.reachable_definitions, 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, declarations_by_binding: self.declarations_by_binding,
bindings_by_definition: self.bindings_by_definition, bindings_by_definition: self.bindings_by_definition,
enclosing_snapshots: self.enclosing_snapshots, enclosing_snapshots: self.enclosing_snapshots,

View file

@ -7,7 +7,7 @@ use ruff_source_file::LineIndex;
use crate::Db; use crate::Db;
use crate::module_name::ModuleName; use crate::module_name::ModuleName;
use crate::module_resolver::{KnownModule, Module, resolve_module}; 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::semantic_index::semantic_index;
use crate::types::ide_support::all_declarations_and_bindings; use crate::types::ide_support::all_declarations_and_bindings;
use crate::types::{Type, binding_type, infer_scope_types}; use crate::types::{Type, binding_type, infer_scope_types};

View file

@ -33,7 +33,8 @@ use crate::module_name::ModuleName;
use crate::module_resolver::{KnownModule, resolve_module}; use crate::module_resolver::{KnownModule, resolve_module};
use crate::place::{Boundness, Place, PlaceAndQualifiers, imported_symbol}; use crate::place::{Boundness, Place, PlaceAndQualifiers, imported_symbol};
use crate::semantic_index::definition::Definition; 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::semantic_index::{imported_modules, place_table, semantic_index};
use crate::suppression::check_suppressions; use crate::suppression::check_suppressions;
use crate::types::call::{Binding, Bindings, CallArguments, CallableBinding}; use crate::types::call::{Binding, Bindings, CallArguments, CallableBinding};
@ -8840,7 +8841,7 @@ impl<'db> TypeIsType<'db> {
let (scope, place) = self.place_info(db)?; let (scope, place) = self.place_info(db)?;
let table = place_table(db, scope); 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> { pub fn unbound(db: &'db dyn Db, ty: Type<'db>) -> Type<'db> {

View file

@ -10,7 +10,7 @@ use super::{
infer_expression_type, infer_unpack_types, infer_expression_type, infer_unpack_types,
}; };
use crate::semantic_index::definition::{Definition, DefinitionState}; 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::semantic_index::{DeclarationWithConstraint, SemanticIndex, attribute_declarations};
use crate::types::context::InferContext; use crate::types::context::InferContext;
use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE}; use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE};
@ -36,8 +36,9 @@ use crate::{
semantic_index::{ semantic_index::{
attribute_assignments, attribute_assignments,
definition::{DefinitionKind, TargetKind}, definition::{DefinitionKind, TargetKind},
place::ScopeId, place_table,
place_table, semantic_index, use_def_map, scope::ScopeId,
semantic_index, use_def_map,
}, },
types::{ types::{
CallArguments, CallError, CallErrorKind, MetaclassCandidate, UnionBuilder, UnionType, CallArguments, CallError, CallErrorKind, MetaclassCandidate, UnionBuilder, UnionType,
@ -1820,7 +1821,7 @@ impl<'db> ClassLiteral<'db> {
let table = place_table(db, class_body_scope); let table = place_table(db, class_body_scope);
let use_def = use_def_map(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 // 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 // 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 // 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; 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 let Ok(attr) = place_from_declarations(db, declarations) {
if attr.is_class_var() { if attr.is_class_var() {
@ -1850,11 +1851,11 @@ impl<'db> ClassLiteral<'db> {
} }
if let Some(attr_ty) = attr.place.ignore_possibly_unbound() { 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(); let default_ty = place_from_bindings(db, bindings).ignore_possibly_unbound();
attributes.insert( attributes.insert(
place_expr.expect_name().clone(), symbol.name().clone(),
DataclassField { DataclassField {
field_ty: attr_ty.apply_optional_specialization(db, specialization), field_ty: attr_ty.apply_optional_specialization(db, specialization),
default_ty: default_ty default_ty: default_ty
@ -2041,9 +2042,9 @@ impl<'db> ClassLiteral<'db> {
let is_method_reachable = let is_method_reachable =
if let Some(method_def) = method_scope.node(db).as_function(&module) { if let Some(method_def) = method_scope.node(db).as_function(&module) {
let method = index.expect_single_definition(method_def); 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 class_map
.all_reachable_bindings(method_place) .all_reachable_symbol_bindings(method_place)
.find_map(|bind| { .find_map(|bind| {
(bind.binding.is_defined_and(|def| def == method)) (bind.binding.is_defined_and(|def| def == method))
.then(|| class_map.is_binding_reachable(db, &bind)) .then(|| class_map.is_binding_reachable(db, &bind))
@ -2258,10 +2259,10 @@ impl<'db> ClassLiteral<'db> {
let body_scope = self.body_scope(db); let body_scope = self.body_scope(db);
let table = place_table(db, body_scope); 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 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); let declared_and_qualifiers = place_from_declarations(db, declarations);
match declared_and_qualifiers { match declared_and_qualifiers {
@ -2288,7 +2289,7 @@ impl<'db> ClassLiteral<'db> {
// The attribute is declared in the class body. // 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 inferred = place_from_bindings(db, bindings);
let has_binding = !inferred.is_unbound(); let has_binding = !inferred.is_unbound();

View file

@ -12,7 +12,7 @@ use ruff_text_size::{Ranged, TextRange};
use super::{Type, TypeCheckDiagnostics, binding_type}; use super::{Type, TypeCheckDiagnostics, binding_type};
use crate::lint::LintSource; use crate::lint::LintSource;
use crate::semantic_index::place::ScopeId; use crate::semantic_index::scope::ScopeId;
use crate::semantic_index::semantic_index; use crate::semantic_index::semantic_index;
use crate::types::function::FunctionDecorators; use crate::types::function::FunctionDecorators;
use crate::{ use crate::{

View file

@ -82,9 +82,8 @@ pub(crate) fn enum_metadata<'db>(
// TODO: handle `StrEnum` which uses lowercase names as values when using `auto()`. // TODO: handle `StrEnum` which uses lowercase names as values when using `auto()`.
let mut auto_counter = 0; let mut auto_counter = 0;
let ignored_names: Option<Vec<&str>> = if let Some(ignore) = table.place_id_by_name("_ignore_") let ignored_names: Option<Vec<&str>> = if let Some(ignore) = table.symbol_id("_ignore_") {
{ let ignore_bindings = use_def_map.all_reachable_symbol_bindings(ignore);
let ignore_bindings = use_def_map.all_reachable_bindings(ignore);
let ignore_place = place_from_bindings(db, ignore_bindings); let ignore_place = place_from_bindings(db, ignore_bindings);
match ignore_place { match ignore_place {
@ -101,9 +100,9 @@ pub(crate) fn enum_metadata<'db>(
let mut aliases = FxHashMap::default(); let mut aliases = FxHashMap::default();
let members = use_def_map let members = use_def_map
.all_end_of_scope_bindings() .all_end_of_scope_symbol_bindings()
.filter_map(|(place_id, bindings)| { .filter_map(|(symbol_id, bindings)| {
let name = table.place_expr(place_id).as_name()?; let name = table.symbol(symbol_id).name();
if name.starts_with("__") && !name.ends_with("__") { if name.starts_with("__") && !name.ends_with("__") {
// Skip private attributes // 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); let declared = place_from_declarations(db, declarations);
match declared { match declared {
@ -213,9 +212,8 @@ pub(crate) fn enum_metadata<'db>(
} }
} }
Some(name) Some(name.clone())
}) })
.cloned()
.collect::<FxOrderSet<_>>(); .collect::<FxOrderSet<_>>();
if members.is_empty() { if members.is_empty() {

View file

@ -62,7 +62,7 @@ use crate::module_resolver::{KnownModule, file_to_module};
use crate::place::{Boundness, Place, place_from_bindings}; use crate::place::{Boundness, Place, place_from_bindings};
use crate::semantic_index::ast_ids::HasScopedUseId; use crate::semantic_index::ast_ids::HasScopedUseId;
use crate::semantic_index::definition::Definition; 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::semantic_index::semantic_index;
use crate::types::call::{Binding, CallArguments}; use crate::types::call::{Binding, CallArguments};
use crate::types::context::InferContext; use crate::types::context::InferContext;

View file

@ -5,7 +5,7 @@ use crate::place::{
}; };
use crate::semantic_index::definition::Definition; use crate::semantic_index::definition::Definition;
use crate::semantic_index::definition::DefinitionKind; use crate::semantic_index::definition::DefinitionKind;
use crate::semantic_index::place::ScopeId; use crate::semantic_index::scope::ScopeId;
use crate::semantic_index::{ use crate::semantic_index::{
attribute_scopes, global_scope, place_table, semantic_index, use_def_map, 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); let table = place_table(db, scope_id);
use_def_map use_def_map
.all_end_of_scope_declarations() .all_end_of_scope_symbol_declarations()
.filter_map(move |(symbol_id, declarations)| { .filter_map(move |(symbol_id, declarations)| {
place_from_declarations(db, declarations) place_from_declarations(db, declarations)
.ok() .ok()
.and_then(|result| { .and_then(|result| {
result.place.ignore_possibly_unbound().and_then(|ty| { result.place.ignore_possibly_unbound().map(|ty| {
table let symbol = table.symbol(symbol_id);
.place_expr(symbol_id) Member {
.as_name() name: symbol.name().clone(),
.cloned() ty,
.map(|name| Member { name, ty }) }
}) })
}) })
}) })
.chain( .chain(use_def_map.all_end_of_scope_symbol_bindings().filter_map(
use_def_map move |(symbol_id, bindings)| {
.all_end_of_scope_bindings()
.filter_map(move |(symbol_id, bindings)| {
place_from_bindings(db, bindings) place_from_bindings(db, bindings)
.ignore_possibly_unbound() .ignore_possibly_unbound()
.and_then(|ty| { .map(|ty| {
table let symbol = table.symbol(symbol_id);
.place_expr(symbol_id) Member {
.as_name() name: symbol.name().clone(),
.cloned() ty,
.map(|name| Member { name, ty }) }
}) })
}), },
) ))
} }
struct AllMembers<'db> { struct AllMembers<'db> {
@ -164,10 +162,8 @@ impl<'db> AllMembers<'db> {
let use_def_map = use_def_map(db, module_scope); let use_def_map = use_def_map(db, module_scope);
let place_table = place_table(db, module_scope); let place_table = place_table(db, module_scope);
for (symbol_id, _) in use_def_map.all_end_of_scope_declarations() { for (symbol_id, _) in use_def_map.all_end_of_scope_symbol_declarations() {
let Some(symbol_name) = place_table.place_expr(symbol_id).as_name() else { let symbol_name = place_table.symbol(symbol_id).name();
continue;
};
let Place::Type(ty, _) = imported_symbol(db, file, symbol_name, None).place let Place::Type(ty, _) = imported_symbol(db, file, symbol_name, None).place
else { else {
continue; continue;
@ -204,7 +200,7 @@ impl<'db> AllMembers<'db> {
} }
self.members.insert(Member { self.members.insert(Member {
name: place_table.place_expr(symbol_id).expect_name().clone(), name: symbol_name.clone(),
ty, ty,
}); });
} }
@ -276,7 +272,7 @@ impl<'db> AllMembers<'db> {
let index = semantic_index(db, file); let index = semantic_index(db, file);
for function_scope_id in attribute_scopes(db, class_body_scope) { for function_scope_id in attribute_scopes(db, class_body_scope) {
let place_table = index.place_table(function_scope_id); 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 { let Some(name) = place_expr.as_instance_attribute() else {
continue; continue;
}; };
@ -376,11 +372,11 @@ pub fn definition_kind_for_name<'db>(
let place_table = index.place_table(file_scope); let place_table = index.place_table(file_scope);
// Look up the place by name // 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 // Get the use-def map and look up definitions for this place
let use_def_map = index.use_def_map(file_scope); 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 // Find the first valid definition and return its kind
for declaration in declarations { 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) { for (scope_id, _scope) in index.visible_ancestor_scopes(file_scope) {
let place_table = index.place_table(scope_id); 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 continue; // Name not found in this scope, try parent scope
}; };
// Check if this place is marked as global or nonlocal // Check if this place is marked as global or nonlocal
let place_expr = place_table.place_expr(place_id); let place_expr = place_table.symbol(symbol_id);
let is_global = place_expr.is_marked_global(); let is_global = place_expr.is_global();
let is_nonlocal = place_expr.is_marked_nonlocal(); let is_nonlocal = place_expr.is_nonlocal();
// TODO: The current algorithm doesn't return definintions or bindings // TODO: The current algorithm doesn't return definintions or bindings
// for other scopes that are outside of this scope hierarchy that target // 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_scope_id = global_scope(db, file);
let global_place_table = crate::semantic_index::place_table(db, global_scope_id); 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_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 = 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 { for binding in global_bindings {
if let Some(def) = binding.binding.definition() { 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); let use_def_map = index.use_def_map(scope_id);
// Get all definitions (both bindings and declarations) for this place // Get all definitions (both bindings and declarations) for this place
let bindings = use_def_map.all_reachable_bindings(place_id); let bindings = use_def_map.all_reachable_symbol_bindings(symbol_id);
let declarations = use_def_map.all_reachable_declarations(place_id); let declarations = use_def_map.all_reachable_symbol_declarations(symbol_id);
for binding in bindings { for binding in bindings {
if let Some(def) = binding.binding.definition() { 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); let class_place_table = crate::semantic_index::place_table(db, class_scope);
// Look for class-level declarations and bindings // 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); let use_def = use_def_map(db, class_scope);
// Check declarations first // 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() { if let Some(def) = decl.declaration.definition() {
resolved.extend(resolve_definition(db, def, Some(name_str))); resolved.extend(resolve_definition(db, def, Some(name_str)));
break 'scopes; break 'scopes;
@ -589,7 +586,7 @@ pub fn definitions_for_attribute<'db>(
} }
// If no declarations found, check bindings // 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() { if let Some(def) = binding.binding.definition() {
resolved.extend(resolve_definition(db, def, Some(name_str))); resolved.extend(resolve_definition(db, def, Some(name_str)));
break 'scopes; break 'scopes;
@ -604,11 +601,11 @@ pub fn definitions_for_attribute<'db>(
for function_scope_id in attribute_scopes(db, class_scope) { for function_scope_id in attribute_scopes(db, class_scope) {
let place_table = index.place_table(function_scope_id); 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); let use_def = index.use_def_map(function_scope_id);
// Check declarations first // 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() { if let Some(def) = decl.declaration.definition() {
resolved.extend(resolve_definition(db, def, Some(name_str))); resolved.extend(resolve_definition(db, def, Some(name_str)));
break 'scopes; break 'scopes;
@ -616,7 +613,7 @@ pub fn definitions_for_attribute<'db>(
} }
// If no declarations found, check bindings // 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() { if let Some(def) = binding.binding.definition() {
resolved.extend(resolve_definition(db, def, Some(name_str))); resolved.extend(resolve_definition(db, def, Some(name_str)));
break 'scopes; break 'scopes;
@ -801,7 +798,7 @@ mod resolve_definition {
use crate::module_resolver::file_to_module; use crate::module_resolver::file_to_module;
use crate::semantic_index::definition::{Definition, DefinitionKind}; 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::semantic_index::{global_scope, place_table, semantic_index, use_def_map};
use crate::{Db, ModuleName, resolve_module, resolve_real_module}; use crate::{Db, ModuleName, resolve_module, resolve_real_module};
@ -971,7 +968,7 @@ mod resolve_definition {
symbol_name: &str, symbol_name: &str,
) -> IndexSet<Definition<'db>> { ) -> IndexSet<Definition<'db>> {
let place_table = place_table(db, scope); 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(); return IndexSet::new();
}; };
@ -979,8 +976,8 @@ mod resolve_definition {
let mut definitions = IndexSet::new(); let mut definitions = IndexSet::new();
// Get all definitions (both bindings and declarations) for this place // Get all definitions (both bindings and declarations) for this place
let bindings = use_def_map.all_reachable_bindings(place_id); let bindings = use_def_map.all_reachable_symbol_bindings(symbol_id);
let declarations = use_def_map.all_reachable_declarations(place_id); let declarations = use_def_map.all_reachable_symbol_declarations(symbol_id);
for binding in bindings { for binding in bindings {
if let Some(def) = binding.binding.definition() { if let Some(def) = binding.binding.definition() {

View file

@ -81,9 +81,11 @@ use crate::semantic_index::definition::{
}; };
use crate::semantic_index::expression::{Expression, ExpressionKind}; use crate::semantic_index::expression::{Expression, ExpressionKind};
use crate::semantic_index::narrowing_constraints::ConstraintKey; use crate::semantic_index::narrowing_constraints::ConstraintKey;
use crate::semantic_index::place::{ use crate::semantic_index::place::{PlaceExpr, PlaceExprRef};
FileScopeId, NodeWithScopeKind, NodeWithScopeRef, PlaceExpr, ScopeId, ScopeKind, ScopedPlaceId, use crate::semantic_index::scope::{
FileScopeId, NodeWithScopeKind, NodeWithScopeRef, ScopeId, ScopeKind,
}; };
use crate::semantic_index::symbol::ScopedSymbolId;
use crate::semantic_index::{ use crate::semantic_index::{
ApplicableConstraints, EnclosingSnapshotResult, SemanticIndex, place_table, 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 { for place in overloaded_function_places {
if let Place::Type(Type::FunctionLiteral(function), Boundness::Bound) = 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 function.file(self.db()) != self.file() {
// If the function is not in this file, we don't need to check it. // If the function is not in this file, we don't need to check it.
@ -1779,22 +1784,29 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let global_use_def_map = self.index.use_def_map(FileScopeId::global()); let global_use_def_map = self.index.use_def_map(FileScopeId::global());
let nonlocal_use_def_map; let nonlocal_use_def_map;
let place_id = binding.place(self.db()); let place_id = binding.place(self.db());
let place = place_table.place_expr(place_id); let place = place_table.place(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 { 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 match self
.index .index
.place_table(FileScopeId::global()) .place_table(FileScopeId::global())
.place_id_by_expr(&place.expr) .symbol_id(symbol.name())
{ {
Some(id) => (global_use_def_map.end_of_scope_declarations(id), false), 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 // This variable shows up in `global` declarations but doesn't have an explicit
// binding in the global scope. // binding in the global scope.
None => (use_def.declarations_at_binding(binding), true), None => (use_def.declarations_at_binding(binding), true),
} }
} else if self } else if self
.index .index
.symbol_is_nonlocal_in_scope(place_id, file_scope_id) .symbol_is_nonlocal_in_scope(symbol_id, file_scope_id)
{ {
// If we run out of ancestor scopes without finding a definition, we'll fall back to // 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 // the local scope. This will also be a syntax error in `infer_nonlocal_statement` (no
@ -1811,22 +1823,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
continue; continue;
} }
let enclosing_place_table = self.index.place_table(enclosing_scope_file_id); 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) let Some(enclosing_symbol_id) = enclosing_place_table.symbol_id(symbol.name())
else { else {
// This ancestor scope doesn't have a binding. Keep going. // This ancestor scope doesn't have a binding. Keep going.
continue; continue;
}; };
if self
.index let enclosing_symbol = enclosing_place_table.symbol(enclosing_symbol_id);
.symbol_is_nonlocal_in_scope(enclosing_place_id, enclosing_scope_file_id) if enclosing_symbol.is_nonlocal() {
{
// The variable is `nonlocal` in this ancestor scope. Keep going. // The variable is `nonlocal` in this ancestor scope. Keep going.
continue; continue;
} }
if self if enclosing_symbol.is_global() {
.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` // 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 // chain, and it's a syntax error in `infer_nonlocal_statement`. Ignore that
// here and just bail out of this loop. // here and just bail out of this loop.
@ -1835,13 +1843,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// We found the closest definition. Note that (as in `infer_place_load`) this does // 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`. // *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); 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); declarations =
nonlocal_use_def_map.end_of_scope_symbol_declarations(enclosing_symbol_id);
is_local = false; is_local = false;
break; break;
} }
(declarations, is_local) (declarations, is_local)
} else { } else {
(use_def.declarations_at_binding(binding), true) (use_def.declarations_at_binding(binding), true)
}
} else {
(use_def.declarations_at_binding(binding), true)
}; };
let (declared_ty, is_modifiable) = place_from_declarations(self.db(), declarations) let (declared_ty, is_modifiable) = place_from_declarations(self.db(), declarations)
@ -1849,14 +1861,20 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Ok( Ok(
if matches!(place_and_quals.place, Place::Type(_, Boundness::Bound)) { if matches!(place_and_quals.place, Place::Type(_, Boundness::Bound)) {
place_and_quals place_and_quals
} else if skip_non_global_scopes } else if let PlaceExprRef::Symbol(symbol) = place {
|| self.scope().file_scope_id(self.db()).is_global() 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 = let module_type_declarations =
module_type_implicit_global_declaration(self.db(), &place.expr)?; module_type_implicit_global_declaration(self.db(), symbol.name())?;
place_and_quals.or_fall_back_to(self.db(), || module_type_declarations) place_and_quals.or_fall_back_to(self.db(), || module_type_declarations)
} else { } else {
place_and_quals place_and_quals
}
} else {
place_and_quals
}, },
) )
}) })
@ -1867,7 +1885,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}| { }| {
let is_modifiable = !qualifiers.contains(TypeQualifiers::FINAL); 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 { if let AnyNodeRef::ExprAttribute(ast::ExprAttribute {
value, attr, .. value, attr, ..
}) = node }) = node
@ -1902,7 +1920,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
) )
.unwrap_or_else(|(ty, conflicting)| { .unwrap_or_else(|(ty, conflicting)| {
// TODO point out the conflicting declarations in the diagnostic? // 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) { if let Some(builder) = self.context.report_lint(&CONFLICTING_DECLARATIONS, node) {
builder.into_diagnostic(format_args!( builder.into_diagnostic(format_args!(
"Conflicting declared types for `{place}`: {}", "Conflicting declared types for `{place}`: {}",
@ -1925,7 +1943,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.and_then(|r| r.binding.definition()); .and_then(|r| r.binding.definition());
if !is_local || previous_definition.is_some() { 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( if let Some(builder) = self.context.report_lint(
&INVALID_ASSIGNMENT, &INVALID_ASSIGNMENT,
binding.full_range(self.db(), self.module()), 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 /// Returns `true` if `symbol_id` should be looked up in the global scope, skipping intervening
/// local scopes. /// 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() !file_scope_id.is_global()
&& self && self
.index .index
@ -2054,9 +2076,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// Fallback to bindings declared on `types.ModuleType` if it's a global symbol // Fallback to bindings declared on `types.ModuleType` if it's a global symbol
let scope = self.scope().file_scope_id(self.db()); let scope = self.scope().file_scope_id(self.db());
let place_table = self.index.place_table(scope); let place_table = self.index.place_table(scope);
let place = place_table.place_expr(declaration.place(self.db())); let place = place_table.place(declaration.place(self.db()));
if scope.is_global() && place.is_name() { if let PlaceExprRef::Symbol(symbol) = &place {
module_type_implicit_global_symbol(self.db(), place.expect_name()) if scope.is_global() {
module_type_implicit_global_symbol(self.db(), symbol.name())
} else {
Place::Unbound.into()
}
} else { } else {
Place::Unbound.into() Place::Unbound.into()
} }
@ -2107,10 +2133,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let file_scope_id = self.scope().file_scope_id(self.db()); let file_scope_id = self.scope().file_scope_id(self.db());
if file_scope_id.is_global() { if file_scope_id.is_global() {
let place_table = self.index.place_table(file_scope_id); let place_table = self.index.place_table(file_scope_id);
let place = place_table.place_expr(definition.place(self.db())); let place = place_table.place(definition.place(self.db()));
if let Some(module_type_implicit_declaration) = if let Some(module_type_implicit_declaration) = place
module_type_implicit_global_declaration(self.db(), &place.expr) .as_symbol()
.ok() .map(|symbol| module_type_implicit_global_symbol(self.db(), symbol.name()))
.and_then(|place| place.place.ignore_possibly_unbound()) .and_then(|place| place.place.ignore_possibly_unbound())
{ {
let declared_type = declared_ty.inner_type(); let declared_type = declared_ty.inner_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, // 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. // 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`). // 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 { if let Some(value) = value {
let inferred_ty = self.infer_maybe_standalone_expression(value); let inferred_ty = self.infer_maybe_standalone_expression(value);
@ -4874,16 +4900,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.kind(self.db()) .kind(self.db())
.as_star_import() .as_star_import()
.map(|star_import| { .map(|star_import| {
let symbol_table = self let place_table = self
.index .index
.place_table(self.scope().file_scope_id(self.db())); .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() { let name = if let Some((star_import, symbol_table)) = star_import_info.as_ref() {
symbol_table symbol_table.symbol(star_import.symbol_id()).name()
.place_expr(star_import.place_id())
.expect_name()
} else { } else {
&alias.name.id &alias.name.id
}; };
@ -5041,9 +5065,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} = global; } = global;
let global_place_table = self.index.place_table(FileScopeId::global()); let global_place_table = self.index.place_table(FileScopeId::global());
for name in names { for name in names {
if let Some(place_id) = global_place_table.place_id_by_name(name) { if let Some(symbol_id) = global_place_table.symbol_id(name) {
let place = global_place_table.place_expr(place_id); let symbol = global_place_table.symbol(symbol_id);
if place.is_bound() || place.is_declared() { if symbol.is_bound() || symbol.is_declared() {
// This name is explicitly defined in the global scope (not just in function // This name is explicitly defined in the global scope (not just in function
// bodies that mark it `global`). // bodies that mark it `global`).
continue; continue;
@ -5095,11 +5119,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
continue; continue;
} }
let enclosing_place_table = self.index.place_table(enclosing_scope_file_id); 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. // This scope doesn't define this name. Keep going.
continue; 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. // 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 // 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 // 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 // `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 // it also can't refer to local variables in enclosing functions that are declared
// `global` (also a `SyntaxError`). // `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 // A "chain" of `nonlocal` statements is "broken" by a `global` statement. Stop
// looping and report that this `nonlocal` statement is invalid. // looping and report that this `nonlocal` statement is invalid.
break; break;
} }
if !enclosing_place.is_bound() if !enclosing_symbol.is_bound()
&& !enclosing_place.is_declared() && !enclosing_symbol.is_declared()
&& !enclosing_place.is_marked_nonlocal() && !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. // The name is only referenced here, not defined. Keep going.
continue; continue;
} }
@ -6041,9 +6065,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} }
None None
} }
Some(expr) => match PlaceExpr::try_from(expr) { Some(expr) => match PlaceExpr::try_from_expr(expr) {
Ok(place_expr) => place_table(db, scope).place_id_by_expr(&place_expr), Some(place_expr) => place_table(db, scope).place_id(&place_expr),
Err(()) => None, 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. // Perform narrowing with applicable constraints between the current scope and the enclosing scope.
fn narrow_place_with_applicable_constraints( fn narrow_place_with_applicable_constraints(
&self, &self,
expr: &PlaceExpr, expr: PlaceExprRef,
mut ty: Type<'db>, mut ty: Type<'db>,
constraint_keys: &[(FileScopeId, ConstraintKey)], constraint_keys: &[(FileScopeId, ConstraintKey)],
) -> Type<'db> { ) -> Type<'db> {
@ -6129,7 +6153,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
for (enclosing_scope_file_id, constraint_key) in constraint_keys { for (enclosing_scope_file_id, constraint_key) in constraint_keys {
let use_def = self.index.use_def_map(*enclosing_scope_file_id); 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_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( match use_def.applicable_constraints(
*constraint_key, *constraint_key,
@ -6258,11 +6282,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
id: symbol_name, id: symbol_name,
ctx: _, ctx: _,
} = name_node; } = name_node;
let Ok(expr) = PlaceExpr::try_from(symbol_name); let expr = PlaceExpr::from_expr_name(name_node);
let db = self.db(); let db = self.db();
let (resolved, constraint_keys) = 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 resolved
// Not found in the module's explicitly declared global symbols? // 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`. // These are looked up as attributes on `types.ModuleType`.
.or_fall_back_to(db, || { .or_fall_back_to(db, || {
module_type_implicit_global_symbol(db, symbol_name).map_type(|ty| { 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 // Not found in globals? Fallback to builtins
@ -6314,7 +6342,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
fn infer_local_place_load( fn infer_local_place_load(
&self, &self,
expr: &PlaceExpr, expr: PlaceExprRef,
expr_ref: ast::ExprRef, expr_ref: ast::ExprRef,
) -> (Place<'db>, Option<ScopedUseId>) { ) -> (Place<'db>, Option<ScopedUseId>) {
let db = self.db(); 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 we're inferring types of deferred expressions, always treat them as public symbols
if self.is_deferred() { 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)) place_from_bindings(db, use_def.all_reachable_bindings(place_id))
} else { } else {
assert!( assert!(
@ -6355,7 +6383,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
/// which is used to narrow by condition rather than by assignment. /// which is used to narrow by condition rather than by assignment.
fn infer_place_load( fn infer_place_load(
&self, &self,
expr: &PlaceExpr, place_expr: PlaceExprRef,
expr_ref: ast::ExprRef, expr_ref: ast::ExprRef,
) -> (PlaceAndQualifiers<'db>, Vec<(FileScopeId, ConstraintKey)>) { ) -> (PlaceAndQualifiers<'db>, Vec<(FileScopeId, ConstraintKey)>) {
let db = self.db(); 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 place_table = self.index.place_table(file_scope_id);
let mut constraint_keys = vec![]; 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 { if let Some(use_id) = use_id {
constraint_keys.push((file_scope_id, ConstraintKey::UseId(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 place = PlaceAndQualifiers::from(local_scope_place).or_fall_back_to(db, || {
let has_bindings_in_this_scope = match place_table.place_by_expr(expr) { let has_bindings_in_this_scope = match place_table.place_id(place_expr) {
Some(place_expr) => place_expr.is_bound(), Some(place_id) => place_table.place(place_id).is_bound(),
None => { None => {
assert!( assert!(
self.deferred_state.in_string_annotation(), self.deferred_state.in_string_annotation(),
@ -6384,16 +6412,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let current_file = self.file(); let current_file = self.file();
let mut is_nonlocal_binding = false; let mut is_nonlocal_binding = false;
if let Some(name) = expr.as_name() { if let Some(symbol) = place_expr.as_symbol() {
if let Some(symbol_id) = place_table.place_id_by_name(name) { if let Some(symbol_id) = place_table.symbol_id(symbol.name()) {
if self.skip_non_global_scopes(file_scope_id, symbol_id) { if self.skip_non_global_scopes(file_scope_id, symbol_id) {
return global_symbol(self.db(), self.file(), name).map_type(|ty| { return global_symbol(self.db(), self.file(), symbol.name()).map_type(
|ty| {
self.narrow_place_with_applicable_constraints( self.narrow_place_with_applicable_constraints(
expr, place_expr,
ty, ty,
&constraint_keys, &constraint_keys,
) )
}); },
);
} }
is_nonlocal_binding = self is_nonlocal_binding = self
.index .index
@ -6414,9 +6444,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
return Place::Unbound.into(); 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; 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 { match expr_ref {
ast::ExprRef::Attribute(attribute) => { ast::ExprRef::Attribute(attribute) => {
expr_ref = ast::ExprRef::from(&attribute.value); expr_ref = ast::ExprRef::from(&attribute.value);
@ -6427,8 +6458,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
_ => unreachable!(), _ => unreachable!(),
} }
} }
let (parent_place, _use_id) = let (parent_place, _use_id) = self.infer_local_place_load(parent_expr, expr_ref);
self.infer_local_place_load(&root_expr.expr, expr_ref);
if let Place::Type(_, _) = parent_place { if let Place::Type(_, _) = parent_place {
return Place::Unbound.into(); return Place::Unbound.into();
} }
@ -6463,7 +6493,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
if !self.is_deferred() { if !self.is_deferred() {
match self.index.enclosing_snapshot( match self.index.enclosing_snapshot(
enclosing_scope_file_id, enclosing_scope_file_id,
expr, place_expr,
file_scope_id, file_scope_id,
) { ) {
EnclosingSnapshotResult::FoundConstraint(constraint) => { EnclosingSnapshotResult::FoundConstraint(constraint) => {
@ -6473,7 +6503,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
)); ));
} }
EnclosingSnapshotResult::FoundBindings(bindings) => { EnclosingSnapshotResult::FoundBindings(bindings) => {
if expr.is_name() if place_expr.is_symbol()
&& !enclosing_scope_id.is_function_like(db) && !enclosing_scope_id.is_function_like(db)
&& !is_immediately_enclosing_scope && !is_immediately_enclosing_scope
{ {
@ -6481,7 +6511,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} }
let place = place_from_bindings(db, bindings).map_type(|ty| { let place = place_from_bindings(db, bindings).map_type(|ty| {
self.narrow_place_with_applicable_constraints( self.narrow_place_with_applicable_constraints(
expr, place_expr,
ty, ty,
&constraint_keys, &constraint_keys,
) )
@ -6497,13 +6527,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
EnclosingSnapshotResult::NotFound => { EnclosingSnapshotResult::NotFound => {
let enclosing_place_table = let enclosing_place_table =
self.index.place_table(enclosing_scope_file_id); 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 enclosing_root_place.is_bound() {
if let Place::Type(_, _) = place( if let Place::Type(_, _) = place(
db, db,
enclosing_scope_id, enclosing_scope_id,
&enclosing_root_place.expr, enclosing_root_place,
ConsideredDefinitions::AllReachable, ConsideredDefinitions::AllReachable,
) )
.place .place
@ -6523,17 +6555,22 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} }
let enclosing_place_table = self.index.place_table(enclosing_scope_file_id); 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; continue;
}; };
let enclosing_place = enclosing_place_table.place(enclosing_place_id);
// Reads of "free" variables terminate at any enclosing scope that marks the // 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 // 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 // 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 // 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 // is `nonlocal`, then this is a semantic syntax error, but we don't enforce that
// here. See `infer_nonlocal_statement`.) // 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; break;
} }
@ -6547,11 +6584,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let local_place_and_qualifiers = place( let local_place_and_qualifiers = place(
db, db,
enclosing_scope_id, enclosing_scope_id,
expr, place_expr,
ConsideredDefinitions::AllReachable, ConsideredDefinitions::AllReachable,
) )
.map_type(|ty| { .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 // We could have Place::Unbound here, despite the checks above, for example if
// this scope contains a `del` statement but no binding or declaration. // 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; 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 // 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, // declared but doesn't mark it `nonlocal`. The name is therefore resolved,
// and we won't consider any scopes outside of this one. // 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() { if !self.is_deferred() {
match self.index.enclosing_snapshot( match self.index.enclosing_snapshot(
FileScopeId::global(), FileScopeId::global(),
expr, place_expr,
file_scope_id, file_scope_id,
) { ) {
EnclosingSnapshotResult::FoundConstraint(constraint) => { EnclosingSnapshotResult::FoundConstraint(constraint) => {
@ -6598,7 +6642,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
EnclosingSnapshotResult::FoundBindings(bindings) => { EnclosingSnapshotResult::FoundBindings(bindings) => {
let place = place_from_bindings(db, bindings).map_type(|ty| { let place = place_from_bindings(db, bindings).map_type(|ty| {
self.narrow_place_with_applicable_constraints( self.narrow_place_with_applicable_constraints(
expr, place_expr,
ty, ty,
&constraint_keys, &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(); return Place::Unbound.into();
}; };
explicit_global_symbol(db, self.file(), name).map_type(|ty| { explicit_global_symbol(db, self.file(), symbol.name()).map_type(|ty| {
self.narrow_place_with_applicable_constraints(expr, ty, &constraint_keys) self.narrow_place_with_applicable_constraints(
place_expr,
ty,
&constraint_keys,
)
}) })
}) })
}); });
@ -6739,8 +6787,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
) -> Type<'db> { ) -> Type<'db> {
let target = target.into(); let target = target.into();
if let Ok(place_expr) = PlaceExpr::try_from(target) { if let Some(place_expr) = PlaceExpr::try_from_expr(target) {
self.narrow_place_with_applicable_constraints(&place_expr, target_ty, constraint_keys) self.narrow_place_with_applicable_constraints(
PlaceExprRef::from(&place_expr),
target_ty,
constraint_keys,
)
} else { } else {
target_ty target_ty
} }
@ -6761,9 +6813,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let mut constraint_keys = vec![]; let mut constraint_keys = vec![];
let mut assigned_type = None; let mut assigned_type = None;
if let Ok(place_expr) = PlaceExpr::try_from(attribute) { if let Some(place_expr) = PlaceExpr::try_from_expr(attribute) {
let (resolved, keys) = let (resolved, keys) = self.infer_place_load(
self.infer_place_load(&place_expr, ast::ExprRef::Attribute(attribute)); PlaceExprRef::from(&place_expr),
ast::ExprRef::Attribute(attribute),
);
constraint_keys.extend(keys); constraint_keys.extend(keys);
if let Place::Type(ty, Boundness::Bound) = resolved.place { if let Place::Type(ty, Boundness::Bound) = resolved.place {
assigned_type = Some(ty); 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` is a valid reference, we attempt type narrowing by assignment.
if !value_ty.is_unknown() { if !value_ty.is_unknown() {
if let Ok(expr) = PlaceExpr::try_from(subscript) { if let Some(expr) = PlaceExpr::try_from_expr(subscript) {
let (place, keys) = let (place, keys) = self.infer_place_load(
self.infer_place_load(&expr, ast::ExprRef::Subscript(subscript)); PlaceExprRef::from(&expr),
ast::ExprRef::Subscript(subscript),
);
constraint_keys.extend(keys); constraint_keys.extend(keys);
if let Place::Type(ty, Boundness::Bound) = place.place { 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 // 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::db::tests::{TestDb, setup_db};
use crate::place::{global_symbol, symbol}; use crate::place::{global_symbol, symbol};
use crate::semantic_index::definition::Definition; 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::semantic_index::{global_scope, place_table, semantic_index, use_def_map};
use crate::types::check_types; use crate::types::check_types;
use ruff_db::diagnostic::Diagnostic; 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> { fn first_public_binding<'db>(db: &'db TestDb, file: File, name: &str) -> Definition<'db> {
let scope = global_scope(db, file); let scope = global_scope(db, file);
use_def_map(db, scope) 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()) .find_map(|b| b.binding.definition())
.expect("no binding found") .expect("no binding found")
} }

View file

@ -1,11 +1,12 @@
use crate::Db; use crate::Db;
use crate::semantic_index::expression::Expression; 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::place_table;
use crate::semantic_index::predicate::{ use crate::semantic_index::predicate::{
CallableAndCallExpr, ClassPatternKind, PatternPredicate, PatternPredicateKind, Predicate, CallableAndCallExpr, ClassPatternKind, PatternPredicate, PatternPredicateKind, Predicate,
PredicateNode, PredicateNode,
}; };
use crate::semantic_index::scope::ScopeId;
use crate::types::enums::{enum_member_literals, enum_metadata}; use crate::types::enums::{enum_member_literals, enum_metadata};
use crate::types::function::KnownFunction; use crate::types::function::KnownFunction;
use crate::types::infer::infer_same_file_expression_type; 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<PlaceExpr> { fn place_expr(expr: &ast::Expr) -> Option<PlaceExpr> {
match expr { match expr {
ast::Expr::Name(name) => Some(PlaceExpr::name(name.id.clone())), ast::Expr::Named(named) => PlaceExpr::try_from_expr(named.target.as_ref()),
ast::Expr::Attribute(attr) => PlaceExpr::try_from(attr).ok(), _ => PlaceExpr::try_from_expr(expr),
ast::Expr::Subscript(subscript) => PlaceExpr::try_from(subscript).ok(),
ast::Expr::Named(named) => PlaceExpr::try_from(named.target.as_ref()).ok(),
_ => None,
} }
} }
@ -447,7 +445,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> {
#[track_caller] #[track_caller]
fn expect_place(&self, place_expr: &PlaceExpr) -> ScopedPlaceId { fn expect_place(&self, place_expr: &PlaceExpr) -> ScopedPlaceId {
self.places() self.places()
.place_id_by_expr(place_expr) .place_id(place_expr)
.expect("We should always have a place for every `PlaceExpr`") .expect("We should always have a place for every `PlaceExpr`")
} }

View file

@ -4,10 +4,12 @@ use itertools::Itertools;
use ruff_python_ast::name::Name; use ruff_python_ast::name::Name;
use super::TypeVarVariance;
use crate::semantic_index::place_table;
use crate::{ use crate::{
Db, FxOrderSet, Db, FxOrderSet,
place::{Boundness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations}, place::{Boundness, Place, PlaceAndQualifiers, place_from_bindings, place_from_declarations},
semantic_index::{place_table, use_def_map}, semantic_index::use_def_map,
types::{ types::{
CallableType, ClassBase, ClassLiteral, KnownFunction, PropertyInstanceType, Signature, CallableType, ClassBase, ClassLiteral, KnownFunction, PropertyInstanceType, Signature,
Type, TypeMapping, TypeQualifiers, TypeRelation, TypeTransformer, TypeVarInstance, Type, TypeMapping, TypeQualifiers, TypeRelation, TypeTransformer, TypeVarInstance,
@ -16,8 +18,6 @@ use crate::{
}, },
}; };
use super::TypeVarVariance;
impl<'db> ClassLiteral<'db> { impl<'db> ClassLiteral<'db> {
/// Returns `Some` if this is a protocol class, `None` otherwise. /// Returns `Some` if this is a protocol class, `None` otherwise.
pub(super) fn into_protocol_class(self, db: &'db dyn Db) -> Option<ProtocolClassLiteral<'db>> { pub(super) fn into_protocol_class(self, db: &'db dyn Db) -> Option<ProtocolClassLiteral<'db>> {
@ -471,15 +471,15 @@ fn cached_protocol_interface<'db>(
members.extend( members.extend(
use_def_map use_def_map
.all_end_of_scope_declarations() .all_end_of_scope_symbol_declarations()
.flat_map(|(place_id, declarations)| { .flat_map(|(symbol_id, declarations)| {
place_from_declarations(db, declarations).map(|place| (place_id, place)) place_from_declarations(db, declarations).map(|place| (symbol_id, place))
}) })
.filter_map(|(place_id, place)| { .filter_map(|(symbol_id, place)| {
place place
.place .place
.ignore_possibly_unbound() .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 // 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 // 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 // members at runtime, and it's important that we accurately understand
// type narrowing that uses `isinstance()` or `issubclass()` with // type narrowing that uses `isinstance()` or `issubclass()` with
// runtime-checkable protocols. // runtime-checkable protocols.
.chain(use_def_map.all_end_of_scope_bindings().filter_map( .chain(use_def_map.all_end_of_scope_symbol_bindings().filter_map(
|(place_id, bindings)| { |(symbol_id, bindings)| {
place_from_bindings(db, bindings) place_from_bindings(db, bindings)
.ignore_possibly_unbound() .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)| { .map(|(symbol_id, member, qualifiers, bound_on_class)| {
Some(( (
place_table.place_expr(place_id).as_name()?, place_table.symbol(symbol_id).name(),
member, member,
qualifiers, qualifiers,
bound_on_class, bound_on_class,
)) )
}) })
.filter(|(name, _, _, _)| !excluded_from_proto_members(name)) .filter(|(name, _, _, _)| !excluded_from_proto_members(name))
.map(|(name, ty, qualifiers, bound_on_class)| { .map(|(name, ty, qualifiers, bound_on_class)| {

View file

@ -7,7 +7,7 @@ use ruff_python_ast::{self as ast, AnyNodeRef};
use crate::Db; use crate::Db;
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey; 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::tuple::{ResizeTupleError, Tuple, TupleLength, TupleSpec, TupleUnpacker};
use crate::types::{Type, TypeCheckDiagnostics, infer_expression_types}; use crate::types::{Type, TypeCheckDiagnostics, infer_expression_types};
use crate::unpack::{UnpackKind, UnpackValue}; use crate::unpack::{UnpackKind, UnpackValue};

View file

@ -6,7 +6,7 @@ use ruff_text_size::{Ranged, TextRange};
use crate::Db; use crate::Db;
use crate::ast_node_ref::AstNodeRef; use crate::ast_node_ref::AstNodeRef;
use crate::semantic_index::expression::Expression; 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. /// This ingredient represents a single unpacking.
/// ///