From dedf4cbdebd5c5622849aab5dea4833c2af198a5 Mon Sep 17 00:00:00 2001 From: Micha Reiser Date: Fri, 17 Mar 2023 17:31:33 +0100 Subject: [PATCH] refactor: Move scope and binding types to `scope.rs` (#3573) --- crates/ruff/src/checkers/ast/deferred.rs | 2 +- crates/ruff/src/checkers/ast/mod.rs | 88 ++-- .../rules/cached_instance_method.rs | 3 +- .../rules/unused_loop_control_variable.rs | 1 - .../rules/private_member_access.rs | 3 +- .../flake8_simplify/rules/ast_unary_op.rs | 3 +- .../src/rules/flake8_type_checking/helpers.rs | 2 +- .../runtime_import_in_type_checking_block.rs | 2 +- .../rules/typing_only_runtime_import.rs | 2 +- .../rules/flake8_unused_arguments/rules.rs | 22 +- .../src/rules/pandas_vet/rules/check_attr.rs | 3 +- .../src/rules/pandas_vet/rules/check_call.rs | 3 +- .../pep8_naming/rules/dunder_function_name.rs | 2 +- ...id_first_argument_name_for_class_method.rs | 3 +- .../invalid_first_argument_name_for_method.rs | 3 +- .../pycodestyle/rules/lambda_assignment.rs | 3 +- .../pyflakes/rules/return_outside_function.rs | 3 +- .../rules/pyflakes/rules/undefined_export.rs | 5 +- .../rules/pyflakes/rules/undefined_local.rs | 7 +- .../rules/pyflakes/rules/unused_annotation.rs | 5 +- .../rules/pyflakes/rules/unused_variable.rs | 6 +- .../pyflakes/rules/yield_outside_function.rs | 3 +- crates/ruff/src/rules/pylint/helpers.rs | 2 +- .../rules/pylint/rules/await_outside_async.rs | 3 +- .../pylint/rules/consider_using_sys_exit.rs | 8 +- .../rules/pylint/rules/global_statement.rs | 2 +- .../rules/used_prior_global_declaration.rs | 3 +- .../rules/super_call_with_parameters.rs | 3 +- .../rules/useless_object_inheritance.rs | 9 +- crates/ruff_python_ast/src/context.rs | 139 +----- crates/ruff_python_ast/src/function_type.rs | 2 +- crates/ruff_python_ast/src/helpers.rs | 3 +- crates/ruff_python_ast/src/lib.rs | 1 + crates/ruff_python_ast/src/operations.rs | 4 +- crates/ruff_python_ast/src/scope.rs | 424 ++++++++++++++++++ crates/ruff_python_ast/src/types.rs | 265 +---------- 36 files changed, 535 insertions(+), 507 deletions(-) create mode 100644 crates/ruff_python_ast/src/scope.rs diff --git a/crates/ruff/src/checkers/ast/deferred.rs b/crates/ruff/src/checkers/ast/deferred.rs index ccef124bd3282..169d9b347b395 100644 --- a/crates/ruff/src/checkers/ast/deferred.rs +++ b/crates/ruff/src/checkers/ast/deferred.rs @@ -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; diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 80830531c9e1d..72b5d101bcbf8 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -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, Range)> = { let global_scope = self.ctx.global_scope(); let all_names: Option<(&Vec, 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> = 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 { diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/cached_instance_method.rs b/crates/ruff/src/rules/flake8_bugbear/rules/cached_instance_method.rs index 6aea88d051ebc..e849ef94ce8c6 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/cached_instance_method.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/cached_instance_method.rs @@ -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; diff --git a/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs b/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs index 2f83bbc2fb12a..9dc1bf6da5744 100644 --- a/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs +++ b/crates/ruff/src/rules/flake8_bugbear/rules/unused_loop_control_variable.rs @@ -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()) diff --git a/crates/ruff/src/rules/flake8_self/rules/private_member_access.rs b/crates/ruff/src/rules/flake8_self/rules/private_member_access.rs index 3154fcdefc0fb..9d1300f75ee24 100644 --- a/crates/ruff/src/rules/flake8_self/rules/private_member_access.rs +++ b/crates/ruff/src/rules/flake8_self/rules/private_member_access.rs @@ -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; diff --git a/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs b/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs index b6c9989ed932a..5913d9bf068b8 100644 --- a/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs +++ b/crates/ruff/src/rules/flake8_simplify/rules/ast_unary_op.rs @@ -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; diff --git a/crates/ruff/src/rules/flake8_type_checking/helpers.rs b/crates/ruff/src/rules/flake8_type_checking/helpers.rs index 6b520fee7c769..a03950d3d0b24 100644 --- a/crates/ruff/src/rules/flake8_type_checking/helpers.rs +++ b/crates/ruff/src/rules/flake8_type_checking/helpers.rs @@ -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 { diff --git a/crates/ruff/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs b/crates/ruff/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs index 12ee0a2560193..27422dc70bcbf 100644 --- a/crates/ruff/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs +++ b/crates/ruff/src/rules/flake8_type_checking/rules/runtime_import_in_type_checking_block.rs @@ -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 { diff --git a/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs b/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs index c376b4c2e3e88..b15642a275a9f 100644 --- a/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs +++ b/crates/ruff/src/rules/flake8_type_checking/rules/typing_only_runtime_import.rs @@ -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; diff --git a/crates/ruff/src/rules/flake8_unused_arguments/rules.rs b/crates/ruff/src/rules/flake8_unused_arguments/rules.rs index c4f94eacfd045..01a4aaf7cf8eb 100644 --- a/crates/ruff/src/rules/flake8_unused_arguments/rules.rs +++ b/crates/ruff/src/rules/flake8_unused_arguments/rules.rs @@ -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, - values: &FxHashMap<&str, BindingId>, + values: &Scope, bindings: &Bindings, dummy_variable_rgx: &Regex, ) -> Vec { let mut diagnostics: Vec = 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 diff --git a/crates/ruff/src/rules/pandas_vet/rules/check_attr.rs b/crates/ruff/src/rules/pandas_vet/rules/check_attr.rs index edfff77903a80..fed2f5638da27 100644 --- a/crates/ruff/src/rules/pandas_vet/rules/check_attr.rs +++ b/crates/ruff/src/rules/pandas_vet/rules/check_attr.rs @@ -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; diff --git a/crates/ruff/src/rules/pandas_vet/rules/check_call.rs b/crates/ruff/src/rules/pandas_vet/rules/check_call.rs index d74aaba1574d0..0e894a4d948d6 100644 --- a/crates/ruff/src/rules/pandas_vet/rules/check_call.rs +++ b/crates/ruff/src/rules/pandas_vet/rules/check_call.rs @@ -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; diff --git a/crates/ruff/src/rules/pep8_naming/rules/dunder_function_name.rs b/crates/ruff/src/rules/pep8_naming/rules/dunder_function_name.rs index fa9b9d4319dd8..8b94be17453bf 100644 --- a/crates/ruff/src/rules/pep8_naming/rules/dunder_function_name.rs +++ b/crates/ruff/src/rules/pep8_naming/rules/dunder_function_name.rs @@ -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 diff --git a/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_class_method.rs b/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_class_method.rs index 233c22c55dec6..19bce277120c9 100644 --- a/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_class_method.rs +++ b/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_class_method.rs @@ -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; diff --git a/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_method.rs b/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_method.rs index b7658e419c6af..f090d8b1cd0ff 100644 --- a/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_method.rs +++ b/crates/ruff/src/rules/pep8_naming/rules/invalid_first_argument_name_for_method.rs @@ -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; diff --git a/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs b/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs index 10cda5f65c9e7..3343ac2524eb5 100644 --- a/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs +++ b/crates/ruff/src/rules/pycodestyle/rules/lambda_assignment.rs @@ -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; diff --git a/crates/ruff/src/rules/pyflakes/rules/return_outside_function.rs b/crates/ruff/src/rules/pyflakes/rules/return_outside_function.rs index 2ec768426a9f4..d5688ac43c3bb 100644 --- a/crates/ruff/src/rules/pyflakes/rules/return_outside_function.rs +++ b/crates/ruff/src/rules/pyflakes/rules/return_outside_function.rs @@ -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; diff --git a/crates/ruff/src/rules/pyflakes/rules/undefined_export.rs b/crates/ruff/src/rules/pyflakes/rules/undefined_export.rs index 6a22cf24bad75..1cfa2709d9f93 100644 --- a/crates/ruff/src/rules/pyflakes/rules/undefined_export.rs +++ b/crates/ruff/src/rules/pyflakes/rules/undefined_export.rs @@ -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(), diff --git a/crates/ruff/src/rules/pyflakes/rules/undefined_local.rs b/crates/ruff/src/rules/pyflakes/rules/undefined_local.rs index e63af15388228..b0f7d5a410c34 100644 --- a/crates/ruff/src/rules/pyflakes/rules/undefined_local.rs +++ b/crates/ruff/src/rules/pyflakes/rules/undefined_local.rs @@ -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 { 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( diff --git a/crates/ruff/src/rules/pyflakes/rules/unused_annotation.rs b/crates/ruff/src/rules/pyflakes/rules/unused_annotation.rs index f7a994b7e76ed..80f87b988efb4 100644 --- a/crates/ruff/src/rules/pyflakes/rules/unused_annotation.rs +++ b/crates/ruff/src/rules/pyflakes/rules/unused_annotation.rs @@ -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() diff --git a/crates/ruff/src/rules/pyflakes/rules/unused_variable.rs b/crates/ruff/src/rules/pyflakes/rules/unused_variable.rs index 109b875ca83f7..8c8245df04d24 100644 --- a/crates/ruff/src/rules/pyflakes/rules/unused_variable.rs +++ b/crates/ruff/src/rules/pyflakes/rules/unused_variable.rs @@ -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() diff --git a/crates/ruff/src/rules/pyflakes/rules/yield_outside_function.rs b/crates/ruff/src/rules/pyflakes/rules/yield_outside_function.rs index 96133bc936f52..27cc3539eb1f4 100644 --- a/crates/ruff/src/rules/pyflakes/rules/yield_outside_function.rs +++ b/crates/ruff/src/rules/pyflakes/rules/yield_outside_function.rs @@ -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; diff --git a/crates/ruff/src/rules/pylint/helpers.rs b/crates/ruff/src/rules/pylint/helpers.rs index e1fb0b413a6bd..d083a42b04bbe 100644 --- a/crates/ruff/src/rules/pylint/helpers.rs +++ b/crates/ruff/src/rules/pylint/helpers.rs @@ -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; diff --git a/crates/ruff/src/rules/pylint/rules/await_outside_async.rs b/crates/ruff/src/rules/pylint/rules/await_outside_async.rs index 4c1728ad43d87..73e72135d4887 100644 --- a/crates/ruff/src/rules/pylint/rules/await_outside_async.rs +++ b/crates/ruff/src/rules/pylint/rules/await_outside_async.rs @@ -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; diff --git a/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs b/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs index 1822a9dc95ae8..ae3032845026e 100644 --- a/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs +++ b/crates/ruff/src/rules/pylint/rules/consider_using_sys_exit.rs @@ -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 { 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` diff --git a/crates/ruff/src/rules/pylint/rules/global_statement.rs b/crates/ruff/src/rules/pylint/rules/global_statement.rs index 530510feab58d..2483c66317eb3 100644 --- a/crates/ruff/src/rules/pylint/rules/global_statement.rs +++ b/crates/ruff/src/rules/pylint/rules/global_statement.rs @@ -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 diff --git a/crates/ruff/src/rules/pylint/rules/used_prior_global_declaration.rs b/crates/ruff/src/rules/pylint/rules/used_prior_global_declaration.rs index 46beebe2d0211..e3bb759db8af5 100644 --- a/crates/ruff/src/rules/pylint/rules/used_prior_global_declaration.rs +++ b/crates/ruff/src/rules/pylint/rules/used_prior_global_declaration.rs @@ -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; diff --git a/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs b/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs index 4fdab4b178676..29c98812c8bb9 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/super_call_with_parameters.rs @@ -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; diff --git a/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs b/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs index e2bf86f1c45d1..064a1755c0568 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/useless_object_inheritance.rs @@ -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, .. diff --git a/crates/ruff_python_ast/src/context.rs b/crates/ruff_python_ast/src/context.rs index 947d3b08a6b61..daf57e51caabf 100644 --- a/crates/ruff_python_ast/src/context.rs +++ b/crates/ruff_python_ast/src/context.rs @@ -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>); - -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 for Scopes<'a> { - type Output = Scope<'a>; - - fn index(&self, index: ScopeId) -> &Self::Output { - &self.0[usize::from(index)] - } -} - -impl<'a> IndexMut 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); - -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 { - self.0.pop() - } - - /// Returns the id of the top-most - pub fn top(&self) -> Option { - self.0.last().copied() - } - - /// Returns an iterator from the current scope to the top scope (reverse iterator) - pub fn iter(&self) -> std::iter::Rev> { - 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>); - -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 for Bindings<'a> { - type Output = Binding<'a>; - - fn index(&self, index: BindingId) -> &Self::Output { - &self.0[usize::from(index)] - } -} - -impl<'a> IndexMut 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> for Bindings<'a> { - fn from_iter>>(iter: T) -> Self { - Self(Vec::from_iter(iter)) - } -} diff --git a/crates/ruff_python_ast/src/function_type.rs b/crates/ruff_python_ast/src/function_type.rs index d19b0882f3d42..79c80f4c327f8 100644 --- a/crates/ruff_python_ast/src/function_type.rs +++ b/crates/ruff_python_ast/src/function_type.rs @@ -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")]; diff --git a/crates/ruff_python_ast/src/helpers.rs b/crates/ruff_python_ast/src/helpers.rs index 9070609d821aa..0c399fe6a72f0 100644 --- a/crates/ruff_python_ast/src/helpers.rs +++ b/crates/ruff_python_ast/src/helpers.rs @@ -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; diff --git a/crates/ruff_python_ast/src/lib.rs b/crates/ruff_python_ast/src/lib.rs index 2856716fbb361..afb3a925586f4 100644 --- a/crates/ruff_python_ast/src/lib.rs +++ b/crates/ruff_python_ast/src/lib.rs @@ -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; diff --git a/crates/ruff_python_ast/src/operations.rs b/crates/ruff_python_ast/src/operations.rs index beb8437aaefe6..86160b4d5f95e 100644 --- a/crates/ruff_python_ast/src/operations.rs +++ b/crates/ruff_python_ast/src/operations.rs @@ -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); } diff --git a/crates/ruff_python_ast/src/scope.rs b/crates/ruff_python_ast/src/scope.rs new file mode 100644 index 0000000000000..e4f1a991f0121 --- /dev/null +++ b/crates/ruff_python_ast/src/scope.rs @@ -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>, +} + +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 { + 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 { + 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 for ScopeId { + type Error = TryFromIntError; + + fn try_from(value: usize) -> Result { + Ok(Self(u32::try_from(value)?)) + } +} + +impl From 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>); + +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 for Scopes<'a> { + type Output = Scope<'a>; + + fn index(&self, index: ScopeId) -> &Self::Output { + &self.0[usize::from(index)] + } +} + +impl<'a> IndexMut 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); + +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 { + self.0.pop() + } + + /// Returns the id of the top-most + pub fn top(&self) -> Option { + self.0.last().copied() + } + + /// Returns an iterator from the current scope to the top scope (reverse iterator) + pub fn iter(&self) -> std::iter::Rev> { + 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>, + /// 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 for usize { + fn from(value: BindingId) -> Self { + value.0 as usize + } +} + +impl TryFrom for BindingId { + type Error = TryFromIntError; + + fn try_from(value: usize) -> Result { + 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), + FutureImportation, + StarImportation(Option, Option), + 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>); + +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 for Bindings<'a> { + type Output = Binding<'a>; + + fn index(&self, index: BindingId) -> &Self::Output { + &self.0[usize::from(index)] + } +} + +impl<'a> IndexMut 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> for Bindings<'a> { + fn from_iter>>(iter: T) -> Self { + Self(Vec::from_iter(iter)) + } +} diff --git a/crates/ruff_python_ast/src/types.rs b/crates/ruff_python_ast/src/types.rs index 1c27a7bf581d3..19c020d7d7c7b 100644 --- a/crates/ruff_python_ast/src/types.rs +++ b/crates/ruff_python_ast/src/types.rs @@ -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 From<&Box>> 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 for ScopeId { - type Error = TryFromIntError; - - fn try_from(value: usize) -> Result { - Ok(Self(u32::try_from(value)?)) - } -} - -impl From 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>, -} - -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), - FutureImportation, - StarImportation(Option, Option), - 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 for usize { - fn from(value: BindingId) -> Self { - value.0 as usize - } -} - -impl TryFrom for BindingId { - type Error = TryFromIntError; - - fn try_from(value: usize) -> Result { - 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>, - /// 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);