refactor: Move scope and binding types to scope.rs (#3573)

This commit is contained in:
Micha Reiser 2023-03-17 17:31:33 +01:00 committed by GitHub
parent 92179e6369
commit dedf4cbdeb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 535 additions and 507 deletions

View file

@ -1,4 +1,4 @@
use ruff_python_ast::context::ScopeStack;
use ruff_python_ast::scope::ScopeStack;
use rustpython_parser::ast::{Expr, Stmt};
use ruff_python_ast::types::Range;

View file

@ -14,17 +14,18 @@ use rustpython_parser::ast::{
};
use ruff_diagnostics::Diagnostic;
use ruff_python_ast::context::{Context, ScopeStack};
use ruff_python_ast::context::Context;
use ruff_python_ast::helpers::{
binding_range, extract_handled_exceptions, to_module_path, Exceptions,
};
use ruff_python_ast::operations::{extract_all_names, AllNamesFlags};
use ruff_python_ast::relocate::relocate_expr;
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
use ruff_python_ast::types::{
Binding, BindingId, BindingKind, ClassDef, ExecutionContext, FunctionDef, Lambda, Node, Range,
RefEquality, Scope, ScopeId, ScopeKind,
use ruff_python_ast::scope::{
Binding, BindingId, BindingKind, ClassDef, ExecutionContext, FunctionDef, Lambda, Scope,
ScopeId, ScopeKind, ScopeStack,
};
use ruff_python_ast::source_code::{Indexer, Locator, Stylist};
use ruff_python_ast::types::{Node, Range, RefEquality};
use ruff_python_ast::typing::{match_annotated_subscript, Callable, SubscriptKind};
use ruff_python_ast::visitor::{walk_excepthandler, walk_pattern, Visitor};
use ruff_python_ast::{
@ -208,7 +209,7 @@ where
source: Some(RefEquality(stmt)),
context,
});
scope.bindings.insert(name, id);
scope.add(name, id);
}
}
@ -237,7 +238,7 @@ where
source: Some(RefEquality(stmt)),
context,
});
scope.bindings.insert(name, id);
scope.add(name, id);
}
// Mark the binding in the defining scopes as used too. (Skip the global scope
@ -249,9 +250,7 @@ where
scopes_iter.next_back();
for index in scopes_iter.skip(1) {
if let Some(index) =
self.ctx.scopes[*index].bindings.get(&name.as_str())
{
if let Some(index) = self.ctx.scopes[*index].get(name.as_str()) {
exists = true;
self.ctx.bindings[*index].runtime_usage = usage;
}
@ -1874,7 +1873,6 @@ where
if self
.ctx
.global_scope()
.bindings
.get(name)
.map_or(true, |index| self.ctx.bindings[*index].kind.is_annotation())
{
@ -1887,7 +1885,7 @@ where
source: Some(RefEquality(stmt)),
context: self.ctx.execution_context(),
});
self.ctx.global_scope_mut().bindings.insert(name, id);
self.ctx.global_scope_mut().add(name, id);
}
}
@ -1937,7 +1935,6 @@ where
if self
.ctx
.global_scope()
.bindings
.get(name)
.map_or(true, |index| self.ctx.bindings[*index].kind.is_annotation())
{
@ -1950,7 +1947,7 @@ where
source: Some(RefEquality(stmt)),
context: self.ctx.execution_context(),
});
self.ctx.global_scope_mut().bindings.insert(name, id);
self.ctx.global_scope_mut().add(name, id);
}
}
@ -3713,7 +3710,7 @@ where
let name_range =
helpers::excepthandler_name_range(excepthandler, self.locator).unwrap();
if self.ctx.scope().bindings.contains_key(&name.as_str()) {
if self.ctx.scope().defines(name.as_str()) {
self.handle_node_store(
name,
&Expr::new(
@ -3727,7 +3724,7 @@ where
);
}
let definition = self.ctx.scope().bindings.get(&name.as_str()).copied();
let definition = self.ctx.scope().get(name.as_str()).copied();
self.handle_node_store(
name,
&Expr::new(
@ -3744,7 +3741,7 @@ where
if let Some(index) = {
let scope = self.ctx.scope_mut();
&scope.bindings.remove(&name.as_str())
&scope.remove(name.as_str())
} {
if !self.ctx.bindings[*index].used() {
if self.settings.rules.enabled(Rule::UnusedVariable) {
@ -3778,7 +3775,7 @@ where
if let Some(index) = definition {
let scope = self.ctx.scope_mut();
scope.bindings.insert(name, index);
scope.add(name, index);
}
}
None => walk_excepthandler(self, excepthandler),
@ -3933,14 +3930,17 @@ impl<'a> Checker<'a> {
'b: 'a,
{
let binding_id = self.ctx.bindings.next_id();
if let Some((stack_index, scope_index)) = self
if let Some((stack_index, existing_binding_index)) = self
.ctx
.scope_stack
.iter()
.enumerate()
.find(|(_, scope_index)| self.ctx.scopes[**scope_index].bindings.contains_key(&name))
.find_map(|(stack_index, scope_index)| {
self.ctx.scopes[*scope_index]
.get(name)
.map(|binding_id| (stack_index, *binding_id))
})
{
let existing_binding_index = self.ctx.scopes[*scope_index].bindings[&name];
let existing = &self.ctx.bindings[existing_binding_index];
let in_current_scope = stack_index == 0;
if !existing.kind.is_builtin()
@ -4012,7 +4012,7 @@ impl<'a> Checker<'a> {
}
let scope = self.ctx.scope();
let binding = if let Some(index) = scope.bindings.get(&name) {
let binding = if let Some(index) = scope.get(name) {
let existing = &self.ctx.bindings[*index];
match &existing.kind {
BindingKind::Builtin => {
@ -4044,8 +4044,8 @@ impl<'a> Checker<'a> {
// Don't treat annotations as assignments if there is an existing value
// in scope.
let scope = self.ctx.scope_mut();
if !(binding.kind.is_annotation() && scope.bindings.contains_key(name)) {
if let Some(rebound_index) = scope.bindings.insert(name, binding_id) {
if !(binding.kind.is_annotation() && scope.defines(name)) {
if let Some(rebound_index) = scope.add(name, binding_id) {
scope
.rebounds
.entry(name)
@ -4076,7 +4076,7 @@ impl<'a> Checker<'a> {
source: None,
context: ExecutionContext::Runtime,
});
scope.bindings.insert(builtin, id);
scope.add(builtin, id);
}
}
@ -4101,7 +4101,7 @@ impl<'a> Checker<'a> {
}
}
if let Some(index) = scope.bindings.get(&id.as_str()) {
if let Some(index) = scope.get(id.as_str()) {
// Mark the binding as used.
let context = self.ctx.execution_context();
self.ctx.bindings[*index].mark_used(scope_id, Range::from(expr), context);
@ -4131,7 +4131,7 @@ impl<'a> Checker<'a> {
.unwrap_or_default();
if has_alias {
// Mark the sub-importation as used.
if let Some(index) = scope.bindings.get(full_name) {
if let Some(index) = scope.get(full_name) {
self.ctx.bindings[*index].mark_used(
scope_id,
Range::from(expr),
@ -4148,7 +4148,7 @@ impl<'a> Checker<'a> {
.unwrap_or_default();
if has_alias {
// Mark the sub-importation as used.
if let Some(index) = scope.bindings.get(full_name.as_str()) {
if let Some(index) = scope.get(full_name.as_str()) {
self.ctx.bindings[*index].mark_used(
scope_id,
Range::from(expr),
@ -4173,11 +4173,7 @@ impl<'a> Checker<'a> {
let mut from_list = vec![];
for scope_index in self.ctx.scope_stack.iter() {
let scope = &self.ctx.scopes[*scope_index];
for binding in scope
.bindings
.values()
.map(|index| &self.ctx.bindings[*index])
{
for binding in scope.binding_ids().map(|index| &self.ctx.bindings[*index]) {
if let BindingKind::StarImportation(level, module) = &binding.kind {
from_list.push(helpers::format_import_from(
level.as_ref(),
@ -4260,7 +4256,6 @@ impl<'a> Checker<'a> {
if !self
.ctx
.scope()
.bindings
.get(id)
.map_or(false, |index| self.ctx.bindings[*index].kind.is_global())
{
@ -4433,7 +4428,7 @@ impl<'a> Checker<'a> {
}
let scope = self.ctx.scope_mut();
if scope.bindings.remove(&id.as_str()).is_some() {
if scope.remove(id.as_str()).is_some() {
return;
}
if !self.settings.rules.enabled(Rule::UndefinedName) {
@ -4663,7 +4658,6 @@ impl<'a> Checker<'a> {
let all_bindings: Option<(Vec<BindingId>, Range)> = {
let global_scope = self.ctx.global_scope();
let all_names: Option<(&Vec<String>, Range)> = global_scope
.bindings
.get("__all__")
.map(|index| &self.ctx.bindings[*index])
.and_then(|binding| match &binding.kind {
@ -4675,7 +4669,7 @@ impl<'a> Checker<'a> {
(
names
.iter()
.filter_map(|name| global_scope.bindings.get(name.as_str()).copied())
.filter_map(|name| global_scope.get(name.as_str()).copied())
.collect(),
range,
)
@ -4696,7 +4690,6 @@ impl<'a> Checker<'a> {
let all_names: Option<(Vec<&str>, Range)> = self
.ctx
.global_scope()
.bindings
.get("__all__")
.map(|index| &self.ctx.bindings[*index])
.and_then(|binding| match &binding.kind {
@ -4718,8 +4711,7 @@ impl<'a> Checker<'a> {
.iter()
.map(|scope| {
scope
.bindings
.values()
.binding_ids()
.map(|index| &self.ctx.bindings[*index])
.filter(|binding| {
flake8_type_checking::helpers::is_valid_runtime_import(binding)
@ -4749,7 +4741,7 @@ impl<'a> Checker<'a> {
// PLW0602
if self.settings.rules.enabled(Rule::GlobalVariableNotAssigned) {
for (name, index) in &scope.bindings {
for (name, index) in scope.bindings() {
let binding = &self.ctx.bindings[*index];
if binding.kind.is_global() {
if let Some(stmt) = &binding.source {
@ -4775,7 +4767,7 @@ impl<'a> Checker<'a> {
// unused. Note that we only store references in `redefinitions` if
// the bindings are in different scopes.
if self.settings.rules.enabled(Rule::RedefinedWhileUnused) {
for (name, index) in &scope.bindings {
for (name, index) in scope.bindings() {
let binding = &self.ctx.bindings[*index];
if matches!(
@ -4818,11 +4810,7 @@ impl<'a> Checker<'a> {
if scope.import_starred {
if let Some((names, range)) = &all_names {
let mut from_list = vec![];
for binding in scope
.bindings
.values()
.map(|index| &self.ctx.bindings[*index])
{
for binding in scope.binding_ids().map(|index| &self.ctx.bindings[*index]) {
if let BindingKind::StarImportation(level, module) = &binding.kind {
from_list.push(helpers::format_import_from(
level.as_ref(),
@ -4833,7 +4821,7 @@ impl<'a> Checker<'a> {
from_list.sort();
for &name in names {
if !scope.bindings.contains_key(name) {
if !scope.defines(name) {
diagnostics.push(Diagnostic::new(
pyflakes::rules::ImportStarUsage {
name: name.to_string(),
@ -4859,7 +4847,7 @@ impl<'a> Checker<'a> {
.copied()
.collect()
};
for (.., index) in &scope.bindings {
for index in scope.binding_ids() {
let binding = &self.ctx.bindings[*index];
if let Some(diagnostic) =
@ -4895,7 +4883,7 @@ impl<'a> Checker<'a> {
let mut ignored: FxHashMap<BindingContext, Vec<UnusedImport>> =
FxHashMap::default();
for index in scope.bindings.values() {
for index in scope.binding_ids() {
let binding = &self.ctx.bindings[*index];
let full_name = match &binding.kind {

View file

@ -2,7 +2,8 @@ use rustpython_parser::ast::{Expr, ExprKind};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{Range, ScopeKind};
use ruff_python_ast::scope::ScopeKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;

View file

@ -165,7 +165,6 @@ pub fn unused_loop_control_variable(
// Find the `BindingKind::LoopVar` corresponding to the name.
let scope = checker.ctx.scope();
let binding = scope
.bindings
.get(name)
.into_iter()
.chain(scope.rebounds.get(name).into_iter().flatten())

View file

@ -3,7 +3,8 @@ use rustpython_parser::ast::{Expr, ExprKind};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::collect_call_path;
use ruff_python_ast::types::{Range, ScopeKind};
use ruff_python_ast::scope::ScopeKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;

View file

@ -3,7 +3,8 @@ use rustpython_parser::ast::{Cmpop, Expr, ExprKind, Stmt, StmtKind, Unaryop};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::{create_expr, unparse_expr};
use ruff_python_ast::types::{Range, ScopeKind};
use ruff_python_ast::scope::ScopeKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;

View file

@ -3,7 +3,7 @@ use rustpython_parser::ast::{Constant, Expr, ExprKind};
use ruff_python_ast::context::Context;
use ruff_python_ast::helpers::{map_callable, to_call_path};
use ruff_python_ast::types::{Binding, BindingKind, ExecutionContext, ScopeKind};
use ruff_python_ast::scope::{Binding, BindingKind, ExecutionContext, ScopeKind};
/// Return `true` if [`Expr`] is a guard for a type-checking block.
pub fn is_type_checking_block(context: &Context, test: &Expr) -> bool {

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{Binding, BindingKind, ExecutionContext};
use ruff_python_ast::scope::{Binding, BindingKind, ExecutionContext};
#[violation]
pub struct RuntimeImportInTypeCheckingBlock {

View file

@ -2,7 +2,7 @@ use std::path::Path;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{Binding, BindingKind, ExecutionContext};
use ruff_python_ast::scope::{Binding, BindingKind, ExecutionContext};
use crate::rules::isort::{categorize, ImportType};
use crate::settings::Settings;

View file

@ -1,15 +1,13 @@
use std::iter;
use regex::Regex;
use rustc_hash::FxHashMap;
use rustpython_parser::ast::{Arg, Arguments};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::context::Bindings;
use ruff_python_ast::function_type;
use ruff_python_ast::function_type::FunctionType;
use ruff_python_ast::types::{BindingId, FunctionDef, Lambda, Scope, ScopeKind};
use ruff_python_ast::scope::{Bindings, FunctionDef, Lambda, Scope, ScopeKind};
use ruff_python_ast::visibility;
use crate::checkers::ast::Checker;
@ -86,7 +84,7 @@ impl Violation for UnusedLambdaArgument {
fn function(
argumentable: &Argumentable,
args: &Arguments,
values: &FxHashMap<&str, BindingId>,
values: &Scope,
bindings: &Bindings,
dummy_variable_rgx: &Regex,
ignore_variadic_names: bool,
@ -113,7 +111,7 @@ fn function(
fn method(
argumentable: &Argumentable,
args: &Arguments,
values: &FxHashMap<&str, BindingId>,
values: &Scope,
bindings: &Bindings,
dummy_variable_rgx: &Regex,
ignore_variadic_names: bool,
@ -140,14 +138,14 @@ fn method(
fn call<'a>(
argumentable: &Argumentable,
args: impl Iterator<Item = &'a Arg>,
values: &FxHashMap<&str, BindingId>,
values: &Scope,
bindings: &Bindings,
dummy_variable_rgx: &Regex,
) -> Vec<Diagnostic> {
let mut diagnostics: Vec<Diagnostic> = vec![];
for arg in args {
if let Some(binding) = values
.get(&arg.node.arg.as_str())
.get(arg.node.arg.as_str())
.map(|index| &bindings[*index])
{
if !binding.used()
@ -197,7 +195,7 @@ pub fn unused_arguments(
function(
&Argumentable::Function,
args,
&scope.bindings,
scope,
bindings,
&checker.settings.dummy_variable_rgx,
checker
@ -226,7 +224,7 @@ pub fn unused_arguments(
method(
&Argumentable::Method,
args,
&scope.bindings,
scope,
bindings,
&checker.settings.dummy_variable_rgx,
checker
@ -255,7 +253,7 @@ pub fn unused_arguments(
method(
&Argumentable::ClassMethod,
args,
&scope.bindings,
scope,
bindings,
&checker.settings.dummy_variable_rgx,
checker
@ -284,7 +282,7 @@ pub fn unused_arguments(
function(
&Argumentable::StaticMethod,
args,
&scope.bindings,
scope,
bindings,
&checker.settings.dummy_variable_rgx,
checker
@ -307,7 +305,7 @@ pub fn unused_arguments(
function(
&Argumentable::Lambda,
args,
&scope.bindings,
scope,
bindings,
&checker.settings.dummy_variable_rgx,
checker

View file

@ -3,7 +3,8 @@ use rustpython_parser::ast::{Expr, ExprKind};
use ruff_diagnostics::Violation;
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{BindingKind, Range};
use ruff_python_ast::scope::BindingKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Rule;

View file

@ -3,7 +3,8 @@ use rustpython_parser::ast::{Expr, ExprKind};
use ruff_diagnostics::Violation;
use ruff_diagnostics::{Diagnostic, DiagnosticKind};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{BindingKind, Range};
use ruff_python_ast::scope::BindingKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Rule;

View file

@ -3,8 +3,8 @@ use rustpython_parser::ast::Stmt;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::identifier_range;
use ruff_python_ast::scope::{Scope, ScopeKind};
use ruff_python_ast::source_code::Locator;
use ruff_python_ast::types::{Scope, ScopeKind};
/// ## What it does
/// Checks for functions with "dunder" names (that is, names with two

View file

@ -3,7 +3,8 @@ use rustpython_parser::ast::{Arguments, Expr};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::function_type;
use ruff_python_ast::types::{Range, Scope};
use ruff_python_ast::scope::Scope;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;

View file

@ -3,7 +3,8 @@ use rustpython_parser::ast::{Arguments, Expr};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::function_type;
use ruff_python_ast::types::{Range, Scope};
use ruff_python_ast::scope::Scope;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;

View file

@ -4,8 +4,9 @@ use ruff_diagnostics::{AutofixKind, Availability, Diagnostic, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::{match_leading_content, match_trailing_content, unparse_stmt};
use ruff_python_ast::newlines::StrExt;
use ruff_python_ast::scope::ScopeKind;
use ruff_python_ast::source_code::Stylist;
use ruff_python_ast::types::{Range, ScopeKind};
use ruff_python_ast::types::Range;
use ruff_python_ast::whitespace::leading_space;
use crate::checkers::ast::Checker;

View file

@ -2,7 +2,8 @@ use rustpython_parser::ast::Stmt;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{Range, ScopeKind};
use ruff_python_ast::scope::ScopeKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;

View file

@ -2,7 +2,8 @@ use std::path::Path;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{Range, Scope};
use ruff_python_ast::scope::Scope;
use ruff_python_ast::types::Range;
#[violation]
pub struct UndefinedExport {
@ -27,7 +28,7 @@ pub fn undefined_export(
let mut diagnostics = Vec::new();
if !scope.import_starred && !path.ends_with("__init__.py") {
for name in names {
if !scope.bindings.contains_key(name) {
if !scope.defines(name) {
diagnostics.push(Diagnostic::new(
UndefinedExport {
name: (*name).to_string(),

View file

@ -2,8 +2,7 @@ use std::string::ToString;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::context::Bindings;
use ruff_python_ast::types::{Scope, ScopeKind};
use ruff_python_ast::scope::{Bindings, Scope, ScopeKind};
#[violation]
pub struct UndefinedLocal {
@ -21,10 +20,10 @@ impl Violation for UndefinedLocal {
/// F821
pub fn undefined_local(name: &str, scopes: &[&Scope], bindings: &Bindings) -> Option<Diagnostic> {
let current = &scopes.last().expect("No current scope found");
if matches!(current.kind, ScopeKind::Function(_)) && !current.bindings.contains_key(name) {
if matches!(current.kind, ScopeKind::Function(_)) && !current.defines(name) {
for scope in scopes.iter().rev().skip(1) {
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
if let Some(binding) = scope.bindings.get(name).map(|index| &bindings[*index]) {
if let Some(binding) = scope.get(name).map(|index| &bindings[*index]) {
if let Some((scope_id, location)) = binding.runtime_usage {
if scope_id == current.id {
return Some(Diagnostic::new(

View file

@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::ScopeId;
use ruff_python_ast::scope::ScopeId;
use crate::checkers::ast::Checker;
@ -21,8 +21,7 @@ impl Violation for UnusedAnnotation {
pub fn unused_annotation(checker: &mut Checker, scope: ScopeId) {
let scope = &checker.ctx.scopes[scope];
for (name, binding) in scope
.bindings
.iter()
.bindings()
.map(|(name, index)| (name, &checker.ctx.bindings[*index]))
{
if !binding.used()

View file

@ -6,8 +6,9 @@ use rustpython_parser::{lexer, Mode, Tok};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::contains_effect;
use ruff_python_ast::scope::{ScopeId, ScopeKind};
use ruff_python_ast::source_code::Locator;
use ruff_python_ast::types::{Range, RefEquality, ScopeId, ScopeKind};
use ruff_python_ast::types::{Range, RefEquality};
use crate::autofix::helpers::delete_stmt;
use crate::checkers::ast::Checker;
@ -319,8 +320,7 @@ pub fn unused_variable(checker: &mut Checker, scope: ScopeId) {
}
for (name, binding) in scope
.bindings
.iter()
.bindings()
.map(|(name, index)| (name, &checker.ctx.bindings[*index]))
{
if !binding.used()

View file

@ -4,7 +4,8 @@ use rustpython_parser::ast::{Expr, ExprKind};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{Range, ScopeKind};
use ruff_python_ast::scope::ScopeKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;

View file

@ -1,6 +1,6 @@
use ruff_python_ast::function_type;
use ruff_python_ast::function_type::FunctionType;
use ruff_python_ast::types::{FunctionDef, ScopeKind};
use ruff_python_ast::scope::{FunctionDef, ScopeKind};
use crate::checkers::ast::Checker;

View file

@ -2,7 +2,8 @@ use rustpython_parser::ast::Expr;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{FunctionDef, Range, ScopeKind};
use ruff_python_ast::scope::{FunctionDef, ScopeKind};
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;

View file

@ -2,7 +2,8 @@ use rustpython_parser::ast::{Expr, ExprKind};
use ruff_diagnostics::{AutofixKind, Availability, Diagnostic, Fix, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{BindingKind, Range};
use ruff_python_ast::scope::BindingKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
@ -29,7 +30,7 @@ impl Violation for ConsiderUsingSysExit {
/// sys import *`).
fn is_module_star_imported(checker: &Checker, module: &str) -> bool {
checker.ctx.scopes().any(|scope| {
scope.bindings.values().any(|index| {
scope.binding_ids().any(|index| {
if let BindingKind::StarImportation(_, name) = &checker.ctx.bindings[*index].kind {
name.as_ref().map(|name| name == module).unwrap_or_default()
} else {
@ -44,8 +45,7 @@ fn is_module_star_imported(checker: &Checker, module: &str) -> bool {
fn get_member_import_name_alias(checker: &Checker, module: &str, member: &str) -> Option<String> {
checker.ctx.scopes().find_map(|scope| {
scope
.bindings
.values()
.binding_ids()
.find_map(|index| match &checker.ctx.bindings[*index].kind {
// e.g. module=sys object=exit
// `import sys` -> `sys.exit`

View file

@ -53,7 +53,7 @@ impl Violation for GlobalStatement {
/// PLW0603
pub fn global_statement(checker: &mut Checker, name: &str) {
let scope = checker.ctx.scope();
if let Some(index) = scope.bindings.get(name) {
if let Some(index) = scope.get(name) {
let binding = &checker.ctx.bindings[*index];
if binding.kind.is_global() {
let source: &Stmt = binding

View file

@ -2,7 +2,8 @@ use rustpython_parser::ast::Expr;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{Range, ScopeKind};
use ruff_python_ast::scope::ScopeKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;

View file

@ -2,7 +2,8 @@ use rustpython_parser::ast::{ArgData, Expr, ExprKind, Stmt, StmtKind};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::types::{Range, ScopeKind};
use ruff_python_ast::scope::ScopeKind;
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;

View file

@ -2,8 +2,8 @@ use rustpython_parser::ast::{Expr, ExprKind, Keyword, Stmt};
use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::context::Bindings;
use ruff_python_ast::types::{Binding, BindingKind, Range, Scope};
use ruff_python_ast::scope::{Binding, BindingKind, Bindings, Scope};
use ruff_python_ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::AsRule;
@ -36,10 +36,7 @@ fn rule(name: &str, bases: &[Expr], scope: &Scope, bindings: &Bindings) -> Optio
continue;
}
if !matches!(
scope
.bindings
.get(&id.as_str())
.map(|index| &bindings[*index]),
scope.get(id.as_str()).map(|index| &bindings[*index]),
None | Some(Binding {
kind: BindingKind::Builtin,
..

View file

@ -1,4 +1,3 @@
use std::ops::{Deref, Index, IndexMut};
use std::path::Path;
use nohash_hasher::{BuildNoHashHasher, IntMap};
@ -10,10 +9,11 @@ use ruff_python_stdlib::path::is_python_stub_file;
use ruff_python_stdlib::typing::TYPING_EXTENSIONS;
use crate::helpers::{collect_call_path, from_relative_import, Exceptions};
use crate::types::{
Binding, BindingId, BindingKind, CallPath, ExecutionContext, RefEquality, Scope, ScopeId,
ScopeKind,
use crate::scope::{
Binding, BindingId, BindingKind, Bindings, ExecutionContext, Scope, ScopeId, ScopeKind,
ScopeStack, Scopes,
};
use crate::types::{CallPath, RefEquality};
use crate::visibility::{module_visibility, Modifier, VisibleScope};
#[allow(clippy::struct_excessive_bools)]
@ -125,7 +125,7 @@ impl<'a> Context<'a> {
/// Return the current `Binding` for a given `name`.
pub fn find_binding(&self, member: &str) -> Option<&Binding> {
self.scopes()
.find_map(|scope| scope.bindings.get(member))
.find_map(|scope| scope.get(member))
.map(|index| &self.bindings[*index])
}
@ -319,132 +319,3 @@ impl<'a> Context<'a> {
}
}
}
/// The scopes of a program indexed by [`ScopeId`]
#[derive(Debug)]
pub struct Scopes<'a>(Vec<Scope<'a>>);
impl<'a> Scopes<'a> {
/// Returns a reference to the global scope
pub fn global(&self) -> &Scope<'a> {
&self[ScopeId::global()]
}
/// Returns a mutable reference to the global scope
pub fn global_mut(&mut self) -> &mut Scope<'a> {
&mut self[ScopeId::global()]
}
/// Pushes a new scope and returns its unique id
fn push_scope(&mut self, kind: ScopeKind<'a>) -> ScopeId {
let next_id = ScopeId::try_from(self.0.len()).unwrap();
self.0.push(Scope::local(next_id, kind));
next_id
}
}
impl Default for Scopes<'_> {
fn default() -> Self {
Self(vec![Scope::global(ScopeKind::Module)])
}
}
impl<'a> Index<ScopeId> for Scopes<'a> {
type Output = Scope<'a>;
fn index(&self, index: ScopeId) -> &Self::Output {
&self.0[usize::from(index)]
}
}
impl<'a> IndexMut<ScopeId> for Scopes<'a> {
fn index_mut(&mut self, index: ScopeId) -> &mut Self::Output {
&mut self.0[usize::from(index)]
}
}
impl<'a> Deref for Scopes<'a> {
type Target = [Scope<'a>];
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone)]
pub struct ScopeStack(Vec<ScopeId>);
impl ScopeStack {
/// Pushes a new scope on the stack
pub fn push(&mut self, id: ScopeId) {
self.0.push(id);
}
/// Pops the top most scope
pub fn pop(&mut self) -> Option<ScopeId> {
self.0.pop()
}
/// Returns the id of the top-most
pub fn top(&self) -> Option<ScopeId> {
self.0.last().copied()
}
/// Returns an iterator from the current scope to the top scope (reverse iterator)
pub fn iter(&self) -> std::iter::Rev<std::slice::Iter<ScopeId>> {
self.0.iter().rev()
}
}
impl Default for ScopeStack {
fn default() -> Self {
Self(vec![ScopeId::global()])
}
}
/// The bindings in a program.
///
/// Bindings are indexed by [`BindingId`]
#[derive(Debug, Clone, Default)]
pub struct Bindings<'a>(Vec<Binding<'a>>);
impl<'a> Bindings<'a> {
/// Pushes a new binding and returns its id
pub fn push(&mut self, binding: Binding<'a>) -> BindingId {
let id = self.next_id();
self.0.push(binding);
id
}
/// Returns the id that will be assigned when pushing the next binding
pub fn next_id(&self) -> BindingId {
BindingId::try_from(self.0.len()).unwrap()
}
}
impl<'a> Index<BindingId> for Bindings<'a> {
type Output = Binding<'a>;
fn index(&self, index: BindingId) -> &Self::Output {
&self.0[usize::from(index)]
}
}
impl<'a> IndexMut<BindingId> for Bindings<'a> {
fn index_mut(&mut self, index: BindingId) -> &mut Self::Output {
&mut self.0[usize::from(index)]
}
}
impl<'a> Deref for Bindings<'a> {
type Target = [Binding<'a>];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> FromIterator<Binding<'a>> for Bindings<'a> {
fn from_iter<T: IntoIterator<Item = Binding<'a>>>(iter: T) -> Self {
Self(Vec::from_iter(iter))
}
}

View file

@ -2,7 +2,7 @@ use rustpython_parser::ast::Expr;
use crate::context::Context;
use crate::helpers::{map_callable, to_call_path};
use crate::types::{Scope, ScopeKind};
use crate::scope::{Scope, ScopeKind};
const CLASS_METHODS: [&str; 3] = ["__new__", "__init_subclass__", "__class_getitem__"];
const METACLASS_BASES: [(&str, &str); 2] = [("", "type"), ("abc", "ABCMeta")];

View file

@ -15,8 +15,9 @@ use smallvec::{smallvec, SmallVec};
use crate::context::Context;
use crate::newlines::StrExt;
use crate::scope::{Binding, BindingKind};
use crate::source_code::{Generator, Indexer, Locator, Stylist};
use crate::types::{Binding, BindingKind, CallPath, Range};
use crate::types::{CallPath, Range};
use crate::visitor;
use crate::visitor::Visitor;

View file

@ -9,6 +9,7 @@ pub mod logging;
pub mod newlines;
pub mod operations;
pub mod relocate;
pub mod scope;
pub mod source_code;
pub mod str;
pub mod types;

View file

@ -5,7 +5,7 @@ use rustpython_parser::{lexer, Mode, Tok};
use crate::context::Context;
use crate::helpers::any_over_expr;
use crate::types::{BindingKind, Scope};
use crate::scope::{BindingKind, Scope};
use crate::visitor;
use crate::visitor::Visitor;
@ -95,7 +95,7 @@ pub fn extract_all_names(
// Grab the existing bound __all__ values.
if let StmtKind::AugAssign { .. } = &stmt.node {
if let Some(index) = scope.bindings.get("__all__") {
if let Some(index) = scope.get("__all__") {
if let BindingKind::Export(existing) = &ctx.bindings[*index].kind {
names.extend_from_slice(existing);
}

View file

@ -0,0 +1,424 @@
use crate::types::{Range, RefEquality};
use rustc_hash::FxHashMap;
use rustpython_parser::ast::{Arguments, Expr, Keyword, Stmt};
use std::num::TryFromIntError;
use std::ops::{Deref, Index, IndexMut};
#[derive(Debug)]
pub struct Scope<'a> {
pub id: ScopeId,
pub kind: ScopeKind<'a>,
pub import_starred: bool,
pub uses_locals: bool,
/// A map from bound name to binding index, for live bindings.
bindings: FxHashMap<&'a str, BindingId>,
/// A map from bound name to binding index, for bindings that were created
/// in the scope but rebound (and thus overridden) later on in the same
/// scope.
pub rebounds: FxHashMap<&'a str, Vec<BindingId>>,
}
impl<'a> Scope<'a> {
pub fn global() -> Self {
Scope::local(ScopeId::global(), ScopeKind::Module)
}
pub fn local(id: ScopeId, kind: ScopeKind<'a>) -> Self {
Scope {
id,
kind,
import_starred: false,
uses_locals: false,
bindings: FxHashMap::default(),
rebounds: FxHashMap::default(),
}
}
/// Returns the [id](BindingId) of the binding with the given name.
pub fn get(&self, name: &str) -> Option<&BindingId> {
self.bindings.get(name)
}
/// Adds a new binding with the given name to this scope.
pub fn add(&mut self, name: &'a str, id: BindingId) -> Option<BindingId> {
self.bindings.insert(name, id)
}
/// Returns `true` if this scope defines a binding with the given name.
pub fn defines(&self, name: &str) -> bool {
self.bindings.contains_key(name)
}
/// Removes the binding with the given name
pub fn remove(&mut self, name: &str) -> Option<BindingId> {
self.bindings.remove(name)
}
/// Returns the ids of all bindings defined in this scope.
pub fn binding_ids(&self) -> std::collections::hash_map::Values<&str, BindingId> {
self.bindings.values()
}
pub fn bindings(&self) -> std::collections::hash_map::Iter<&'a str, BindingId> {
self.bindings.iter()
}
}
/// Id uniquely identifying a scope in a program.
///
/// Using a `u32` is sufficient because Ruff only supports parsing documents with a size of max `u32::max`
/// and it is impossible to have more scopes than characters in the file (because defining a function or class
/// requires more than one character).
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct ScopeId(u32);
impl ScopeId {
/// Returns the ID for the global scope
#[inline]
pub const fn global() -> Self {
ScopeId(0)
}
/// Returns `true` if this is the id of the global scope
pub const fn is_global(&self) -> bool {
self.0 == 0
}
}
impl TryFrom<usize> for ScopeId {
type Error = TryFromIntError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
Ok(Self(u32::try_from(value)?))
}
}
impl From<ScopeId> for usize {
fn from(value: ScopeId) -> Self {
value.0 as usize
}
}
#[derive(Debug)]
pub enum ScopeKind<'a> {
Class(ClassDef<'a>),
Function(FunctionDef<'a>),
Generator,
Module,
Lambda(Lambda<'a>),
}
#[derive(Debug)]
pub struct FunctionDef<'a> {
// Properties derived from StmtKind::FunctionDef.
pub name: &'a str,
pub args: &'a Arguments,
pub body: &'a [Stmt],
pub decorator_list: &'a [Expr],
// pub returns: Option<&'a Expr>,
// pub type_comment: Option<&'a str>,
// Scope-specific properties.
// TODO(charlie): Create AsyncFunctionDef to mirror the AST.
pub async_: bool,
pub globals: FxHashMap<&'a str, &'a Stmt>,
}
#[derive(Debug)]
pub struct ClassDef<'a> {
// Properties derived from StmtKind::ClassDef.
pub name: &'a str,
pub bases: &'a [Expr],
pub keywords: &'a [Keyword],
// pub body: &'a [Stmt],
pub decorator_list: &'a [Expr],
// Scope-specific properties.
pub globals: FxHashMap<&'a str, &'a Stmt>,
}
#[derive(Debug)]
pub struct Lambda<'a> {
pub args: &'a Arguments,
pub body: &'a Expr,
}
/// The scopes of a program indexed by [`ScopeId`]
#[derive(Debug)]
pub struct Scopes<'a>(Vec<Scope<'a>>);
impl<'a> Scopes<'a> {
/// Returns a reference to the global scope
pub fn global(&self) -> &Scope<'a> {
&self[ScopeId::global()]
}
/// Returns a mutable reference to the global scope
pub fn global_mut(&mut self) -> &mut Scope<'a> {
&mut self[ScopeId::global()]
}
/// Pushes a new scope and returns its unique id
pub(crate) fn push_scope(&mut self, kind: ScopeKind<'a>) -> ScopeId {
let next_id = ScopeId::try_from(self.0.len()).unwrap();
self.0.push(Scope::local(next_id, kind));
next_id
}
}
impl Default for Scopes<'_> {
fn default() -> Self {
Self(vec![Scope::global()])
}
}
impl<'a> Index<ScopeId> for Scopes<'a> {
type Output = Scope<'a>;
fn index(&self, index: ScopeId) -> &Self::Output {
&self.0[usize::from(index)]
}
}
impl<'a> IndexMut<ScopeId> for Scopes<'a> {
fn index_mut(&mut self, index: ScopeId) -> &mut Self::Output {
&mut self.0[usize::from(index)]
}
}
impl<'a> Deref for Scopes<'a> {
type Target = [Scope<'a>];
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone)]
pub struct ScopeStack(Vec<ScopeId>);
impl ScopeStack {
/// Pushes a new scope on the stack
pub fn push(&mut self, id: ScopeId) {
self.0.push(id);
}
/// Pops the top most scope
pub fn pop(&mut self) -> Option<ScopeId> {
self.0.pop()
}
/// Returns the id of the top-most
pub fn top(&self) -> Option<ScopeId> {
self.0.last().copied()
}
/// Returns an iterator from the current scope to the top scope (reverse iterator)
pub fn iter(&self) -> std::iter::Rev<std::slice::Iter<ScopeId>> {
self.0.iter().rev()
}
}
impl Default for ScopeStack {
fn default() -> Self {
Self(vec![ScopeId::global()])
}
}
#[derive(Debug, Clone)]
pub struct Binding<'a> {
pub kind: BindingKind<'a>,
pub range: Range,
/// The context in which the binding was created.
pub context: ExecutionContext,
/// The statement in which the [`Binding`] was defined.
pub source: Option<RefEquality<'a, Stmt>>,
/// Tuple of (scope index, range) indicating the scope and range at which
/// the binding was last used in a runtime context.
pub runtime_usage: Option<(ScopeId, Range)>,
/// Tuple of (scope index, range) indicating the scope and range at which
/// the binding was last used in a typing-time context.
pub typing_usage: Option<(ScopeId, Range)>,
/// Tuple of (scope index, range) indicating the scope and range at which
/// the binding was last used in a synthetic context. This is used for
/// (e.g.) `__future__` imports, explicit re-exports, and other bindings
/// that should be considered used even if they're never referenced.
pub synthetic_usage: Option<(ScopeId, Range)>,
}
impl<'a> Binding<'a> {
pub fn mark_used(&mut self, scope: ScopeId, range: Range, context: ExecutionContext) {
match context {
ExecutionContext::Runtime => self.runtime_usage = Some((scope, range)),
ExecutionContext::Typing => self.typing_usage = Some((scope, range)),
}
}
pub const fn used(&self) -> bool {
self.runtime_usage.is_some()
|| self.synthetic_usage.is_some()
|| self.typing_usage.is_some()
}
pub const fn is_definition(&self) -> bool {
matches!(
self.kind,
BindingKind::ClassDefinition
| BindingKind::FunctionDefinition
| BindingKind::Builtin
| BindingKind::FutureImportation
| BindingKind::StarImportation(..)
| BindingKind::Importation(..)
| BindingKind::FromImportation(..)
| BindingKind::SubmoduleImportation(..)
)
}
pub fn redefines(&self, existing: &'a Binding) -> bool {
match &self.kind {
BindingKind::Importation(.., full_name) => {
if let BindingKind::SubmoduleImportation(.., existing) = &existing.kind {
return full_name == existing;
}
}
BindingKind::FromImportation(.., full_name) => {
if let BindingKind::SubmoduleImportation(.., existing) = &existing.kind {
return full_name == existing;
}
}
BindingKind::SubmoduleImportation(.., full_name) => match &existing.kind {
BindingKind::Importation(.., existing)
| BindingKind::SubmoduleImportation(.., existing) => {
return full_name == existing;
}
BindingKind::FromImportation(.., existing) => {
return full_name == existing;
}
_ => {}
},
BindingKind::Annotation => {
return false;
}
BindingKind::FutureImportation => {
return false;
}
BindingKind::StarImportation(..) => {
return false;
}
_ => {}
}
existing.is_definition()
}
}
#[derive(Copy, Debug, Clone)]
pub enum ExecutionContext {
Runtime,
Typing,
}
/// ID uniquely identifying a [Binding] in a program.
///
/// Using a `u32` to identify [Binding]s should is sufficient because Ruff only supports documents with a
/// size smaller than or equal to `u32::max`. A document with the size of `u32::max` must have fewer than `u32::max`
/// bindings because bindings must be separated by whitespace (and have an assignment).
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct BindingId(u32);
impl From<BindingId> for usize {
fn from(value: BindingId) -> Self {
value.0 as usize
}
}
impl TryFrom<usize> for BindingId {
type Error = TryFromIntError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
Ok(Self(u32::try_from(value)?))
}
}
impl nohash_hasher::IsEnabled for BindingId {}
// Pyflakes defines the following binding hierarchy (via inheritance):
// Binding
// ExportBinding
// Annotation
// Argument
// Assignment
// NamedExprAssignment
// Definition
// FunctionDefinition
// ClassDefinition
// Builtin
// Importation
// SubmoduleImportation
// ImportationFrom
// StarImportation
// FutureImportation
#[derive(Clone, Debug, is_macro::Is)]
pub enum BindingKind<'a> {
Annotation,
Argument,
Assignment,
Binding,
LoopVar,
Global,
Nonlocal,
Builtin,
ClassDefinition,
FunctionDefinition,
Export(Vec<String>),
FutureImportation,
StarImportation(Option<usize>, Option<String>),
Importation(&'a str, &'a str),
FromImportation(&'a str, String),
SubmoduleImportation(&'a str, &'a str),
}
/// The bindings in a program.
///
/// Bindings are indexed by [`BindingId`]
#[derive(Debug, Clone, Default)]
pub struct Bindings<'a>(Vec<Binding<'a>>);
impl<'a> Bindings<'a> {
/// Pushes a new binding and returns its id
pub fn push(&mut self, binding: Binding<'a>) -> BindingId {
let id = self.next_id();
self.0.push(binding);
id
}
/// Returns the id that will be assigned when pushing the next binding
pub fn next_id(&self) -> BindingId {
BindingId::try_from(self.0.len()).unwrap()
}
}
impl<'a> Index<BindingId> for Bindings<'a> {
type Output = Binding<'a>;
fn index(&self, index: BindingId) -> &Self::Output {
&self.0[usize::from(index)]
}
}
impl<'a> IndexMut<BindingId> for Bindings<'a> {
fn index_mut(&mut self, index: BindingId) -> &mut Self::Output {
&mut self.0[usize::from(index)]
}
}
impl<'a> Deref for Bindings<'a> {
type Target = [Binding<'a>];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> FromIterator<Binding<'a>> for Bindings<'a> {
fn from_iter<T: IntoIterator<Item = Binding<'a>>>(iter: T) -> Self {
Self(Vec::from_iter(iter))
}
}

View file

@ -1,8 +1,6 @@
use std::num::TryFromIntError;
use std::ops::Deref;
use rustc_hash::FxHashMap;
use rustpython_parser::ast::{Arguments, Expr, Keyword, Located, Location, Stmt};
use rustpython_parser::ast::{Expr, Located, Location, Stmt};
#[derive(Clone)]
pub enum Node<'a> {
@ -37,267 +35,6 @@ impl<T> From<&Box<Located<T>>> for Range {
}
}
#[derive(Debug)]
pub struct FunctionDef<'a> {
// Properties derived from StmtKind::FunctionDef.
pub name: &'a str,
pub args: &'a Arguments,
pub body: &'a [Stmt],
pub decorator_list: &'a [Expr],
// pub returns: Option<&'a Expr>,
// pub type_comment: Option<&'a str>,
// Scope-specific properties.
// TODO(charlie): Create AsyncFunctionDef to mirror the AST.
pub async_: bool,
pub globals: FxHashMap<&'a str, &'a Stmt>,
}
#[derive(Debug)]
pub struct ClassDef<'a> {
// Properties derived from StmtKind::ClassDef.
pub name: &'a str,
pub bases: &'a [Expr],
pub keywords: &'a [Keyword],
// pub body: &'a [Stmt],
pub decorator_list: &'a [Expr],
// Scope-specific properties.
pub globals: FxHashMap<&'a str, &'a Stmt>,
}
#[derive(Debug)]
pub struct Lambda<'a> {
pub args: &'a Arguments,
pub body: &'a Expr,
}
#[derive(Debug)]
pub enum ScopeKind<'a> {
Class(ClassDef<'a>),
Function(FunctionDef<'a>),
Generator,
Module,
Lambda(Lambda<'a>),
}
/// Id uniquely identifying a scope in a program.
///
/// Using a `u32` is sufficient because Ruff only supports parsing documents with a size of max `u32::max`
/// and it is impossible to have more scopes than characters in the file (because defining a function or class
/// requires more than one character).
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct ScopeId(u32);
impl ScopeId {
/// Returns the ID for the global scope
#[inline]
pub const fn global() -> Self {
ScopeId(0)
}
/// Returns `true` if this is the id of the global scope
pub const fn is_global(&self) -> bool {
self.0 == 0
}
}
impl TryFrom<usize> for ScopeId {
type Error = TryFromIntError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
Ok(Self(u32::try_from(value)?))
}
}
impl From<ScopeId> for usize {
fn from(value: ScopeId) -> Self {
value.0 as usize
}
}
#[derive(Debug)]
pub struct Scope<'a> {
pub id: ScopeId,
pub kind: ScopeKind<'a>,
pub import_starred: bool,
pub uses_locals: bool,
/// A map from bound name to binding index, for live bindings.
pub bindings: FxHashMap<&'a str, BindingId>,
/// A map from bound name to binding index, for bindings that were created
/// in the scope but rebound (and thus overridden) later on in the same
/// scope.
pub rebounds: FxHashMap<&'a str, Vec<BindingId>>,
}
impl<'a> Scope<'a> {
pub fn global(kind: ScopeKind<'a>) -> Self {
Self::local(ScopeId::global(), kind)
}
pub fn local(id: ScopeId, kind: ScopeKind<'a>) -> Self {
Scope {
id,
kind,
import_starred: false,
uses_locals: false,
bindings: FxHashMap::default(),
rebounds: FxHashMap::default(),
}
}
}
// Pyflakes defines the following binding hierarchy (via inheritance):
// Binding
// ExportBinding
// Annotation
// Argument
// Assignment
// NamedExprAssignment
// Definition
// FunctionDefinition
// ClassDefinition
// Builtin
// Importation
// SubmoduleImportation
// ImportationFrom
// StarImportation
// FutureImportation
#[derive(Clone, Debug, is_macro::Is)]
pub enum BindingKind<'a> {
Annotation,
Argument,
Assignment,
Binding,
LoopVar,
Global,
Nonlocal,
Builtin,
ClassDefinition,
FunctionDefinition,
Export(Vec<String>),
FutureImportation,
StarImportation(Option<usize>, Option<String>),
Importation(&'a str, &'a str),
FromImportation(&'a str, String),
SubmoduleImportation(&'a str, &'a str),
}
/// ID uniquely identifying a [Binding] in a program.
///
/// Using a `u32` to identify [Binding]s should is sufficient because Ruff only supports documents with a
/// size smaller than or equal to `u32::max`. A document with the size of `u32::max` must have fewer than `u32::max`
/// bindings because bindings must be separated by whitespace (and have an assignment).
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct BindingId(u32);
impl From<BindingId> for usize {
fn from(value: BindingId) -> Self {
value.0 as usize
}
}
impl TryFrom<usize> for BindingId {
type Error = TryFromIntError;
fn try_from(value: usize) -> Result<Self, Self::Error> {
Ok(Self(u32::try_from(value)?))
}
}
impl nohash_hasher::IsEnabled for BindingId {}
#[derive(Debug, Clone)]
pub struct Binding<'a> {
pub kind: BindingKind<'a>,
pub range: Range,
/// The context in which the binding was created.
pub context: ExecutionContext,
/// The statement in which the [`Binding`] was defined.
pub source: Option<RefEquality<'a, Stmt>>,
/// Tuple of (scope index, range) indicating the scope and range at which
/// the binding was last used in a runtime context.
pub runtime_usage: Option<(ScopeId, Range)>,
/// Tuple of (scope index, range) indicating the scope and range at which
/// the binding was last used in a typing-time context.
pub typing_usage: Option<(ScopeId, Range)>,
/// Tuple of (scope index, range) indicating the scope and range at which
/// the binding was last used in a synthetic context. This is used for
/// (e.g.) `__future__` imports, explicit re-exports, and other bindings
/// that should be considered used even if they're never referenced.
pub synthetic_usage: Option<(ScopeId, Range)>,
}
#[derive(Copy, Debug, Clone)]
pub enum ExecutionContext {
Runtime,
Typing,
}
impl<'a> Binding<'a> {
pub fn mark_used(&mut self, scope: ScopeId, range: Range, context: ExecutionContext) {
match context {
ExecutionContext::Runtime => self.runtime_usage = Some((scope, range)),
ExecutionContext::Typing => self.typing_usage = Some((scope, range)),
}
}
pub const fn used(&self) -> bool {
self.runtime_usage.is_some()
|| self.synthetic_usage.is_some()
|| self.typing_usage.is_some()
}
pub const fn is_definition(&self) -> bool {
matches!(
self.kind,
BindingKind::ClassDefinition
| BindingKind::FunctionDefinition
| BindingKind::Builtin
| BindingKind::FutureImportation
| BindingKind::StarImportation(..)
| BindingKind::Importation(..)
| BindingKind::FromImportation(..)
| BindingKind::SubmoduleImportation(..)
)
}
pub fn redefines(&self, existing: &'a Binding) -> bool {
match &self.kind {
BindingKind::Importation(.., full_name) => {
if let BindingKind::SubmoduleImportation(.., existing) = &existing.kind {
return full_name == existing;
}
}
BindingKind::FromImportation(.., full_name) => {
if let BindingKind::SubmoduleImportation(.., existing) = &existing.kind {
return full_name == existing;
}
}
BindingKind::SubmoduleImportation(.., full_name) => match &existing.kind {
BindingKind::Importation(.., existing)
| BindingKind::SubmoduleImportation(.., existing) => {
return full_name == existing;
}
BindingKind::FromImportation(.., existing) => {
return full_name == existing;
}
_ => {}
},
BindingKind::Annotation => {
return false;
}
BindingKind::FutureImportation => {
return false;
}
BindingKind::StarImportation(..) => {
return false;
}
_ => {}
}
existing.is_definition()
}
}
#[derive(Debug, Copy, Clone)]
pub struct RefEquality<'a, T>(pub &'a T);