mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 22:01:47 +00:00
Move reference-resolution into Context (#4510)
This commit is contained in:
parent
b42ff08612
commit
bb4e674415
2 changed files with 141 additions and 96 deletions
|
@ -27,7 +27,7 @@ use ruff_python_semantic::binding::{
|
||||||
Binding, BindingId, BindingKind, Exceptions, ExecutionContext, Export, FromImportation,
|
Binding, BindingId, BindingKind, Exceptions, ExecutionContext, Export, FromImportation,
|
||||||
Importation, StarImportation, SubmoduleImportation,
|
Importation, StarImportation, SubmoduleImportation,
|
||||||
};
|
};
|
||||||
use ruff_python_semantic::context::{Context, ContextFlags};
|
use ruff_python_semantic::context::{Context, ContextFlags, ResolvedReference};
|
||||||
use ruff_python_semantic::definition::{ContextualizedDefinition, Module, ModuleKind};
|
use ruff_python_semantic::definition::{ContextualizedDefinition, Module, ModuleKind};
|
||||||
use ruff_python_semantic::node::NodeId;
|
use ruff_python_semantic::node::NodeId;
|
||||||
use ruff_python_semantic::scope::{ClassDef, FunctionDef, Lambda, Scope, ScopeId, ScopeKind};
|
use ruff_python_semantic::scope::{ClassDef, FunctionDef, Lambda, Scope, ScopeId, ScopeKind};
|
||||||
|
@ -4746,40 +4746,20 @@ impl<'a> Checker<'a> {
|
||||||
let Expr::Name(ast::ExprName { id, .. } )= expr else {
|
let Expr::Name(ast::ExprName { id, .. } )= expr else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let id = id.as_str();
|
match self.ctx.resolve_reference(id, expr.range()) {
|
||||||
|
ResolvedReference::Resolved(scope_id, binding_id) => {
|
||||||
let mut first_iter = true;
|
|
||||||
let mut import_starred = false;
|
|
||||||
|
|
||||||
for scope in self.ctx.scopes.ancestors(self.ctx.scope_id) {
|
|
||||||
if scope.kind.is_class() {
|
|
||||||
if id == "__class__" {
|
|
||||||
return;
|
|
||||||
} else if !first_iter {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(index) = scope.get(id) {
|
|
||||||
// Mark the binding as used.
|
|
||||||
let context = self.ctx.execution_context();
|
|
||||||
self.ctx.bindings[*index].mark_used(self.ctx.scope_id, expr.range(), context);
|
|
||||||
|
|
||||||
if !self.ctx.in_deferred_type_definition()
|
|
||||||
&& self.ctx.bindings[*index].kind.is_annotation()
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the name of the sub-importation is the same as an alias of another
|
// If the name of the sub-importation is the same as an alias of another
|
||||||
// importation and the alias is used, that sub-importation should be
|
// importation and the alias is used, that sub-importation should be
|
||||||
// marked as used too.
|
// marked as used too.
|
||||||
//
|
//
|
||||||
// This handles code like:
|
// For example, mark `pa` as used in:
|
||||||
|
//
|
||||||
|
// ```python
|
||||||
// import pyarrow as pa
|
// import pyarrow as pa
|
||||||
// import pyarrow.csv
|
// import pyarrow.csv
|
||||||
// print(pa.csv.read_csv("test.csv"))
|
// print(pa.csv.read_csv("test.csv"))
|
||||||
match &self.ctx.bindings[*index].kind {
|
// ```
|
||||||
|
match &self.ctx.bindings[binding_id].kind {
|
||||||
BindingKind::Importation(Importation { name, full_name })
|
BindingKind::Importation(Importation { name, full_name })
|
||||||
| BindingKind::SubmoduleImportation(SubmoduleImportation { name, full_name }) =>
|
| BindingKind::SubmoduleImportation(SubmoduleImportation { name, full_name }) =>
|
||||||
{
|
{
|
||||||
|
@ -4790,8 +4770,9 @@ impl<'a> Checker<'a> {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
if has_alias {
|
if has_alias {
|
||||||
// Mark the sub-importation as used.
|
// Mark the sub-importation as used.
|
||||||
if let Some(index) = scope.get(full_name) {
|
if let Some(binding_id) = self.ctx.scopes[scope_id].get(full_name) {
|
||||||
self.ctx.bindings[*index].mark_used(
|
let context = self.ctx.execution_context();
|
||||||
|
self.ctx.bindings[*binding_id].mark_used(
|
||||||
self.ctx.scope_id,
|
self.ctx.scope_id,
|
||||||
expr.range(),
|
expr.range(),
|
||||||
context,
|
context,
|
||||||
|
@ -4807,8 +4788,11 @@ impl<'a> Checker<'a> {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
if has_alias {
|
if has_alias {
|
||||||
// Mark the sub-importation as used.
|
// Mark the sub-importation as used.
|
||||||
if let Some(index) = scope.get(full_name.as_str()) {
|
if let Some(binding_id) =
|
||||||
self.ctx.bindings[*index].mark_used(
|
self.ctx.scopes[scope_id].get(full_name.as_str())
|
||||||
|
{
|
||||||
|
let context = self.ctx.execution_context();
|
||||||
|
self.ctx.bindings[*binding_id].mark_used(
|
||||||
self.ctx.scope_id,
|
self.ctx.scope_id,
|
||||||
expr.range(),
|
expr.range(),
|
||||||
context,
|
context,
|
||||||
|
@ -4818,15 +4802,11 @@ impl<'a> Checker<'a> {
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
ResolvedReference::ImplicitGlobal => {
|
||||||
first_iter = false;
|
// Nothing to do.
|
||||||
import_starred = import_starred || scope.uses_star_imports();
|
|
||||||
}
|
}
|
||||||
|
ResolvedReference::StarImport => {
|
||||||
if import_starred {
|
|
||||||
// F405
|
// F405
|
||||||
if self
|
if self
|
||||||
.settings
|
.settings
|
||||||
|
@ -4852,9 +4832,9 @@ impl<'a> Checker<'a> {
|
||||||
expr.range(),
|
expr.range(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
ResolvedReference::NotFound => {
|
||||||
|
// F821
|
||||||
if self.settings.rules.enabled(Rule::UndefinedName) {
|
if self.settings.rules.enabled(Rule::UndefinedName) {
|
||||||
// Allow __path__.
|
// Allow __path__.
|
||||||
if self.path.ends_with("__init__.py") && id == "__path__" {
|
if self.path.ends_with("__init__.py") && id == "__path__" {
|
||||||
|
@ -4868,7 +4848,7 @@ impl<'a> Checker<'a> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Avoid flagging if NameError is handled.
|
// Avoid flagging if `NameError` is handled.
|
||||||
if self
|
if self
|
||||||
.ctx
|
.ctx
|
||||||
.handled_exceptions
|
.handled_exceptions
|
||||||
|
@ -4886,6 +4866,8 @@ impl<'a> Checker<'a> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
|
fn handle_node_store(&mut self, id: &'a str, expr: &Expr) {
|
||||||
let parent = self.ctx.stmt();
|
let parent = self.ctx.stmt();
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::path::Path;
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use nohash_hasher::{BuildNoHashHasher, IntMap};
|
use nohash_hasher::{BuildNoHashHasher, IntMap};
|
||||||
|
use ruff_text_size::TextRange;
|
||||||
use rustpython_parser::ast::{Expr, Stmt};
|
use rustpython_parser::ast::{Expr, Stmt};
|
||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
|
|
||||||
|
@ -113,6 +114,54 @@ impl<'a> Context<'a> {
|
||||||
.map_or(false, |binding| binding.kind.is_builtin())
|
.map_or(false, |binding| binding.kind.is_builtin())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve a reference to the given symbol.
|
||||||
|
pub fn resolve_reference(&mut self, symbol: &str, range: TextRange) -> ResolvedReference {
|
||||||
|
let mut import_starred = false;
|
||||||
|
for (index, scope_id) in self.scopes.ancestor_ids(self.scope_id).enumerate() {
|
||||||
|
let scope = &self.scopes[scope_id];
|
||||||
|
if scope.kind.is_class() {
|
||||||
|
if symbol == "__class__" {
|
||||||
|
return ResolvedReference::ImplicitGlobal;
|
||||||
|
}
|
||||||
|
if index > 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(binding_id) = scope.get(symbol) {
|
||||||
|
// Mark the binding as used.
|
||||||
|
let context = self.execution_context();
|
||||||
|
self.bindings[*binding_id].mark_used(self.scope_id, range, context);
|
||||||
|
|
||||||
|
// But if it's a type annotation, don't treat it as resolved, unless we're in a
|
||||||
|
// forward reference. For example, given:
|
||||||
|
//
|
||||||
|
// ```python
|
||||||
|
// name: str
|
||||||
|
// print(name)
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// 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()
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResolvedReference::Resolved(scope_id, *binding_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
import_starred = import_starred || scope.uses_star_imports();
|
||||||
|
}
|
||||||
|
|
||||||
|
if import_starred {
|
||||||
|
ResolvedReference::StarImport
|
||||||
|
} else {
|
||||||
|
ResolvedReference::NotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolves the [`Expr`] to a fully-qualified symbol-name, if `value` resolves to an imported
|
/// Resolves the [`Expr`] to a fully-qualified symbol-name, if `value` resolves to an imported
|
||||||
/// or builtin symbol.
|
/// or builtin symbol.
|
||||||
///
|
///
|
||||||
|
@ -708,3 +757,17 @@ pub struct Snapshot {
|
||||||
definition_id: DefinitionId,
|
definition_id: DefinitionId,
|
||||||
flags: ContextFlags,
|
flags: ContextFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ResolvedReference {
|
||||||
|
/// The reference is resolved to a specific binding.
|
||||||
|
Resolved(ScopeId, BindingId),
|
||||||
|
/// The reference is resolved to a context-specific, implicit global (e.g., `__class__` within
|
||||||
|
/// a class scope).
|
||||||
|
ImplicitGlobal,
|
||||||
|
/// The reference is unresolved, but at least one of the containing scopes contains a star
|
||||||
|
/// import.
|
||||||
|
StarImport,
|
||||||
|
/// The reference is definitively unresolved.
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue