From fa28dc5ccff977f37df99ad05c60e3dcb6ce1bf7 Mon Sep 17 00:00:00 2001 From: Junhson Jean-Baptiste Date: Fri, 14 Feb 2025 09:55:07 -0500 Subject: [PATCH] [internal] Move Linter `OperatorPrecedence` into `ruff_python_ast` crate (#16162) ## Summary This change begins to resolve #16071 by moving the `OperatorPrecedence` structs from the `ruff_python_linter` crate into `ruff_python_ast`. This PR also implements `precedence()` methods on the `Expr` and `ExprRef` enums. ## Test Plan Since this change mainly shifts existing logic, I didn't add any additional tests. Existing tests do pass. --- .../pylint/rules/unnecessary_dunder_call.rs | 166 +---------------- .../rules/pyupgrade/rules/native_literals.rs | 3 +- crates/ruff_python_ast/src/lib.rs | 2 + crates/ruff_python_ast/src/nodes.rs | 14 +- .../src/operator_precedence.rs | 176 ++++++++++++++++++ 5 files changed, 193 insertions(+), 168 deletions(-) create mode 100644 crates/ruff_python_ast/src/operator_precedence.rs diff --git a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs index 5f20e9b2d1..b5ac652bed 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/unnecessary_dunder_call.rs @@ -1,6 +1,6 @@ use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; -use ruff_python_ast::{self as ast, BoolOp, Expr, Operator, Stmt, UnaryOp}; +use ruff_python_ast::{self as ast, Expr, OperatorPrecedence, Stmt}; use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; @@ -572,167 +572,3 @@ fn in_dunder_method_definition(semantic: &SemanticModel) -> bool { func_def.name.starts_with("__") && func_def.name.ends_with("__") }) } - -/// Represents the precedence levels for Python expressions. -/// Variants at the top have lower precedence and variants at the bottom have -/// higher precedence. -/// -/// See: -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) enum OperatorPrecedence { - /// The lowest (virtual) precedence level - None, - /// Precedence of `yield` and `yield from` expressions. - Yield, - /// Precedence of assignment expressions (`name := expr`). - Assign, - /// Precedence of starred expressions (`*expr`). - Starred, - /// Precedence of lambda expressions (`lambda args: expr`). - Lambda, - /// Precedence of if/else expressions (`expr if cond else expr`). - IfElse, - /// Precedence of boolean `or` expressions. - Or, - /// Precedence of boolean `and` expressions. - And, - /// Precedence of boolean `not` expressions. - Not, - /// Precedence of comparisons (`<`, `<=`, `>`, `>=`, `!=`, `==`), - /// memberships (`in`, `not in`) and identity tests (`is`, `is not`). - ComparisonsMembershipIdentity, - /// Precedence of bitwise `|` and `^` operators. - BitXorOr, - /// Precedence of bitwise `&` operator. - BitAnd, - /// Precedence of left and right shift expressions (`<<`, `>>`). - LeftRightShift, - /// Precedence of addition and subtraction expressions (`+`, `-`). - AddSub, - /// Precedence of multiplication (`*`), matrix multiplication (`@`), division (`/`), - /// floor division (`//`) and remainder (`%`) expressions. - MulDivRemain, - /// Precedence of unary positive (`+`), negative (`-`), and bitwise NOT (`~`) expressions. - PosNegBitNot, - /// Precedence of exponentiation expressions (`**`). - Exponent, - /// Precedence of `await` expressions. - Await, - /// Precedence of call expressions (`()`), attribute access (`.`), and subscript (`[]`) expressions. - CallAttribute, - /// Precedence of atomic expressions (literals, names, containers). - Atomic, -} - -impl OperatorPrecedence { - fn from_expr(expr: &Expr) -> Self { - match expr { - // Binding or parenthesized expression, list display, dictionary display, set display - Expr::Tuple(_) - | Expr::Dict(_) - | Expr::Set(_) - | Expr::ListComp(_) - | Expr::List(_) - | Expr::SetComp(_) - | Expr::DictComp(_) - | Expr::Generator(_) - | Expr::Name(_) - | Expr::StringLiteral(_) - | Expr::BytesLiteral(_) - | Expr::NumberLiteral(_) - | Expr::BooleanLiteral(_) - | Expr::NoneLiteral(_) - | Expr::EllipsisLiteral(_) - | Expr::FString(_) => Self::Atomic, - // Subscription, slicing, call, attribute reference - Expr::Attribute(_) | Expr::Subscript(_) | Expr::Call(_) | Expr::Slice(_) => { - Self::CallAttribute - } - - // Await expression - Expr::Await(_) => Self::Await, - - // Exponentiation ** - // Handled below along with other binary operators - - // Unary operators: +x, -x, ~x (except boolean not) - Expr::UnaryOp(operator) => match operator.op { - UnaryOp::UAdd | UnaryOp::USub | UnaryOp::Invert => Self::PosNegBitNot, - UnaryOp::Not => Self::Not, - }, - - // Math binary ops - Expr::BinOp(binary_operation) => Self::from(binary_operation.op), - - // Comparisons: <, <=, >, >=, ==, !=, in, not in, is, is not - Expr::Compare(_) => Self::ComparisonsMembershipIdentity, - - // Boolean not - // Handled above in unary operators - - // Boolean operations: and, or - Expr::BoolOp(bool_op) => Self::from(bool_op.op), - - // Conditional expressions: x if y else z - Expr::If(_) => Self::IfElse, - - // Lambda expressions - Expr::Lambda(_) => Self::Lambda, - - // Unpacking also omitted in the docs, but has almost the lowest precedence, - // except for assignment & yield expressions. E.g. `[*(v := [1,2])]` is valid - // but `[*v := [1,2]] would fail on incorrect syntax because * will associate - // `v` before the assignment. - Expr::Starred(_) => Self::Starred, - - // Assignment expressions (aka named) - Expr::Named(_) => Self::Assign, - - // Although omitted in docs, yield expressions may be used inside an expression - // but must be parenthesized. So for our purposes we assume they just have - // the lowest "real" precedence. - Expr::Yield(_) | Expr::YieldFrom(_) => Self::Yield, - - // Not a real python expression, so treat as lowest as well - Expr::IpyEscapeCommand(_) => Self::None, - } - } -} - -impl From<&Expr> for OperatorPrecedence { - fn from(expr: &Expr) -> Self { - Self::from_expr(expr) - } -} - -impl From for OperatorPrecedence { - fn from(operator: Operator) -> Self { - match operator { - // Multiplication, matrix multiplication, division, floor division, remainder: - // *, @, /, //, % - Operator::Mult - | Operator::MatMult - | Operator::Div - | Operator::Mod - | Operator::FloorDiv => Self::MulDivRemain, - // Addition, subtraction - Operator::Add | Operator::Sub => Self::AddSub, - // Bitwise shifts: <<, >> - Operator::LShift | Operator::RShift => Self::LeftRightShift, - // Bitwise operations: &, ^, | - Operator::BitAnd => Self::BitAnd, - Operator::BitXor | Operator::BitOr => Self::BitXorOr, - // Exponentiation ** - Operator::Pow => Self::Exponent, - } - } -} - -impl From for OperatorPrecedence { - fn from(operator: BoolOp) -> Self { - match operator { - BoolOp::And => Self::And, - BoolOp::Or => Self::Or, - } - } -} diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs index 2a488bea47..a1d8bf31de 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/native_literals.rs @@ -3,11 +3,10 @@ use std::str::FromStr; use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, ViolationMetadata}; -use ruff_python_ast::{self as ast, Expr, Int, LiteralExpressionRef, UnaryOp}; +use ruff_python_ast::{self as ast, Expr, Int, LiteralExpressionRef, OperatorPrecedence, UnaryOp}; use ruff_text_size::{Ranged, TextRange}; use crate::checkers::ast::Checker; -use crate::rules::pylint::rules::OperatorPrecedence; #[derive(Debug, PartialEq, Eq, Copy, Clone)] enum LiteralType { diff --git a/crates/ruff_python_ast/src/lib.rs b/crates/ruff_python_ast/src/lib.rs index 4465ea73e5..f76087fca0 100644 --- a/crates/ruff_python_ast/src/lib.rs +++ b/crates/ruff_python_ast/src/lib.rs @@ -5,6 +5,7 @@ pub use expression::*; pub use generated::*; pub use int::*; pub use nodes::*; +pub use operator_precedence::*; pub mod comparable; pub mod docstrings; @@ -16,6 +17,7 @@ mod int; pub mod name; mod node; mod nodes; +pub mod operator_precedence; pub mod parenthesize; pub mod relocate; pub mod script; diff --git a/crates/ruff_python_ast/src/nodes.rs b/crates/ruff_python_ast/src/nodes.rs index e2fd58f032..83bfa7e434 100644 --- a/crates/ruff_python_ast/src/nodes.rs +++ b/crates/ruff_python_ast/src/nodes.rs @@ -18,7 +18,8 @@ use crate::{ name::Name, str::{Quote, TripleQuotes}, str_prefix::{AnyStringPrefix, ByteStringPrefix, FStringPrefix, StringLiteralPrefix}, - ExceptHandler, Expr, FStringElement, LiteralExpressionRef, Pattern, Stmt, TypeParam, + ExceptHandler, Expr, ExprRef, FStringElement, LiteralExpressionRef, OperatorPrecedence, + Pattern, Stmt, TypeParam, }; /// See also [Module](https://docs.python.org/3/library/ast.html#ast.Module) @@ -365,6 +366,17 @@ impl Expr { _ => None, } } + + /// Return the [`OperatorPrecedence`] of this expression + pub fn precedence(&self) -> OperatorPrecedence { + OperatorPrecedence::from(self) + } +} + +impl ExprRef<'_> { + pub fn precedence(&self) -> OperatorPrecedence { + OperatorPrecedence::from(self) + } } /// An AST node used to represent a IPython escape command at the expression level. diff --git a/crates/ruff_python_ast/src/operator_precedence.rs b/crates/ruff_python_ast/src/operator_precedence.rs new file mode 100644 index 0000000000..1f58843f18 --- /dev/null +++ b/crates/ruff_python_ast/src/operator_precedence.rs @@ -0,0 +1,176 @@ +use crate::{BoolOp, Expr, ExprRef, Operator, UnaryOp}; + +/// Represents the precedence levels for Python expressions. +/// Variants at the top have lower precedence and variants at the bottom have +/// higher precedence. +/// +/// See: +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum OperatorPrecedence { + /// The lowest (virtual) precedence level + None, + /// Precedence of `yield` and `yield from` expressions. + Yield, + /// Precedence of assignment expressions (`name := expr`). + Assign, + /// Precedence of starred expressions (`*expr`). + Starred, + /// Precedence of lambda expressions (`lambda args: expr`). + Lambda, + /// Precedence of if/else expressions (`expr if cond else expr`). + IfElse, + /// Precedence of boolean `or` expressions. + Or, + /// Precedence of boolean `and` expressions. + And, + /// Precedence of boolean `not` expressions. + Not, + /// Precedence of comparisons (`<`, `<=`, `>`, `>=`, `!=`, `==`), + /// memberships (`in`, `not in`) and identity tests (`is`, `is not`). + ComparisonsMembershipIdentity, + /// Precedence of bitwise `|` and `^` operators. + BitXorOr, + /// Precedence of bitwise `&` operator. + BitAnd, + /// Precedence of left and right shift expressions (`<<`, `>>`). + LeftRightShift, + /// Precedence of addition and subtraction expressions (`+`, `-`). + AddSub, + /// Precedence of multiplication (`*`), matrix multiplication (`@`), division (`/`), + /// floor division (`//`) and remainder (`%`) expressions. + MulDivRemain, + /// Precedence of unary positive (`+`), negative (`-`), and bitwise NOT (`~`) expressions. + PosNegBitNot, + /// Precedence of exponentiation expressions (`**`). + Exponent, + /// Precedence of `await` expressions. + Await, + /// Precedence of call expressions (`()`), attribute access (`.`), and subscript (`[]`) expressions. + CallAttribute, + /// Precedence of atomic expressions (literals, names, containers). + Atomic, +} + +impl OperatorPrecedence { + pub fn from_expr_ref(expr: &ExprRef) -> Self { + match expr { + // Binding or parenthesized expression, list display, dictionary display, set display + ExprRef::Tuple(_) + | ExprRef::Dict(_) + | ExprRef::Set(_) + | ExprRef::ListComp(_) + | ExprRef::List(_) + | ExprRef::SetComp(_) + | ExprRef::DictComp(_) + | ExprRef::Generator(_) + | ExprRef::Name(_) + | ExprRef::StringLiteral(_) + | ExprRef::BytesLiteral(_) + | ExprRef::NumberLiteral(_) + | ExprRef::BooleanLiteral(_) + | ExprRef::NoneLiteral(_) + | ExprRef::EllipsisLiteral(_) + | ExprRef::FString(_) => Self::Atomic, + // Subscription, slicing, call, attribute reference + ExprRef::Attribute(_) + | ExprRef::Subscript(_) + | ExprRef::Call(_) + | ExprRef::Slice(_) => Self::CallAttribute, + + // Await expression + ExprRef::Await(_) => Self::Await, + + // Exponentiation ** + // Handled below along with other binary operators + + // Unary operators: +x, -x, ~x (except boolean not) + ExprRef::UnaryOp(operator) => match operator.op { + UnaryOp::UAdd | UnaryOp::USub | UnaryOp::Invert => Self::PosNegBitNot, + UnaryOp::Not => Self::Not, + }, + + // Math binary ops + ExprRef::BinOp(binary_operation) => Self::from(binary_operation.op), + + // Comparisons: <, <=, >, >=, ==, !=, in, not in, is, is not + ExprRef::Compare(_) => Self::ComparisonsMembershipIdentity, + + // Boolean not + // Handled above in unary operators + + // Boolean operations: and, or + ExprRef::BoolOp(bool_op) => Self::from(bool_op.op), + + // Conditional expressions: x if y else z + ExprRef::If(_) => Self::IfElse, + + // Lambda expressions + ExprRef::Lambda(_) => Self::Lambda, + + // Unpacking also omitted in the docs, but has almost the lowest precedence, + // except for assignment & yield expressions. E.g. `[*(v := [1,2])]` is valid + // but `[*v := [1,2]] would fail on incorrect syntax because * will associate + // `v` before the assignment. + ExprRef::Starred(_) => Self::Starred, + + // Assignment expressions (aka named) + ExprRef::Named(_) => Self::Assign, + + // Although omitted in docs, yield expressions may be used inside an expression + // but must be parenthesized. So for our purposes we assume they just have + // the lowest "real" precedence. + ExprRef::Yield(_) | ExprRef::YieldFrom(_) => Self::Yield, + + // Not a real python expression, so treat as lowest as well + ExprRef::IpyEscapeCommand(_) => Self::None, + } + } + + pub fn from_expr(expr: &Expr) -> Self { + Self::from(&ExprRef::from(expr)) + } +} + +impl From<&Expr> for OperatorPrecedence { + fn from(expr: &Expr) -> Self { + Self::from_expr(expr) + } +} + +impl<'a> From<&ExprRef<'a>> for OperatorPrecedence { + fn from(expr_ref: &ExprRef<'a>) -> Self { + Self::from_expr_ref(expr_ref) + } +} + +impl From for OperatorPrecedence { + fn from(operator: Operator) -> Self { + match operator { + // Multiplication, matrix multiplication, division, floor division, remainder: + // *, @, /, //, % + Operator::Mult + | Operator::MatMult + | Operator::Div + | Operator::Mod + | Operator::FloorDiv => Self::MulDivRemain, + // Addition, subtraction + Operator::Add | Operator::Sub => Self::AddSub, + // Bitwise shifts: <<, >> + Operator::LShift | Operator::RShift => Self::LeftRightShift, + // Bitwise operations: &, ^, | + Operator::BitAnd => Self::BitAnd, + Operator::BitXor | Operator::BitOr => Self::BitXorOr, + // Exponentiation ** + Operator::Pow => Self::Exponent, + } + } +} + +impl From for OperatorPrecedence { + fn from(operator: BoolOp) -> Self { + match operator { + BoolOp::And => Self::And, + BoolOp::Or => Self::Or, + } + } +}