mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-07 17:10:31 +00:00
[ty] Split ScopedPlaceId
into ScopedSymbolId
and ScopedMemberId
(#19497)
This commit is contained in:
parent
f722bfa9e6
commit
b033fb6bfd
30 changed files with 2454 additions and 1647 deletions
|
@ -6,7 +6,7 @@ use ruff_python_ast::ExprRef;
|
|||
|
||||
use crate::Db;
|
||||
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
|
||||
use crate::semantic_index::place::ScopeId;
|
||||
use crate::semantic_index::scope::ScopeId;
|
||||
use crate::semantic_index::semantic_index;
|
||||
|
||||
/// AST ids for a single scope.
|
||||
|
@ -40,7 +40,7 @@ fn ast_ids<'db>(db: &'db dyn Db, scope: ScopeId) -> &'db AstIds {
|
|||
semantic_index(db, scope.file(db)).ast_ids(scope.file_scope_id(db))
|
||||
}
|
||||
|
||||
/// Uniquely identifies a use of a name in a [`crate::semantic_index::place::FileScopeId`].
|
||||
/// Uniquely identifies a use of a name in a [`crate::semantic_index::FileScopeId`].
|
||||
#[newtype_index]
|
||||
#[derive(get_size2::GetSize)]
|
||||
pub struct ScopedUseId;
|
||||
|
|
|
@ -30,10 +30,7 @@ use crate::semantic_index::definition::{
|
|||
StarImportDefinitionNodeRef, WithItemDefinitionNodeRef,
|
||||
};
|
||||
use crate::semantic_index::expression::{Expression, ExpressionKind};
|
||||
use crate::semantic_index::place::{
|
||||
FileScopeId, NodeWithScopeKey, NodeWithScopeKind, NodeWithScopeRef, PlaceExpr,
|
||||
PlaceExprWithFlags, PlaceTableBuilder, Scope, ScopeId, ScopeKind, ScopedPlaceId,
|
||||
};
|
||||
use crate::semantic_index::place::{PlaceExpr, PlaceTableBuilder, ScopedPlaceId};
|
||||
use crate::semantic_index::predicate::{
|
||||
CallableAndCallExpr, ClassPatternKind, PatternPredicate, PatternPredicateKind, Predicate,
|
||||
PredicateNode, PredicateOrLiteral, ScopedPredicateId, StarImportPlaceholderPredicate,
|
||||
|
@ -42,10 +39,15 @@ use crate::semantic_index::re_exports::exported_names;
|
|||
use crate::semantic_index::reachability_constraints::{
|
||||
ReachabilityConstraintsBuilder, ScopedReachabilityConstraintId,
|
||||
};
|
||||
use crate::semantic_index::scope::{
|
||||
FileScopeId, NodeWithScopeKey, NodeWithScopeKind, NodeWithScopeRef,
|
||||
};
|
||||
use crate::semantic_index::scope::{Scope, ScopeId, ScopeKind, ScopeLaziness};
|
||||
use crate::semantic_index::symbol::{ScopedSymbolId, Symbol};
|
||||
use crate::semantic_index::use_def::{
|
||||
EnclosingSnapshotKey, FlowSnapshot, ScopedEnclosingSnapshotId, UseDefMapBuilder,
|
||||
};
|
||||
use crate::semantic_index::{ArcUseDefMap, ExpressionsScopeMap, ScopeLaziness, SemanticIndex};
|
||||
use crate::semantic_index::{ArcUseDefMap, ExpressionsScopeMap, SemanticIndex};
|
||||
use crate::semantic_model::HasTrackedScope;
|
||||
use crate::unpack::{Unpack, UnpackKind, UnpackPosition, UnpackValue};
|
||||
use crate::{Db, Program};
|
||||
|
@ -295,18 +297,16 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
let enclosing_scope_kind = self.scopes[enclosing_scope_id].kind();
|
||||
let enclosing_place_table = &self.place_tables[enclosing_scope_id];
|
||||
|
||||
for nested_place in self.place_tables[popped_scope_id].places() {
|
||||
for nested_place in self.place_tables[popped_scope_id].iter() {
|
||||
// Skip this place if this enclosing scope doesn't contain any bindings for it.
|
||||
// Note that even if this place is bound in the popped scope,
|
||||
// it may refer to the enclosing scope bindings
|
||||
// so we also need to snapshot the bindings of the enclosing scope.
|
||||
|
||||
let Some(enclosing_place_id) =
|
||||
enclosing_place_table.place_id_by_expr(&nested_place.expr)
|
||||
else {
|
||||
let Some(enclosing_place_id) = enclosing_place_table.place_id(nested_place) else {
|
||||
continue;
|
||||
};
|
||||
let enclosing_place = enclosing_place_table.place_expr(enclosing_place_id);
|
||||
let enclosing_place = enclosing_place_table.place(enclosing_place_id);
|
||||
|
||||
// Snapshot the state of this place that are visible at this point in this
|
||||
// enclosing scope.
|
||||
|
@ -332,11 +332,7 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
}
|
||||
}
|
||||
|
||||
fn bound_scope(
|
||||
&self,
|
||||
enclosing_scope: FileScopeId,
|
||||
place_expr: &PlaceExpr,
|
||||
) -> Option<FileScopeId> {
|
||||
fn bound_scope(&self, enclosing_scope: FileScopeId, symbol: &Symbol) -> Option<FileScopeId> {
|
||||
self.scope_stack
|
||||
.iter()
|
||||
.rev()
|
||||
|
@ -344,12 +340,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
.find_map(|scope_info| {
|
||||
let scope_id = scope_info.file_scope_id;
|
||||
let place_table = &self.place_tables[scope_id];
|
||||
let place_id = place_table.place_id_by_expr(place_expr)?;
|
||||
if place_table.place_expr(place_id).is_bound() {
|
||||
Some(scope_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let place_id = place_table.symbol_id(symbol.name())?;
|
||||
place_table.place(place_id).is_bound().then_some(scope_id)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -360,13 +352,11 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
let enclosing_scope_kind = self.scopes[enclosing_scope_id].kind();
|
||||
let enclosing_place_table = &self.place_tables[enclosing_scope_id];
|
||||
|
||||
for nested_place in self.place_tables[popped_scope_id].places() {
|
||||
// We don't record lazy snapshots of attributes or subscripts, because these are difficult to track as they modify.
|
||||
// We don't record lazy snapshots of attributes or subscripts, because these are difficult to track as they modify.
|
||||
for nested_symbol in self.place_tables[popped_scope_id].symbols() {
|
||||
// For the same reason, symbols declared as nonlocal or global are not recorded.
|
||||
// Also, if the enclosing scope allows its members to be modified from elsewhere, the snapshot will not be recorded.
|
||||
if !nested_place.is_name()
|
||||
|| self.scopes[enclosing_scope_id].visibility().is_public()
|
||||
{
|
||||
if self.scopes[enclosing_scope_id].visibility().is_public() {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -374,17 +364,16 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
// Note that even if this place is bound in the popped scope,
|
||||
// it may refer to the enclosing scope bindings
|
||||
// so we also need to snapshot the bindings of the enclosing scope.
|
||||
|
||||
let Some(enclosing_place_id) =
|
||||
enclosing_place_table.place_id_by_expr(&nested_place.expr)
|
||||
let Some(enclosed_symbol_id) =
|
||||
enclosing_place_table.symbol_id(nested_symbol.name())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let enclosing_place = enclosing_place_table.place_expr(enclosing_place_id);
|
||||
let enclosing_place = enclosing_place_table.symbol(enclosed_symbol_id);
|
||||
if !enclosing_place.is_bound() {
|
||||
// If the bound scope of a place can be modified from elsewhere, the snapshot will not be recorded.
|
||||
if self
|
||||
.bound_scope(enclosing_scope_id, &nested_place.expr)
|
||||
.bound_scope(enclosing_scope_id, nested_symbol)
|
||||
.is_none_or(|scope| self.scopes[scope].visibility().is_public())
|
||||
{
|
||||
continue;
|
||||
|
@ -395,14 +384,14 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
// enclosing scope (this may later be invalidated and swept away).
|
||||
let key = EnclosingSnapshotKey {
|
||||
enclosing_scope: enclosing_scope_id,
|
||||
enclosing_place: enclosing_place_id,
|
||||
enclosing_place: enclosed_symbol_id.into(),
|
||||
nested_scope: popped_scope_id,
|
||||
nested_laziness: ScopeLaziness::Lazy,
|
||||
};
|
||||
let lazy_snapshot = self.use_def_maps[enclosing_scope_id].snapshot_outer_state(
|
||||
enclosing_place_id,
|
||||
enclosed_symbol_id.into(),
|
||||
enclosing_scope_kind,
|
||||
enclosing_place,
|
||||
enclosing_place.into(),
|
||||
);
|
||||
self.enclosing_snapshots.insert(key, lazy_snapshot);
|
||||
}
|
||||
|
@ -415,7 +404,10 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
let place_table = &self.place_tables[key.enclosing_scope];
|
||||
key.nested_laziness.is_eager()
|
||||
|| key.enclosing_scope != popped_scope_id
|
||||
|| !place_table.is_place_reassigned(key.enclosing_place)
|
||||
|| !key
|
||||
.enclosing_place
|
||||
.as_symbol()
|
||||
.is_some_and(|symbol_id| place_table.symbol(symbol_id).is_reassigned())
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -423,24 +415,27 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
self.enclosing_snapshots.retain(|key, _| {
|
||||
let place_table = &self.place_tables[key.enclosing_scope];
|
||||
|
||||
let is_place_bound_and_nonlocal = || -> bool {
|
||||
let place_expr = place_table.place_expr(key.enclosing_place);
|
||||
let is_bound_and_non_local = || -> bool {
|
||||
let ScopedPlaceId::Symbol(symbol_id) = key.enclosing_place else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let symbol = place_table.symbol(symbol_id);
|
||||
self.scopes
|
||||
.iter_enumerated()
|
||||
.skip_while(|(scope_id, _)| *scope_id != key.enclosing_scope)
|
||||
.any(|(scope_id, _)| {
|
||||
let other_scope_place_table = &self.place_tables[scope_id];
|
||||
let Some(place_id) =
|
||||
other_scope_place_table.place_id_by_expr(&place_expr.expr)
|
||||
let Some(symbol_id) = other_scope_place_table.symbol_id(symbol.name())
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let place = other_scope_place_table.place_expr(place_id);
|
||||
place.is_marked_nonlocal() && place.is_bound()
|
||||
let symbol = other_scope_place_table.symbol(symbol_id);
|
||||
symbol.is_nonlocal() && symbol.is_bound()
|
||||
})
|
||||
};
|
||||
|
||||
key.nested_laziness.is_eager() || !is_place_bound_and_nonlocal()
|
||||
key.nested_laziness.is_eager() || !is_bound_and_non_local()
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -515,17 +510,17 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
|
||||
/// Add a symbol to the place table and the use-def map.
|
||||
/// Return the [`ScopedPlaceId`] that uniquely identifies the symbol in both.
|
||||
fn add_symbol(&mut self, name: Name) -> ScopedPlaceId {
|
||||
let (place_id, added) = self.current_place_table_mut().add_symbol(name);
|
||||
fn add_symbol(&mut self, name: Name) -> ScopedSymbolId {
|
||||
let (symbol_id, added) = self.current_place_table_mut().add_symbol(Symbol::new(name));
|
||||
if added {
|
||||
self.current_use_def_map_mut().add_place(place_id);
|
||||
self.current_use_def_map_mut().add_place(symbol_id.into());
|
||||
}
|
||||
place_id
|
||||
symbol_id
|
||||
}
|
||||
|
||||
/// Add a place to the place table and the use-def map.
|
||||
/// Return the [`ScopedPlaceId`] that uniquely identifies the place in both.
|
||||
fn add_place(&mut self, place_expr: PlaceExprWithFlags) -> ScopedPlaceId {
|
||||
fn add_place(&mut self, place_expr: PlaceExpr) -> ScopedPlaceId {
|
||||
let (place_id, added) = self.current_place_table_mut().add_place(place_expr);
|
||||
if added {
|
||||
self.current_use_def_map_mut().add_place(place_id);
|
||||
|
@ -533,16 +528,19 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
place_id
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn mark_place_bound(&mut self, id: ScopedPlaceId) {
|
||||
self.current_place_table_mut().mark_place_bound(id);
|
||||
self.current_place_table_mut().mark_bound(id);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn mark_place_declared(&mut self, id: ScopedPlaceId) {
|
||||
self.current_place_table_mut().mark_place_declared(id);
|
||||
self.current_place_table_mut().mark_declared(id);
|
||||
}
|
||||
|
||||
fn mark_place_used(&mut self, id: ScopedPlaceId) {
|
||||
self.current_place_table_mut().mark_place_used(id);
|
||||
#[track_caller]
|
||||
fn mark_symbol_used(&mut self, id: ScopedSymbolId) {
|
||||
self.current_place_table_mut().symbol_mut(id).mark_used();
|
||||
}
|
||||
|
||||
fn add_entry_for_definition_key(&mut self, key: DefinitionNodeKey) -> &mut Definitions<'db> {
|
||||
|
@ -572,23 +570,20 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
fn delete_associated_bindings(&mut self, place: ScopedPlaceId) {
|
||||
let scope = self.current_scope();
|
||||
// Don't delete associated bindings if the scope is a class scope & place is a name (it's never visible to nested scopes)
|
||||
if self.scopes[scope].kind() == ScopeKind::Class
|
||||
&& self.place_tables[scope].place_expr(place).is_name()
|
||||
{
|
||||
if self.scopes[scope].kind() == ScopeKind::Class && place.is_symbol() {
|
||||
return;
|
||||
}
|
||||
for associated_place in self.place_tables[scope].associated_place_ids(place) {
|
||||
let is_place_name = self.place_tables[scope]
|
||||
.place_expr(associated_place)
|
||||
.is_name();
|
||||
self.use_def_maps[scope].delete_binding(associated_place, is_place_name);
|
||||
for associated_place in self.place_tables[scope]
|
||||
.associated_place_ids(place)
|
||||
.iter()
|
||||
.copied()
|
||||
{
|
||||
self.use_def_maps[scope].delete_binding(associated_place.into());
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_binding(&mut self, place: ScopedPlaceId) {
|
||||
let is_place_name = self.current_place_table().place_expr(place).is_name();
|
||||
self.current_use_def_map_mut()
|
||||
.delete_binding(place, is_place_name);
|
||||
self.current_use_def_map_mut().delete_binding(place);
|
||||
}
|
||||
|
||||
/// Push a new [`Definition`] onto the list of definitions
|
||||
|
@ -637,16 +632,15 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
self.mark_place_declared(place);
|
||||
}
|
||||
|
||||
let is_place_name = self.current_place_table().place_expr(place).is_name();
|
||||
let use_def = self.current_use_def_map_mut();
|
||||
match category {
|
||||
DefinitionCategory::DeclarationAndBinding => {
|
||||
use_def.record_declaration_and_binding(place, definition, is_place_name);
|
||||
use_def.record_declaration_and_binding(place, definition);
|
||||
self.delete_associated_bindings(place);
|
||||
}
|
||||
DefinitionCategory::Declaration => use_def.record_declaration(place, definition),
|
||||
DefinitionCategory::Binding => {
|
||||
use_def.record_binding(place, definition, is_place_name);
|
||||
use_def.record_binding(place, definition);
|
||||
self.delete_associated_bindings(place);
|
||||
}
|
||||
}
|
||||
|
@ -963,8 +957,8 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
// TODO create Definition for PEP 695 typevars
|
||||
// note that the "bound" on the typevar is a totally different thing than whether
|
||||
// or not a name is "bound" by a typevar declaration; the latter is always true.
|
||||
self.mark_place_bound(symbol);
|
||||
self.mark_place_declared(symbol);
|
||||
self.mark_place_bound(symbol.into());
|
||||
self.mark_place_declared(symbol.into());
|
||||
if let Some(bounds) = bound {
|
||||
self.visit_expr(bounds);
|
||||
}
|
||||
|
@ -972,9 +966,9 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
self.visit_expr(default);
|
||||
}
|
||||
match type_param {
|
||||
ast::TypeParam::TypeVar(node) => self.add_definition(symbol, node),
|
||||
ast::TypeParam::ParamSpec(node) => self.add_definition(symbol, node),
|
||||
ast::TypeParam::TypeVarTuple(node) => self.add_definition(symbol, node),
|
||||
ast::TypeParam::TypeVar(node) => self.add_definition(symbol.into(), node),
|
||||
ast::TypeParam::ParamSpec(node) => self.add_definition(symbol.into(), node),
|
||||
ast::TypeParam::TypeVarTuple(node) => self.add_definition(symbol.into(), node),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1061,20 +1055,23 @@ impl<'db, 'ast> SemanticIndexBuilder<'db, 'ast> {
|
|||
if let Some(vararg) = parameters.vararg.as_ref() {
|
||||
let symbol = self.add_symbol(vararg.name.id().clone());
|
||||
self.add_definition(
|
||||
symbol,
|
||||
symbol.into(),
|
||||
DefinitionNodeRef::VariadicPositionalParameter(vararg),
|
||||
);
|
||||
}
|
||||
if let Some(kwarg) = parameters.kwarg.as_ref() {
|
||||
let symbol = self.add_symbol(kwarg.name.id().clone());
|
||||
self.add_definition(symbol, DefinitionNodeRef::VariadicKeywordParameter(kwarg));
|
||||
self.add_definition(
|
||||
symbol.into(),
|
||||
DefinitionNodeRef::VariadicKeywordParameter(kwarg),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn declare_parameter(&mut self, parameter: &'ast ast::ParameterWithDefault) {
|
||||
let symbol = self.add_symbol(parameter.name().id().clone());
|
||||
|
||||
let definition = self.add_definition(symbol, parameter);
|
||||
let definition = self.add_definition(symbol.into(), parameter);
|
||||
|
||||
// Insert a mapping from the inner Parameter node to the same definition. This
|
||||
// ensures that calling `HasType::inferred_type` on the inner parameter returns
|
||||
|
@ -1295,12 +1292,15 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
// used to collect all the overloaded definitions of a function. This needs to be
|
||||
// done on the `Identifier` node as opposed to `ExprName` because that's what the
|
||||
// AST uses.
|
||||
self.mark_place_used(symbol);
|
||||
self.mark_symbol_used(symbol);
|
||||
let use_id = self.current_ast_ids().record_use(name);
|
||||
self.current_use_def_map_mut()
|
||||
.record_use(symbol, use_id, NodeKey::from_node(name));
|
||||
self.current_use_def_map_mut().record_use(
|
||||
symbol.into(),
|
||||
use_id,
|
||||
NodeKey::from_node(name),
|
||||
);
|
||||
|
||||
self.add_definition(symbol, function_def);
|
||||
self.add_definition(symbol.into(), function_def);
|
||||
}
|
||||
ast::Stmt::ClassDef(class) => {
|
||||
for decorator in &class.decorator_list {
|
||||
|
@ -1324,7 +1324,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
|
||||
// In Python runtime semantics, a class is registered after its scope is evaluated.
|
||||
let symbol = self.add_symbol(class.name.id.clone());
|
||||
self.add_definition(symbol, class);
|
||||
self.add_definition(symbol.into(), class);
|
||||
}
|
||||
ast::Stmt::TypeAlias(type_alias) => {
|
||||
let symbol = self.add_symbol(
|
||||
|
@ -1334,7 +1334,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
.map(|name| name.id.clone())
|
||||
.unwrap_or("<unknown>".into()),
|
||||
);
|
||||
self.add_definition(symbol, type_alias);
|
||||
self.add_definition(symbol.into(), type_alias);
|
||||
self.visit_expr(&type_alias.name);
|
||||
|
||||
self.with_type_params(
|
||||
|
@ -1366,7 +1366,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
|
||||
let symbol = self.add_symbol(symbol_name);
|
||||
self.add_definition(
|
||||
symbol,
|
||||
symbol.into(),
|
||||
ImportDefinitionNodeRef {
|
||||
node,
|
||||
alias_index,
|
||||
|
@ -1438,10 +1438,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
// For more details, see the doc-comment on `StarImportPlaceholderPredicate`.
|
||||
for export in exported_names(self.db, referenced_module) {
|
||||
let symbol_id = self.add_symbol(export.clone());
|
||||
let node_ref = StarImportDefinitionNodeRef {
|
||||
node,
|
||||
place_id: symbol_id,
|
||||
};
|
||||
let node_ref = StarImportDefinitionNodeRef { node, symbol_id };
|
||||
let star_import = StarImportPlaceholderPredicate::new(
|
||||
self.db,
|
||||
self.file,
|
||||
|
@ -1451,8 +1448,9 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
|
||||
let star_import_predicate = self.add_predicate(star_import.into());
|
||||
|
||||
let pre_definition =
|
||||
self.current_use_def_map().single_place_snapshot(symbol_id);
|
||||
let pre_definition = self
|
||||
.current_use_def_map()
|
||||
.single_symbol_place_snapshot(symbol_id);
|
||||
let pre_definition_reachability =
|
||||
self.current_use_def_map().reachability;
|
||||
|
||||
|
@ -1469,7 +1467,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
);
|
||||
self.current_use_def_map_mut().reachability = definition_reachability;
|
||||
|
||||
self.push_additional_definition(symbol_id, node_ref);
|
||||
self.push_additional_definition(symbol_id.into(), node_ref);
|
||||
|
||||
self.current_use_def_map_mut()
|
||||
.record_and_negate_star_import_reachability_constraint(
|
||||
|
@ -1503,7 +1501,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
let symbol = self.add_symbol(symbol_name.clone());
|
||||
|
||||
self.add_definition(
|
||||
symbol,
|
||||
symbol.into(),
|
||||
ImportFromDefinitionNodeRef {
|
||||
node,
|
||||
alias_index,
|
||||
|
@ -1580,9 +1578,9 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
|
||||
if let ast::Expr::Name(name) = &*node.target {
|
||||
let symbol_id = self.add_symbol(name.id.clone());
|
||||
let symbol = self.current_place_table().place_expr(symbol_id);
|
||||
let symbol = self.current_place_table().symbol(symbol_id);
|
||||
// Check whether the variable has been declared global.
|
||||
if symbol.is_marked_global() {
|
||||
if symbol.is_global() {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::AnnotatedGlobal(name.id.as_str().into()),
|
||||
range: name.range,
|
||||
|
@ -1590,7 +1588,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
});
|
||||
}
|
||||
// Check whether the variable has been declared nonlocal.
|
||||
if symbol.is_marked_nonlocal() {
|
||||
if symbol.is_nonlocal() {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::AnnotatedNonlocal(
|
||||
name.id.as_str().into(),
|
||||
|
@ -2006,7 +2004,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
let symbol = self.add_symbol(symbol_name.id.clone());
|
||||
|
||||
self.add_definition(
|
||||
symbol,
|
||||
symbol.into(),
|
||||
DefinitionNodeRef::ExceptHandler(ExceptHandlerDefinitionNodeRef {
|
||||
handler: except_handler,
|
||||
is_star: *is_star,
|
||||
|
@ -2020,7 +2018,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
self.visit_body(handler_body);
|
||||
// The caught exception is cleared at the end of the except clause
|
||||
if let Some(symbol) = symbol {
|
||||
self.delete_binding(symbol);
|
||||
self.delete_binding(symbol.into());
|
||||
}
|
||||
// Each `except` block is mutually exclusive with all other `except` blocks.
|
||||
post_except_states.push(self.flow_snapshot());
|
||||
|
@ -2078,7 +2076,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
}) => {
|
||||
for name in names {
|
||||
let symbol_id = self.add_symbol(name.id.clone());
|
||||
let symbol = self.current_place_table().place_expr(symbol_id);
|
||||
let symbol = self.current_place_table().symbol(symbol_id);
|
||||
// Check whether the variable has already been accessed in this scope.
|
||||
if symbol.is_bound() || symbol.is_declared() || symbol.is_used() {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
|
@ -2091,14 +2089,16 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
});
|
||||
}
|
||||
// Check whether the variable has also been declared nonlocal.
|
||||
if symbol.is_marked_nonlocal() {
|
||||
if symbol.is_nonlocal() {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::NonlocalAndGlobal(name.to_string()),
|
||||
range: name.range,
|
||||
python_version: self.python_version,
|
||||
});
|
||||
}
|
||||
self.current_place_table_mut().mark_place_global(symbol_id);
|
||||
self.current_place_table_mut()
|
||||
.symbol_mut(symbol_id)
|
||||
.mark_global();
|
||||
}
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
|
@ -2109,7 +2109,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
}) => {
|
||||
for name in names {
|
||||
let symbol_id = self.add_symbol(name.id.clone());
|
||||
let symbol = self.current_place_table().place_expr(symbol_id);
|
||||
let symbol = self.current_place_table().symbol(symbol_id);
|
||||
// Check whether the variable has already been accessed in this scope.
|
||||
if symbol.is_bound() || symbol.is_declared() || symbol.is_used() {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
|
@ -2122,7 +2122,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
});
|
||||
}
|
||||
// Check whether the variable has also been declared global.
|
||||
if symbol.is_marked_global() {
|
||||
if symbol.is_global() {
|
||||
self.report_semantic_error(SemanticSyntaxError {
|
||||
kind: SemanticSyntaxErrorKind::NonlocalAndGlobal(name.to_string()),
|
||||
range: name.range,
|
||||
|
@ -2139,7 +2139,8 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
// x = 1
|
||||
// ```
|
||||
self.current_place_table_mut()
|
||||
.mark_place_nonlocal(symbol_id);
|
||||
.symbol_mut(symbol_id)
|
||||
.mark_nonlocal();
|
||||
}
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
|
@ -2151,11 +2152,8 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
// We will check the target expressions and then delete them.
|
||||
walk_stmt(self, stmt);
|
||||
for target in targets {
|
||||
if let Ok(target) = PlaceExpr::try_from(target) {
|
||||
let is_name = target.is_name();
|
||||
let place_id = self.add_place(PlaceExprWithFlags::new(target));
|
||||
let place_table = self.current_place_table_mut();
|
||||
if is_name {
|
||||
if let Some(mut target) = PlaceExpr::try_from_expr(target) {
|
||||
if let PlaceExpr::Symbol(symbol) = &mut target {
|
||||
// `del x` behaves like an assignment in that it forces all references
|
||||
// to `x` in the current scope (including *prior* references) to refer
|
||||
// to the current scope's binding (unless `x` is declared `global` or
|
||||
|
@ -2169,9 +2167,11 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
// del x
|
||||
// foo()
|
||||
// ```
|
||||
place_table.mark_place_bound(place_id);
|
||||
symbol.mark_bound();
|
||||
symbol.mark_used();
|
||||
}
|
||||
place_table.mark_place_used(place_id);
|
||||
|
||||
let place_id = self.add_place(target);
|
||||
self.delete_binding(place_id);
|
||||
}
|
||||
}
|
||||
|
@ -2238,19 +2238,20 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
ast::Expr::Name(ast::ExprName { ctx, .. })
|
||||
| ast::Expr::Attribute(ast::ExprAttribute { ctx, .. })
|
||||
| ast::Expr::Subscript(ast::ExprSubscript { ctx, .. }) => {
|
||||
if let Ok(place_expr) = PlaceExpr::try_from(expr) {
|
||||
let mut place_expr = PlaceExprWithFlags::new(place_expr);
|
||||
if self.is_method_of_class().is_some()
|
||||
&& place_expr.is_instance_attribute_candidate()
|
||||
{
|
||||
// We specifically mark attribute assignments to the first parameter of a method,
|
||||
// i.e. typically `self` or `cls`.
|
||||
let accessed_object_refers_to_first_parameter = self
|
||||
.current_first_parameter_name
|
||||
.is_some_and(|fst| place_expr.expr.root_name() == fst);
|
||||
if let Some(mut place_expr) = PlaceExpr::try_from_expr(expr) {
|
||||
if self.is_method_of_class().is_some() {
|
||||
if let PlaceExpr::Member(member) = &mut place_expr {
|
||||
if member.is_instance_attribute_candidate() {
|
||||
// We specifically mark attribute assignments to the first parameter of a method,
|
||||
// i.e. typically `self` or `cls`.
|
||||
let accessed_object_refers_to_first_parameter = self
|
||||
.current_first_parameter_name
|
||||
.is_some_and(|first| member.symbol_name() == first);
|
||||
|
||||
if accessed_object_refers_to_first_parameter && place_expr.is_member() {
|
||||
place_expr.mark_instance_attribute();
|
||||
if accessed_object_refers_to_first_parameter {
|
||||
member.mark_instance_attribute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2267,7 +2268,9 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
let place_id = self.add_place(place_expr);
|
||||
|
||||
if is_use {
|
||||
self.mark_place_used(place_id);
|
||||
if let ScopedPlaceId::Symbol(symbol_id) = place_id {
|
||||
self.mark_symbol_used(symbol_id);
|
||||
}
|
||||
let use_id = self.current_ast_ids().record_use(expr);
|
||||
self.current_use_def_map_mut()
|
||||
.record_use(place_id, use_id, node_key);
|
||||
|
@ -2561,7 +2564,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
let symbol = self.add_symbol(name.id().clone());
|
||||
let state = self.current_match_case.as_ref().unwrap();
|
||||
self.add_definition(
|
||||
symbol,
|
||||
symbol.into(),
|
||||
MatchPatternDefinitionNodeRef {
|
||||
pattern: state.pattern,
|
||||
identifier: name,
|
||||
|
@ -2582,7 +2585,7 @@ impl<'ast> Visitor<'ast> for SemanticIndexBuilder<'_, 'ast> {
|
|||
let symbol = self.add_symbol(name.id().clone());
|
||||
let state = self.current_match_case.as_ref().unwrap();
|
||||
self.add_definition(
|
||||
symbol,
|
||||
symbol.into(),
|
||||
MatchPatternDefinitionNodeRef {
|
||||
pattern: state.pattern,
|
||||
identifier: name,
|
||||
|
|
|
@ -8,7 +8,9 @@ use ruff_text_size::{Ranged, TextRange};
|
|||
use crate::Db;
|
||||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::node_key::NodeKey;
|
||||
use crate::semantic_index::place::{FileScopeId, ScopeId, ScopedPlaceId};
|
||||
use crate::semantic_index::place::ScopedPlaceId;
|
||||
use crate::semantic_index::scope::{FileScopeId, ScopeId};
|
||||
use crate::semantic_index::symbol::ScopedSymbolId;
|
||||
use crate::unpack::{Unpack, UnpackPosition};
|
||||
|
||||
/// A definition of a place.
|
||||
|
@ -305,7 +307,7 @@ pub(crate) struct ImportDefinitionNodeRef<'ast> {
|
|||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct StarImportDefinitionNodeRef<'ast> {
|
||||
pub(crate) node: &'ast ast::StmtImportFrom,
|
||||
pub(crate) place_id: ScopedPlaceId,
|
||||
pub(crate) symbol_id: ScopedSymbolId,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
@ -395,10 +397,10 @@ impl<'db> DefinitionNodeRef<'_, 'db> {
|
|||
is_reexported,
|
||||
}),
|
||||
DefinitionNodeRef::ImportStar(star_import) => {
|
||||
let StarImportDefinitionNodeRef { node, place_id } = star_import;
|
||||
let StarImportDefinitionNodeRef { node, symbol_id } = star_import;
|
||||
DefinitionKind::StarImport(StarImportDefinitionKind {
|
||||
node: AstNodeRef::new(parsed, node),
|
||||
place_id,
|
||||
symbol_id,
|
||||
})
|
||||
}
|
||||
DefinitionNodeRef::Function(function) => {
|
||||
|
@ -522,7 +524,7 @@ impl<'db> DefinitionNodeRef<'_, 'db> {
|
|||
|
||||
// INVARIANT: for an invalid-syntax statement such as `from foo import *, bar, *`,
|
||||
// we only create a `StarImportDefinitionKind` for the *first* `*` alias in the names list.
|
||||
Self::ImportStar(StarImportDefinitionNodeRef { node, place_id: _ }) => node
|
||||
Self::ImportStar(StarImportDefinitionNodeRef { node, symbol_id: _ }) => node
|
||||
.names
|
||||
.iter()
|
||||
.find(|alias| &alias.name == "*")
|
||||
|
@ -822,7 +824,7 @@ impl<'db> From<Option<(UnpackPosition, Unpack<'db>)>> for TargetKind<'db> {
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct StarImportDefinitionKind {
|
||||
node: AstNodeRef<ast::StmtImportFrom>,
|
||||
place_id: ScopedPlaceId,
|
||||
symbol_id: ScopedSymbolId,
|
||||
}
|
||||
|
||||
impl StarImportDefinitionKind {
|
||||
|
@ -844,8 +846,8 @@ impl StarImportDefinitionKind {
|
|||
)
|
||||
}
|
||||
|
||||
pub(crate) fn place_id(&self) -> ScopedPlaceId {
|
||||
self.place_id
|
||||
pub(crate) fn symbol_id(&self) -> ScopedSymbolId {
|
||||
self.symbol_id
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::ast_node_ref::AstNodeRef;
|
||||
use crate::db::Db;
|
||||
use crate::semantic_index::place::{FileScopeId, ScopeId};
|
||||
use crate::semantic_index::scope::{FileScopeId, ScopeId};
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::parsed::ParsedModuleRef;
|
||||
use ruff_python_ast as ast;
|
||||
|
|
423
crates/ty_python_semantic/src/semantic_index/member.rs
Normal file
423
crates/ty_python_semantic/src/semantic_index/member.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -30,8 +30,8 @@
|
|||
|
||||
use crate::list::{List, ListBuilder, ListSetReverseIterator, ListStorage};
|
||||
use crate::semantic_index::ast_ids::ScopedUseId;
|
||||
use crate::semantic_index::place::FileScopeId;
|
||||
use crate::semantic_index::predicate::ScopedPredicateId;
|
||||
use crate::semantic_index::scope::FileScopeId;
|
||||
|
||||
/// A narrowing constraint associated with a live binding.
|
||||
///
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -14,7 +14,8 @@ use ruff_python_ast::Singleton;
|
|||
use crate::db::Db;
|
||||
use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::global_scope;
|
||||
use crate::semantic_index::place::{FileScopeId, ScopeId, ScopedPlaceId};
|
||||
use crate::semantic_index::scope::{FileScopeId, ScopeId};
|
||||
use crate::semantic_index::symbol::ScopedSymbolId;
|
||||
|
||||
// A scoped identifier for each `Predicate` in a scope.
|
||||
#[derive(Clone, Debug, Copy, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)]
|
||||
|
@ -217,7 +218,7 @@ pub(crate) struct StarImportPlaceholderPredicate<'db> {
|
|||
/// for valid `*`-import definitions, and valid `*`-import definitions can only ever
|
||||
/// exist in the global scope; thus, we know that the `symbol_id` here will be relative
|
||||
/// to the global scope of the importing file.
|
||||
pub(crate) symbol_id: ScopedPlaceId,
|
||||
pub(crate) symbol_id: ScopedSymbolId,
|
||||
|
||||
pub(crate) referenced_file: File,
|
||||
}
|
||||
|
|
|
@ -844,19 +844,17 @@ impl ReachabilityConstraints {
|
|||
PredicateNode::Pattern(inner) => Self::analyze_single_pattern_predicate(db, inner),
|
||||
PredicateNode::StarImportPlaceholder(star_import) => {
|
||||
let place_table = place_table(db, star_import.scope(db));
|
||||
let symbol_name = place_table
|
||||
.place_expr(star_import.symbol_id(db))
|
||||
.expect_name();
|
||||
let symbol = place_table.symbol(star_import.symbol_id(db));
|
||||
let referenced_file = star_import.referenced_file(db);
|
||||
|
||||
let requires_explicit_reexport = match dunder_all_names(db, referenced_file) {
|
||||
Some(all_names) => {
|
||||
if all_names.contains(symbol_name) {
|
||||
if all_names.contains(symbol.name()) {
|
||||
Some(RequiresExplicitReExport::No)
|
||||
} else {
|
||||
tracing::trace!(
|
||||
"Symbol `{}` (via star import) not found in `__all__` of `{}`",
|
||||
symbol_name,
|
||||
symbol.name(),
|
||||
referenced_file.path(db)
|
||||
);
|
||||
return Truthiness::AlwaysFalse;
|
||||
|
@ -865,8 +863,13 @@ impl ReachabilityConstraints {
|
|||
None => None,
|
||||
};
|
||||
|
||||
match imported_symbol(db, referenced_file, symbol_name, requires_explicit_reexport)
|
||||
.place
|
||||
match imported_symbol(
|
||||
db,
|
||||
referenced_file,
|
||||
symbol.name(),
|
||||
requires_explicit_reexport,
|
||||
)
|
||||
.place
|
||||
{
|
||||
crate::place::Place::Type(_, crate::place::Boundness::Bound) => {
|
||||
Truthiness::AlwaysTrue
|
||||
|
|
458
crates/ty_python_semantic/src/semantic_index/scope.rs
Normal file
458
crates/ty_python_semantic/src/semantic_index/scope.rs
Normal 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),
|
||||
}
|
237
crates/ty_python_semantic/src/semantic_index/symbol.rs
Normal file
237
crates/ty_python_semantic/src/semantic_index/symbol.rs
Normal 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
|
||||
}
|
||||
}
|
|
@ -251,20 +251,21 @@ use crate::node_key::NodeKey;
|
|||
use crate::place::BoundnessAnalysis;
|
||||
use crate::semantic_index::ast_ids::ScopedUseId;
|
||||
use crate::semantic_index::definition::{Definition, DefinitionState};
|
||||
use crate::semantic_index::member::ScopedMemberId;
|
||||
use crate::semantic_index::narrowing_constraints::{
|
||||
ConstraintKey, NarrowingConstraints, NarrowingConstraintsBuilder, NarrowingConstraintsIterator,
|
||||
};
|
||||
use crate::semantic_index::place::{
|
||||
FileScopeId, PlaceExpr, PlaceExprWithFlags, ScopeKind, ScopedPlaceId,
|
||||
};
|
||||
use crate::semantic_index::place::{PlaceExprRef, ScopedPlaceId};
|
||||
use crate::semantic_index::predicate::{
|
||||
Predicate, PredicateOrLiteral, Predicates, PredicatesBuilder, ScopedPredicateId,
|
||||
};
|
||||
use crate::semantic_index::reachability_constraints::{
|
||||
ReachabilityConstraints, ReachabilityConstraintsBuilder, ScopedReachabilityConstraintId,
|
||||
};
|
||||
use crate::semantic_index::scope::{FileScopeId, ScopeKind, ScopeLaziness};
|
||||
use crate::semantic_index::symbol::ScopedSymbolId;
|
||||
use crate::semantic_index::use_def::place_state::PreviousDefinitions;
|
||||
use crate::semantic_index::{EnclosingSnapshotResult, ScopeLaziness, SemanticIndex};
|
||||
use crate::semantic_index::{EnclosingSnapshotResult, SemanticIndex};
|
||||
use crate::types::{IntersectionBuilder, Truthiness, Type, infer_narrowing_constraint};
|
||||
|
||||
mod place_state;
|
||||
|
@ -311,11 +312,17 @@ pub(crate) struct UseDefMap<'db> {
|
|||
/// bindings to that symbol. If there are any, the assignment is invalid.
|
||||
bindings_by_definition: FxHashMap<Definition<'db>, Bindings>,
|
||||
|
||||
/// [`PlaceState`] visible at end of scope for each place.
|
||||
end_of_scope_places: IndexVec<ScopedPlaceId, PlaceState>,
|
||||
/// [`PlaceState`] visible at end of scope for each symbol.
|
||||
end_of_scope_symbols: IndexVec<ScopedSymbolId, PlaceState>,
|
||||
|
||||
/// All potentially reachable bindings and declarations, for each place.
|
||||
reachable_definitions: IndexVec<ScopedPlaceId, ReachableDefinitions>,
|
||||
/// [`PlaceState`] visible at end of scope for each member.
|
||||
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
|
||||
/// scope.
|
||||
|
@ -361,7 +368,7 @@ impl<'db> UseDefMap<'db> {
|
|||
&self,
|
||||
constraint_key: ConstraintKey,
|
||||
enclosing_scope: FileScopeId,
|
||||
expr: &PlaceExpr,
|
||||
expr: PlaceExprRef,
|
||||
index: &'db SemanticIndex,
|
||||
) -> ApplicableConstraints<'_, 'db> {
|
||||
match constraint_key {
|
||||
|
@ -419,9 +426,29 @@ impl<'db> UseDefMap<'db> {
|
|||
pub(crate) fn end_of_scope_bindings(
|
||||
&self,
|
||||
place: ScopedPlaceId,
|
||||
) -> BindingWithConstraintsIterator<'_, 'db> {
|
||||
match place {
|
||||
ScopedPlaceId::Symbol(symbol) => self.end_of_scope_symbol_bindings(symbol),
|
||||
ScopedPlaceId::Member(member) => self.end_of_scope_member_bindings(member),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn end_of_scope_symbol_bindings(
|
||||
&self,
|
||||
symbol: ScopedSymbolId,
|
||||
) -> BindingWithConstraintsIterator<'_, 'db> {
|
||||
self.bindings_iterator(
|
||||
self.end_of_scope_places[place].bindings(),
|
||||
self.end_of_scope_symbols[symbol].bindings(),
|
||||
BoundnessAnalysis::BasedOnUnboundVisibility,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn end_of_scope_member_bindings(
|
||||
&self,
|
||||
member: ScopedMemberId,
|
||||
) -> BindingWithConstraintsIterator<'_, 'db> {
|
||||
self.bindings_iterator(
|
||||
self.end_of_scope_members[member].bindings(),
|
||||
BoundnessAnalysis::BasedOnUnboundVisibility,
|
||||
)
|
||||
}
|
||||
|
@ -430,10 +457,26 @@ impl<'db> UseDefMap<'db> {
|
|||
&self,
|
||||
place: ScopedPlaceId,
|
||||
) -> BindingWithConstraintsIterator<'_, 'db> {
|
||||
self.bindings_iterator(
|
||||
&self.reachable_definitions[place].bindings,
|
||||
BoundnessAnalysis::AssumeBound,
|
||||
)
|
||||
match place {
|
||||
ScopedPlaceId::Symbol(symbol) => self.all_reachable_symbol_bindings(symbol),
|
||||
ScopedPlaceId::Member(member) => self.all_reachable_member_bindings(member),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn all_reachable_symbol_bindings(
|
||||
&self,
|
||||
symbol: ScopedSymbolId,
|
||||
) -> BindingWithConstraintsIterator<'_, 'db> {
|
||||
let bindings = &self.reachable_definitions_by_symbol[symbol].bindings;
|
||||
self.bindings_iterator(bindings, BoundnessAnalysis::AssumeBound)
|
||||
}
|
||||
|
||||
pub(crate) fn all_reachable_member_bindings(
|
||||
&self,
|
||||
symbol: ScopedMemberId,
|
||||
) -> BindingWithConstraintsIterator<'_, 'db> {
|
||||
let bindings = &self.reachable_definitions_by_member[symbol].bindings;
|
||||
self.bindings_iterator(bindings, BoundnessAnalysis::AssumeBound)
|
||||
}
|
||||
|
||||
pub(crate) fn enclosing_snapshot(
|
||||
|
@ -482,33 +525,69 @@ impl<'db> UseDefMap<'db> {
|
|||
&'map self,
|
||||
place: ScopedPlaceId,
|
||||
) -> DeclarationsIterator<'map, 'db> {
|
||||
let declarations = self.end_of_scope_places[place].declarations();
|
||||
match place {
|
||||
ScopedPlaceId::Symbol(symbol) => self.end_of_scope_symbol_declarations(symbol),
|
||||
ScopedPlaceId::Member(member) => self.end_of_scope_member_declarations(member),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn end_of_scope_symbol_declarations<'map>(
|
||||
&'map self,
|
||||
symbol: ScopedSymbolId,
|
||||
) -> DeclarationsIterator<'map, 'db> {
|
||||
let declarations = self.end_of_scope_symbols[symbol].declarations();
|
||||
self.declarations_iterator(declarations, BoundnessAnalysis::BasedOnUnboundVisibility)
|
||||
}
|
||||
|
||||
pub(crate) fn end_of_scope_member_declarations<'map>(
|
||||
&'map self,
|
||||
member: ScopedMemberId,
|
||||
) -> DeclarationsIterator<'map, 'db> {
|
||||
let declarations = self.end_of_scope_members[member].declarations();
|
||||
self.declarations_iterator(declarations, BoundnessAnalysis::BasedOnUnboundVisibility)
|
||||
}
|
||||
|
||||
pub(crate) fn all_reachable_symbol_declarations(
|
||||
&self,
|
||||
symbol: ScopedSymbolId,
|
||||
) -> DeclarationsIterator<'_, 'db> {
|
||||
let declarations = &self.reachable_definitions_by_symbol[symbol].declarations;
|
||||
self.declarations_iterator(declarations, BoundnessAnalysis::AssumeBound)
|
||||
}
|
||||
|
||||
pub(crate) fn all_reachable_member_declarations(
|
||||
&self,
|
||||
member: ScopedMemberId,
|
||||
) -> DeclarationsIterator<'_, 'db> {
|
||||
let declarations = &self.reachable_definitions_by_member[member].declarations;
|
||||
self.declarations_iterator(declarations, BoundnessAnalysis::AssumeBound)
|
||||
}
|
||||
|
||||
pub(crate) fn all_reachable_declarations(
|
||||
&self,
|
||||
place: ScopedPlaceId,
|
||||
) -> DeclarationsIterator<'_, 'db> {
|
||||
let declarations = &self.reachable_definitions[place].declarations;
|
||||
self.declarations_iterator(declarations, BoundnessAnalysis::AssumeBound)
|
||||
match place {
|
||||
ScopedPlaceId::Symbol(symbol) => self.all_reachable_symbol_declarations(symbol),
|
||||
ScopedPlaceId::Member(member) => self.all_reachable_member_declarations(member),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn all_end_of_scope_declarations<'map>(
|
||||
pub(crate) fn all_end_of_scope_symbol_declarations<'map>(
|
||||
&'map self,
|
||||
) -> impl Iterator<Item = (ScopedPlaceId, DeclarationsIterator<'map, 'db>)> + 'map {
|
||||
(0..self.end_of_scope_places.len())
|
||||
.map(ScopedPlaceId::from_usize)
|
||||
.map(|place_id| (place_id, self.end_of_scope_declarations(place_id)))
|
||||
) -> impl Iterator<Item = (ScopedSymbolId, DeclarationsIterator<'map, 'db>)> + 'map {
|
||||
self.end_of_scope_symbols
|
||||
.indices()
|
||||
.map(|symbol_id| (symbol_id, self.end_of_scope_symbol_declarations(symbol_id)))
|
||||
}
|
||||
|
||||
pub(crate) fn all_end_of_scope_bindings<'map>(
|
||||
pub(crate) fn all_end_of_scope_symbol_bindings<'map>(
|
||||
&'map self,
|
||||
) -> impl Iterator<Item = (ScopedPlaceId, BindingWithConstraintsIterator<'map, 'db>)> + 'map
|
||||
) -> impl Iterator<Item = (ScopedSymbolId, BindingWithConstraintsIterator<'map, 'db>)> + 'map
|
||||
{
|
||||
(0..self.end_of_scope_places.len())
|
||||
.map(ScopedPlaceId::from_usize)
|
||||
.map(|place_id| (place_id, self.end_of_scope_bindings(place_id)))
|
||||
self.end_of_scope_symbols
|
||||
.indices()
|
||||
.map(|symbol_id| (symbol_id, self.end_of_scope_symbol_bindings(symbol_id)))
|
||||
}
|
||||
|
||||
/// This function is intended to be called only once inside `TypeInferenceBuilder::infer_function_body`.
|
||||
|
@ -730,7 +809,8 @@ struct ReachableDefinitions {
|
|||
/// A snapshot of the definitions and constraints state at a particular point in control flow.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct FlowSnapshot {
|
||||
place_states: IndexVec<ScopedPlaceId, PlaceState>,
|
||||
symbol_states: IndexVec<ScopedSymbolId, PlaceState>,
|
||||
member_states: IndexVec<ScopedMemberId, PlaceState>,
|
||||
reachability: ScopedReachabilityConstraintId,
|
||||
}
|
||||
|
||||
|
@ -765,10 +845,14 @@ pub(super) struct UseDefMapBuilder<'db> {
|
|||
bindings_by_definition: FxHashMap<Definition<'db>, Bindings>,
|
||||
|
||||
/// 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.
|
||||
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
|
||||
/// nested scope.
|
||||
|
@ -790,8 +874,10 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
node_reachability: FxHashMap::default(),
|
||||
declarations_by_binding: FxHashMap::default(),
|
||||
bindings_by_definition: FxHashMap::default(),
|
||||
place_states: IndexVec::new(),
|
||||
reachable_definitions: IndexVec::new(),
|
||||
symbol_states: IndexVec::new(),
|
||||
member_states: IndexVec::new(),
|
||||
reachable_member_definitions: IndexVec::new(),
|
||||
reachable_symbol_definitions: IndexVec::new(),
|
||||
enclosing_snapshots: EnclosingSnapshots::default(),
|
||||
is_class_scope,
|
||||
}
|
||||
|
@ -800,7 +886,14 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
pub(super) fn mark_unreachable(&mut self) {
|
||||
self.reachability = ScopedReachabilityConstraintId::ALWAYS_FALSE;
|
||||
|
||||
for state in &mut self.place_states {
|
||||
for state in &mut self.symbol_states {
|
||||
state.record_reachability_constraint(
|
||||
&mut self.reachability_constraints,
|
||||
ScopedReachabilityConstraintId::ALWAYS_FALSE,
|
||||
);
|
||||
}
|
||||
|
||||
for state in &mut self.member_states {
|
||||
state.record_reachability_constraint(
|
||||
&mut self.reachability_constraints,
|
||||
ScopedReachabilityConstraintId::ALWAYS_FALSE,
|
||||
|
@ -809,42 +902,73 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
}
|
||||
|
||||
pub(super) fn add_place(&mut self, place: ScopedPlaceId) {
|
||||
let new_place = self
|
||||
.place_states
|
||||
.push(PlaceState::undefined(self.reachability));
|
||||
debug_assert_eq!(place, new_place);
|
||||
let new_place = self.reachable_definitions.push(ReachableDefinitions {
|
||||
bindings: Bindings::unbound(self.reachability),
|
||||
declarations: Declarations::undeclared(self.reachability),
|
||||
});
|
||||
debug_assert_eq!(place, new_place);
|
||||
match place {
|
||||
ScopedPlaceId::Symbol(symbol) => {
|
||||
let new_place = self
|
||||
.symbol_states
|
||||
.push(PlaceState::undefined(self.reachability));
|
||||
debug_assert_eq!(symbol, new_place);
|
||||
let new_place = self
|
||||
.reachable_symbol_definitions
|
||||
.push(ReachableDefinitions {
|
||||
bindings: Bindings::unbound(self.reachability),
|
||||
declarations: Declarations::undeclared(self.reachability),
|
||||
});
|
||||
debug_assert_eq!(symbol, new_place);
|
||||
}
|
||||
ScopedPlaceId::Member(member) => {
|
||||
let new_place = self
|
||||
.member_states
|
||||
.push(PlaceState::undefined(self.reachability));
|
||||
debug_assert_eq!(member, new_place);
|
||||
let new_place = self
|
||||
.reachable_member_definitions
|
||||
.push(ReachableDefinitions {
|
||||
bindings: Bindings::unbound(self.reachability),
|
||||
declarations: Declarations::undeclared(self.reachability),
|
||||
});
|
||||
debug_assert_eq!(member, new_place);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn record_binding(
|
||||
&mut self,
|
||||
place: ScopedPlaceId,
|
||||
binding: Definition<'db>,
|
||||
is_place_name: bool,
|
||||
) {
|
||||
pub(super) fn record_binding(&mut self, place: ScopedPlaceId, binding: Definition<'db>) {
|
||||
let bindings = match place {
|
||||
ScopedPlaceId::Symbol(symbol) => self.symbol_states[symbol].bindings(),
|
||||
ScopedPlaceId::Member(member) => self.member_states[member].bindings(),
|
||||
};
|
||||
|
||||
self.bindings_by_definition
|
||||
.insert(binding, self.place_states[place].bindings().clone());
|
||||
.insert(binding, bindings.clone());
|
||||
|
||||
let def_id = self.all_definitions.push(DefinitionState::Defined(binding));
|
||||
let place_state = &mut self.place_states[place];
|
||||
let place_state = match place {
|
||||
ScopedPlaceId::Symbol(symbol) => &mut self.symbol_states[symbol],
|
||||
ScopedPlaceId::Member(member) => &mut self.member_states[member],
|
||||
};
|
||||
self.declarations_by_binding
|
||||
.insert(binding, place_state.declarations().clone());
|
||||
place_state.record_binding(
|
||||
def_id,
|
||||
self.reachability,
|
||||
self.is_class_scope,
|
||||
is_place_name,
|
||||
place.is_symbol(),
|
||||
);
|
||||
|
||||
self.reachable_definitions[place].bindings.record_binding(
|
||||
let bindings = match place {
|
||||
ScopedPlaceId::Symbol(symbol) => {
|
||||
&mut self.reachable_symbol_definitions[symbol].bindings
|
||||
}
|
||||
ScopedPlaceId::Member(member) => {
|
||||
&mut self.reachable_member_definitions[member].bindings
|
||||
}
|
||||
};
|
||||
|
||||
bindings.record_binding(
|
||||
def_id,
|
||||
self.reachability,
|
||||
self.is_class_scope,
|
||||
is_place_name,
|
||||
place.is_symbol(),
|
||||
PreviousDefinitions::AreKept,
|
||||
);
|
||||
}
|
||||
|
@ -869,7 +993,12 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
}
|
||||
|
||||
let narrowing_constraint = predicate.into();
|
||||
for state in &mut self.place_states {
|
||||
for state in &mut self.symbol_states {
|
||||
state
|
||||
.record_narrowing_constraint(&mut self.narrowing_constraints, narrowing_constraint);
|
||||
}
|
||||
|
||||
for state in &mut self.member_states {
|
||||
state
|
||||
.record_narrowing_constraint(&mut self.narrowing_constraints, narrowing_constraint);
|
||||
}
|
||||
|
@ -880,8 +1009,8 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
/// This is only used for `*`-import reachability constraints, which are handled differently
|
||||
/// to most other reachability constraints. See the doc-comment for
|
||||
/// [`Self::record_and_negate_star_import_reachability_constraint`] for more details.
|
||||
pub(super) fn single_place_snapshot(&self, place: ScopedPlaceId) -> PlaceState {
|
||||
self.place_states[place].clone()
|
||||
pub(super) fn single_symbol_place_snapshot(&self, symbol: ScopedSymbolId) -> PlaceState {
|
||||
self.symbol_states[symbol].clone()
|
||||
}
|
||||
|
||||
/// This method exists solely for handling `*`-import reachability constraints.
|
||||
|
@ -916,7 +1045,7 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
pub(super) fn record_and_negate_star_import_reachability_constraint(
|
||||
&mut self,
|
||||
reachability_id: ScopedReachabilityConstraintId,
|
||||
symbol: ScopedPlaceId,
|
||||
symbol: ScopedSymbolId,
|
||||
pre_definition_state: PlaceState,
|
||||
) {
|
||||
let negated_reachability_id = self
|
||||
|
@ -924,17 +1053,17 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
.add_not_constraint(reachability_id);
|
||||
|
||||
let mut post_definition_state =
|
||||
std::mem::replace(&mut self.place_states[symbol], pre_definition_state);
|
||||
std::mem::replace(&mut self.symbol_states[symbol], pre_definition_state);
|
||||
|
||||
post_definition_state
|
||||
.record_reachability_constraint(&mut self.reachability_constraints, reachability_id);
|
||||
|
||||
self.place_states[symbol].record_reachability_constraint(
|
||||
self.symbol_states[symbol].record_reachability_constraint(
|
||||
&mut self.reachability_constraints,
|
||||
negated_reachability_id,
|
||||
);
|
||||
|
||||
self.place_states[symbol].merge(
|
||||
self.symbol_states[symbol].merge(
|
||||
post_definition_state,
|
||||
&mut self.narrowing_constraints,
|
||||
&mut self.reachability_constraints,
|
||||
|
@ -949,7 +1078,11 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
.reachability_constraints
|
||||
.add_and_constraint(self.reachability, constraint);
|
||||
|
||||
for state in &mut self.place_states {
|
||||
for state in &mut self.symbol_states {
|
||||
state.record_reachability_constraint(&mut self.reachability_constraints, constraint);
|
||||
}
|
||||
|
||||
for state in &mut self.member_states {
|
||||
state.record_reachability_constraint(&mut self.reachability_constraints, constraint);
|
||||
}
|
||||
}
|
||||
|
@ -962,56 +1095,81 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
let def_id = self
|
||||
.all_definitions
|
||||
.push(DefinitionState::Defined(declaration));
|
||||
let place_state = &mut self.place_states[place];
|
||||
|
||||
let place_state = match place {
|
||||
ScopedPlaceId::Symbol(symbol) => &mut self.symbol_states[symbol],
|
||||
ScopedPlaceId::Member(member) => &mut self.member_states[member],
|
||||
};
|
||||
|
||||
self.bindings_by_definition
|
||||
.insert(declaration, place_state.bindings().clone());
|
||||
place_state.record_declaration(def_id, self.reachability);
|
||||
|
||||
self.reachable_definitions[place]
|
||||
.declarations
|
||||
.record_declaration(def_id, self.reachability, PreviousDefinitions::AreKept);
|
||||
let definitions = match place {
|
||||
ScopedPlaceId::Symbol(symbol) => &mut self.reachable_symbol_definitions[symbol],
|
||||
ScopedPlaceId::Member(member) => &mut self.reachable_member_definitions[member],
|
||||
};
|
||||
|
||||
definitions.declarations.record_declaration(
|
||||
def_id,
|
||||
self.reachability,
|
||||
PreviousDefinitions::AreKept,
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn record_declaration_and_binding(
|
||||
&mut self,
|
||||
place: ScopedPlaceId,
|
||||
definition: Definition<'db>,
|
||||
is_place_name: bool,
|
||||
) {
|
||||
// We don't need to store anything in self.bindings_by_declaration or
|
||||
// self.declarations_by_binding.
|
||||
let def_id = self
|
||||
.all_definitions
|
||||
.push(DefinitionState::Defined(definition));
|
||||
let place_state = &mut self.place_states[place];
|
||||
let place_state = match place {
|
||||
ScopedPlaceId::Symbol(symbol) => &mut self.symbol_states[symbol],
|
||||
ScopedPlaceId::Member(member) => &mut self.member_states[member],
|
||||
};
|
||||
place_state.record_declaration(def_id, self.reachability);
|
||||
place_state.record_binding(
|
||||
def_id,
|
||||
self.reachability,
|
||||
self.is_class_scope,
|
||||
is_place_name,
|
||||
place.is_symbol(),
|
||||
);
|
||||
|
||||
self.reachable_definitions[place]
|
||||
.declarations
|
||||
.record_declaration(def_id, self.reachability, PreviousDefinitions::AreKept);
|
||||
self.reachable_definitions[place].bindings.record_binding(
|
||||
let reachable_definitions = match place {
|
||||
ScopedPlaceId::Symbol(symbol) => &mut self.reachable_symbol_definitions[symbol],
|
||||
ScopedPlaceId::Member(member) => &mut self.reachable_member_definitions[member],
|
||||
};
|
||||
|
||||
reachable_definitions.declarations.record_declaration(
|
||||
def_id,
|
||||
self.reachability,
|
||||
PreviousDefinitions::AreKept,
|
||||
);
|
||||
reachable_definitions.bindings.record_binding(
|
||||
def_id,
|
||||
self.reachability,
|
||||
self.is_class_scope,
|
||||
is_place_name,
|
||||
place.is_symbol(),
|
||||
PreviousDefinitions::AreKept,
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn delete_binding(&mut self, place: ScopedPlaceId, is_place_name: bool) {
|
||||
pub(super) fn delete_binding(&mut self, place: ScopedPlaceId) {
|
||||
let def_id = self.all_definitions.push(DefinitionState::Deleted);
|
||||
let place_state = &mut self.place_states[place];
|
||||
let place_state = match place {
|
||||
ScopedPlaceId::Symbol(symbol) => &mut self.symbol_states[symbol],
|
||||
ScopedPlaceId::Member(member) => &mut self.member_states[member],
|
||||
};
|
||||
|
||||
place_state.record_binding(
|
||||
def_id,
|
||||
self.reachability,
|
||||
self.is_class_scope,
|
||||
is_place_name,
|
||||
place.is_symbol(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1021,11 +1179,13 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
use_id: ScopedUseId,
|
||||
node_key: NodeKey,
|
||||
) {
|
||||
let bindings = match place {
|
||||
ScopedPlaceId::Symbol(symbol) => &mut self.symbol_states[symbol].bindings(),
|
||||
ScopedPlaceId::Member(member) => &mut self.member_states[member].bindings(),
|
||||
};
|
||||
// We have a use of a place; clone the current bindings for that place, and record them
|
||||
// as the live bindings for this use.
|
||||
let new_use = self
|
||||
.bindings_by_use
|
||||
.push(self.place_states[place].bindings().clone());
|
||||
let new_use = self.bindings_by_use.push(bindings.clone());
|
||||
debug_assert_eq!(use_id, new_use);
|
||||
|
||||
// Track reachability of all uses of places to silence `unresolved-reference`
|
||||
|
@ -1041,28 +1201,30 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
&mut self,
|
||||
enclosing_place: ScopedPlaceId,
|
||||
scope: ScopeKind,
|
||||
enclosing_place_expr: &PlaceExprWithFlags,
|
||||
enclosing_place_expr: PlaceExprRef,
|
||||
) -> ScopedEnclosingSnapshotId {
|
||||
let bindings = match enclosing_place {
|
||||
ScopedPlaceId::Symbol(symbol) => self.symbol_states[symbol].bindings(),
|
||||
ScopedPlaceId::Member(member) => self.member_states[member].bindings(),
|
||||
};
|
||||
|
||||
// Names bound in class scopes are never visible to nested scopes (but attributes/subscripts are visible),
|
||||
// so we never need to save eager scope bindings in a class scope.
|
||||
if (scope.is_class() && enclosing_place_expr.is_name()) || !enclosing_place_expr.is_bound()
|
||||
{
|
||||
if (scope.is_class() && enclosing_place.is_symbol()) || !enclosing_place_expr.is_bound() {
|
||||
self.enclosing_snapshots.push(EnclosingSnapshot::Constraint(
|
||||
self.place_states[enclosing_place]
|
||||
.bindings()
|
||||
.unbound_narrowing_constraint(),
|
||||
bindings.unbound_narrowing_constraint(),
|
||||
))
|
||||
} else {
|
||||
self.enclosing_snapshots.push(EnclosingSnapshot::Bindings(
|
||||
self.place_states[enclosing_place].bindings().clone(),
|
||||
))
|
||||
self.enclosing_snapshots
|
||||
.push(EnclosingSnapshot::Bindings(bindings.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Take a snapshot of the current visible-places state.
|
||||
pub(super) fn snapshot(&self) -> FlowSnapshot {
|
||||
FlowSnapshot {
|
||||
place_states: self.place_states.clone(),
|
||||
symbol_states: self.symbol_states.clone(),
|
||||
member_states: self.member_states.clone(),
|
||||
reachability: self.reachability,
|
||||
}
|
||||
}
|
||||
|
@ -1072,18 +1234,23 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
// We never remove places from `place_states` (it's an IndexVec, and the place
|
||||
// IDs must line up), so the current number of known places must always be equal to or
|
||||
// greater than the number of known places in a previously-taken snapshot.
|
||||
let num_places = self.place_states.len();
|
||||
debug_assert!(num_places >= snapshot.place_states.len());
|
||||
let num_symbols = self.symbol_states.len();
|
||||
let num_members = self.member_states.len();
|
||||
debug_assert!(num_symbols >= snapshot.symbol_states.len());
|
||||
|
||||
// Restore the current visible-definitions state to the given snapshot.
|
||||
self.place_states = snapshot.place_states;
|
||||
self.symbol_states = snapshot.symbol_states;
|
||||
self.member_states = snapshot.member_states;
|
||||
self.reachability = snapshot.reachability;
|
||||
|
||||
// If the snapshot we are restoring is missing some places we've recorded since, we need
|
||||
// to fill them in so the place IDs continue to line up. Since they don't exist in the
|
||||
// snapshot, the correct state to fill them in with is "undefined".
|
||||
self.place_states
|
||||
.resize(num_places, PlaceState::undefined(self.reachability));
|
||||
self.symbol_states
|
||||
.resize(num_symbols, PlaceState::undefined(self.reachability));
|
||||
|
||||
self.member_states
|
||||
.resize(num_members, PlaceState::undefined(self.reachability));
|
||||
}
|
||||
|
||||
/// Merge the given snapshot into the current state, reflecting that we might have taken either
|
||||
|
@ -1108,10 +1275,29 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
// We never remove places from `place_states` (it's an IndexVec, and the place
|
||||
// IDs must line up), so the current number of known places must always be equal to or
|
||||
// greater than the number of known places in a previously-taken snapshot.
|
||||
debug_assert!(self.place_states.len() >= snapshot.place_states.len());
|
||||
debug_assert!(self.symbol_states.len() >= snapshot.symbol_states.len());
|
||||
debug_assert!(self.member_states.len() >= snapshot.member_states.len());
|
||||
|
||||
let mut snapshot_definitions_iter = snapshot.place_states.into_iter();
|
||||
for current in &mut self.place_states {
|
||||
let mut snapshot_definitions_iter = snapshot.symbol_states.into_iter();
|
||||
for current in &mut self.symbol_states {
|
||||
if let Some(snapshot) = snapshot_definitions_iter.next() {
|
||||
current.merge(
|
||||
snapshot,
|
||||
&mut self.narrowing_constraints,
|
||||
&mut self.reachability_constraints,
|
||||
);
|
||||
} else {
|
||||
current.merge(
|
||||
PlaceState::undefined(snapshot.reachability),
|
||||
&mut self.narrowing_constraints,
|
||||
&mut self.reachability_constraints,
|
||||
);
|
||||
// Place not present in snapshot, so it's unbound/undeclared from that path.
|
||||
}
|
||||
}
|
||||
|
||||
let mut snapshot_definitions_iter = snapshot.member_states.into_iter();
|
||||
for current in &mut self.member_states {
|
||||
if let Some(snapshot) = snapshot_definitions_iter.next() {
|
||||
current.merge(
|
||||
snapshot,
|
||||
|
@ -1142,10 +1328,21 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
for constraint in self.node_reachability.values() {
|
||||
self.reachability_constraints.mark_used(*constraint);
|
||||
}
|
||||
for place_state in &mut self.place_states {
|
||||
place_state.finish(&mut self.reachability_constraints);
|
||||
for symbol_state in &mut self.symbol_states {
|
||||
symbol_state.finish(&mut self.reachability_constraints);
|
||||
}
|
||||
for reachable_definition in &mut self.reachable_definitions {
|
||||
for member_state in &mut self.member_states {
|
||||
member_state.finish(&mut self.reachability_constraints);
|
||||
}
|
||||
for reachable_definition in &mut self.reachable_symbol_definitions {
|
||||
reachable_definition
|
||||
.bindings
|
||||
.finish(&mut self.reachability_constraints);
|
||||
reachable_definition
|
||||
.declarations
|
||||
.finish(&mut self.reachability_constraints);
|
||||
}
|
||||
for reachable_definition in &mut self.reachable_member_definitions {
|
||||
reachable_definition
|
||||
.bindings
|
||||
.finish(&mut self.reachability_constraints);
|
||||
|
@ -1169,8 +1366,10 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
self.mark_reachability_constraints();
|
||||
|
||||
self.all_definitions.shrink_to_fit();
|
||||
self.place_states.shrink_to_fit();
|
||||
self.reachable_definitions.shrink_to_fit();
|
||||
self.symbol_states.shrink_to_fit();
|
||||
self.member_states.shrink_to_fit();
|
||||
self.reachable_symbol_definitions.shrink_to_fit();
|
||||
self.reachable_member_definitions.shrink_to_fit();
|
||||
self.bindings_by_use.shrink_to_fit();
|
||||
self.node_reachability.shrink_to_fit();
|
||||
self.declarations_by_binding.shrink_to_fit();
|
||||
|
@ -1184,8 +1383,10 @@ impl<'db> UseDefMapBuilder<'db> {
|
|||
reachability_constraints: self.reachability_constraints.build(),
|
||||
bindings_by_use: self.bindings_by_use,
|
||||
node_reachability: self.node_reachability,
|
||||
end_of_scope_places: self.place_states,
|
||||
reachable_definitions: self.reachable_definitions,
|
||||
end_of_scope_symbols: self.symbol_states,
|
||||
end_of_scope_members: self.member_states,
|
||||
reachable_definitions_by_symbol: self.reachable_symbol_definitions,
|
||||
reachable_definitions_by_member: self.reachable_member_definitions,
|
||||
declarations_by_binding: self.declarations_by_binding,
|
||||
bindings_by_definition: self.bindings_by_definition,
|
||||
enclosing_snapshots: self.enclosing_snapshots,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue