Add ComparableExpr hierarchy for comparing expressions (#1721)

This commit is contained in:
Charlie Marsh 2023-01-07 17:29:21 -05:00 committed by GitHub
parent 4de6c26ff9
commit a9cc56b2ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 547 additions and 418 deletions

View file

@ -1,394 +0,0 @@
//! Compare two AST nodes for equality, ignoring locations.
use rustpython_ast::{Arg, Arguments, Comprehension, Expr, ExprKind, Keyword};
/// Returns `true` if the two `Expr` are equal, ignoring locations.
pub fn expr(a: &Expr, b: &Expr) -> bool {
match (&a.node, &b.node) {
(
ExprKind::BoolOp {
op: op_a,
values: values_a,
},
ExprKind::BoolOp {
op: op_b,
values: values_b,
},
) => {
op_a == op_b
&& values_a.len() == values_b.len()
&& values_a.iter().zip(values_b).all(|(a, b)| expr(a, b))
}
(
ExprKind::NamedExpr {
target: target_a,
value: value_a,
},
ExprKind::NamedExpr {
target: target_b,
value: value_b,
},
) => expr(target_a, target_b) && expr(value_a, value_b),
(
ExprKind::BinOp {
left: left_a,
op: op_a,
right: right_a,
},
ExprKind::BinOp {
left: left_b,
op: op_b,
right: right_b,
},
) => op_a == op_b && expr(left_a, left_b) && expr(right_a, right_b),
(
ExprKind::UnaryOp {
op: op_a,
operand: operand_a,
},
ExprKind::UnaryOp {
op: op_b,
operand: operand_b,
},
) => op_a == op_b && expr(operand_a, operand_b),
(
ExprKind::Lambda {
args: args_a,
body: body_a,
},
ExprKind::Lambda {
args: args_b,
body: body_b,
},
) => expr(body_a, body_b) && arguments(args_a, args_b),
(
ExprKind::IfExp {
test: test_a,
body: body_a,
orelse: orelse_a,
},
ExprKind::IfExp {
test: test_b,
body: body_b,
orelse: orelse_b,
},
) => expr(test_a, test_b) && expr(body_a, body_b) && expr(orelse_a, orelse_b),
(
ExprKind::Dict {
keys: keys_a,
values: values_a,
},
ExprKind::Dict {
keys: keys_b,
values: values_b,
},
) => {
keys_a.len() == keys_b.len()
&& values_a.len() == values_b.len()
&& keys_a.iter().zip(keys_b).all(|(a, b)| expr(a, b))
&& values_a.iter().zip(values_b).all(|(a, b)| expr(a, b))
}
(ExprKind::Set { elts: elts_a }, ExprKind::Set { elts: elts_b }) => {
elts_a.len() == elts_b.len() && elts_a.iter().zip(elts_b).all(|(a, b)| expr(a, b))
}
(
ExprKind::ListComp {
elt: elt_a,
generators: generators_a,
},
ExprKind::ListComp {
elt: elt_b,
generators: generators_b,
},
) => {
expr(elt_a, elt_b)
&& generators_a.len() == generators_b.len()
&& generators_a
.iter()
.zip(generators_b)
.all(|(a, b)| comprehension(a, b))
}
(
ExprKind::SetComp {
elt: elt_a,
generators: generators_a,
},
ExprKind::SetComp {
elt: elt_b,
generators: generators_b,
},
) => {
expr(elt_a, elt_b)
&& generators_a.len() == generators_b.len()
&& generators_a
.iter()
.zip(generators_b)
.all(|(a, b)| comprehension(a, b))
}
(
ExprKind::DictComp {
key: key_a,
value: value_a,
generators: generators_a,
},
ExprKind::DictComp {
key: key_b,
value: value_b,
generators: generators_b,
},
) => {
expr(key_a, key_b)
&& expr(value_a, value_b)
&& generators_a.len() == generators_b.len()
&& generators_a
.iter()
.zip(generators_b)
.all(|(a, b)| comprehension(a, b))
}
(
ExprKind::GeneratorExp {
elt: elt_a,
generators: generators_a,
},
ExprKind::GeneratorExp {
elt: elt_b,
generators: generators_b,
},
) => {
expr(elt_a, elt_b)
&& generators_a.len() == generators_b.len()
&& generators_a
.iter()
.zip(generators_b)
.all(|(a, b)| comprehension(a, b))
}
(ExprKind::Await { value: value_a }, ExprKind::Await { value: value_b }) => {
expr(value_a, value_b)
}
(ExprKind::Yield { value: value_a }, ExprKind::Yield { value: value_b }) => {
option_expr(value_a.as_deref(), value_b.as_deref())
}
(ExprKind::YieldFrom { value: value_a }, ExprKind::YieldFrom { value: value_b }) => {
expr(value_a, value_b)
}
(
ExprKind::Compare {
left: left_a,
ops: ops_a,
comparators: comparators_a,
},
ExprKind::Compare {
left: left_b,
ops: ops_b,
comparators: comparators_b,
},
) => {
expr(left_a, left_b)
&& ops_a == ops_b
&& comparators_a.len() == comparators_b.len()
&& comparators_a
.iter()
.zip(comparators_b)
.all(|(a, b)| expr(a, b))
}
(
ExprKind::Call {
func: func_a,
args: args_a,
keywords: keywords_a,
},
ExprKind::Call {
func: func_b,
args: args_b,
keywords: keywords_b,
},
) => {
expr(func_a, func_b)
&& args_a.len() == args_b.len()
&& args_a.iter().zip(args_b).all(|(a, b)| expr(a, b))
&& keywords_a.len() == keywords_b.len()
&& keywords_a
.iter()
.zip(keywords_b)
.all(|(a, b)| keyword(a, b))
}
(
ExprKind::FormattedValue {
value: value_a,
conversion: conversion_a,
format_spec: format_spec_a,
},
ExprKind::FormattedValue {
value: value_b,
conversion: conversion_b,
format_spec: format_spec_b,
},
) => {
expr(value_a, value_b)
&& conversion_a == conversion_b
&& option_expr(format_spec_a.as_deref(), format_spec_b.as_deref())
}
(ExprKind::JoinedStr { values: values_a }, ExprKind::JoinedStr { values: values_b }) => {
values_a.len() == values_b.len()
&& values_a.iter().zip(values_b).all(|(a, b)| expr(a, b))
}
(
ExprKind::Constant {
value: value_a,
kind: kind_a,
},
ExprKind::Constant {
value: value_b,
kind: kind_b,
},
) => value_a == value_b && kind_a == kind_b,
(
ExprKind::Attribute {
value: value_a,
attr: attr_a,
ctx: ctx_a,
},
ExprKind::Attribute {
value: value_b,
attr: attr_b,
ctx: ctx_b,
},
) => attr_a == attr_b && ctx_a == ctx_b && expr(value_a, value_b),
(
ExprKind::Subscript {
value: value_a,
slice: slice_a,
ctx: ctx_a,
},
ExprKind::Subscript {
value: value_b,
slice: slice_b,
ctx: ctx_b,
},
) => ctx_a == ctx_b && expr(value_a, value_b) && expr(slice_a, slice_b),
(
ExprKind::Starred {
value: value_a,
ctx: ctx_a,
},
ExprKind::Starred {
value: value_b,
ctx: ctx_b,
},
) => ctx_a == ctx_b && expr(value_a, value_b),
(
ExprKind::Name {
id: id_a,
ctx: ctx_a,
},
ExprKind::Name {
id: id_b,
ctx: ctx_b,
},
) => id_a == id_b && ctx_a == ctx_b,
(
ExprKind::List {
elts: elts_a,
ctx: ctx_a,
},
ExprKind::List {
elts: elts_b,
ctx: ctx_b,
},
) => {
ctx_a == ctx_b
&& elts_a.len() == elts_b.len()
&& elts_a.iter().zip(elts_b).all(|(a, b)| expr(a, b))
}
(
ExprKind::Tuple {
elts: elts_a,
ctx: ctx_a,
},
ExprKind::Tuple {
elts: elts_b,
ctx: ctx_b,
},
) => {
ctx_a == ctx_b
&& elts_a.len() == elts_b.len()
&& elts_a.iter().zip(elts_b).all(|(a, b)| expr(a, b))
}
(
ExprKind::Slice {
lower: lower_a,
upper: upper_a,
step: step_a,
},
ExprKind::Slice {
lower: lower_b,
upper: upper_b,
step: step_b,
},
) => {
option_expr(lower_a.as_deref(), lower_b.as_deref())
&& option_expr(upper_a.as_deref(), upper_b.as_deref())
&& option_expr(step_a.as_deref(), step_b.as_deref())
}
_ => false,
}
}
fn option_expr(a: Option<&Expr>, b: Option<&Expr>) -> bool {
match (a, b) {
(Some(a), Some(b)) => expr(a, b),
(None, None) => true,
_ => false,
}
}
fn arguments(a: &Arguments, b: &Arguments) -> bool {
a.posonlyargs.len() == b.posonlyargs.len()
&& a.posonlyargs
.iter()
.zip(b.posonlyargs.iter())
.all(|(a, b)| arg(a, b))
&& a.args.len() == b.args.len()
&& a.args.iter().zip(b.args.iter()).all(|(a, b)| arg(a, b))
&& option_arg(a.vararg.as_deref(), b.vararg.as_deref())
&& a.kwonlyargs.len() == b.kwonlyargs.len()
&& a.kwonlyargs
.iter()
.zip(b.kwonlyargs.iter())
.all(|(a, b)| arg(a, b))
&& a.kw_defaults.len() == b.kw_defaults.len()
&& a.kw_defaults
.iter()
.zip(b.kw_defaults.iter())
.all(|(a, b)| expr(a, b))
&& option_arg(a.kwarg.as_deref(), b.kwarg.as_deref())
&& a.defaults.len() == b.defaults.len()
&& a.defaults
.iter()
.zip(b.defaults.iter())
.all(|(a, b)| expr(a, b))
}
fn arg(a: &Arg, b: &Arg) -> bool {
a.node.arg == b.node.arg
&& option_expr(a.node.annotation.as_deref(), b.node.annotation.as_deref())
}
fn option_arg(a: Option<&Arg>, b: Option<&Arg>) -> bool {
match (a, b) {
(Some(a), Some(b)) => arg(a, b),
(None, None) => true,
_ => false,
}
}
fn keyword(a: &Keyword, b: &Keyword) -> bool {
a.node.arg == b.node.arg && expr(&a.node.value, &b.node.value)
}
fn comprehension(a: &Comprehension, b: &Comprehension) -> bool {
expr(&a.iter, &b.iter)
&& expr(&a.target, &b.target)
&& a.ifs.len() == b.ifs.len()
&& a.ifs.iter().zip(b.ifs.iter()).all(|(a, b)| expr(a, b))
}

524
src/ast/comparable.rs Normal file
View file

@ -0,0 +1,524 @@
//! An equivalent object hierarchy to the `Expr` hierarchy, but with the ability
//! to compare expressions for equality (via `Eq` and `Hash`).
use num_bigint::BigInt;
use rustpython_ast::{
Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Expr, ExprContext, ExprKind, Keyword,
Operator, Unaryop,
};
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ComparableExprContext {
Load,
Store,
Del,
}
impl From<&ExprContext> for ComparableExprContext {
fn from(ctx: &ExprContext) -> Self {
match ctx {
ExprContext::Load => Self::Load,
ExprContext::Store => Self::Store,
ExprContext::Del => Self::Del,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ComparableBoolop {
And,
Or,
}
impl From<&Boolop> for ComparableBoolop {
fn from(op: &Boolop) -> Self {
match op {
Boolop::And => Self::And,
Boolop::Or => Self::Or,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ComparableOperator {
Add,
Sub,
Mult,
MatMult,
Div,
Mod,
Pow,
LShift,
RShift,
BitOr,
BitXor,
BitAnd,
FloorDiv,
}
impl From<&Operator> for ComparableOperator {
fn from(op: &Operator) -> Self {
match op {
Operator::Add => Self::Add,
Operator::Sub => Self::Sub,
Operator::Mult => Self::Mult,
Operator::MatMult => Self::MatMult,
Operator::Div => Self::Div,
Operator::Mod => Self::Mod,
Operator::Pow => Self::Pow,
Operator::LShift => Self::LShift,
Operator::RShift => Self::RShift,
Operator::BitOr => Self::BitOr,
Operator::BitXor => Self::BitXor,
Operator::BitAnd => Self::BitAnd,
Operator::FloorDiv => Self::FloorDiv,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ComparableUnaryop {
Invert,
Not,
UAdd,
USub,
}
impl From<&Unaryop> for ComparableUnaryop {
fn from(op: &Unaryop) -> Self {
match op {
Unaryop::Invert => Self::Invert,
Unaryop::Not => Self::Not,
Unaryop::UAdd => Self::UAdd,
Unaryop::USub => Self::USub,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ComparableCmpop {
Eq,
NotEq,
Lt,
LtE,
Gt,
GtE,
Is,
IsNot,
In,
NotIn,
}
impl From<&Cmpop> for ComparableCmpop {
fn from(op: &Cmpop) -> Self {
match op {
Cmpop::Eq => Self::Eq,
Cmpop::NotEq => Self::NotEq,
Cmpop::Lt => Self::Lt,
Cmpop::LtE => Self::LtE,
Cmpop::Gt => Self::Gt,
Cmpop::GtE => Self::GtE,
Cmpop::Is => Self::Is,
Cmpop::IsNot => Self::IsNot,
Cmpop::In => Self::In,
Cmpop::NotIn => Self::NotIn,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ComparableConstant<'a> {
None,
Bool(&'a bool),
Str(&'a str),
Bytes(&'a [u8]),
Int(&'a BigInt),
Tuple(Vec<ComparableConstant<'a>>),
Float(u64),
Complex { real: u64, imag: u64 },
Ellipsis,
}
impl<'a> From<&'a Constant> for ComparableConstant<'a> {
fn from(constant: &'a Constant) -> Self {
match constant {
Constant::None => Self::None,
Constant::Bool(value) => Self::Bool(value),
Constant::Str(value) => Self::Str(value),
Constant::Bytes(value) => Self::Bytes(value),
Constant::Int(value) => Self::Int(value),
Constant::Tuple(value) => {
Self::Tuple(value.iter().map(std::convert::Into::into).collect())
}
Constant::Float(value) => Self::Float(value.to_bits()),
Constant::Complex { real, imag } => Self::Complex {
real: real.to_bits(),
imag: imag.to_bits(),
},
Constant::Ellipsis => Self::Ellipsis,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ComparableArguments<'a> {
pub posonlyargs: Vec<ComparableArg<'a>>,
pub args: Vec<ComparableArg<'a>>,
pub vararg: Option<ComparableArg<'a>>,
pub kwonlyargs: Vec<ComparableArg<'a>>,
pub kw_defaults: Vec<ComparableExpr<'a>>,
pub kwarg: Option<ComparableArg<'a>>,
pub defaults: Vec<ComparableExpr<'a>>,
}
impl<'a> From<&'a Arguments> for ComparableArguments<'a> {
fn from(arguments: &'a Arguments) -> Self {
Self {
posonlyargs: arguments
.posonlyargs
.iter()
.map(std::convert::Into::into)
.collect(),
args: arguments
.args
.iter()
.map(std::convert::Into::into)
.collect(),
vararg: arguments.vararg.as_ref().map(std::convert::Into::into),
kwonlyargs: arguments
.kwonlyargs
.iter()
.map(std::convert::Into::into)
.collect(),
kw_defaults: arguments
.kw_defaults
.iter()
.map(std::convert::Into::into)
.collect(),
kwarg: arguments.vararg.as_ref().map(std::convert::Into::into),
defaults: arguments
.defaults
.iter()
.map(std::convert::Into::into)
.collect(),
}
}
}
impl<'a> From<&'a Box<Arg>> for ComparableArg<'a> {
fn from(arg: &'a Box<Arg>) -> Self {
(&**arg).into()
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ComparableArg<'a> {
pub arg: &'a str,
pub annotation: Option<Box<ComparableExpr<'a>>>,
pub type_comment: Option<&'a str>,
}
impl<'a> From<&'a Arg> for ComparableArg<'a> {
fn from(arg: &'a Arg) -> Self {
Self {
arg: &arg.node.arg,
annotation: arg.node.annotation.as_ref().map(std::convert::Into::into),
type_comment: arg.node.type_comment.as_deref(),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ComparableKeyword<'a> {
pub arg: Option<&'a str>,
pub value: ComparableExpr<'a>,
}
impl<'a> From<&'a Keyword> for ComparableKeyword<'a> {
fn from(keyword: &'a Keyword) -> Self {
Self {
arg: keyword.node.arg.as_deref(),
value: (&keyword.node.value).into(),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct ComparableComprehension<'a> {
pub target: ComparableExpr<'a>,
pub iter: ComparableExpr<'a>,
pub ifs: Vec<ComparableExpr<'a>>,
pub is_async: &'a usize,
}
impl<'a> From<&'a Comprehension> for ComparableComprehension<'a> {
fn from(comprehension: &'a Comprehension) -> Self {
Self {
target: (&comprehension.target).into(),
iter: (&comprehension.iter).into(),
ifs: comprehension
.ifs
.iter()
.map(std::convert::Into::into)
.collect(),
is_async: &comprehension.is_async,
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum ComparableExpr<'a> {
BoolOp {
op: ComparableBoolop,
values: Vec<ComparableExpr<'a>>,
},
NamedExpr {
target: Box<ComparableExpr<'a>>,
value: Box<ComparableExpr<'a>>,
},
BinOp {
left: Box<ComparableExpr<'a>>,
op: ComparableOperator,
right: Box<ComparableExpr<'a>>,
},
UnaryOp {
op: ComparableUnaryop,
operand: Box<ComparableExpr<'a>>,
},
Lambda {
args: ComparableArguments<'a>,
body: Box<ComparableExpr<'a>>,
},
IfExp {
test: Box<ComparableExpr<'a>>,
body: Box<ComparableExpr<'a>>,
orelse: Box<ComparableExpr<'a>>,
},
Dict {
keys: Vec<ComparableExpr<'a>>,
values: Vec<ComparableExpr<'a>>,
},
Set {
elts: Vec<ComparableExpr<'a>>,
},
ListComp {
elt: Box<ComparableExpr<'a>>,
generators: Vec<ComparableComprehension<'a>>,
},
SetComp {
elt: Box<ComparableExpr<'a>>,
generators: Vec<ComparableComprehension<'a>>,
},
DictComp {
key: Box<ComparableExpr<'a>>,
value: Box<ComparableExpr<'a>>,
generators: Vec<ComparableComprehension<'a>>,
},
GeneratorExp {
elt: Box<ComparableExpr<'a>>,
generators: Vec<ComparableComprehension<'a>>,
},
Await {
value: Box<ComparableExpr<'a>>,
},
Yield {
value: Option<Box<ComparableExpr<'a>>>,
},
YieldFrom {
value: Box<ComparableExpr<'a>>,
},
Compare {
left: Box<ComparableExpr<'a>>,
ops: Vec<ComparableCmpop>,
comparators: Vec<ComparableExpr<'a>>,
},
Call {
func: Box<ComparableExpr<'a>>,
args: Vec<ComparableExpr<'a>>,
keywords: Vec<ComparableKeyword<'a>>,
},
FormattedValue {
value: Box<ComparableExpr<'a>>,
conversion: &'a usize,
format_spec: Option<Box<ComparableExpr<'a>>>,
},
JoinedStr {
values: Vec<ComparableExpr<'a>>,
},
Constant {
value: ComparableConstant<'a>,
kind: Option<&'a str>,
},
Attribute {
value: Box<ComparableExpr<'a>>,
attr: &'a str,
ctx: ComparableExprContext,
},
Subscript {
value: Box<ComparableExpr<'a>>,
slice: Box<ComparableExpr<'a>>,
ctx: ComparableExprContext,
},
Starred {
value: Box<ComparableExpr<'a>>,
ctx: ComparableExprContext,
},
Name {
id: &'a str,
ctx: ComparableExprContext,
},
List {
elts: Vec<ComparableExpr<'a>>,
ctx: ComparableExprContext,
},
Tuple {
elts: Vec<ComparableExpr<'a>>,
ctx: ComparableExprContext,
},
Slice {
lower: Option<Box<ComparableExpr<'a>>>,
upper: Option<Box<ComparableExpr<'a>>>,
step: Option<Box<ComparableExpr<'a>>>,
},
}
impl<'a> From<&'a Box<Expr>> for Box<ComparableExpr<'a>> {
fn from(expr: &'a Box<Expr>) -> Self {
Box::new((&**expr).into())
}
}
impl<'a> From<&'a Expr> for ComparableExpr<'a> {
fn from(expr: &'a Expr) -> Self {
match &expr.node {
ExprKind::BoolOp { op, values } => Self::BoolOp {
op: op.into(),
values: values.iter().map(std::convert::Into::into).collect(),
},
ExprKind::NamedExpr { target, value } => Self::NamedExpr {
target: target.into(),
value: value.into(),
},
ExprKind::BinOp { left, op, right } => Self::BinOp {
left: left.into(),
op: op.into(),
right: right.into(),
},
ExprKind::UnaryOp { op, operand } => Self::UnaryOp {
op: op.into(),
operand: operand.into(),
},
ExprKind::Lambda { args, body } => Self::Lambda {
args: (&**args).into(),
body: body.into(),
},
ExprKind::IfExp { test, body, orelse } => Self::IfExp {
test: test.into(),
body: body.into(),
orelse: orelse.into(),
},
ExprKind::Dict { keys, values } => Self::Dict {
keys: keys.iter().map(std::convert::Into::into).collect(),
values: values.iter().map(std::convert::Into::into).collect(),
},
ExprKind::Set { elts } => Self::Set {
elts: elts.iter().map(std::convert::Into::into).collect(),
},
ExprKind::ListComp { elt, generators } => Self::ListComp {
elt: elt.into(),
generators: generators.iter().map(std::convert::Into::into).collect(),
},
ExprKind::SetComp { elt, generators } => Self::SetComp {
elt: elt.into(),
generators: generators.iter().map(std::convert::Into::into).collect(),
},
ExprKind::DictComp {
key,
value,
generators,
} => Self::DictComp {
key: key.into(),
value: value.into(),
generators: generators.iter().map(std::convert::Into::into).collect(),
},
ExprKind::GeneratorExp { elt, generators } => Self::GeneratorExp {
elt: elt.into(),
generators: generators.iter().map(std::convert::Into::into).collect(),
},
ExprKind::Await { value } => Self::Await {
value: value.into(),
},
ExprKind::Yield { value } => Self::Yield {
value: value.as_ref().map(std::convert::Into::into),
},
ExprKind::YieldFrom { value } => Self::YieldFrom {
value: value.into(),
},
ExprKind::Compare {
left,
ops,
comparators,
} => Self::Compare {
left: left.into(),
ops: ops.iter().map(std::convert::Into::into).collect(),
comparators: comparators.iter().map(std::convert::Into::into).collect(),
},
ExprKind::Call {
func,
args,
keywords,
} => Self::Call {
func: func.into(),
args: args.iter().map(std::convert::Into::into).collect(),
keywords: keywords.iter().map(std::convert::Into::into).collect(),
},
ExprKind::FormattedValue {
value,
conversion,
format_spec,
} => Self::FormattedValue {
value: value.into(),
conversion,
format_spec: format_spec.as_ref().map(std::convert::Into::into),
},
ExprKind::JoinedStr { values } => Self::JoinedStr {
values: values.iter().map(std::convert::Into::into).collect(),
},
ExprKind::Constant { value, kind } => Self::Constant {
value: value.into(),
kind: kind.as_ref().map(String::as_str),
},
ExprKind::Attribute { value, attr, ctx } => Self::Attribute {
value: value.into(),
attr,
ctx: ctx.into(),
},
ExprKind::Subscript { value, slice, ctx } => Self::Subscript {
value: value.into(),
slice: slice.into(),
ctx: ctx.into(),
},
ExprKind::Starred { value, ctx } => Self::Starred {
value: value.into(),
ctx: ctx.into(),
},
ExprKind::Name { id, ctx } => Self::Name {
id,
ctx: ctx.into(),
},
ExprKind::List { elts, ctx } => Self::List {
elts: elts.iter().map(std::convert::Into::into).collect(),
ctx: ctx.into(),
},
ExprKind::Tuple { elts, ctx } => Self::Tuple {
elts: elts.iter().map(std::convert::Into::into).collect(),
ctx: ctx.into(),
},
ExprKind::Slice { lower, upper, step } => Self::Slice {
lower: lower.as_ref().map(std::convert::Into::into),
upper: upper.as_ref().map(std::convert::Into::into),
step: step.as_ref().map(std::convert::Into::into),
},
}
}
}

View file

@ -1,6 +1,6 @@
pub mod branch_detection;
pub mod cast;
pub mod cmp;
pub mod comparable;
pub mod function_type;
pub mod helpers;
pub mod operations;

View file

@ -1,29 +1,25 @@
use std::hash::{BuildHasherDefault, Hash};
use rustc_hash::FxHashMap;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Expr, ExprKind};
use crate::ast::cmp;
use crate::ast::comparable::{ComparableConstant, ComparableExpr};
use crate::ast::helpers::unparse_expr;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checkers::ast::Checker;
use crate::registry::{Check, CheckCode};
use crate::source_code_style::SourceCodeStyleDetector;
use crate::violations;
#[derive(Debug, Eq, PartialEq, Hash)]
enum DictionaryKey<'a> {
Constant(String),
Constant(ComparableConstant<'a>),
Variable(&'a str),
}
fn into_dictionary_key<'a>(
expr: &'a Expr,
stylist: &SourceCodeStyleDetector,
) -> Option<DictionaryKey<'a>> {
fn into_dictionary_key(expr: &Expr) -> Option<DictionaryKey> {
match &expr.node {
ExprKind::Constant { .. } => Some(DictionaryKey::Constant(unparse_expr(expr, stylist))),
ExprKind::Constant { value, .. } => Some(DictionaryKey::Constant(value.into())),
ExprKind::Name { id, .. } => Some(DictionaryKey::Variable(id)),
_ => None,
}
@ -32,23 +28,26 @@ fn into_dictionary_key<'a>(
/// F601, F602
pub fn repeated_keys(checker: &mut Checker, keys: &[Expr], values: &[Expr]) {
// Generate a map from key to (index, value).
let mut seen: FxHashMap<DictionaryKey, Vec<&Expr>> =
let mut seen: FxHashMap<DictionaryKey, FxHashSet<ComparableExpr>> =
FxHashMap::with_capacity_and_hasher(keys.len(), BuildHasherDefault::default());
// Detect duplicate keys.
for (i, key) in keys.iter().enumerate() {
if let Some(key) = into_dictionary_key(key, checker.style) {
if let Some(key) = into_dictionary_key(key) {
if let Some(seen_values) = seen.get_mut(&key) {
match key {
DictionaryKey::Constant(key) => {
DictionaryKey::Constant(..) => {
if checker.settings.enabled.contains(&CheckCode::F601) {
let repeated_value =
seen_values.iter().any(|value| cmp::expr(value, &values[i]));
let comparable_value: ComparableExpr = (&values[i]).into();
let is_duplicate_value = seen_values.contains(&comparable_value);
let mut check = Check::new(
violations::MultiValueRepeatedKeyLiteral(key, repeated_value),
violations::MultiValueRepeatedKeyLiteral(
unparse_expr(&keys[i], checker.style),
is_duplicate_value,
),
Range::from_located(&keys[i]),
);
if repeated_value {
if is_duplicate_value {
if checker.patch(&CheckCode::F601) {
check.amend(Fix::deletion(
values[i - 1].end_location.unwrap(),
@ -56,23 +55,23 @@ pub fn repeated_keys(checker: &mut Checker, keys: &[Expr], values: &[Expr]) {
));
}
} else {
seen_values.push(&values[i]);
seen_values.insert(comparable_value);
}
checker.checks.push(check);
}
}
DictionaryKey::Variable(key) => {
if checker.settings.enabled.contains(&CheckCode::F602) {
let repeated_value =
seen_values.iter().any(|value| cmp::expr(value, &values[i]));
let comparable_value: ComparableExpr = (&values[i]).into();
let is_duplicate_value = seen_values.contains(&comparable_value);
let mut check = Check::new(
violations::MultiValueRepeatedKeyVariable(
key.to_string(),
repeated_value,
is_duplicate_value,
),
Range::from_located(&keys[i]),
);
if repeated_value {
if is_duplicate_value {
if checker.patch(&CheckCode::F602) {
check.amend(Fix::deletion(
values[i - 1].end_location.unwrap(),
@ -80,14 +79,14 @@ pub fn repeated_keys(checker: &mut Checker, keys: &[Expr], values: &[Expr]) {
));
}
} else {
seen_values.push(&values[i]);
seen_values.insert(comparable_value);
}
checker.checks.push(check);
}
}
}
} else {
seen.insert(key, vec![&values[i]]);
seen.insert(key, FxHashSet::from_iter([(&values[i]).into()]));
}
}
}