mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-12 22:59:30 +00:00
Track all read references in semantic model (#4610)
This commit is contained in:
parent
31bddef98f
commit
8961d8eb6f
14 changed files with 382 additions and 209 deletions
|
@ -18,6 +18,7 @@ use crate::binding::{
|
|||
};
|
||||
use crate::definition::{Definition, DefinitionId, Definitions, Member, Module};
|
||||
use crate::node::{NodeId, Nodes};
|
||||
use crate::reference::{ReferenceContext, References};
|
||||
use crate::scope::{Scope, ScopeId, ScopeKind, Scopes};
|
||||
|
||||
/// A semantic model for a Python module, to enable querying the module's semantic information.
|
||||
|
@ -39,6 +40,8 @@ pub struct SemanticModel<'a> {
|
|||
pub definition_id: DefinitionId,
|
||||
// A stack of all bindings created in any scope, at any point in execution.
|
||||
pub bindings: Bindings<'a>,
|
||||
// Stack of all references created in any scope, at any point in execution.
|
||||
pub references: References,
|
||||
// Map from binding index to indexes of bindings that shadow it in other scopes.
|
||||
pub shadowed_bindings: HashMap<BindingId, Vec<BindingId>, BuildNoHashHasher<BindingId>>,
|
||||
// Body iteration; used to peek at siblings.
|
||||
|
@ -63,6 +66,7 @@ impl<'a> SemanticModel<'a> {
|
|||
definitions: Definitions::for_module(module),
|
||||
definition_id: DefinitionId::module(),
|
||||
bindings: Bindings::default(),
|
||||
references: References::default(),
|
||||
shadowed_bindings: IntMap::default(),
|
||||
body: &[],
|
||||
body_index: 0,
|
||||
|
@ -119,17 +123,33 @@ impl<'a> SemanticModel<'a> {
|
|||
// PEP 563 indicates that if a forward reference can be resolved in the module scope, we
|
||||
// should prefer it over local resolutions.
|
||||
if self.in_deferred_type_definition() {
|
||||
if let Some(binding_id) = self.scopes.global().get(symbol) {
|
||||
if let Some(binding_id) = self.scopes.global().get(symbol).copied() {
|
||||
// Mark the binding as used.
|
||||
let context = self.execution_context();
|
||||
self.bindings[*binding_id].mark_used(ScopeId::global(), range, context);
|
||||
let reference_id = self.references.push(
|
||||
ScopeId::global(),
|
||||
range,
|
||||
match context {
|
||||
ExecutionContext::Runtime => ReferenceContext::Runtime,
|
||||
ExecutionContext::Typing => ReferenceContext::Typing,
|
||||
},
|
||||
);
|
||||
self.bindings[binding_id].references.push(reference_id);
|
||||
|
||||
// Mark any submodule aliases as used.
|
||||
if let Some(binding_id) = self.resolve_submodule(ScopeId::global(), *binding_id) {
|
||||
self.bindings[binding_id].mark_used(ScopeId::global(), range, context);
|
||||
if let Some(binding_id) = self.resolve_submodule(ScopeId::global(), binding_id) {
|
||||
let reference_id = self.references.push(
|
||||
ScopeId::global(),
|
||||
range,
|
||||
match context {
|
||||
ExecutionContext::Runtime => ReferenceContext::Runtime,
|
||||
ExecutionContext::Typing => ReferenceContext::Typing,
|
||||
},
|
||||
);
|
||||
self.bindings[binding_id].references.push(reference_id);
|
||||
}
|
||||
|
||||
return ResolvedReference::Resolved(*binding_id);
|
||||
return ResolvedReference::Resolved(binding_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,14 +173,30 @@ impl<'a> SemanticModel<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(binding_id) = scope.get(symbol) {
|
||||
if let Some(binding_id) = scope.get(symbol).copied() {
|
||||
// Mark the binding as used.
|
||||
let context = self.execution_context();
|
||||
self.bindings[*binding_id].mark_used(self.scope_id, range, context);
|
||||
let reference_id = self.references.push(
|
||||
self.scope_id,
|
||||
range,
|
||||
match context {
|
||||
ExecutionContext::Runtime => ReferenceContext::Runtime,
|
||||
ExecutionContext::Typing => ReferenceContext::Typing,
|
||||
},
|
||||
);
|
||||
self.bindings[binding_id].references.push(reference_id);
|
||||
|
||||
// Mark any submodule aliases as used.
|
||||
if let Some(binding_id) = self.resolve_submodule(scope_id, *binding_id) {
|
||||
self.bindings[binding_id].mark_used(ScopeId::global(), range, context);
|
||||
if let Some(binding_id) = self.resolve_submodule(scope_id, binding_id) {
|
||||
let reference_id = self.references.push(
|
||||
self.scope_id,
|
||||
range,
|
||||
match context {
|
||||
ExecutionContext::Runtime => ReferenceContext::Runtime,
|
||||
ExecutionContext::Typing => ReferenceContext::Typing,
|
||||
},
|
||||
);
|
||||
self.bindings[binding_id].references.push(reference_id);
|
||||
}
|
||||
|
||||
// But if it's a type annotation, don't treat it as resolved, unless we're in a
|
||||
|
@ -174,12 +210,12 @@ impl<'a> SemanticModel<'a> {
|
|||
// The `name` in `print(name)` should be treated as unresolved, but the `name` in
|
||||
// `name: str` should be treated as used.
|
||||
if !self.in_deferred_type_definition()
|
||||
&& self.bindings[*binding_id].kind.is_annotation()
|
||||
&& self.bindings[binding_id].kind.is_annotation()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return ResolvedReference::Resolved(*binding_id);
|
||||
return ResolvedReference::Resolved(binding_id);
|
||||
}
|
||||
|
||||
// Allow usages of `__module__` and `__qualname__` within class scopes, e.g.:
|
||||
|
@ -343,8 +379,8 @@ impl<'a> SemanticModel<'a> {
|
|||
member: &str,
|
||||
) -> Option<(&Stmt, String)> {
|
||||
self.scopes().enumerate().find_map(|(scope_index, scope)| {
|
||||
scope.binding_ids().find_map(|binding_index| {
|
||||
let binding = &self.bindings[*binding_index];
|
||||
scope.binding_ids().copied().find_map(|binding_id| {
|
||||
let binding = &self.bindings[binding_id];
|
||||
match &binding.kind {
|
||||
// Ex) Given `module="sys"` and `object="exit"`:
|
||||
// `import sys` -> `sys.exit`
|
||||
|
@ -519,11 +555,17 @@ impl<'a> SemanticModel<'a> {
|
|||
self.scopes.ancestors(self.scope_id)
|
||||
}
|
||||
|
||||
/// Returns an iterator over all parent statements.
|
||||
pub fn parents(&self) -> impl Iterator<Item = &Stmt> + '_ {
|
||||
let node_id = self.stmt_id.expect("No current statement");
|
||||
self.stmts.ancestor_ids(node_id).map(|id| self.stmts[id])
|
||||
}
|
||||
|
||||
/// Return `true` if the given [`ScopeId`] matches that of the current scope.
|
||||
pub fn is_current_scope(&self, scope_id: ScopeId) -> bool {
|
||||
self.scope_id == scope_id
|
||||
}
|
||||
|
||||
/// Return `true` if the context is at the top level of the module (i.e., in the module scope,
|
||||
/// and not nested within any statements).
|
||||
pub fn at_top_level(&self) -> bool {
|
||||
|
@ -533,6 +575,33 @@ impl<'a> SemanticModel<'a> {
|
|||
.map_or(true, |stmt_id| self.stmts.parent_id(stmt_id).is_none())
|
||||
}
|
||||
|
||||
/// Returns `true` if the given [`BindingId`] is used.
|
||||
pub fn is_used(&self, binding_id: BindingId) -> bool {
|
||||
self.bindings[binding_id].is_used()
|
||||
}
|
||||
|
||||
/// Add a reference to the given [`BindingId`] in the local scope.
|
||||
pub fn add_local_reference(
|
||||
&mut self,
|
||||
binding_id: BindingId,
|
||||
range: TextRange,
|
||||
context: ReferenceContext,
|
||||
) {
|
||||
let reference_id = self.references.push(self.scope_id, range, context);
|
||||
self.bindings[binding_id].references.push(reference_id);
|
||||
}
|
||||
|
||||
/// Add a reference to the given [`BindingId`] in the global scope.
|
||||
pub fn add_global_reference(
|
||||
&mut self,
|
||||
binding_id: BindingId,
|
||||
range: TextRange,
|
||||
context: ReferenceContext,
|
||||
) {
|
||||
let reference_id = self.references.push(ScopeId::global(), range, context);
|
||||
self.bindings[binding_id].references.push(reference_id);
|
||||
}
|
||||
|
||||
/// Return the [`ExecutionContext`] of the current scope.
|
||||
pub const fn execution_context(&self) -> ExecutionContext {
|
||||
if self.in_type_checking_block()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue