//! An equivalent object hierarchy to the `RustPython` AST hierarchy, but with the //! ability to compare expressions for equality (via [`Eq`] and [`Hash`]). //! //! Two [`ComparableExpr`]s are considered equal if the underlying AST nodes have the //! same shape, ignoring trivia (e.g., parentheses, comments, and whitespace), the //! location in the source code, and other contextual information (e.g., whether they //! represent reads or writes, which is typically encoded in the Python AST). //! //! For example, in `[(a, b) for a, b in c]`, the `(a, b)` and `a, b` expressions are //! considered equal, despite the former being parenthesized, and despite the former //! being a write ([`ast::ExprContext::Store`]) and the latter being a read //! ([`ast::ExprContext::Load`]). //! //! Similarly, `"a" "b"` and `"ab"` would be considered equal, despite the former being //! an implicit concatenation of string literals, as these expressions are considered to //! have the same shape in that they evaluate to the same value. use crate as ast; use crate::{Expr, Number}; use std::borrow::Cow; use std::hash::Hash; #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] pub enum ComparableBoolOp { And, Or, } impl From for ComparableBoolOp { fn from(op: ast::BoolOp) -> Self { match op { ast::BoolOp::And => Self::And, ast::BoolOp::Or => Self::Or, } } } #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] pub enum ComparableOperator { Add, Sub, Mult, MatMult, Div, Mod, Pow, LShift, RShift, BitOr, BitXor, BitAnd, FloorDiv, } impl From for ComparableOperator { fn from(op: ast::Operator) -> Self { match op { ast::Operator::Add => Self::Add, ast::Operator::Sub => Self::Sub, ast::Operator::Mult => Self::Mult, ast::Operator::MatMult => Self::MatMult, ast::Operator::Div => Self::Div, ast::Operator::Mod => Self::Mod, ast::Operator::Pow => Self::Pow, ast::Operator::LShift => Self::LShift, ast::Operator::RShift => Self::RShift, ast::Operator::BitOr => Self::BitOr, ast::Operator::BitXor => Self::BitXor, ast::Operator::BitAnd => Self::BitAnd, ast::Operator::FloorDiv => Self::FloorDiv, } } } #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] pub enum ComparableUnaryOp { Invert, Not, UAdd, USub, } impl From for ComparableUnaryOp { fn from(op: ast::UnaryOp) -> Self { match op { ast::UnaryOp::Invert => Self::Invert, ast::UnaryOp::Not => Self::Not, ast::UnaryOp::UAdd => Self::UAdd, ast::UnaryOp::USub => Self::USub, } } } #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] pub enum ComparableCmpOp { Eq, NotEq, Lt, LtE, Gt, GtE, Is, IsNot, In, NotIn, } impl From for ComparableCmpOp { fn from(op: ast::CmpOp) -> Self { match op { ast::CmpOp::Eq => Self::Eq, ast::CmpOp::NotEq => Self::NotEq, ast::CmpOp::Lt => Self::Lt, ast::CmpOp::LtE => Self::LtE, ast::CmpOp::Gt => Self::Gt, ast::CmpOp::GtE => Self::GtE, ast::CmpOp::Is => Self::Is, ast::CmpOp::IsNot => Self::IsNot, ast::CmpOp::In => Self::In, ast::CmpOp::NotIn => Self::NotIn, } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableAlias<'a> { name: &'a str, asname: Option<&'a str>, } impl<'a> From<&'a ast::Alias> for ComparableAlias<'a> { fn from(alias: &'a ast::Alias) -> Self { Self { name: alias.name.as_str(), asname: alias.asname.as_deref(), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableWithItem<'a> { context_expr: ComparableExpr<'a>, optional_vars: Option>, } impl<'a> From<&'a ast::WithItem> for ComparableWithItem<'a> { fn from(with_item: &'a ast::WithItem) -> Self { Self { context_expr: (&with_item.context_expr).into(), optional_vars: with_item.optional_vars.as_ref().map(Into::into), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparablePatternArguments<'a> { patterns: Vec>, keywords: Vec>, } impl<'a> From<&'a ast::PatternArguments> for ComparablePatternArguments<'a> { fn from(parameters: &'a ast::PatternArguments) -> Self { Self { patterns: parameters.patterns.iter().map(Into::into).collect(), keywords: parameters.keywords.iter().map(Into::into).collect(), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparablePatternKeyword<'a> { attr: &'a str, pattern: ComparablePattern<'a>, } impl<'a> From<&'a ast::PatternKeyword> for ComparablePatternKeyword<'a> { fn from(keyword: &'a ast::PatternKeyword) -> Self { Self { attr: keyword.attr.as_str(), pattern: (&keyword.pattern).into(), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct PatternMatchValue<'a> { value: ComparableExpr<'a>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct PatternMatchSingleton { value: ComparableSingleton, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct PatternMatchSequence<'a> { patterns: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct PatternMatchMapping<'a> { keys: Vec>, patterns: Vec>, rest: Option<&'a str>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct PatternMatchClass<'a> { cls: ComparableExpr<'a>, arguments: ComparablePatternArguments<'a>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct PatternMatchStar<'a> { name: Option<&'a str>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct PatternMatchAs<'a> { pattern: Option>>, name: Option<&'a str>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct PatternMatchOr<'a> { patterns: Vec>, } #[allow(clippy::enum_variant_names)] #[derive(Debug, PartialEq, Eq, Hash)] pub enum ComparablePattern<'a> { MatchValue(PatternMatchValue<'a>), MatchSingleton(PatternMatchSingleton), MatchSequence(PatternMatchSequence<'a>), MatchMapping(PatternMatchMapping<'a>), MatchClass(PatternMatchClass<'a>), MatchStar(PatternMatchStar<'a>), MatchAs(PatternMatchAs<'a>), MatchOr(PatternMatchOr<'a>), } impl<'a> From<&'a ast::Pattern> for ComparablePattern<'a> { fn from(pattern: &'a ast::Pattern) -> Self { match pattern { ast::Pattern::MatchValue(ast::PatternMatchValue { value, .. }) => { Self::MatchValue(PatternMatchValue { value: value.into(), }) } ast::Pattern::MatchSingleton(ast::PatternMatchSingleton { value, .. }) => { Self::MatchSingleton(PatternMatchSingleton { value: value.into(), }) } ast::Pattern::MatchSequence(ast::PatternMatchSequence { patterns, .. }) => { Self::MatchSequence(PatternMatchSequence { patterns: patterns.iter().map(Into::into).collect(), }) } ast::Pattern::MatchMapping(ast::PatternMatchMapping { keys, patterns, rest, .. }) => Self::MatchMapping(PatternMatchMapping { keys: keys.iter().map(Into::into).collect(), patterns: patterns.iter().map(Into::into).collect(), rest: rest.as_deref(), }), ast::Pattern::MatchClass(ast::PatternMatchClass { cls, arguments, .. }) => { Self::MatchClass(PatternMatchClass { cls: cls.into(), arguments: arguments.into(), }) } ast::Pattern::MatchStar(ast::PatternMatchStar { name, .. }) => { Self::MatchStar(PatternMatchStar { name: name.as_deref(), }) } ast::Pattern::MatchAs(ast::PatternMatchAs { pattern, name, .. }) => { Self::MatchAs(PatternMatchAs { pattern: pattern.as_ref().map(Into::into), name: name.as_deref(), }) } ast::Pattern::MatchOr(ast::PatternMatchOr { patterns, .. }) => { Self::MatchOr(PatternMatchOr { patterns: patterns.iter().map(Into::into).collect(), }) } } } } impl<'a> From<&'a Box> for Box> { fn from(pattern: &'a Box) -> Self { Box::new((pattern.as_ref()).into()) } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableMatchCase<'a> { pattern: ComparablePattern<'a>, guard: Option>, body: Vec>, } impl<'a> From<&'a ast::MatchCase> for ComparableMatchCase<'a> { fn from(match_case: &'a ast::MatchCase) -> Self { Self { pattern: (&match_case.pattern).into(), guard: match_case.guard.as_ref().map(Into::into), body: match_case.body.iter().map(Into::into).collect(), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableDecorator<'a> { expression: ComparableExpr<'a>, } impl<'a> From<&'a ast::Decorator> for ComparableDecorator<'a> { fn from(decorator: &'a ast::Decorator) -> Self { Self { expression: (&decorator.expression).into(), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub enum ComparableSingleton { None, True, False, } impl From<&ast::Singleton> for ComparableSingleton { fn from(singleton: &ast::Singleton) -> Self { match singleton { ast::Singleton::None => Self::None, ast::Singleton::True => Self::True, ast::Singleton::False => Self::False, } } } #[derive(Debug, PartialEq, Eq, Hash)] pub enum ComparableNumber<'a> { Int(&'a ast::Int), Float(u64), Complex { real: u64, imag: u64 }, } impl<'a> From<&'a ast::Number> for ComparableNumber<'a> { fn from(number: &'a ast::Number) -> Self { match number { ast::Number::Int(value) => Self::Int(value), ast::Number::Float(value) => Self::Float(value.to_bits()), ast::Number::Complex { real, imag } => Self::Complex { real: real.to_bits(), imag: imag.to_bits(), }, } } } #[derive(Debug, Default, PartialEq, Eq, Hash)] pub struct ComparableArguments<'a> { args: Vec>, keywords: Vec>, } impl<'a> From<&'a ast::Arguments> for ComparableArguments<'a> { fn from(arguments: &'a ast::Arguments) -> Self { Self { args: arguments.args.iter().map(Into::into).collect(), keywords: arguments.keywords.iter().map(Into::into).collect(), } } } impl<'a> From<&'a Box> for ComparableArguments<'a> { fn from(arguments: &'a Box) -> Self { (arguments.as_ref()).into() } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableParameters<'a> { posonlyargs: Vec>, args: Vec>, vararg: Option>, kwonlyargs: Vec>, kwarg: Option>, } impl<'a> From<&'a ast::Parameters> for ComparableParameters<'a> { fn from(parameters: &'a ast::Parameters) -> Self { Self { posonlyargs: parameters.posonlyargs.iter().map(Into::into).collect(), args: parameters.args.iter().map(Into::into).collect(), vararg: parameters.vararg.as_ref().map(Into::into), kwonlyargs: parameters.kwonlyargs.iter().map(Into::into).collect(), kwarg: parameters.kwarg.as_ref().map(Into::into), } } } impl<'a> From<&'a Box> for ComparableParameters<'a> { fn from(parameters: &'a Box) -> Self { (parameters.as_ref()).into() } } impl<'a> From<&'a Box> for ComparableParameter<'a> { fn from(arg: &'a Box) -> Self { (arg.as_ref()).into() } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableParameter<'a> { arg: &'a str, annotation: Option>>, } impl<'a> From<&'a ast::Parameter> for ComparableParameter<'a> { fn from(arg: &'a ast::Parameter) -> Self { Self { arg: arg.name.as_str(), annotation: arg.annotation.as_ref().map(Into::into), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableParameterWithDefault<'a> { def: ComparableParameter<'a>, default: Option>, } impl<'a> From<&'a ast::ParameterWithDefault> for ComparableParameterWithDefault<'a> { fn from(arg: &'a ast::ParameterWithDefault) -> Self { Self { def: (&arg.parameter).into(), default: arg.default.as_ref().map(Into::into), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableKeyword<'a> { arg: Option<&'a str>, value: ComparableExpr<'a>, } impl<'a> From<&'a ast::Keyword> for ComparableKeyword<'a> { fn from(keyword: &'a ast::Keyword) -> Self { Self { arg: keyword.arg.as_ref().map(ast::Identifier::as_str), value: (&keyword.value).into(), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableComprehension<'a> { target: ComparableExpr<'a>, iter: ComparableExpr<'a>, ifs: Vec>, is_async: bool, } impl<'a> From<&'a ast::Comprehension> for ComparableComprehension<'a> { fn from(comprehension: &'a ast::Comprehension) -> Self { Self { target: (&comprehension.target).into(), iter: (&comprehension.iter).into(), ifs: comprehension.ifs.iter().map(Into::into).collect(), is_async: comprehension.is_async, } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExceptHandlerExceptHandler<'a> { type_: Option>>, name: Option<&'a str>, body: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub enum ComparableExceptHandler<'a> { ExceptHandler(ExceptHandlerExceptHandler<'a>), } impl<'a> From<&'a ast::ExceptHandler> for ComparableExceptHandler<'a> { fn from(except_handler: &'a ast::ExceptHandler) -> Self { let ast::ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { type_, name, body, .. }) = except_handler; Self::ExceptHandler(ExceptHandlerExceptHandler { type_: type_.as_ref().map(Into::into), name: name.as_deref(), body: body.iter().map(Into::into).collect(), }) } } #[derive(Debug, PartialEq, Eq, Hash)] pub enum ComparableFStringElement<'a> { Literal(Cow<'a, str>), FStringExpressionElement(FStringExpressionElement<'a>), } #[derive(Debug, PartialEq, Eq, Hash)] pub struct FStringExpressionElement<'a> { expression: ComparableExpr<'a>, debug_text: Option<&'a ast::DebugText>, conversion: ast::ConversionFlag, format_spec: Option>>, } impl<'a> From<&'a ast::FStringElement> for ComparableFStringElement<'a> { fn from(fstring_element: &'a ast::FStringElement) -> Self { match fstring_element { ast::FStringElement::Literal(ast::FStringLiteralElement { value, .. }) => { Self::Literal(value.as_ref().into()) } ast::FStringElement::Expression(formatted_value) => formatted_value.into(), } } } impl<'a> From<&'a ast::FStringExpressionElement> for ComparableFStringElement<'a> { fn from(fstring_expression_element: &'a ast::FStringExpressionElement) -> Self { let ast::FStringExpressionElement { expression, debug_text, conversion, format_spec, range: _, } = fstring_expression_element; Self::FStringExpressionElement(FStringExpressionElement { expression: (expression).into(), debug_text: debug_text.as_ref(), conversion: *conversion, format_spec: format_spec .as_ref() .map(|spec| spec.elements.iter().map(Into::into).collect()), }) } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableElifElseClause<'a> { test: Option>, body: Vec>, } impl<'a> From<&'a ast::ElifElseClause> for ComparableElifElseClause<'a> { fn from(elif_else_clause: &'a ast::ElifElseClause) -> Self { let ast::ElifElseClause { range: _, test, body, } = elif_else_clause; Self { test: test.as_ref().map(Into::into), body: body.iter().map(Into::into).collect(), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub enum ComparableLiteral<'a> { None, Ellipsis, Bool(&'a bool), Str(Vec>), Bytes(Vec>), Number(ComparableNumber<'a>), } impl<'a> From> for ComparableLiteral<'a> { fn from(literal: ast::LiteralExpressionRef<'a>) -> Self { match literal { ast::LiteralExpressionRef::NoneLiteral(_) => Self::None, ast::LiteralExpressionRef::EllipsisLiteral(_) => Self::Ellipsis, ast::LiteralExpressionRef::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) => Self::Bool(value), ast::LiteralExpressionRef::StringLiteral(ast::ExprStringLiteral { value, .. }) => { Self::Str(value.iter().map(Into::into).collect()) } ast::LiteralExpressionRef::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => { Self::Bytes(value.iter().map(Into::into).collect()) } ast::LiteralExpressionRef::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => { Self::Number(value.into()) } } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableFString<'a> { elements: Box<[ComparableFStringElement<'a>]>, } impl<'a> From<&'a ast::FStringValue> for ComparableFString<'a> { // The approach below is somewhat complicated, so it may // require some justification. // // Suppose given an f-string of the form // `f"{foo!r} one" " and two " f" and three {bar!s}"` // This decomposes as: // - An `FStringPart::FString`, `f"{foo!r} one"` with elements // - `FStringElement::Expression` encoding `{foo!r}` // - `FStringElement::Literal` encoding " one" // - An `FStringPart::Literal` capturing `" and two "` // - An `FStringPart::FString`, `f" and three {bar!s}"` with elements // - `FStringElement::Literal` encoding " and three " // - `FStringElement::Expression` encoding `{bar!s}` // // We would like to extract from this a vector of (comparable) f-string // _elements_ which alternate between expression elements and literal // elements. In order to do so, we need to concatenate adjacent string // literals. String literals may be separated for two reasons: either // they appear in adjacent string literal parts, or else a string literal // part is adjacent to a string literal _element_ inside of an f-string part. fn from(value: &'a ast::FStringValue) -> Self { #[derive(Default)] struct Collector<'a> { elements: Vec>, } impl<'a> Collector<'a> { // The logic for concatenating adjacent string literals // occurs here, implicitly: when we encounter a sequence // of string literals, the first gets pushed to the // `elements` vector, while subsequent strings // are concatenated onto this top string. fn push_literal(&mut self, literal: &'a str) { if let Some(ComparableFStringElement::Literal(existing_literal)) = self.elements.last_mut() { existing_literal.to_mut().push_str(literal); } else { self.elements .push(ComparableFStringElement::Literal(literal.into())); } } fn push_expression(&mut self, expression: &'a ast::FStringExpressionElement) { self.elements.push(expression.into()); } } let mut collector = Collector::default(); for part in value { match part { ast::FStringPart::Literal(string_literal) => { collector.push_literal(&string_literal.value); } ast::FStringPart::FString(fstring) => { for element in &fstring.elements { match element { ast::FStringElement::Literal(literal) => { collector.push_literal(&literal.value); } ast::FStringElement::Expression(expression) => { collector.push_expression(expression); } } } } } } Self { elements: collector.elements.into_boxed_slice(), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableStringLiteral<'a> { value: &'a str, } impl<'a> From<&'a ast::StringLiteral> for ComparableStringLiteral<'a> { fn from(string_literal: &'a ast::StringLiteral) -> Self { Self { value: &string_literal.value, } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableBytesLiteral<'a> { value: Cow<'a, [u8]>, } impl<'a> From<&'a ast::BytesLiteral> for ComparableBytesLiteral<'a> { fn from(bytes_literal: &'a ast::BytesLiteral) -> Self { Self { value: Cow::Borrowed(&bytes_literal.value), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprBoolOp<'a> { op: ComparableBoolOp, values: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprNamed<'a> { target: Box>, value: Box>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprBinOp<'a> { left: Box>, op: ComparableOperator, right: Box>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprUnaryOp<'a> { op: ComparableUnaryOp, operand: Box>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprLambda<'a> { parameters: Option>, body: Box>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprIf<'a> { test: Box>, body: Box>, orelse: Box>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableDictItem<'a> { key: Option>, value: ComparableExpr<'a>, } impl<'a> From<&'a ast::DictItem> for ComparableDictItem<'a> { fn from(ast::DictItem { key, value }: &'a ast::DictItem) -> Self { Self { key: key.as_ref().map(ComparableExpr::from), value: value.into(), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprDict<'a> { items: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprSet<'a> { elts: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprListComp<'a> { elt: Box>, generators: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprSetComp<'a> { elt: Box>, generators: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprDictComp<'a> { key: Box>, value: Box>, generators: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprGenerator<'a> { elt: Box>, generators: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprAwait<'a> { value: Box>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprYield<'a> { value: Option>>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprYieldFrom<'a> { value: Box>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprCompare<'a> { left: Box>, ops: Vec, comparators: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprCall<'a> { func: Box>, arguments: ComparableArguments<'a>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprFStringExpressionElement<'a> { value: Box>, debug_text: Option<&'a ast::DebugText>, conversion: ast::ConversionFlag, format_spec: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprFString<'a> { value: ComparableFString<'a>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprStringLiteral<'a> { value: ComparableStringLiteral<'a>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprBytesLiteral<'a> { value: ComparableBytesLiteral<'a>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprNumberLiteral<'a> { value: ComparableNumber<'a>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprBoolLiteral { value: bool, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprAttribute<'a> { value: Box>, attr: &'a str, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprSubscript<'a> { value: Box>, slice: Box>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprStarred<'a> { value: Box>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprName<'a> { id: &'a str, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprList<'a> { elts: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprTuple<'a> { elts: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprSlice<'a> { lower: Option>>, upper: Option>>, step: Option>>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ExprIpyEscapeCommand<'a> { kind: ast::IpyEscapeKind, value: &'a str, } #[derive(Debug, PartialEq, Eq, Hash)] pub enum ComparableExpr<'a> { BoolOp(ExprBoolOp<'a>), NamedExpr(ExprNamed<'a>), BinOp(ExprBinOp<'a>), UnaryOp(ExprUnaryOp<'a>), Lambda(ExprLambda<'a>), IfExp(ExprIf<'a>), Dict(ExprDict<'a>), Set(ExprSet<'a>), ListComp(ExprListComp<'a>), SetComp(ExprSetComp<'a>), DictComp(ExprDictComp<'a>), GeneratorExp(ExprGenerator<'a>), Await(ExprAwait<'a>), Yield(ExprYield<'a>), YieldFrom(ExprYieldFrom<'a>), Compare(ExprCompare<'a>), Call(ExprCall<'a>), FStringExpressionElement(ExprFStringExpressionElement<'a>), FString(ExprFString<'a>), StringLiteral(ExprStringLiteral<'a>), BytesLiteral(ExprBytesLiteral<'a>), NumberLiteral(ExprNumberLiteral<'a>), BoolLiteral(ExprBoolLiteral), NoneLiteral, EllipsisLiteral, Attribute(ExprAttribute<'a>), Subscript(ExprSubscript<'a>), Starred(ExprStarred<'a>), Name(ExprName<'a>), List(ExprList<'a>), Tuple(ExprTuple<'a>), Slice(ExprSlice<'a>), IpyEscapeCommand(ExprIpyEscapeCommand<'a>), } impl<'a> From<&'a Box> for Box> { fn from(expr: &'a Box) -> Self { Box::new((expr.as_ref()).into()) } } impl<'a> From<&'a Box> for ComparableExpr<'a> { fn from(expr: &'a Box) -> Self { (expr.as_ref()).into() } } impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> { fn from(expr: &'a ast::Expr) -> Self { match expr { ast::Expr::BoolOp(ast::ExprBoolOp { op, values, range: _, }) => Self::BoolOp(ExprBoolOp { op: (*op).into(), values: values.iter().map(Into::into).collect(), }), ast::Expr::Named(ast::ExprNamed { target, value, range: _, }) => Self::NamedExpr(ExprNamed { target: target.into(), value: value.into(), }), ast::Expr::BinOp(ast::ExprBinOp { left, op, right, range: _, }) => Self::BinOp(ExprBinOp { left: left.into(), op: (*op).into(), right: right.into(), }), ast::Expr::UnaryOp(ast::ExprUnaryOp { op, operand, range: _, }) => Self::UnaryOp(ExprUnaryOp { op: (*op).into(), operand: operand.into(), }), ast::Expr::Lambda(ast::ExprLambda { parameters, body, range: _, }) => Self::Lambda(ExprLambda { parameters: parameters.as_ref().map(Into::into), body: body.into(), }), ast::Expr::If(ast::ExprIf { test, body, orelse, range: _, }) => Self::IfExp(ExprIf { test: test.into(), body: body.into(), orelse: orelse.into(), }), ast::Expr::Dict(ast::ExprDict { items, range: _ }) => Self::Dict(ExprDict { items: items.iter().map(ComparableDictItem::from).collect(), }), ast::Expr::Set(ast::ExprSet { elts, range: _ }) => Self::Set(ExprSet { elts: elts.iter().map(Into::into).collect(), }), ast::Expr::ListComp(ast::ExprListComp { elt, generators, range: _, }) => Self::ListComp(ExprListComp { elt: elt.into(), generators: generators.iter().map(Into::into).collect(), }), ast::Expr::SetComp(ast::ExprSetComp { elt, generators, range: _, }) => Self::SetComp(ExprSetComp { elt: elt.into(), generators: generators.iter().map(Into::into).collect(), }), ast::Expr::DictComp(ast::ExprDictComp { key, value, generators, range: _, }) => Self::DictComp(ExprDictComp { key: key.into(), value: value.into(), generators: generators.iter().map(Into::into).collect(), }), ast::Expr::Generator(ast::ExprGenerator { elt, generators, range: _, parenthesized: _, }) => Self::GeneratorExp(ExprGenerator { elt: elt.into(), generators: generators.iter().map(Into::into).collect(), }), ast::Expr::Await(ast::ExprAwait { value, range: _ }) => Self::Await(ExprAwait { value: value.into(), }), ast::Expr::Yield(ast::ExprYield { value, range: _ }) => Self::Yield(ExprYield { value: value.as_ref().map(Into::into), }), ast::Expr::YieldFrom(ast::ExprYieldFrom { value, range: _ }) => { Self::YieldFrom(ExprYieldFrom { value: value.into(), }) } ast::Expr::Compare(ast::ExprCompare { left, ops, comparators, range: _, }) => Self::Compare(ExprCompare { left: left.into(), ops: ops.iter().copied().map(Into::into).collect(), comparators: comparators.iter().map(Into::into).collect(), }), ast::Expr::Call(ast::ExprCall { func, arguments, range: _, }) => Self::Call(ExprCall { func: func.into(), arguments: arguments.into(), }), ast::Expr::FString(ast::ExprFString { value, range: _ }) => { Self::FString(ExprFString { value: value.into(), }) } ast::Expr::StringLiteral(ast::ExprStringLiteral { value, range: _ }) => { Self::StringLiteral(ExprStringLiteral { value: ComparableStringLiteral { value: value.to_str(), }, }) } ast::Expr::BytesLiteral(ast::ExprBytesLiteral { value, range: _ }) => { Self::BytesLiteral(ExprBytesLiteral { value: ComparableBytesLiteral { value: Cow::from(value), }, }) } ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value, range: _ }) => { Self::NumberLiteral(ExprNumberLiteral { value: value.into(), }) } ast::Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, range: _ }) => { Self::BoolLiteral(ExprBoolLiteral { value: *value }) } ast::Expr::NoneLiteral(_) => Self::NoneLiteral, ast::Expr::EllipsisLiteral(_) => Self::EllipsisLiteral, ast::Expr::Attribute(ast::ExprAttribute { value, attr, ctx: _, range: _, }) => Self::Attribute(ExprAttribute { value: value.into(), attr: attr.as_str(), }), ast::Expr::Subscript(ast::ExprSubscript { value, slice, ctx: _, range: _, }) => Self::Subscript(ExprSubscript { value: value.into(), slice: slice.into(), }), ast::Expr::Starred(ast::ExprStarred { value, ctx: _, range: _, }) => Self::Starred(ExprStarred { value: value.into(), }), ast::Expr::Name(name) => name.into(), ast::Expr::List(ast::ExprList { elts, ctx: _, range: _, }) => Self::List(ExprList { elts: elts.iter().map(Into::into).collect(), }), ast::Expr::Tuple(ast::ExprTuple { elts, ctx: _, range: _, parenthesized: _, }) => Self::Tuple(ExprTuple { elts: elts.iter().map(Into::into).collect(), }), ast::Expr::Slice(ast::ExprSlice { lower, upper, step, range: _, }) => Self::Slice(ExprSlice { lower: lower.as_ref().map(Into::into), upper: upper.as_ref().map(Into::into), step: step.as_ref().map(Into::into), }), ast::Expr::IpyEscapeCommand(ast::ExprIpyEscapeCommand { kind, value, range: _, }) => Self::IpyEscapeCommand(ExprIpyEscapeCommand { kind: *kind, value }), } } } impl<'a> From<&'a ast::ExprName> for ComparableExpr<'a> { fn from(expr: &'a ast::ExprName) -> Self { Self::Name(ExprName { id: expr.id.as_str(), }) } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtFunctionDef<'a> { is_async: bool, decorator_list: Vec>, name: &'a str, type_params: Option>, parameters: ComparableParameters<'a>, returns: Option>, body: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtClassDef<'a> { decorator_list: Vec>, name: &'a str, type_params: Option>, arguments: ComparableArguments<'a>, body: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtReturn<'a> { value: Option>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtDelete<'a> { targets: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtTypeAlias<'a> { pub name: Box>, pub type_params: Option>, pub value: Box>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableTypeParams<'a> { pub type_params: Vec>, } impl<'a> From<&'a ast::TypeParams> for ComparableTypeParams<'a> { fn from(type_params: &'a ast::TypeParams) -> Self { Self { type_params: type_params.iter().map(Into::into).collect(), } } } impl<'a> From<&'a Box> for ComparableTypeParams<'a> { fn from(type_params: &'a Box) -> Self { type_params.as_ref().into() } } #[derive(Debug, PartialEq, Eq, Hash)] pub enum ComparableTypeParam<'a> { TypeVar(TypeParamTypeVar<'a>), ParamSpec(TypeParamParamSpec<'a>), TypeVarTuple(TypeParamTypeVarTuple<'a>), } impl<'a> From<&'a ast::TypeParam> for ComparableTypeParam<'a> { fn from(type_param: &'a ast::TypeParam) -> Self { match type_param { ast::TypeParam::TypeVar(ast::TypeParamTypeVar { name, bound, default, range: _, }) => Self::TypeVar(TypeParamTypeVar { name: name.as_str(), bound: bound.as_ref().map(Into::into), default: default.as_ref().map(Into::into), }), ast::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple { name, default, range: _, }) => Self::TypeVarTuple(TypeParamTypeVarTuple { name: name.as_str(), default: default.as_ref().map(Into::into), }), ast::TypeParam::ParamSpec(ast::TypeParamParamSpec { name, default, range: _, }) => Self::ParamSpec(TypeParamParamSpec { name: name.as_str(), default: default.as_ref().map(Into::into), }), } } } #[derive(Debug, PartialEq, Eq, Hash)] pub struct TypeParamTypeVar<'a> { pub name: &'a str, pub bound: Option>>, pub default: Option>>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct TypeParamParamSpec<'a> { pub name: &'a str, pub default: Option>>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct TypeParamTypeVarTuple<'a> { pub name: &'a str, pub default: Option>>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtAssign<'a> { targets: Vec>, value: ComparableExpr<'a>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtAugAssign<'a> { target: ComparableExpr<'a>, op: ComparableOperator, value: ComparableExpr<'a>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtAnnAssign<'a> { target: ComparableExpr<'a>, annotation: ComparableExpr<'a>, value: Option>, simple: bool, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtFor<'a> { is_async: bool, target: ComparableExpr<'a>, iter: ComparableExpr<'a>, body: Vec>, orelse: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtWhile<'a> { test: ComparableExpr<'a>, body: Vec>, orelse: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtIf<'a> { test: ComparableExpr<'a>, body: Vec>, elif_else_clauses: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtWith<'a> { is_async: bool, items: Vec>, body: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtMatch<'a> { subject: ComparableExpr<'a>, cases: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtRaise<'a> { exc: Option>, cause: Option>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtTry<'a> { body: Vec>, handlers: Vec>, orelse: Vec>, finalbody: Vec>, is_star: bool, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtAssert<'a> { test: ComparableExpr<'a>, msg: Option>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtImport<'a> { names: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtImportFrom<'a> { module: Option<&'a str>, names: Vec>, level: u32, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtGlobal<'a> { names: Vec<&'a str>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtNonlocal<'a> { names: Vec<&'a str>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtExpr<'a> { value: ComparableExpr<'a>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct StmtIpyEscapeCommand<'a> { kind: ast::IpyEscapeKind, value: &'a str, } #[derive(Debug, PartialEq, Eq, Hash)] pub enum ComparableStmt<'a> { FunctionDef(StmtFunctionDef<'a>), ClassDef(StmtClassDef<'a>), Return(StmtReturn<'a>), Delete(StmtDelete<'a>), Assign(StmtAssign<'a>), AugAssign(StmtAugAssign<'a>), AnnAssign(StmtAnnAssign<'a>), For(StmtFor<'a>), While(StmtWhile<'a>), If(StmtIf<'a>), With(StmtWith<'a>), Match(StmtMatch<'a>), Raise(StmtRaise<'a>), Try(StmtTry<'a>), TypeAlias(StmtTypeAlias<'a>), Assert(StmtAssert<'a>), Import(StmtImport<'a>), ImportFrom(StmtImportFrom<'a>), Global(StmtGlobal<'a>), Nonlocal(StmtNonlocal<'a>), IpyEscapeCommand(StmtIpyEscapeCommand<'a>), Expr(StmtExpr<'a>), Pass, Break, Continue, } impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> { fn from(stmt: &'a ast::Stmt) -> Self { match stmt { ast::Stmt::FunctionDef(ast::StmtFunctionDef { is_async, name, parameters, body, decorator_list, returns, type_params, range: _, }) => Self::FunctionDef(StmtFunctionDef { is_async: *is_async, name: name.as_str(), parameters: parameters.into(), body: body.iter().map(Into::into).collect(), decorator_list: decorator_list.iter().map(Into::into).collect(), returns: returns.as_ref().map(Into::into), type_params: type_params.as_ref().map(Into::into), }), ast::Stmt::ClassDef(ast::StmtClassDef { name, arguments, body, decorator_list, type_params, range: _, }) => Self::ClassDef(StmtClassDef { name: name.as_str(), arguments: arguments.as_ref().map(Into::into).unwrap_or_default(), body: body.iter().map(Into::into).collect(), decorator_list: decorator_list.iter().map(Into::into).collect(), type_params: type_params.as_ref().map(Into::into), }), ast::Stmt::Return(ast::StmtReturn { value, range: _ }) => Self::Return(StmtReturn { value: value.as_ref().map(Into::into), }), ast::Stmt::Delete(ast::StmtDelete { targets, range: _ }) => Self::Delete(StmtDelete { targets: targets.iter().map(Into::into).collect(), }), ast::Stmt::TypeAlias(ast::StmtTypeAlias { range: _, name, type_params, value, }) => Self::TypeAlias(StmtTypeAlias { name: name.into(), type_params: type_params.as_ref().map(Into::into), value: value.into(), }), ast::Stmt::Assign(ast::StmtAssign { targets, value, range: _, }) => Self::Assign(StmtAssign { targets: targets.iter().map(Into::into).collect(), value: value.into(), }), ast::Stmt::AugAssign(ast::StmtAugAssign { target, op, value, range: _, }) => Self::AugAssign(StmtAugAssign { target: target.into(), op: (*op).into(), value: value.into(), }), ast::Stmt::AnnAssign(ast::StmtAnnAssign { target, annotation, value, simple, range: _, }) => Self::AnnAssign(StmtAnnAssign { target: target.into(), annotation: annotation.into(), value: value.as_ref().map(Into::into), simple: *simple, }), ast::Stmt::For(ast::StmtFor { is_async, target, iter, body, orelse, range: _, }) => Self::For(StmtFor { is_async: *is_async, target: target.into(), iter: iter.into(), body: body.iter().map(Into::into).collect(), orelse: orelse.iter().map(Into::into).collect(), }), ast::Stmt::While(ast::StmtWhile { test, body, orelse, range: _, }) => Self::While(StmtWhile { test: test.into(), body: body.iter().map(Into::into).collect(), orelse: orelse.iter().map(Into::into).collect(), }), ast::Stmt::If(ast::StmtIf { test, body, elif_else_clauses, range: _, }) => Self::If(StmtIf { test: test.into(), body: body.iter().map(Into::into).collect(), elif_else_clauses: elif_else_clauses.iter().map(Into::into).collect(), }), ast::Stmt::With(ast::StmtWith { is_async, items, body, range: _, }) => Self::With(StmtWith { is_async: *is_async, items: items.iter().map(Into::into).collect(), body: body.iter().map(Into::into).collect(), }), ast::Stmt::Match(ast::StmtMatch { subject, cases, range: _, }) => Self::Match(StmtMatch { subject: subject.into(), cases: cases.iter().map(Into::into).collect(), }), ast::Stmt::Raise(ast::StmtRaise { exc, cause, range: _, }) => Self::Raise(StmtRaise { exc: exc.as_ref().map(Into::into), cause: cause.as_ref().map(Into::into), }), ast::Stmt::Try(ast::StmtTry { body, handlers, orelse, finalbody, is_star, range: _, }) => Self::Try(StmtTry { body: body.iter().map(Into::into).collect(), handlers: handlers.iter().map(Into::into).collect(), orelse: orelse.iter().map(Into::into).collect(), finalbody: finalbody.iter().map(Into::into).collect(), is_star: *is_star, }), ast::Stmt::Assert(ast::StmtAssert { test, msg, range: _, }) => Self::Assert(StmtAssert { test: test.into(), msg: msg.as_ref().map(Into::into), }), ast::Stmt::Import(ast::StmtImport { names, range: _ }) => Self::Import(StmtImport { names: names.iter().map(Into::into).collect(), }), ast::Stmt::ImportFrom(ast::StmtImportFrom { module, names, level, range: _, }) => Self::ImportFrom(StmtImportFrom { module: module.as_deref(), names: names.iter().map(Into::into).collect(), level: *level, }), ast::Stmt::Global(ast::StmtGlobal { names, range: _ }) => Self::Global(StmtGlobal { names: names.iter().map(ast::Identifier::as_str).collect(), }), ast::Stmt::Nonlocal(ast::StmtNonlocal { names, range: _ }) => { Self::Nonlocal(StmtNonlocal { names: names.iter().map(ast::Identifier::as_str).collect(), }) } ast::Stmt::IpyEscapeCommand(ast::StmtIpyEscapeCommand { kind, value, range: _, }) => Self::IpyEscapeCommand(StmtIpyEscapeCommand { kind: *kind, value }), ast::Stmt::Expr(ast::StmtExpr { value, range: _ }) => Self::Expr(StmtExpr { value: value.into(), }), ast::Stmt::Pass(_) => Self::Pass, ast::Stmt::Break(_) => Self::Break, ast::Stmt::Continue(_) => Self::Continue, } } } #[derive(Debug, PartialEq, Eq, Hash)] pub enum ComparableMod<'a> { Module(ComparableModModule<'a>), Expression(ComparableModExpression<'a>), } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableModModule<'a> { body: Vec>, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct ComparableModExpression<'a> { body: Box>, } impl<'a> From<&'a ast::Mod> for ComparableMod<'a> { fn from(mod_: &'a ast::Mod) -> Self { match mod_ { ast::Mod::Module(module) => Self::Module(module.into()), ast::Mod::Expression(expr) => Self::Expression(expr.into()), } } } impl<'a> From<&'a ast::ModModule> for ComparableModModule<'a> { fn from(module: &'a ast::ModModule) -> Self { Self { body: module.body.iter().map(Into::into).collect(), } } } impl<'a> From<&'a ast::ModExpression> for ComparableModExpression<'a> { fn from(expr: &'a ast::ModExpression) -> Self { Self { body: (&expr.body).into(), } } } /// Wrapper around [`Expr`] that implements [`Hash`] and [`PartialEq`] according to Python /// semantics: /// /// > Values that compare equal (such as 1, 1.0, and True) can be used interchangeably to index the /// > same dictionary entry. /// /// For example, considers `True`, `1`, and `1.0` to be equal, as they hash to the same value /// in Python, along with `False`, `0`, and `0.0`. /// /// See: #[derive(Debug)] pub struct HashableExpr<'a>(ComparableExpr<'a>); impl Hash for HashableExpr<'_> { fn hash(&self, state: &mut H) { self.0.hash(state); } } impl PartialEq for HashableExpr<'_> { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } impl Eq for HashableExpr<'_> {} impl<'a> From<&'a Expr> for HashableExpr<'a> { fn from(expr: &'a Expr) -> Self { /// Returns a version of the given expression that can be hashed and compared according to /// Python semantics. fn as_hashable(expr: &Expr) -> ComparableExpr { match expr { Expr::Named(named) => ComparableExpr::NamedExpr(ExprNamed { target: Box::new(ComparableExpr::from(&named.target)), value: Box::new(as_hashable(&named.value)), }), Expr::NumberLiteral(number) => as_bool(number) .map(|value| ComparableExpr::BoolLiteral(ExprBoolLiteral { value })) .unwrap_or_else(|| ComparableExpr::from(expr)), Expr::Tuple(tuple) => ComparableExpr::Tuple(ExprTuple { elts: tuple.iter().map(as_hashable).collect(), }), _ => ComparableExpr::from(expr), } } /// Returns the `bool` value of the given expression, if it has an equivalent hash to /// `True` or `False`. fn as_bool(number: &crate::ExprNumberLiteral) -> Option { match &number.value { Number::Int(int) => match int.as_u8() { Some(0) => Some(false), Some(1) => Some(true), _ => None, }, Number::Float(float) => match float { 0.0 => Some(false), 1.0 => Some(true), _ => None, }, Number::Complex { real, imag } => match (real, imag) { (0.0, 0.0) => Some(false), (1.0, 0.0) => Some(true), _ => None, }, } } Self(as_hashable(expr)) } }