Remove some allocations in argument detection (#5481)

## Summary

Drive-by PR to remove some allocations around argument name matching.
This commit is contained in:
Charlie Marsh 2023-07-03 12:21:26 -04:00 committed by GitHub
parent d2450c25ab
commit dadad0e9ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 97 additions and 97 deletions

View file

@ -72,7 +72,7 @@ pub(crate) struct Checker<'a> {
deferred: Deferred<'a>, deferred: Deferred<'a>,
pub(crate) diagnostics: Vec<Diagnostic>, pub(crate) diagnostics: Vec<Diagnostic>,
// Check-specific state. // Check-specific state.
pub(crate) flake8_bugbear_seen: Vec<&'a Expr>, pub(crate) flake8_bugbear_seen: Vec<&'a ast::ExprName>,
} }
impl<'a> Checker<'a> { impl<'a> Checker<'a> {

View file

@ -1,31 +1,28 @@
use rustpython_parser::ast::{self, Constant, Expr, Keyword, Ranged}; use rustpython_parser::ast::{Expr, Keyword, Ranged};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::SimpleCallArgs; use ruff_python_ast::helpers::{is_const_none, SimpleCallArgs};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
#[violation] #[violation]
pub struct RequestWithoutTimeout { pub struct RequestWithoutTimeout {
pub timeout: Option<String>, implicit: bool,
} }
impl Violation for RequestWithoutTimeout { impl Violation for RequestWithoutTimeout {
#[derive_message_formats] #[derive_message_formats]
fn message(&self) -> String { fn message(&self) -> String {
let RequestWithoutTimeout { timeout } = self; let RequestWithoutTimeout { implicit } = self;
match timeout { if *implicit {
Some(value) => { format!("Probable use of requests call without timeout")
format!("Probable use of requests call with timeout set to `{value}`") } else {
} format!("Probable use of requests call with timeout set to `None`")
None => format!("Probable use of requests call without timeout"),
} }
} }
} }
const HTTP_VERBS: [&str; 7] = ["get", "options", "head", "post", "put", "patch", "delete"];
/// S113 /// S113
pub(crate) fn request_without_timeout( pub(crate) fn request_without_timeout(
checker: &mut Checker, checker: &mut Checker,
@ -37,30 +34,26 @@ pub(crate) fn request_without_timeout(
.semantic() .semantic()
.resolve_call_path(func) .resolve_call_path(func)
.map_or(false, |call_path| { .map_or(false, |call_path| {
HTTP_VERBS matches!(
.iter() call_path.as_slice(),
.any(|func_name| call_path.as_slice() == ["requests", func_name]) [
"requests",
"get" | "options" | "head" | "post" | "put" | "patch" | "delete"
]
)
}) })
{ {
let call_args = SimpleCallArgs::new(args, keywords); let call_args = SimpleCallArgs::new(args, keywords);
if let Some(timeout_arg) = call_args.keyword_argument("timeout") { if let Some(timeout) = call_args.keyword_argument("timeout") {
if let Some(timeout) = match timeout_arg { if is_const_none(timeout) {
Expr::Constant(ast::ExprConstant {
value: value @ Constant::None,
..
}) => Some(checker.generator().constant(value)),
_ => None,
} {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
RequestWithoutTimeout { RequestWithoutTimeout { implicit: false },
timeout: Some(timeout), timeout.range(),
},
timeout_arg.range(),
)); ));
} }
} else { } else {
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
RequestWithoutTimeout { timeout: None }, RequestWithoutTimeout { implicit: true },
func.range(), func.range(),
)); ));
} }

View file

@ -1,9 +1,8 @@
use rustc_hash::FxHashSet;
use rustpython_parser::ast::{self, Comprehension, Expr, ExprContext, Ranged, Stmt}; use rustpython_parser::ast::{self, Comprehension, Expr, ExprContext, Ranged, Stmt};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::helpers::collect_arg_names; use ruff_python_ast::helpers::includes_arg_name;
use ruff_python_ast::types::Node; use ruff_python_ast::types::Node;
use ruff_python_ast::visitor; use ruff_python_ast::visitor;
use ruff_python_ast::visitor::Visitor; use ruff_python_ast::visitor::Visitor;
@ -58,19 +57,17 @@ impl Violation for FunctionUsesLoopVariable {
#[derive(Default)] #[derive(Default)]
struct LoadedNamesVisitor<'a> { struct LoadedNamesVisitor<'a> {
// Tuple of: name, defining expression, and defining range. loaded: Vec<&'a ast::ExprName>,
loaded: Vec<(&'a str, &'a Expr)>, stored: Vec<&'a ast::ExprName>,
// Tuple of: name, defining expression, and defining range.
stored: Vec<(&'a str, &'a Expr)>,
} }
/// `Visitor` to collect all used identifiers in a statement. /// `Visitor` to collect all used identifiers in a statement.
impl<'a> Visitor<'a> for LoadedNamesVisitor<'a> { impl<'a> Visitor<'a> for LoadedNamesVisitor<'a> {
fn visit_expr(&mut self, expr: &'a Expr) { fn visit_expr(&mut self, expr: &'a Expr) {
match expr { match expr {
Expr::Name(ast::ExprName { id, ctx, range: _ }) => match ctx { Expr::Name(name) => match &name.ctx {
ExprContext::Load => self.loaded.push((id, expr)), ExprContext::Load => self.loaded.push(name),
ExprContext::Store => self.stored.push((id, expr)), ExprContext::Store => self.stored.push(name),
ExprContext::Del => {} ExprContext::Del => {}
}, },
_ => visitor::walk_expr(self, expr), _ => visitor::walk_expr(self, expr),
@ -80,7 +77,7 @@ impl<'a> Visitor<'a> for LoadedNamesVisitor<'a> {
#[derive(Default)] #[derive(Default)]
struct SuspiciousVariablesVisitor<'a> { struct SuspiciousVariablesVisitor<'a> {
names: Vec<(&'a str, &'a Expr)>, names: Vec<&'a ast::ExprName>,
safe_functions: Vec<&'a Expr>, safe_functions: Vec<&'a Expr>,
} }
@ -95,17 +92,20 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
let mut visitor = LoadedNamesVisitor::default(); let mut visitor = LoadedNamesVisitor::default();
visitor.visit_body(body); visitor.visit_body(body);
// Collect all argument names.
let mut arg_names = collect_arg_names(args);
arg_names.extend(visitor.stored.iter().map(|(id, ..)| id));
// Treat any non-arguments as "suspicious". // Treat any non-arguments as "suspicious".
self.names.extend( self.names
visitor .extend(visitor.loaded.into_iter().filter(|loaded| {
.loaded if visitor.stored.iter().any(|stored| stored.id == loaded.id) {
.into_iter() return false;
.filter(|(id, ..)| !arg_names.contains(id)), }
);
if includes_arg_name(&loaded.id, args) {
return false;
}
true
}));
return; return;
} }
Stmt::Return(ast::StmtReturn { Stmt::Return(ast::StmtReturn {
@ -132,10 +132,9 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
}) => { }) => {
match func.as_ref() { match func.as_ref() {
Expr::Name(ast::ExprName { id, .. }) => { Expr::Name(ast::ExprName { id, .. }) => {
let id = id.as_str(); if matches!(id.as_str(), "filter" | "reduce" | "map") {
if id == "filter" || id == "reduce" || id == "map" {
for arg in args { for arg in args {
if matches!(arg, Expr::Lambda(_)) { if arg.is_lambda_expr() {
self.safe_functions.push(arg); self.safe_functions.push(arg);
} }
} }
@ -159,7 +158,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
for keyword in keywords { for keyword in keywords {
if keyword.arg.as_ref().map_or(false, |arg| arg == "key") if keyword.arg.as_ref().map_or(false, |arg| arg == "key")
&& matches!(keyword.value, Expr::Lambda(_)) && keyword.value.is_lambda_expr()
{ {
self.safe_functions.push(&keyword.value); self.safe_functions.push(&keyword.value);
} }
@ -175,17 +174,19 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
let mut visitor = LoadedNamesVisitor::default(); let mut visitor = LoadedNamesVisitor::default();
visitor.visit_expr(body); visitor.visit_expr(body);
// Collect all argument names.
let mut arg_names = collect_arg_names(args);
arg_names.extend(visitor.stored.iter().map(|(id, ..)| id));
// Treat any non-arguments as "suspicious". // Treat any non-arguments as "suspicious".
self.names.extend( self.names
visitor .extend(visitor.loaded.into_iter().filter(|loaded| {
.loaded if visitor.stored.iter().any(|stored| stored.id == loaded.id) {
.iter() return false;
.filter(|(id, ..)| !arg_names.contains(id)), }
);
if includes_arg_name(&loaded.id, args) {
return false;
}
true
}));
return; return;
} }
@ -198,7 +199,7 @@ impl<'a> Visitor<'a> for SuspiciousVariablesVisitor<'a> {
#[derive(Default)] #[derive(Default)]
struct NamesFromAssignmentsVisitor<'a> { struct NamesFromAssignmentsVisitor<'a> {
names: FxHashSet<&'a str>, names: Vec<&'a str>,
} }
/// `Visitor` to collect all names used in an assignment expression. /// `Visitor` to collect all names used in an assignment expression.
@ -206,7 +207,7 @@ impl<'a> Visitor<'a> for NamesFromAssignmentsVisitor<'a> {
fn visit_expr(&mut self, expr: &'a Expr) { fn visit_expr(&mut self, expr: &'a Expr) {
match expr { match expr {
Expr::Name(ast::ExprName { id, .. }) => { Expr::Name(ast::ExprName { id, .. }) => {
self.names.insert(id.as_str()); self.names.push(id.as_str());
} }
Expr::Starred(ast::ExprStarred { value, .. }) => { Expr::Starred(ast::ExprStarred { value, .. }) => {
self.visit_expr(value); self.visit_expr(value);
@ -223,7 +224,7 @@ impl<'a> Visitor<'a> for NamesFromAssignmentsVisitor<'a> {
#[derive(Default)] #[derive(Default)]
struct AssignedNamesVisitor<'a> { struct AssignedNamesVisitor<'a> {
names: FxHashSet<&'a str>, names: Vec<&'a str>,
} }
/// `Visitor` to collect all used identifiers in a statement. /// `Visitor` to collect all used identifiers in a statement.
@ -257,7 +258,7 @@ impl<'a> Visitor<'a> for AssignedNamesVisitor<'a> {
} }
fn visit_expr(&mut self, expr: &'a Expr) { fn visit_expr(&mut self, expr: &'a Expr) {
if matches!(expr, Expr::Lambda(_)) { if expr.is_lambda_expr() {
// Don't recurse. // Don't recurse.
return; return;
} }
@ -300,15 +301,15 @@ pub(crate) fn function_uses_loop_variable<'a>(checker: &mut Checker<'a>, node: &
// If a variable was used in a function or lambda body, and assigned in the // If a variable was used in a function or lambda body, and assigned in the
// loop, flag it. // loop, flag it.
for (name, expr) in suspicious_variables { for name in suspicious_variables {
if reassigned_in_loop.contains(name) { if reassigned_in_loop.contains(&name.id.as_str()) {
if !checker.flake8_bugbear_seen.contains(&expr) { if !checker.flake8_bugbear_seen.contains(&name) {
checker.flake8_bugbear_seen.push(expr); checker.flake8_bugbear_seen.push(name);
checker.diagnostics.push(Diagnostic::new( checker.diagnostics.push(Diagnostic::new(
FunctionUsesLoopVariable { FunctionUsesLoopVariable {
name: name.to_string(), name: name.id.to_string(),
}, },
expr.range(), name.range(),
)); ));
} }
} }

View file

@ -8,7 +8,7 @@ use ruff_diagnostics::{AlwaysAutofixableViolation, Violation};
use ruff_diagnostics::{Diagnostic, Edit, Fix}; use ruff_diagnostics::{Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::collect_call_path; use ruff_python_ast::call_path::collect_call_path;
use ruff_python_ast::helpers::collect_arg_names; use ruff_python_ast::helpers::includes_arg_name;
use ruff_python_ast::identifier::Identifier; use ruff_python_ast::identifier::Identifier;
use ruff_python_ast::visitor; use ruff_python_ast::visitor;
use ruff_python_ast::visitor::Visitor; use ruff_python_ast::visitor::Visitor;
@ -446,7 +446,7 @@ fn check_fixture_decorator_name(checker: &mut Checker, decorator: &Decorator) {
/// PT021 /// PT021
fn check_fixture_addfinalizer(checker: &mut Checker, args: &Arguments, body: &[Stmt]) { fn check_fixture_addfinalizer(checker: &mut Checker, args: &Arguments, body: &[Stmt]) {
if !collect_arg_names(args).contains(&"request") { if !includes_arg_name("request", args) {
return; return;
} }

View file

@ -1,10 +1,9 @@
use rustc_hash::FxHashSet; use rustpython_parser::ast::{self, Arguments, Expr, Keyword, Ranged};
use rustpython_parser::ast::{self, Expr, Keyword, Ranged};
use ruff_diagnostics::{Diagnostic, Violation}; use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation}; use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::call_path::collect_call_path; use ruff_python_ast::call_path::collect_call_path;
use ruff_python_ast::helpers::{collect_arg_names, SimpleCallArgs}; use ruff_python_ast::helpers::{includes_arg_name, SimpleCallArgs};
use ruff_python_ast::visitor; use ruff_python_ast::visitor;
use ruff_python_ast::visitor::Visitor; use ruff_python_ast::visitor::Visitor;
@ -18,10 +17,10 @@ impl Violation for PytestPatchWithLambda {
} }
} }
#[derive(Default)]
/// Visitor that checks references the argument names in the lambda body. /// Visitor that checks references the argument names in the lambda body.
#[derive(Debug)]
struct LambdaBodyVisitor<'a> { struct LambdaBodyVisitor<'a> {
names: FxHashSet<&'a str>, arguments: &'a Arguments,
uses_args: bool, uses_args: bool,
} }
@ -32,11 +31,15 @@ where
fn visit_expr(&mut self, expr: &'b Expr) { fn visit_expr(&mut self, expr: &'b Expr) {
match expr { match expr {
Expr::Name(ast::ExprName { id, .. }) => { Expr::Name(ast::ExprName { id, .. }) => {
if self.names.contains(&id.as_str()) { if includes_arg_name(id, self.arguments) {
self.uses_args = true; self.uses_args = true;
} }
} }
_ => visitor::walk_expr(self, expr), _ => {
if !self.uses_args {
visitor::walk_expr(self, expr);
}
}
} }
} }
} }
@ -60,7 +63,7 @@ fn check_patch_call(
{ {
// Walk the lambda body. // Walk the lambda body.
let mut visitor = LambdaBodyVisitor { let mut visitor = LambdaBodyVisitor {
names: collect_arg_names(args), arguments: args,
uses_args: false, uses_args: false,
}; };
visitor.visit_expr(body); visitor.visit_expr(body);

View file

@ -4,7 +4,7 @@ use std::path::Path;
use num_traits::Zero; use num_traits::Zero;
use ruff_text_size::{TextRange, TextSize}; use ruff_text_size::{TextRange, TextSize};
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::FxHashMap;
use rustpython_ast::CmpOp; use rustpython_ast::CmpOp;
use rustpython_parser::ast::{ use rustpython_parser::ast::{
self, Arguments, Constant, ExceptHandler, Expr, Keyword, MatchCase, Pattern, Ranged, Stmt, self, Arguments, Constant, ExceptHandler, Expr, Keyword, MatchCase, Pattern, Ranged, Stmt,
@ -669,25 +669,28 @@ pub fn extract_handled_exceptions(handlers: &[ExceptHandler]) -> Vec<&Expr> {
handled_exceptions handled_exceptions
} }
/// Return the set of all bound argument names. /// Returns `true` if the given name is included in the given [`Arguments`].
pub fn collect_arg_names<'a>(arguments: &'a Arguments) -> FxHashSet<&'a str> { pub fn includes_arg_name(name: &str, arguments: &Arguments) -> bool {
let mut arg_names: FxHashSet<&'a str> = FxHashSet::default(); if arguments
for arg_with_default in &arguments.posonlyargs { .posonlyargs
arg_names.insert(arg_with_default.def.arg.as_str()); .iter()
} .chain(&arguments.args)
for arg_with_default in &arguments.args { .chain(&arguments.kwonlyargs)
arg_names.insert(arg_with_default.def.arg.as_str()); .any(|arg| arg.def.arg.as_str() == name)
{
return true;
} }
if let Some(arg) = &arguments.vararg { if let Some(arg) = &arguments.vararg {
arg_names.insert(arg.arg.as_str()); if arg.arg.as_str() == name {
} return true;
for arg_with_default in &arguments.kwonlyargs { }
arg_names.insert(arg_with_default.def.arg.as_str());
} }
if let Some(arg) = &arguments.kwarg { if let Some(arg) = &arguments.kwarg {
arg_names.insert(arg.arg.as_str()); if arg.arg.as_str() == name {
return true;
}
} }
arg_names false
} }
/// Given an [`Expr`] that can be callable or not (like a decorator, which could /// Given an [`Expr`] that can be callable or not (like a decorator, which could