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

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

View file

@ -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;

View file

@ -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,

View file

@ -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
}
}

View file

@ -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;

View file

@ -0,0 +1,423 @@
use bitflags::bitflags;
use hashbrown::hash_table::Entry;
use ruff_index::{IndexVec, newtype_index};
use ruff_python_ast::{self as ast, name::Name};
use rustc_hash::FxHasher;
use smallvec::SmallVec;
use std::hash::{Hash as _, Hasher as _};
use std::ops::{Deref, DerefMut};
/// A member access, e.g. `x.y` or `x[1]` or `x["foo"]`.
#[derive(Clone, Debug, PartialEq, Eq, get_size2::GetSize)]
pub(crate) struct Member {
expression: MemberExpr,
flags: MemberFlags,
}
impl Member {
pub(crate) fn new(expression: MemberExpr) -> Self {
Self {
expression,
flags: MemberFlags::empty(),
}
}
/// Returns the left most part of the member expression, e.g. `x` in `x.y.z`.
///
/// This is the symbol on which the member access is performed.
pub(crate) fn symbol_name(&self) -> &Name {
self.expression.symbol_name()
}
pub(crate) fn expression(&self) -> &MemberExpr {
&self.expression
}
/// Is the place given a value in its containing scope?
pub(crate) const fn is_bound(&self) -> bool {
self.flags.contains(MemberFlags::IS_BOUND)
}
/// Is the place declared in its containing scope?
pub(crate) fn is_declared(&self) -> bool {
self.flags.contains(MemberFlags::IS_DECLARED)
}
pub(super) fn mark_bound(&mut self) {
self.insert_flags(MemberFlags::IS_BOUND);
}
pub(super) fn mark_declared(&mut self) {
self.insert_flags(MemberFlags::IS_DECLARED);
}
pub(super) fn mark_instance_attribute(&mut self) {
self.flags.insert(MemberFlags::IS_INSTANCE_ATTRIBUTE);
}
/// Is the place an instance attribute?
pub(crate) fn is_instance_attribute(&self) -> bool {
let is_instance_attribute = self.flags.contains(MemberFlags::IS_INSTANCE_ATTRIBUTE);
if is_instance_attribute {
debug_assert!(self.is_instance_attribute_candidate());
}
is_instance_attribute
}
fn insert_flags(&mut self, flags: MemberFlags) {
self.flags.insert(flags);
}
/// If the place expression has the form `<NAME>.<MEMBER>`
/// (meaning it *may* be an instance attribute),
/// return `Some(<MEMBER>)`. Else, return `None`.
///
/// This method is internal to the semantic-index submodule.
/// It *only* checks that the AST structure of the `Place` is
/// correct. It does not check whether the `Place` actually occurred in
/// a method context, or whether the `<NAME>` actually refers to the first
/// parameter of the method (i.e. `self`). To answer those questions,
/// use [`Self::as_instance_attribute`].
pub(super) fn as_instance_attribute_candidate(&self) -> Option<&Name> {
match &*self.expression.segments {
[MemberSegment::Attribute(name)] => Some(name),
_ => None,
}
}
/// Return `true` if the place expression has the form `<NAME>.<MEMBER>`,
/// indicating that it *may* be an instance attribute if we are in a method context.
///
/// This method is internal to the semantic-index submodule.
/// It *only* checks that the AST structure of the `Place` is
/// correct. It does not check whether the `Place` actually occurred in
/// a method context, or whether the `<NAME>` actually refers to the first
/// parameter of the method (i.e. `self`). To answer those questions,
/// use [`Self::is_instance_attribute`].
pub(super) fn is_instance_attribute_candidate(&self) -> bool {
self.as_instance_attribute_candidate().is_some()
}
/// Does the place expression have the form `self.{name}` (`self` is the first parameter of the method)?
pub(super) fn is_instance_attribute_named(&self, name: &str) -> bool {
self.as_instance_attribute().map(Name::as_str) == Some(name)
}
/// Return `Some(<ATTRIBUTE>)` if the place expression is an instance attribute.
pub(crate) fn as_instance_attribute(&self) -> Option<&Name> {
if self.is_instance_attribute() {
debug_assert!(self.as_instance_attribute_candidate().is_some());
self.as_instance_attribute_candidate()
} else {
None
}
}
}
impl std::fmt::Display for Member {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.expression, f)
}
}
bitflags! {
/// Flags that can be queried to obtain information about a member in a given scope.
///
/// See the doc-comment at the top of [`super::use_def`] for explanations of what it
/// means for a member to be *bound* as opposed to *declared*.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct MemberFlags: u8 {
const IS_BOUND = 1 << 0;
const IS_DECLARED = 1 << 1;
const IS_INSTANCE_ATTRIBUTE = 1 << 2;
}
}
impl get_size2::GetSize for MemberFlags {}
/// An expression accessing a member on a symbol named `symbol_name`, e.g. `x.y.z`.
///
/// The parts after the symbol name are called segments, and they can be either:
/// * An attribute access, e.g. `.y` in `x.y`
/// * An integer-based subscript, e.g. `[1]` in `x[1]`
/// * A string-based subscript, e.g. `["foo"]` in `x["foo"]`
///
/// Internally, the segments are stored in reverse order. This allows constructing
/// a `MemberExpr` from an ast expression without having to reverse the segments.
#[derive(Clone, Debug, PartialEq, Eq, get_size2::GetSize, Hash)]
pub(crate) struct MemberExpr {
symbol_name: Name,
segments: SmallVec<[MemberSegment; 1]>,
}
impl MemberExpr {
pub(super) fn new(symbol_name: Name, segments: SmallVec<[MemberSegment; 1]>) -> Self {
debug_assert!(
!segments.is_empty(),
"A member without segments is a symbol."
);
Self {
symbol_name,
segments,
}
}
fn shrink_to_fit(&mut self) {
self.segments.shrink_to_fit();
self.segments.shrink_to_fit();
}
/// Returns the left most part of the member expression, e.g. `x` in `x.y.z`.
///
/// This is the symbol on which the member access is performed.
pub(crate) fn symbol_name(&self) -> &Name {
&self.symbol_name
}
/// Returns the segments of the member expression, e.g. `[MemberSegment::Attribute("y"), MemberSegment::IntSubscript(1)]` for `x.y[1]`.
pub(crate) fn member_segments(
&self,
) -> impl ExactSizeIterator<Item = &MemberSegment> + DoubleEndedIterator {
self.segments.iter().rev()
}
pub(crate) fn as_ref(&self) -> MemberExprRef {
MemberExprRef {
name: self.symbol_name.as_str(),
segments: self.segments.as_slice(),
}
}
}
impl std::fmt::Display for MemberExpr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.symbol_name.as_str())?;
for segment in self.member_segments() {
match segment {
MemberSegment::Attribute(name) => write!(f, ".{name}")?,
MemberSegment::IntSubscript(int) => write!(f, "[{int}]")?,
MemberSegment::StringSubscript(string) => write!(f, "[\"{string}\"]")?,
}
}
Ok(())
}
}
impl PartialEq<MemberExprRef<'_>> for MemberExpr {
fn eq(&self, other: &MemberExprRef) -> bool {
self.as_ref() == *other
}
}
impl PartialEq<MemberExprRef<'_>> for &MemberExpr {
fn eq(&self, other: &MemberExprRef) -> bool {
self.as_ref() == *other
}
}
impl PartialEq<MemberExpr> for MemberExprRef<'_> {
fn eq(&self, other: &MemberExpr) -> bool {
other == self
}
}
impl PartialEq<&MemberExpr> for MemberExprRef<'_> {
fn eq(&self, other: &&MemberExpr) -> bool {
*other == self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
pub(crate) enum MemberSegment {
/// An attribute access, e.g. `.y` in `x.y`
Attribute(Name),
/// An integer-based index access, e.g. `[1]` in `x[1]`
IntSubscript(ast::Int),
/// A string-based index access, e.g. `["foo"]` in `x["foo"]`
StringSubscript(String),
}
/// Reference to a member expression.
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub(crate) struct MemberExprRef<'a> {
name: &'a str,
segments: &'a [MemberSegment],
}
impl<'a> MemberExprRef<'a> {
pub(super) fn symbol_name(&self) -> &'a str {
self.name
}
/// Create a new `MemberExprRef` from a name and segments.
///
/// Note that the segments are expected to be in reverse order, i.e. the last segment is the first one in the expression.
pub(super) fn from_raw(name: &'a str, segments: &'a [MemberSegment]) -> Self {
debug_assert!(
!segments.is_empty(),
"A member without segments is a symbol."
);
Self { name, segments }
}
/// Returns a slice over the member segments. The segments are in reverse order,
pub(super) fn rev_member_segments(&self) -> &'a [MemberSegment] {
self.segments
}
}
impl<'a> From<&'a MemberExpr> for MemberExprRef<'a> {
fn from(value: &'a MemberExpr) -> Self {
value.as_ref()
}
}
/// Uniquely identifies a member in a scope.
#[newtype_index]
#[derive(get_size2::GetSize, salsa::Update)]
pub struct ScopedMemberId;
/// The members of a scope. Allows lookup by member path and [`ScopedMemberId`].
#[derive(Default, get_size2::GetSize)]
pub(super) struct MemberTable {
members: IndexVec<ScopedMemberId, Member>,
/// Map from member path to its ID.
///
/// Uses a hash table to avoid storing the path twice.
map: hashbrown::HashTable<ScopedMemberId>,
}
impl MemberTable {
/// Returns the member with the given ID.
///
/// ## Panics
/// If the ID is not valid for this table.
#[track_caller]
pub(crate) fn member(&self, id: ScopedMemberId) -> &Member {
&self.members[id]
}
/// Returns a mutable reference to the member with the given ID.
///
/// ## Panics
/// If the ID is not valid for this table.
#[track_caller]
pub(super) fn member_mut(&mut self, id: ScopedMemberId) -> &mut Member {
&mut self.members[id]
}
/// Returns an iterator over all members in the table.
pub(crate) fn iter(&self) -> std::slice::Iter<Member> {
self.members.iter()
}
fn hash_member_expression_ref(member: MemberExprRef) -> u64 {
let mut h = FxHasher::default();
member.hash(&mut h);
h.finish()
}
/// Returns the ID of the member with the given expression, if it exists.
pub(crate) fn member_id<'a>(
&self,
member: impl Into<MemberExprRef<'a>>,
) -> Option<ScopedMemberId> {
let member = member.into();
let hash = Self::hash_member_expression_ref(member);
self.map
.find(hash, |id| self.members[*id].expression == member)
.copied()
}
pub(crate) fn place_id_by_instance_attribute_name(&self, name: &str) -> Option<ScopedMemberId> {
for (id, member) in self.members.iter_enumerated() {
if member.is_instance_attribute_named(name) {
return Some(id);
}
}
None
}
}
impl PartialEq for MemberTable {
fn eq(&self, other: &Self) -> bool {
// It's sufficient to compare the members as the map is only a reverse lookup.
self.members == other.members
}
}
impl Eq for MemberTable {}
impl std::fmt::Debug for MemberTable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("MemberTable").field(&self.members).finish()
}
}
#[derive(Debug, Default)]
pub(super) struct MemberTableBuilder {
table: MemberTable,
}
impl MemberTableBuilder {
/// Adds a member to the table or updates the flags of an existing member if it already exists.
///
/// Members are identified by their expression, which is hashed to find the entry in the table.
pub(super) fn add(&mut self, mut member: Member) -> (ScopedMemberId, bool) {
let hash = MemberTable::hash_member_expression_ref(member.expression.as_ref());
let entry = self.table.map.entry(
hash,
|id| self.table.members[*id].expression == member.expression,
|id| {
MemberTable::hash_member_expression_ref(self.table.members[*id].expression.as_ref())
},
);
match entry {
Entry::Occupied(entry) => {
let id = *entry.get();
if !member.flags.is_empty() {
self.members[id].flags.insert(member.flags);
}
(id, false)
}
Entry::Vacant(entry) => {
member.expression.shrink_to_fit();
let id = self.table.members.push(member);
entry.insert(id);
(id, true)
}
}
}
pub(super) fn build(self) -> MemberTable {
let mut table = self.table;
table.members.shrink_to_fit();
table.map.shrink_to_fit(|id| {
MemberTable::hash_member_expression_ref(table.members[*id].expression.as_ref())
});
table
}
}
impl Deref for MemberTableBuilder {
type Target = MemberTable;
fn deref(&self) -> &Self::Target {
&self.table
}
}
impl DerefMut for MemberTableBuilder {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.table
}
}

View file

@ -30,8 +30,8 @@
use crate::list::{List, ListBuilder, ListSetReverseIterator, ListStorage};
use crate::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

View file

@ -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,
}

View 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

View file

@ -0,0 +1,458 @@
use std::ops::Range;
use ruff_db::{files::File, parsed::ParsedModuleRef};
use ruff_index::newtype_index;
use ruff_python_ast as ast;
use crate::{
Db,
ast_node_ref::AstNodeRef,
node_key::NodeKey,
semantic_index::{
SemanticIndex, reachability_constraints::ScopedReachabilityConstraintId, semantic_index,
},
};
/// A cross-module identifier of a scope that can be used as a salsa query parameter.
#[salsa::tracked(debug)]
pub struct ScopeId<'db> {
pub file: File,
pub file_scope_id: FileScopeId,
}
// The Salsa heap is tracked separately.
impl get_size2::GetSize for ScopeId<'_> {}
impl<'db> ScopeId<'db> {
pub(crate) fn is_function_like(self, db: &'db dyn Db) -> bool {
self.node(db).scope_kind().is_function_like()
}
pub(crate) fn is_type_parameter(self, db: &'db dyn Db) -> bool {
self.node(db).scope_kind().is_type_parameter()
}
pub(crate) fn node(self, db: &dyn Db) -> &NodeWithScopeKind {
self.scope(db).node()
}
pub(crate) fn scope(self, db: &dyn Db) -> &Scope {
semantic_index(db, self.file(db)).scope(self.file_scope_id(db))
}
#[cfg(test)]
pub(crate) fn name<'ast>(self, db: &'db dyn Db, module: &'ast ParsedModuleRef) -> &'ast str {
match self.node(db) {
NodeWithScopeKind::Module => "<module>",
NodeWithScopeKind::Class(class) | NodeWithScopeKind::ClassTypeParameters(class) => {
class.node(module).name.as_str()
}
NodeWithScopeKind::Function(function)
| NodeWithScopeKind::FunctionTypeParameters(function) => {
function.node(module).name.as_str()
}
NodeWithScopeKind::TypeAlias(type_alias)
| NodeWithScopeKind::TypeAliasTypeParameters(type_alias) => type_alias
.node(module)
.name
.as_name_expr()
.map(|name| name.id.as_str())
.unwrap_or("<type alias>"),
NodeWithScopeKind::Lambda(_) => "<lambda>",
NodeWithScopeKind::ListComprehension(_) => "<listcomp>",
NodeWithScopeKind::SetComprehension(_) => "<setcomp>",
NodeWithScopeKind::DictComprehension(_) => "<dictcomp>",
NodeWithScopeKind::GeneratorExpression(_) => "<generator>",
}
}
}
/// ID that uniquely identifies a scope inside of a module.
#[newtype_index]
#[derive(salsa::Update, get_size2::GetSize)]
pub struct FileScopeId;
impl FileScopeId {
/// Returns the scope id of the module-global scope.
pub fn global() -> Self {
FileScopeId::from_u32(0)
}
pub fn is_global(self) -> bool {
self == FileScopeId::global()
}
pub fn to_scope_id(self, db: &dyn Db, file: File) -> ScopeId<'_> {
let index = semantic_index(db, file);
index.scope_ids_by_scope[self]
}
pub(crate) fn is_generator_function(self, index: &SemanticIndex) -> bool {
index.generator_functions.contains(&self)
}
}
#[derive(Debug, salsa::Update, get_size2::GetSize)]
pub(crate) struct Scope {
/// The parent scope, if any.
parent: Option<FileScopeId>,
/// The node that introduces this scope.
node: NodeWithScopeKind,
/// The range of [`FileScopeId`]s that are descendants of this scope.
descendants: Range<FileScopeId>,
/// The constraint that determines the reachability of this scope.
reachability: ScopedReachabilityConstraintId,
/// Whether this scope is defined inside an `if TYPE_CHECKING:` block.
in_type_checking_block: bool,
}
impl Scope {
pub(super) fn new(
parent: Option<FileScopeId>,
node: NodeWithScopeKind,
descendants: Range<FileScopeId>,
reachability: ScopedReachabilityConstraintId,
in_type_checking_block: bool,
) -> Self {
Scope {
parent,
node,
descendants,
reachability,
in_type_checking_block,
}
}
pub(crate) fn parent(&self) -> Option<FileScopeId> {
self.parent
}
pub(crate) fn node(&self) -> &NodeWithScopeKind {
&self.node
}
pub(crate) fn kind(&self) -> ScopeKind {
self.node().scope_kind()
}
pub(crate) fn visibility(&self) -> ScopeVisibility {
self.kind().visibility()
}
pub(crate) fn descendants(&self) -> Range<FileScopeId> {
self.descendants.clone()
}
pub(super) fn extend_descendants(&mut self, children_end: FileScopeId) {
self.descendants = self.descendants.start..children_end;
}
pub(crate) fn is_eager(&self) -> bool {
self.kind().is_eager()
}
pub(crate) fn reachability(&self) -> ScopedReachabilityConstraintId {
self.reachability
}
pub(crate) fn in_type_checking_block(&self) -> bool {
self.in_type_checking_block
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, get_size2::GetSize)]
pub(crate) enum ScopeVisibility {
/// The scope is private (e.g. function, type alias, comprehension scope).
Private,
/// The scope is public (e.g. module, class scope).
Public,
}
impl ScopeVisibility {
pub(crate) const fn is_public(self) -> bool {
matches!(self, ScopeVisibility::Public)
}
pub(crate) const fn is_private(self) -> bool {
matches!(self, ScopeVisibility::Private)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, get_size2::GetSize)]
pub(crate) enum ScopeLaziness {
/// The scope is evaluated lazily (e.g. function, type alias scope).
Lazy,
/// The scope is evaluated eagerly (e.g. module, class, comprehension scope).
Eager,
}
impl ScopeLaziness {
pub(crate) const fn is_eager(self) -> bool {
matches!(self, ScopeLaziness::Eager)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum ScopeKind {
Module,
Annotation,
Class,
Function,
Lambda,
Comprehension,
TypeAlias,
}
impl ScopeKind {
pub(crate) const fn is_eager(self) -> bool {
self.laziness().is_eager()
}
pub(crate) const fn laziness(self) -> ScopeLaziness {
match self {
ScopeKind::Module | ScopeKind::Class | ScopeKind::Comprehension => ScopeLaziness::Eager,
ScopeKind::Annotation
| ScopeKind::Function
| ScopeKind::Lambda
| ScopeKind::TypeAlias => ScopeLaziness::Lazy,
}
}
pub(crate) const fn visibility(self) -> ScopeVisibility {
match self {
ScopeKind::Module | ScopeKind::Class => ScopeVisibility::Public,
ScopeKind::Annotation
| ScopeKind::TypeAlias
| ScopeKind::Function
| ScopeKind::Lambda
| ScopeKind::Comprehension => ScopeVisibility::Private,
}
}
pub(crate) const fn is_function_like(self) -> bool {
// Type parameter scopes behave like function scopes in terms of name resolution; CPython
// symbol table also uses the term "function-like" for these scopes.
matches!(
self,
ScopeKind::Annotation
| ScopeKind::Function
| ScopeKind::Lambda
| ScopeKind::TypeAlias
| ScopeKind::Comprehension
)
}
pub(crate) const fn is_class(self) -> bool {
matches!(self, ScopeKind::Class)
}
pub(crate) const fn is_type_parameter(self) -> bool {
matches!(self, ScopeKind::Annotation | ScopeKind::TypeAlias)
}
pub(crate) const fn is_non_lambda_function(self) -> bool {
matches!(self, ScopeKind::Function)
}
}
/// Reference to a node that introduces a new scope.
#[derive(Copy, Clone, Debug)]
pub(crate) enum NodeWithScopeRef<'a> {
Module,
Class(&'a ast::StmtClassDef),
Function(&'a ast::StmtFunctionDef),
Lambda(&'a ast::ExprLambda),
FunctionTypeParameters(&'a ast::StmtFunctionDef),
ClassTypeParameters(&'a ast::StmtClassDef),
TypeAlias(&'a ast::StmtTypeAlias),
TypeAliasTypeParameters(&'a ast::StmtTypeAlias),
ListComprehension(&'a ast::ExprListComp),
SetComprehension(&'a ast::ExprSetComp),
DictComprehension(&'a ast::ExprDictComp),
GeneratorExpression(&'a ast::ExprGenerator),
}
impl NodeWithScopeRef<'_> {
/// Converts the unowned reference to an owned [`NodeWithScopeKind`].
///
/// Note that node wrapped by `self` must be a child of `module`.
pub(super) fn to_kind(self, module: &ParsedModuleRef) -> NodeWithScopeKind {
match self {
NodeWithScopeRef::Module => NodeWithScopeKind::Module,
NodeWithScopeRef::Class(class) => {
NodeWithScopeKind::Class(AstNodeRef::new(module, class))
}
NodeWithScopeRef::Function(function) => {
NodeWithScopeKind::Function(AstNodeRef::new(module, function))
}
NodeWithScopeRef::TypeAlias(type_alias) => {
NodeWithScopeKind::TypeAlias(AstNodeRef::new(module, type_alias))
}
NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => {
NodeWithScopeKind::TypeAliasTypeParameters(AstNodeRef::new(module, type_alias))
}
NodeWithScopeRef::Lambda(lambda) => {
NodeWithScopeKind::Lambda(AstNodeRef::new(module, lambda))
}
NodeWithScopeRef::FunctionTypeParameters(function) => {
NodeWithScopeKind::FunctionTypeParameters(AstNodeRef::new(module, function))
}
NodeWithScopeRef::ClassTypeParameters(class) => {
NodeWithScopeKind::ClassTypeParameters(AstNodeRef::new(module, class))
}
NodeWithScopeRef::ListComprehension(comprehension) => {
NodeWithScopeKind::ListComprehension(AstNodeRef::new(module, comprehension))
}
NodeWithScopeRef::SetComprehension(comprehension) => {
NodeWithScopeKind::SetComprehension(AstNodeRef::new(module, comprehension))
}
NodeWithScopeRef::DictComprehension(comprehension) => {
NodeWithScopeKind::DictComprehension(AstNodeRef::new(module, comprehension))
}
NodeWithScopeRef::GeneratorExpression(generator) => {
NodeWithScopeKind::GeneratorExpression(AstNodeRef::new(module, generator))
}
}
}
pub(crate) fn node_key(self) -> NodeWithScopeKey {
match self {
NodeWithScopeRef::Module => NodeWithScopeKey::Module,
NodeWithScopeRef::Class(class) => NodeWithScopeKey::Class(NodeKey::from_node(class)),
NodeWithScopeRef::Function(function) => {
NodeWithScopeKey::Function(NodeKey::from_node(function))
}
NodeWithScopeRef::Lambda(lambda) => {
NodeWithScopeKey::Lambda(NodeKey::from_node(lambda))
}
NodeWithScopeRef::FunctionTypeParameters(function) => {
NodeWithScopeKey::FunctionTypeParameters(NodeKey::from_node(function))
}
NodeWithScopeRef::ClassTypeParameters(class) => {
NodeWithScopeKey::ClassTypeParameters(NodeKey::from_node(class))
}
NodeWithScopeRef::TypeAlias(type_alias) => {
NodeWithScopeKey::TypeAlias(NodeKey::from_node(type_alias))
}
NodeWithScopeRef::TypeAliasTypeParameters(type_alias) => {
NodeWithScopeKey::TypeAliasTypeParameters(NodeKey::from_node(type_alias))
}
NodeWithScopeRef::ListComprehension(comprehension) => {
NodeWithScopeKey::ListComprehension(NodeKey::from_node(comprehension))
}
NodeWithScopeRef::SetComprehension(comprehension) => {
NodeWithScopeKey::SetComprehension(NodeKey::from_node(comprehension))
}
NodeWithScopeRef::DictComprehension(comprehension) => {
NodeWithScopeKey::DictComprehension(NodeKey::from_node(comprehension))
}
NodeWithScopeRef::GeneratorExpression(generator) => {
NodeWithScopeKey::GeneratorExpression(NodeKey::from_node(generator))
}
}
}
}
/// Node that introduces a new scope.
#[derive(Clone, Debug, salsa::Update, get_size2::GetSize)]
pub(crate) enum NodeWithScopeKind {
Module,
Class(AstNodeRef<ast::StmtClassDef>),
ClassTypeParameters(AstNodeRef<ast::StmtClassDef>),
Function(AstNodeRef<ast::StmtFunctionDef>),
FunctionTypeParameters(AstNodeRef<ast::StmtFunctionDef>),
TypeAliasTypeParameters(AstNodeRef<ast::StmtTypeAlias>),
TypeAlias(AstNodeRef<ast::StmtTypeAlias>),
Lambda(AstNodeRef<ast::ExprLambda>),
ListComprehension(AstNodeRef<ast::ExprListComp>),
SetComprehension(AstNodeRef<ast::ExprSetComp>),
DictComprehension(AstNodeRef<ast::ExprDictComp>),
GeneratorExpression(AstNodeRef<ast::ExprGenerator>),
}
impl NodeWithScopeKind {
pub(crate) const fn scope_kind(&self) -> ScopeKind {
match self {
Self::Module => ScopeKind::Module,
Self::Class(_) => ScopeKind::Class,
Self::Function(_) => ScopeKind::Function,
Self::Lambda(_) => ScopeKind::Lambda,
Self::FunctionTypeParameters(_)
| Self::ClassTypeParameters(_)
| Self::TypeAliasTypeParameters(_) => ScopeKind::Annotation,
Self::TypeAlias(_) => ScopeKind::TypeAlias,
Self::ListComprehension(_)
| Self::SetComprehension(_)
| Self::DictComprehension(_)
| Self::GeneratorExpression(_) => ScopeKind::Comprehension,
}
}
pub(crate) fn expect_class<'ast>(
&self,
module: &'ast ParsedModuleRef,
) -> &'ast ast::StmtClassDef {
match self {
Self::Class(class) => class.node(module),
_ => panic!("expected class"),
}
}
pub(crate) fn as_class<'ast>(
&self,
module: &'ast ParsedModuleRef,
) -> Option<&'ast ast::StmtClassDef> {
match self {
Self::Class(class) => Some(class.node(module)),
_ => None,
}
}
pub(crate) fn expect_function<'ast>(
&self,
module: &'ast ParsedModuleRef,
) -> &'ast ast::StmtFunctionDef {
self.as_function(module).expect("expected function")
}
pub(crate) fn expect_type_alias<'ast>(
&self,
module: &'ast ParsedModuleRef,
) -> &'ast ast::StmtTypeAlias {
match self {
Self::TypeAlias(type_alias) => type_alias.node(module),
_ => panic!("expected type alias"),
}
}
pub(crate) fn as_function<'ast>(
&self,
module: &'ast ParsedModuleRef,
) -> Option<&'ast ast::StmtFunctionDef> {
match self {
Self::Function(function) => Some(function.node(module)),
_ => None,
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
pub(crate) enum NodeWithScopeKey {
Module,
Class(NodeKey),
ClassTypeParameters(NodeKey),
Function(NodeKey),
FunctionTypeParameters(NodeKey),
TypeAlias(NodeKey),
TypeAliasTypeParameters(NodeKey),
Lambda(NodeKey),
ListComprehension(NodeKey),
SetComprehension(NodeKey),
DictComprehension(NodeKey),
GeneratorExpression(NodeKey),
}

View file

@ -0,0 +1,237 @@
use bitflags::bitflags;
use hashbrown::hash_table::Entry;
use ruff_index::{IndexVec, newtype_index};
use ruff_python_ast::name::Name;
use rustc_hash::FxHasher;
use std::hash::{Hash as _, Hasher as _};
use std::ops::{Deref, DerefMut};
/// Uniquely identifies a symbol in a given scope.
#[newtype_index]
#[derive(get_size2::GetSize)]
pub struct ScopedSymbolId;
/// A symbol in a given scope.
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize, salsa::Update)]
pub(crate) struct Symbol {
name: Name,
flags: SymbolFlags,
}
impl std::fmt::Display for Symbol {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.name.fmt(f)
}
}
bitflags! {
/// Flags that can be queried to obtain information about a symbol in a given scope.
///
/// See the doc-comment at the top of [`super::use_def`] for explanations of what it
/// means for a symbol to be *bound* as opposed to *declared*.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
struct SymbolFlags: u8 {
const IS_USED = 1 << 0;
const IS_BOUND = 1 << 1;
const IS_DECLARED = 1 << 2;
const MARKED_GLOBAL = 1 << 3;
const MARKED_NONLOCAL = 1 << 4;
const IS_REASSIGNED = 1 << 5;
}
}
impl get_size2::GetSize for SymbolFlags {}
impl Symbol {
pub(crate) const fn new(name: Name) -> Self {
Self {
name,
flags: SymbolFlags::empty(),
}
}
pub(crate) fn name(&self) -> &Name {
&self.name
}
/// Is the symbol used in its containing scope?
pub(crate) fn is_used(&self) -> bool {
self.flags.contains(SymbolFlags::IS_USED)
}
/// Is the symbol given a value in its containing scope?
pub(crate) const fn is_bound(&self) -> bool {
self.flags.contains(SymbolFlags::IS_BOUND)
}
/// Is the symbol declared in its containing scope?
pub(crate) fn is_declared(&self) -> bool {
self.flags.contains(SymbolFlags::IS_DECLARED)
}
/// Is the symbol `global` its containing scope?
pub(crate) fn is_global(&self) -> bool {
self.flags.contains(SymbolFlags::MARKED_GLOBAL)
}
/// Is the symbol `nonlocal` its containing scope?
pub(crate) fn is_nonlocal(&self) -> bool {
self.flags.contains(SymbolFlags::MARKED_NONLOCAL)
}
pub(crate) const fn is_reassigned(&self) -> bool {
self.flags.contains(SymbolFlags::IS_REASSIGNED)
}
pub(super) fn mark_global(&mut self) {
self.insert_flags(SymbolFlags::MARKED_GLOBAL);
}
pub(super) fn mark_nonlocal(&mut self) {
self.insert_flags(SymbolFlags::MARKED_NONLOCAL);
}
pub(super) fn mark_bound(&mut self) {
if self.is_bound() {
self.insert_flags(SymbolFlags::IS_REASSIGNED);
}
self.insert_flags(SymbolFlags::IS_BOUND);
}
pub(super) fn mark_used(&mut self) {
self.insert_flags(SymbolFlags::IS_USED);
}
pub(super) fn mark_declared(&mut self) {
self.insert_flags(SymbolFlags::IS_DECLARED);
}
fn insert_flags(&mut self, flags: SymbolFlags) {
self.flags.insert(flags);
}
}
/// The symbols of a given scope.
///
/// Allows lookup by name and a symbol's ID.
#[derive(Default, get_size2::GetSize)]
pub(super) struct SymbolTable {
symbols: IndexVec<ScopedSymbolId, Symbol>,
/// Map from symbol name to its ID.
///
/// Uses a hash table to avoid storing the name twice.
map: hashbrown::HashTable<ScopedSymbolId>,
}
impl SymbolTable {
/// Look up a symbol by its ID.
///
/// ## Panics
/// If the ID is not valid for this symbol table.
#[track_caller]
pub(crate) fn symbol(&self, id: ScopedSymbolId) -> &Symbol {
&self.symbols[id]
}
/// Look up a symbol by its ID, mutably.
///
/// ## Panics
/// If the ID is not valid for this symbol table.
#[track_caller]
pub(crate) fn symbol_mut(&mut self, id: ScopedSymbolId) -> &mut Symbol {
&mut self.symbols[id]
}
/// Look up the ID of a symbol by its name.
pub(crate) fn symbol_id(&self, name: &str) -> Option<ScopedSymbolId> {
self.map
.find(Self::hash_name(name), |id| self.symbols[*id].name == name)
.copied()
}
/// Iterate over the symbols in this symbol table.
pub(crate) fn iter(&self) -> std::slice::Iter<Symbol> {
self.symbols.iter()
}
fn hash_name(name: &str) -> u64 {
let mut h = FxHasher::default();
name.hash(&mut h);
h.finish()
}
}
impl PartialEq for SymbolTable {
fn eq(&self, other: &Self) -> bool {
// It's sufficient to compare the symbols as the map is only a reverse lookup.
self.symbols == other.symbols
}
}
impl Eq for SymbolTable {}
impl std::fmt::Debug for SymbolTable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("SymbolTable").field(&self.symbols).finish()
}
}
#[derive(Debug, Default)]
pub(super) struct SymbolTableBuilder {
table: SymbolTable,
}
impl SymbolTableBuilder {
/// Add a new symbol to this scope or update the flags if a symbol with the same name already exists.
pub(super) fn add(&mut self, mut symbol: Symbol) -> (ScopedSymbolId, bool) {
let hash = SymbolTable::hash_name(symbol.name());
let entry = self.table.map.entry(
hash,
|id| &self.table.symbols[*id].name == symbol.name(),
|id| SymbolTable::hash_name(&self.table.symbols[*id].name),
);
match entry {
Entry::Occupied(entry) => {
let id = *entry.get();
if !symbol.flags.is_empty() {
self.symbols[id].flags.insert(symbol.flags);
}
(id, false)
}
Entry::Vacant(entry) => {
symbol.name.shrink_to_fit();
let id = self.table.symbols.push(symbol);
entry.insert(id);
(id, true)
}
}
}
pub(super) fn build(self) -> SymbolTable {
let mut table = self.table;
table.symbols.shrink_to_fit();
table
.map
.shrink_to_fit(|id| SymbolTable::hash_name(&table.symbols[*id].name));
table
}
}
impl Deref for SymbolTableBuilder {
type Target = SymbolTable;
fn deref(&self) -> &Self::Target {
&self.table
}
}
impl DerefMut for SymbolTableBuilder {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.table
}
}

View file

@ -251,20 +251,21 @@ use crate::node_key::NodeKey;
use crate::place::BoundnessAnalysis;
use crate::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,