Skip to main content

ruff_python_ast/
comparable.rs

1//! An equivalent object hierarchy to the `RustPython` AST hierarchy, but with the
2//! ability to compare expressions for equality (via [`Eq`] and [`Hash`]).
3//!
4//! Two [`ComparableExpr`]s are considered equal if the underlying AST nodes have the
5//! same shape, ignoring trivia (e.g., parentheses, comments, and whitespace), the
6//! location in the source code, and other contextual information (e.g., whether they
7//! represent reads or writes, which is typically encoded in the Python AST).
8//!
9//! For example, in `[(a, b) for a, b in c]`, the `(a, b)` and `a, b` expressions are
10//! considered equal, despite the former being parenthesized, and despite the former
11//! being a write ([`ast::ExprContext::Store`]) and the latter being a read
12//! ([`ast::ExprContext::Load`]).
13//!
14//! Similarly, `"a" "b"` and `"ab"` would be considered equal, despite the former being
15//! an implicit concatenation of string literals, as these expressions are considered to
16//! have the same shape in that they evaluate to the same value.
17
18use crate as ast;
19use crate::{Expr, Number};
20use std::borrow::Cow;
21use std::hash::Hash;
22
23#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
24pub enum ComparableBoolOp {
25    And,
26    Or,
27}
28
29impl From<ast::BoolOp> for ComparableBoolOp {
30    fn from(op: ast::BoolOp) -> Self {
31        match op {
32            ast::BoolOp::And => Self::And,
33            ast::BoolOp::Or => Self::Or,
34        }
35    }
36}
37
38#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
39pub enum ComparableOperator {
40    Add,
41    Sub,
42    Mult,
43    MatMult,
44    Div,
45    Mod,
46    Pow,
47    LShift,
48    RShift,
49    BitOr,
50    BitXor,
51    BitAnd,
52    FloorDiv,
53}
54
55impl From<ast::Operator> for ComparableOperator {
56    fn from(op: ast::Operator) -> Self {
57        match op {
58            ast::Operator::Add => Self::Add,
59            ast::Operator::Sub => Self::Sub,
60            ast::Operator::Mult => Self::Mult,
61            ast::Operator::MatMult => Self::MatMult,
62            ast::Operator::Div => Self::Div,
63            ast::Operator::Mod => Self::Mod,
64            ast::Operator::Pow => Self::Pow,
65            ast::Operator::LShift => Self::LShift,
66            ast::Operator::RShift => Self::RShift,
67            ast::Operator::BitOr => Self::BitOr,
68            ast::Operator::BitXor => Self::BitXor,
69            ast::Operator::BitAnd => Self::BitAnd,
70            ast::Operator::FloorDiv => Self::FloorDiv,
71        }
72    }
73}
74
75#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
76pub enum ComparableUnaryOp {
77    Invert,
78    Not,
79    UAdd,
80    USub,
81}
82
83impl From<ast::UnaryOp> for ComparableUnaryOp {
84    fn from(op: ast::UnaryOp) -> Self {
85        match op {
86            ast::UnaryOp::Invert => Self::Invert,
87            ast::UnaryOp::Not => Self::Not,
88            ast::UnaryOp::UAdd => Self::UAdd,
89            ast::UnaryOp::USub => Self::USub,
90        }
91    }
92}
93
94#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
95pub enum ComparableCmpOp {
96    Eq,
97    NotEq,
98    Lt,
99    LtE,
100    Gt,
101    GtE,
102    Is,
103    IsNot,
104    In,
105    NotIn,
106}
107
108impl From<ast::CmpOp> for ComparableCmpOp {
109    fn from(op: ast::CmpOp) -> Self {
110        match op {
111            ast::CmpOp::Eq => Self::Eq,
112            ast::CmpOp::NotEq => Self::NotEq,
113            ast::CmpOp::Lt => Self::Lt,
114            ast::CmpOp::LtE => Self::LtE,
115            ast::CmpOp::Gt => Self::Gt,
116            ast::CmpOp::GtE => Self::GtE,
117            ast::CmpOp::Is => Self::Is,
118            ast::CmpOp::IsNot => Self::IsNot,
119            ast::CmpOp::In => Self::In,
120            ast::CmpOp::NotIn => Self::NotIn,
121        }
122    }
123}
124
125#[derive(Debug, PartialEq, Eq, Hash)]
126pub struct ComparableAlias<'a> {
127    name: &'a str,
128    asname: Option<&'a str>,
129}
130
131impl<'a> From<&'a ast::Alias> for ComparableAlias<'a> {
132    fn from(alias: &'a ast::Alias) -> Self {
133        Self {
134            name: alias.name.as_str(),
135            asname: alias.asname.as_deref(),
136        }
137    }
138}
139
140#[derive(Debug, PartialEq, Eq, Hash)]
141pub struct ComparableWithItem<'a> {
142    context_expr: ComparableExpr<'a>,
143    optional_vars: Option<ComparableExpr<'a>>,
144}
145
146impl<'a> From<&'a ast::WithItem> for ComparableWithItem<'a> {
147    fn from(with_item: &'a ast::WithItem) -> Self {
148        Self {
149            context_expr: (&with_item.context_expr).into(),
150            optional_vars: with_item.optional_vars.as_ref().map(Into::into),
151        }
152    }
153}
154
155#[derive(Debug, PartialEq, Eq, Hash)]
156pub struct ComparablePatternArguments<'a> {
157    patterns: Vec<ComparablePattern<'a>>,
158    keywords: Vec<ComparablePatternKeyword<'a>>,
159}
160
161impl<'a> From<&'a ast::PatternArguments> for ComparablePatternArguments<'a> {
162    fn from(parameters: &'a ast::PatternArguments) -> Self {
163        Self {
164            patterns: parameters.patterns.iter().map(Into::into).collect(),
165            keywords: parameters.keywords.iter().map(Into::into).collect(),
166        }
167    }
168}
169
170#[derive(Debug, PartialEq, Eq, Hash)]
171pub struct ComparablePatternKeyword<'a> {
172    attr: &'a str,
173    pattern: ComparablePattern<'a>,
174}
175
176impl<'a> From<&'a ast::PatternKeyword> for ComparablePatternKeyword<'a> {
177    fn from(keyword: &'a ast::PatternKeyword) -> Self {
178        Self {
179            attr: keyword.attr.as_str(),
180            pattern: (&keyword.pattern).into(),
181        }
182    }
183}
184
185#[derive(Debug, PartialEq, Eq, Hash)]
186pub struct PatternMatchValue<'a> {
187    value: ComparableExpr<'a>,
188}
189
190#[derive(Debug, PartialEq, Eq, Hash)]
191pub struct PatternMatchSingleton {
192    value: ComparableSingleton,
193}
194
195#[derive(Debug, PartialEq, Eq, Hash)]
196pub struct PatternMatchSequence<'a> {
197    patterns: Vec<ComparablePattern<'a>>,
198}
199
200#[derive(Debug, PartialEq, Eq, Hash)]
201pub struct PatternMatchMapping<'a> {
202    keys: Vec<ComparableExpr<'a>>,
203    patterns: Vec<ComparablePattern<'a>>,
204    rest: Option<&'a str>,
205}
206
207#[derive(Debug, PartialEq, Eq, Hash)]
208pub struct PatternMatchClass<'a> {
209    cls: ComparableExpr<'a>,
210    arguments: ComparablePatternArguments<'a>,
211}
212
213#[derive(Debug, PartialEq, Eq, Hash)]
214pub struct PatternMatchStar<'a> {
215    name: Option<&'a str>,
216}
217
218#[derive(Debug, PartialEq, Eq, Hash)]
219pub struct PatternMatchAs<'a> {
220    pattern: Option<Box<ComparablePattern<'a>>>,
221    name: Option<&'a str>,
222}
223
224#[derive(Debug, PartialEq, Eq, Hash)]
225pub struct PatternMatchOr<'a> {
226    patterns: Vec<ComparablePattern<'a>>,
227}
228
229#[derive(Debug, PartialEq, Eq, Hash)]
230pub enum ComparablePattern<'a> {
231    MatchValue(PatternMatchValue<'a>),
232    MatchSingleton(PatternMatchSingleton),
233    MatchSequence(PatternMatchSequence<'a>),
234    MatchMapping(PatternMatchMapping<'a>),
235    MatchClass(PatternMatchClass<'a>),
236    MatchStar(PatternMatchStar<'a>),
237    MatchAs(PatternMatchAs<'a>),
238    MatchOr(PatternMatchOr<'a>),
239}
240
241impl<'a> From<&'a ast::Pattern> for ComparablePattern<'a> {
242    fn from(pattern: &'a ast::Pattern) -> Self {
243        match pattern {
244            ast::Pattern::MatchValue(ast::PatternMatchValue { value, .. }) => {
245                Self::MatchValue(PatternMatchValue {
246                    value: value.into(),
247                })
248            }
249            ast::Pattern::MatchSingleton(ast::PatternMatchSingleton { value, .. }) => {
250                Self::MatchSingleton(PatternMatchSingleton {
251                    value: value.into(),
252                })
253            }
254            ast::Pattern::MatchSequence(ast::PatternMatchSequence { patterns, .. }) => {
255                Self::MatchSequence(PatternMatchSequence {
256                    patterns: patterns.iter().map(Into::into).collect(),
257                })
258            }
259            ast::Pattern::MatchMapping(ast::PatternMatchMapping {
260                keys,
261                patterns,
262                rest,
263                ..
264            }) => Self::MatchMapping(PatternMatchMapping {
265                keys: keys.iter().map(Into::into).collect(),
266                patterns: patterns.iter().map(Into::into).collect(),
267                rest: rest.as_deref(),
268            }),
269            ast::Pattern::MatchClass(ast::PatternMatchClass { cls, arguments, .. }) => {
270                Self::MatchClass(PatternMatchClass {
271                    cls: cls.into(),
272                    arguments: arguments.into(),
273                })
274            }
275            ast::Pattern::MatchStar(ast::PatternMatchStar { name, .. }) => {
276                Self::MatchStar(PatternMatchStar {
277                    name: name.as_deref(),
278                })
279            }
280            ast::Pattern::MatchAs(ast::PatternMatchAs { pattern, name, .. }) => {
281                Self::MatchAs(PatternMatchAs {
282                    pattern: pattern.as_ref().map(Into::into),
283                    name: name.as_deref(),
284                })
285            }
286            ast::Pattern::MatchOr(ast::PatternMatchOr { patterns, .. }) => {
287                Self::MatchOr(PatternMatchOr {
288                    patterns: patterns.iter().map(Into::into).collect(),
289                })
290            }
291        }
292    }
293}
294
295impl<'a> From<&'a Box<ast::Pattern>> for Box<ComparablePattern<'a>> {
296    fn from(pattern: &'a Box<ast::Pattern>) -> Self {
297        Box::new((pattern.as_ref()).into())
298    }
299}
300
301#[derive(Debug, PartialEq, Eq, Hash)]
302pub struct ComparableMatchCase<'a> {
303    pattern: ComparablePattern<'a>,
304    guard: Option<ComparableExpr<'a>>,
305    body: Vec<ComparableStmt<'a>>,
306}
307
308impl<'a> From<&'a ast::MatchCase> for ComparableMatchCase<'a> {
309    fn from(match_case: &'a ast::MatchCase) -> Self {
310        Self {
311            pattern: (&match_case.pattern).into(),
312            guard: match_case.guard.as_ref().map(Into::into),
313            body: match_case.body.iter().map(Into::into).collect(),
314        }
315    }
316}
317
318#[derive(Debug, PartialEq, Eq, Hash)]
319pub struct ComparableDecorator<'a> {
320    expression: ComparableExpr<'a>,
321}
322
323impl<'a> From<&'a ast::Decorator> for ComparableDecorator<'a> {
324    fn from(decorator: &'a ast::Decorator) -> Self {
325        Self {
326            expression: (&decorator.expression).into(),
327        }
328    }
329}
330
331#[derive(Debug, PartialEq, Eq, Hash)]
332pub enum ComparableSingleton {
333    None,
334    True,
335    False,
336}
337
338impl From<&ast::Singleton> for ComparableSingleton {
339    fn from(singleton: &ast::Singleton) -> Self {
340        match singleton {
341            ast::Singleton::None => Self::None,
342            ast::Singleton::True => Self::True,
343            ast::Singleton::False => Self::False,
344        }
345    }
346}
347
348#[derive(Debug, PartialEq, Eq, Hash)]
349pub enum ComparableNumber<'a> {
350    Int(&'a ast::Int),
351    Float(u64),
352    Complex { real: u64, imag: u64 },
353}
354
355impl<'a> From<&'a ast::Number> for ComparableNumber<'a> {
356    fn from(number: &'a ast::Number) -> Self {
357        match number {
358            ast::Number::Int(value) => Self::Int(value),
359            ast::Number::Float(value) => Self::Float(value.to_bits()),
360            ast::Number::Complex { real, imag } => Self::Complex {
361                real: real.to_bits(),
362                imag: imag.to_bits(),
363            },
364        }
365    }
366}
367
368#[derive(Debug, Default, PartialEq, Eq, Hash)]
369pub struct ComparableArguments<'a> {
370    args: Vec<ComparableExpr<'a>>,
371    keywords: Vec<ComparableKeyword<'a>>,
372}
373
374impl<'a> From<&'a ast::Arguments> for ComparableArguments<'a> {
375    fn from(arguments: &'a ast::Arguments) -> Self {
376        Self {
377            args: arguments.args.iter().map(Into::into).collect(),
378            keywords: arguments.keywords.iter().map(Into::into).collect(),
379        }
380    }
381}
382
383impl<'a> From<&'a Box<ast::Arguments>> for ComparableArguments<'a> {
384    fn from(arguments: &'a Box<ast::Arguments>) -> Self {
385        (arguments.as_ref()).into()
386    }
387}
388
389#[derive(Debug, PartialEq, Eq, Hash)]
390pub struct ComparableParameters<'a> {
391    posonlyargs: Vec<ComparableParameterWithDefault<'a>>,
392    args: Vec<ComparableParameterWithDefault<'a>>,
393    vararg: Option<ComparableParameter<'a>>,
394    kwonlyargs: Vec<ComparableParameterWithDefault<'a>>,
395    kwarg: Option<ComparableParameter<'a>>,
396}
397
398impl<'a> From<&'a ast::Parameters> for ComparableParameters<'a> {
399    fn from(parameters: &'a ast::Parameters) -> Self {
400        Self {
401            posonlyargs: parameters.posonlyargs.iter().map(Into::into).collect(),
402            args: parameters.args.iter().map(Into::into).collect(),
403            vararg: parameters.vararg.as_ref().map(Into::into),
404            kwonlyargs: parameters.kwonlyargs.iter().map(Into::into).collect(),
405            kwarg: parameters.kwarg.as_ref().map(Into::into),
406        }
407    }
408}
409
410impl<'a> From<&'a Box<ast::Parameters>> for ComparableParameters<'a> {
411    fn from(parameters: &'a Box<ast::Parameters>) -> Self {
412        (parameters.as_ref()).into()
413    }
414}
415
416impl<'a> From<&'a Box<ast::Parameter>> for ComparableParameter<'a> {
417    fn from(arg: &'a Box<ast::Parameter>) -> Self {
418        (arg.as_ref()).into()
419    }
420}
421
422#[derive(Debug, PartialEq, Eq, Hash)]
423pub struct ComparableParameter<'a> {
424    arg: &'a str,
425    annotation: Option<Box<ComparableExpr<'a>>>,
426}
427
428impl<'a> From<&'a ast::Parameter> for ComparableParameter<'a> {
429    fn from(arg: &'a ast::Parameter) -> Self {
430        Self {
431            arg: arg.name.as_str(),
432            annotation: arg.annotation.as_ref().map(Into::into),
433        }
434    }
435}
436
437#[derive(Debug, PartialEq, Eq, Hash)]
438pub struct ComparableParameterWithDefault<'a> {
439    def: ComparableParameter<'a>,
440    default: Option<ComparableExpr<'a>>,
441}
442
443impl<'a> From<&'a ast::ParameterWithDefault> for ComparableParameterWithDefault<'a> {
444    fn from(arg: &'a ast::ParameterWithDefault) -> Self {
445        Self {
446            def: (&arg.parameter).into(),
447            default: arg.default.as_ref().map(Into::into),
448        }
449    }
450}
451
452#[derive(Debug, PartialEq, Eq, Hash)]
453pub struct ComparableKeyword<'a> {
454    arg: Option<&'a str>,
455    value: ComparableExpr<'a>,
456}
457
458impl<'a> From<&'a ast::Keyword> for ComparableKeyword<'a> {
459    fn from(keyword: &'a ast::Keyword) -> Self {
460        Self {
461            arg: keyword.arg.as_ref().map(ast::Identifier::as_str),
462            value: (&keyword.value).into(),
463        }
464    }
465}
466
467#[derive(Debug, PartialEq, Eq, Hash)]
468pub struct ComparableComprehension<'a> {
469    target: ComparableExpr<'a>,
470    iter: ComparableExpr<'a>,
471    ifs: Vec<ComparableExpr<'a>>,
472    is_async: bool,
473}
474
475impl<'a> From<&'a ast::Comprehension> for ComparableComprehension<'a> {
476    fn from(comprehension: &'a ast::Comprehension) -> Self {
477        Self {
478            target: (&comprehension.target).into(),
479            iter: (&comprehension.iter).into(),
480            ifs: comprehension.ifs.iter().map(Into::into).collect(),
481            is_async: comprehension.is_async,
482        }
483    }
484}
485
486#[derive(Debug, PartialEq, Eq, Hash)]
487pub struct ExceptHandlerExceptHandler<'a> {
488    type_: Option<Box<ComparableExpr<'a>>>,
489    name: Option<&'a str>,
490    body: Vec<ComparableStmt<'a>>,
491}
492
493#[derive(Debug, PartialEq, Eq, Hash)]
494pub enum ComparableExceptHandler<'a> {
495    ExceptHandler(ExceptHandlerExceptHandler<'a>),
496}
497
498impl<'a> From<&'a ast::ExceptHandler> for ComparableExceptHandler<'a> {
499    fn from(except_handler: &'a ast::ExceptHandler) -> Self {
500        let ast::ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
501            type_,
502            name,
503            body,
504            ..
505        }) = except_handler;
506        Self::ExceptHandler(ExceptHandlerExceptHandler {
507            type_: type_.as_ref().map(Into::into),
508            name: name.as_deref(),
509            body: body.iter().map(Into::into).collect(),
510        })
511    }
512}
513
514#[derive(Debug, PartialEq, Eq, Hash)]
515pub enum ComparableInterpolatedStringElement<'a> {
516    Literal(Cow<'a, str>),
517    InterpolatedElement(InterpolatedElement<'a>),
518}
519
520/// Comparable wrapper for [`ast::DebugText`].
521///
522/// Compares the full debug text (leading + expression source + trailing) rather than only the
523/// expression source, because whitespace is part of the f-string's runtime output: `f"{x =}"`
524/// produces `"x =<value>"` while `f"{x=}"` produces `"x=<value>"`, making them distinct
525/// `Literal` types.
526#[derive(Debug, PartialEq, Eq, Hash)]
527pub struct ComparableDebugText<'a> {
528    text: Cow<'a, str>,
529}
530
531impl<'a> From<&'a ast::DebugText> for ComparableDebugText<'a> {
532    fn from(debug_text: &'a ast::DebugText) -> Self {
533        // Normalizing newlines is safe because Python normalizes `\r\n` and `\r` to `\n`
534        // at compile time, so they produce identical runtime values.
535        Self {
536            text: normalize_newlines(debug_text.as_str()),
537        }
538    }
539}
540
541fn normalize_newlines(contents: &str) -> Cow<'_, str> {
542    if contents.contains('\r') {
543        Cow::Owned(contents.replace("\r\n", "\n").replace('\r', "\n"))
544    } else {
545        Cow::Borrowed(contents)
546    }
547}
548
549#[derive(Debug, PartialEq, Eq, Hash)]
550pub struct InterpolatedElement<'a> {
551    expression: ComparableExpr<'a>,
552    debug_text: Option<ComparableDebugText<'a>>,
553    conversion: ast::ConversionFlag,
554    format_spec: Option<Vec<ComparableInterpolatedStringElement<'a>>>,
555}
556
557impl<'a> From<&'a ast::InterpolatedStringElement> for ComparableInterpolatedStringElement<'a> {
558    fn from(interpolated_string_element: &'a ast::InterpolatedStringElement) -> Self {
559        match interpolated_string_element {
560            ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement {
561                value,
562                ..
563            }) => Self::Literal(value.as_ref().into()),
564            ast::InterpolatedStringElement::Interpolation(formatted_value) => {
565                formatted_value.into()
566            }
567        }
568    }
569}
570
571impl<'a> From<&'a ast::InterpolatedElement> for InterpolatedElement<'a> {
572    fn from(interpolated_element: &'a ast::InterpolatedElement) -> Self {
573        let ast::InterpolatedElement {
574            expression,
575            debug_text,
576            conversion,
577            format_spec,
578            range: _,
579            node_index: _,
580        } = interpolated_element;
581
582        Self {
583            expression: (expression).into(),
584            debug_text: debug_text.as_ref().map(Into::into),
585            conversion: *conversion,
586            format_spec: format_spec
587                .as_ref()
588                .map(|spec| spec.elements.iter().map(Into::into).collect()),
589        }
590    }
591}
592
593impl<'a> From<&'a ast::InterpolatedElement> for ComparableInterpolatedStringElement<'a> {
594    fn from(interpolated_element: &'a ast::InterpolatedElement) -> Self {
595        Self::InterpolatedElement(interpolated_element.into())
596    }
597}
598
599#[derive(Debug, PartialEq, Eq, Hash)]
600pub struct ComparableElifElseClause<'a> {
601    test: Option<ComparableExpr<'a>>,
602    body: Vec<ComparableStmt<'a>>,
603}
604
605impl<'a> From<&'a ast::ElifElseClause> for ComparableElifElseClause<'a> {
606    fn from(elif_else_clause: &'a ast::ElifElseClause) -> Self {
607        let ast::ElifElseClause {
608            range: _,
609            node_index: _,
610            test,
611            body,
612        } = elif_else_clause;
613        Self {
614            test: test.as_ref().map(Into::into),
615            body: body.iter().map(Into::into).collect(),
616        }
617    }
618}
619
620#[derive(Debug, PartialEq, Eq, Hash)]
621pub enum ComparableLiteral<'a> {
622    None,
623    Ellipsis,
624    Bool(&'a bool),
625    Str(Vec<ComparableStringLiteral<'a>>),
626    Bytes(Vec<ComparableBytesLiteral<'a>>),
627    Number(ComparableNumber<'a>),
628}
629
630impl<'a> From<ast::LiteralExpressionRef<'a>> for ComparableLiteral<'a> {
631    fn from(literal: ast::LiteralExpressionRef<'a>) -> Self {
632        match literal {
633            ast::LiteralExpressionRef::NoneLiteral(_) => Self::None,
634            ast::LiteralExpressionRef::EllipsisLiteral(_) => Self::Ellipsis,
635            ast::LiteralExpressionRef::BooleanLiteral(ast::ExprBooleanLiteral {
636                value, ..
637            }) => Self::Bool(value),
638            ast::LiteralExpressionRef::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
639                Self::Str(value.iter().map(Into::into).collect())
640            }
641            ast::LiteralExpressionRef::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
642                Self::Bytes(value.iter().map(Into::into).collect())
643            }
644            ast::LiteralExpressionRef::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
645                Self::Number(value.into())
646            }
647        }
648    }
649}
650
651#[derive(Debug, PartialEq, Eq, Hash)]
652pub struct ComparableFString<'a> {
653    elements: Box<[ComparableInterpolatedStringElement<'a>]>,
654}
655
656impl<'a> From<&'a ast::FStringValue> for ComparableFString<'a> {
657    // The approach below is somewhat complicated, so it may
658    // require some justification.
659    //
660    // Suppose given an f-string of the form
661    // `f"{foo!r} one" " and two " f" and three {bar!s}"`
662    // This decomposes as:
663    // - An `FStringPart::FString`, `f"{foo!r} one"` with elements
664    //      - `FStringElement::Expression` encoding `{foo!r}`
665    //      - `FStringElement::Literal` encoding " one"
666    // - An `FStringPart::Literal` capturing `" and two "`
667    // - An `FStringPart::FString`, `f" and three {bar!s}"` with elements
668    //      - `FStringElement::Literal` encoding " and three "
669    //      - `FStringElement::Expression` encoding `{bar!s}`
670    //
671    // We would like to extract from this a vector of (comparable) f-string
672    // _elements_ which alternate between expression elements and literal
673    // elements. In order to do so, we need to concatenate adjacent string
674    // literals. String literals may be separated for two reasons: either
675    // they appear in adjacent string literal parts, or else a string literal
676    // part is adjacent to a string literal _element_ inside of an f-string part.
677    fn from(value: &'a ast::FStringValue) -> Self {
678        #[derive(Default)]
679        struct Collector<'a> {
680            elements: Vec<ComparableInterpolatedStringElement<'a>>,
681        }
682
683        impl<'a> Collector<'a> {
684            // The logic for concatenating adjacent string literals
685            // occurs here, implicitly: when we encounter a sequence
686            // of string literals, the first gets pushed to the
687            // `elements` vector, while subsequent strings
688            // are concatenated onto this top string.
689            fn push_literal(&mut self, literal: &'a str) {
690                if let Some(ComparableInterpolatedStringElement::Literal(existing_literal)) =
691                    self.elements.last_mut()
692                {
693                    existing_literal.to_mut().push_str(literal);
694                } else {
695                    self.elements
696                        .push(ComparableInterpolatedStringElement::Literal(literal.into()));
697                }
698            }
699
700            fn push_expression(&mut self, expression: &'a ast::InterpolatedElement) {
701                self.elements.push(expression.into());
702            }
703        }
704
705        let mut collector = Collector::default();
706
707        for part in value {
708            match part {
709                ast::FStringPart::Literal(string_literal) => {
710                    collector.push_literal(&string_literal.value);
711                }
712                ast::FStringPart::FString(fstring) => {
713                    for element in &fstring.elements {
714                        match element {
715                            ast::InterpolatedStringElement::Literal(literal) => {
716                                collector.push_literal(&literal.value);
717                            }
718                            ast::InterpolatedStringElement::Interpolation(expression) => {
719                                collector.push_expression(expression);
720                            }
721                        }
722                    }
723                }
724            }
725        }
726
727        Self {
728            elements: collector.elements.into_boxed_slice(),
729        }
730    }
731}
732
733#[derive(Debug, PartialEq, Eq, Hash)]
734pub struct ComparableTString<'a> {
735    strings: Box<[ComparableInterpolatedStringElement<'a>]>,
736    interpolations: Box<[InterpolatedElement<'a>]>,
737}
738
739impl<'a> From<&'a ast::TStringValue> for ComparableTString<'a> {
740    // We model a [`ComparableTString`] on the actual
741    // [CPython implementation] of a `string.templatelib.Template` object.
742    //
743    // As in CPython, we must be careful to ensure that the length
744    // of `strings` is always one more than the length of `interpolations` -
745    // that way we can recover the original reading order by interleaving
746    // starting with `strings`. This is how we can tell the
747    // difference between, e.g. `t"{foo}bar"` and `t"bar{foo}"`.
748    //
749    // - [CPython implementation](https://github.com/python/cpython/blob/c91ad5da9d92eac4718e4da8d53689c3cc24535e/Python/codegen.c#L4052-L4103)
750    fn from(value: &'a ast::TStringValue) -> Self {
751        struct Collector<'a> {
752            strings: Vec<ComparableInterpolatedStringElement<'a>>,
753            interpolations: Vec<InterpolatedElement<'a>>,
754        }
755
756        impl Default for Collector<'_> {
757            fn default() -> Self {
758                Self {
759                    strings: vec![ComparableInterpolatedStringElement::Literal("".into())],
760                    interpolations: vec![],
761                }
762            }
763        }
764
765        impl<'a> Collector<'a> {
766            // The logic for concatenating adjacent string literals
767            // occurs here, implicitly: when we encounter a sequence
768            // of string literals, the first gets pushed to the
769            // `strings` vector, while subsequent strings
770            // are concatenated onto this top string.
771            fn push_literal(&mut self, literal: &'a str) {
772                if let Some(ComparableInterpolatedStringElement::Literal(existing_literal)) =
773                    self.strings.last_mut()
774                {
775                    existing_literal.to_mut().push_str(literal);
776                } else {
777                    self.strings
778                        .push(ComparableInterpolatedStringElement::Literal(literal.into()));
779                }
780            }
781
782            fn start_new_literal(&mut self) {
783                self.strings
784                    .push(ComparableInterpolatedStringElement::Literal("".into()));
785            }
786
787            fn push_tstring_interpolation(&mut self, expression: &'a ast::InterpolatedElement) {
788                self.interpolations.push(expression.into());
789                self.start_new_literal();
790            }
791        }
792
793        let mut collector = Collector::default();
794
795        for element in value.elements() {
796            match element {
797                ast::InterpolatedStringElement::Literal(literal) => {
798                    collector.push_literal(&literal.value);
799                }
800                ast::InterpolatedStringElement::Interpolation(interpolation) => {
801                    collector.push_tstring_interpolation(interpolation);
802                }
803            }
804        }
805
806        Self {
807            strings: collector.strings.into_boxed_slice(),
808            interpolations: collector.interpolations.into_boxed_slice(),
809        }
810    }
811}
812
813#[derive(Debug, PartialEq, Eq, Hash)]
814pub struct ComparableStringLiteral<'a> {
815    value: &'a str,
816}
817
818impl<'a> From<&'a ast::StringLiteral> for ComparableStringLiteral<'a> {
819    fn from(string_literal: &'a ast::StringLiteral) -> Self {
820        Self {
821            value: &string_literal.value,
822        }
823    }
824}
825
826#[derive(Debug, PartialEq, Eq, Hash)]
827pub struct ComparableBytesLiteral<'a> {
828    value: Cow<'a, [u8]>,
829}
830
831impl<'a> From<&'a ast::BytesLiteral> for ComparableBytesLiteral<'a> {
832    fn from(bytes_literal: &'a ast::BytesLiteral) -> Self {
833        Self {
834            value: Cow::Borrowed(&bytes_literal.value),
835        }
836    }
837}
838
839#[derive(Debug, PartialEq, Eq, Hash)]
840pub struct ExprBoolOp<'a> {
841    op: ComparableBoolOp,
842    values: Vec<ComparableExpr<'a>>,
843}
844
845#[derive(Debug, PartialEq, Eq, Hash)]
846pub struct ExprNamed<'a> {
847    target: Box<ComparableExpr<'a>>,
848    value: Box<ComparableExpr<'a>>,
849}
850
851#[derive(Debug, PartialEq, Eq, Hash)]
852pub struct ExprBinOp<'a> {
853    left: Box<ComparableExpr<'a>>,
854    op: ComparableOperator,
855    right: Box<ComparableExpr<'a>>,
856}
857
858#[derive(Debug, PartialEq, Eq, Hash)]
859pub struct ExprUnaryOp<'a> {
860    op: ComparableUnaryOp,
861    operand: Box<ComparableExpr<'a>>,
862}
863
864#[derive(Debug, PartialEq, Eq, Hash)]
865pub struct ExprLambda<'a> {
866    parameters: Option<ComparableParameters<'a>>,
867    body: Box<ComparableExpr<'a>>,
868}
869
870#[derive(Debug, PartialEq, Eq, Hash)]
871pub struct ExprIf<'a> {
872    test: Box<ComparableExpr<'a>>,
873    body: Box<ComparableExpr<'a>>,
874    orelse: Box<ComparableExpr<'a>>,
875}
876
877#[derive(Debug, PartialEq, Eq, Hash)]
878pub struct ComparableDictItem<'a> {
879    key: Option<ComparableExpr<'a>>,
880    value: ComparableExpr<'a>,
881}
882
883impl<'a> From<&'a ast::DictItem> for ComparableDictItem<'a> {
884    fn from(ast::DictItem { key, value }: &'a ast::DictItem) -> Self {
885        Self {
886            key: key.as_ref().map(ComparableExpr::from),
887            value: value.into(),
888        }
889    }
890}
891
892#[derive(Debug, PartialEq, Eq, Hash)]
893pub struct ExprDict<'a> {
894    items: Vec<ComparableDictItem<'a>>,
895}
896
897#[derive(Debug, PartialEq, Eq, Hash)]
898pub struct ExprSet<'a> {
899    elts: Vec<ComparableExpr<'a>>,
900}
901
902#[derive(Debug, PartialEq, Eq, Hash)]
903pub struct ExprListComp<'a> {
904    elt: Box<ComparableExpr<'a>>,
905    generators: Vec<ComparableComprehension<'a>>,
906}
907
908#[derive(Debug, PartialEq, Eq, Hash)]
909pub struct ExprSetComp<'a> {
910    elt: Box<ComparableExpr<'a>>,
911    generators: Vec<ComparableComprehension<'a>>,
912}
913
914#[derive(Debug, PartialEq, Eq, Hash)]
915pub struct ExprDictComp<'a> {
916    key: Option<Box<ComparableExpr<'a>>>,
917    value: Box<ComparableExpr<'a>>,
918    generators: Vec<ComparableComprehension<'a>>,
919}
920
921#[derive(Debug, PartialEq, Eq, Hash)]
922pub struct ExprGenerator<'a> {
923    elt: Box<ComparableExpr<'a>>,
924    generators: Vec<ComparableComprehension<'a>>,
925}
926
927#[derive(Debug, PartialEq, Eq, Hash)]
928pub struct ExprAwait<'a> {
929    value: Box<ComparableExpr<'a>>,
930}
931
932#[derive(Debug, PartialEq, Eq, Hash)]
933pub struct ExprYield<'a> {
934    value: Option<Box<ComparableExpr<'a>>>,
935}
936
937#[derive(Debug, PartialEq, Eq, Hash)]
938pub struct ExprYieldFrom<'a> {
939    value: Box<ComparableExpr<'a>>,
940}
941
942#[derive(Debug, PartialEq, Eq, Hash)]
943pub struct ExprCompare<'a> {
944    left: Box<ComparableExpr<'a>>,
945    ops: Vec<ComparableCmpOp>,
946    comparators: Vec<ComparableExpr<'a>>,
947}
948
949#[derive(Debug, PartialEq, Eq, Hash)]
950pub struct ExprCall<'a> {
951    func: Box<ComparableExpr<'a>>,
952    arguments: ComparableArguments<'a>,
953}
954
955#[derive(Debug, PartialEq, Eq, Hash)]
956pub struct ExprInterpolatedElement<'a> {
957    value: Box<ComparableExpr<'a>>,
958    debug_text: Option<ComparableDebugText<'a>>,
959    conversion: ast::ConversionFlag,
960    format_spec: Vec<ComparableInterpolatedStringElement<'a>>,
961}
962
963#[derive(Debug, PartialEq, Eq, Hash)]
964pub struct ExprFString<'a> {
965    value: ComparableFString<'a>,
966}
967
968#[derive(Debug, PartialEq, Eq, Hash)]
969pub struct ExprTString<'a> {
970    value: ComparableTString<'a>,
971}
972
973#[derive(Debug, PartialEq, Eq, Hash)]
974pub struct ExprStringLiteral<'a> {
975    value: ComparableStringLiteral<'a>,
976}
977
978#[derive(Debug, PartialEq, Eq, Hash)]
979pub struct ExprBytesLiteral<'a> {
980    value: ComparableBytesLiteral<'a>,
981}
982
983#[derive(Debug, PartialEq, Eq, Hash)]
984pub struct ExprNumberLiteral<'a> {
985    value: ComparableNumber<'a>,
986}
987
988#[derive(Debug, PartialEq, Eq, Hash)]
989pub struct ExprBoolLiteral {
990    value: bool,
991}
992
993#[derive(Debug, PartialEq, Eq, Hash)]
994pub struct ExprAttribute<'a> {
995    value: Box<ComparableExpr<'a>>,
996    attr: &'a str,
997}
998
999#[derive(Debug, PartialEq, Eq, Hash)]
1000pub struct ExprSubscript<'a> {
1001    value: Box<ComparableExpr<'a>>,
1002    slice: Box<ComparableExpr<'a>>,
1003}
1004
1005#[derive(Debug, PartialEq, Eq, Hash)]
1006pub struct ExprStarred<'a> {
1007    value: Box<ComparableExpr<'a>>,
1008}
1009
1010#[derive(Debug, PartialEq, Eq, Hash)]
1011pub struct ExprName<'a> {
1012    id: &'a str,
1013}
1014
1015#[derive(Debug, PartialEq, Eq, Hash)]
1016pub struct ExprList<'a> {
1017    elts: Vec<ComparableExpr<'a>>,
1018}
1019
1020#[derive(Debug, PartialEq, Eq, Hash)]
1021pub struct ExprTuple<'a> {
1022    elts: Vec<ComparableExpr<'a>>,
1023}
1024
1025#[derive(Debug, PartialEq, Eq, Hash)]
1026pub struct ExprSlice<'a> {
1027    lower: Option<Box<ComparableExpr<'a>>>,
1028    upper: Option<Box<ComparableExpr<'a>>>,
1029    step: Option<Box<ComparableExpr<'a>>>,
1030}
1031
1032#[derive(Debug, PartialEq, Eq, Hash)]
1033pub struct ExprIpyEscapeCommand<'a> {
1034    kind: ast::IpyEscapeKind,
1035    value: &'a str,
1036}
1037
1038#[derive(Debug, PartialEq, Eq, Hash)]
1039pub enum ComparableExpr<'a> {
1040    BoolOp(ExprBoolOp<'a>),
1041    NamedExpr(ExprNamed<'a>),
1042    BinOp(ExprBinOp<'a>),
1043    UnaryOp(ExprUnaryOp<'a>),
1044    Lambda(ExprLambda<'a>),
1045    IfExp(ExprIf<'a>),
1046    Dict(ExprDict<'a>),
1047    Set(ExprSet<'a>),
1048    ListComp(ExprListComp<'a>),
1049    SetComp(ExprSetComp<'a>),
1050    DictComp(ExprDictComp<'a>),
1051    GeneratorExp(ExprGenerator<'a>),
1052    Await(ExprAwait<'a>),
1053    Yield(ExprYield<'a>),
1054    YieldFrom(ExprYieldFrom<'a>),
1055    Compare(ExprCompare<'a>),
1056    Call(ExprCall<'a>),
1057    FStringExpressionElement(ExprInterpolatedElement<'a>),
1058    FString(ExprFString<'a>),
1059    TStringInterpolationElement(ExprInterpolatedElement<'a>),
1060    TString(ExprTString<'a>),
1061    StringLiteral(ExprStringLiteral<'a>),
1062    BytesLiteral(ExprBytesLiteral<'a>),
1063    NumberLiteral(ExprNumberLiteral<'a>),
1064    BoolLiteral(ExprBoolLiteral),
1065    NoneLiteral,
1066    EllipsisLiteral,
1067    Attribute(ExprAttribute<'a>),
1068    Subscript(ExprSubscript<'a>),
1069    Starred(ExprStarred<'a>),
1070    Name(ExprName<'a>),
1071    List(ExprList<'a>),
1072    Tuple(ExprTuple<'a>),
1073    Slice(ExprSlice<'a>),
1074    IpyEscapeCommand(ExprIpyEscapeCommand<'a>),
1075}
1076
1077impl<'a> From<&'a Box<ast::Expr>> for Box<ComparableExpr<'a>> {
1078    fn from(expr: &'a Box<ast::Expr>) -> Self {
1079        Box::new((expr.as_ref()).into())
1080    }
1081}
1082
1083impl<'a> From<&'a Box<ast::Expr>> for ComparableExpr<'a> {
1084    fn from(expr: &'a Box<ast::Expr>) -> Self {
1085        (expr.as_ref()).into()
1086    }
1087}
1088
1089impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> {
1090    fn from(expr: &'a ast::Expr) -> Self {
1091        match expr {
1092            ast::Expr::BoolOp(ast::ExprBoolOp {
1093                op,
1094                values,
1095                range: _,
1096                node_index: _,
1097            }) => Self::BoolOp(ExprBoolOp {
1098                op: (*op).into(),
1099                values: values.iter().map(Into::into).collect(),
1100            }),
1101            ast::Expr::Named(ast::ExprNamed {
1102                target,
1103                value,
1104                range: _,
1105                node_index: _,
1106            }) => Self::NamedExpr(ExprNamed {
1107                target: target.into(),
1108                value: value.into(),
1109            }),
1110            ast::Expr::BinOp(ast::ExprBinOp {
1111                left,
1112                op,
1113                right,
1114                range: _,
1115                node_index: _,
1116            }) => Self::BinOp(ExprBinOp {
1117                left: left.into(),
1118                op: (*op).into(),
1119                right: right.into(),
1120            }),
1121            ast::Expr::UnaryOp(ast::ExprUnaryOp {
1122                op,
1123                operand,
1124                range: _,
1125                node_index: _,
1126            }) => Self::UnaryOp(ExprUnaryOp {
1127                op: (*op).into(),
1128                operand: operand.into(),
1129            }),
1130            ast::Expr::Lambda(ast::ExprLambda {
1131                parameters,
1132                body,
1133                range: _,
1134                node_index: _,
1135            }) => Self::Lambda(ExprLambda {
1136                parameters: parameters.as_ref().map(Into::into),
1137                body: body.into(),
1138            }),
1139            ast::Expr::If(ast::ExprIf {
1140                test,
1141                body,
1142                orelse,
1143                range: _,
1144                node_index: _,
1145            }) => Self::IfExp(ExprIf {
1146                test: test.into(),
1147                body: body.into(),
1148                orelse: orelse.into(),
1149            }),
1150            ast::Expr::Dict(ast::ExprDict {
1151                items,
1152                range: _,
1153                node_index: _,
1154            }) => Self::Dict(ExprDict {
1155                items: items.iter().map(ComparableDictItem::from).collect(),
1156            }),
1157            ast::Expr::Set(ast::ExprSet {
1158                elts,
1159                range: _,
1160                node_index: _,
1161            }) => Self::Set(ExprSet {
1162                elts: elts.iter().map(Into::into).collect(),
1163            }),
1164            ast::Expr::ListComp(ast::ExprListComp {
1165                elt,
1166                generators,
1167                range: _,
1168                node_index: _,
1169            }) => Self::ListComp(ExprListComp {
1170                elt: elt.into(),
1171                generators: generators.iter().map(Into::into).collect(),
1172            }),
1173            ast::Expr::SetComp(ast::ExprSetComp {
1174                elt,
1175                generators,
1176                range: _,
1177                node_index: _,
1178            }) => Self::SetComp(ExprSetComp {
1179                elt: elt.into(),
1180                generators: generators.iter().map(Into::into).collect(),
1181            }),
1182            ast::Expr::DictComp(ast::ExprDictComp {
1183                key,
1184                value,
1185                generators,
1186                range: _,
1187                node_index: _,
1188            }) => Self::DictComp(ExprDictComp {
1189                key: key.as_ref().map(Into::into),
1190                value: value.into(),
1191                generators: generators.iter().map(Into::into).collect(),
1192            }),
1193            ast::Expr::Generator(ast::ExprGenerator {
1194                elt,
1195                generators,
1196                range: _,
1197                node_index: _,
1198                parenthesized: _,
1199            }) => Self::GeneratorExp(ExprGenerator {
1200                elt: elt.into(),
1201                generators: generators.iter().map(Into::into).collect(),
1202            }),
1203            ast::Expr::Await(ast::ExprAwait {
1204                value,
1205                range: _,
1206                node_index: _,
1207            }) => Self::Await(ExprAwait {
1208                value: value.into(),
1209            }),
1210            ast::Expr::Yield(ast::ExprYield {
1211                value,
1212                range: _,
1213                node_index: _,
1214            }) => Self::Yield(ExprYield {
1215                value: value.as_ref().map(Into::into),
1216            }),
1217            ast::Expr::YieldFrom(ast::ExprYieldFrom {
1218                value,
1219                range: _,
1220                node_index: _,
1221            }) => Self::YieldFrom(ExprYieldFrom {
1222                value: value.into(),
1223            }),
1224            ast::Expr::Compare(ast::ExprCompare {
1225                left,
1226                ops,
1227                comparators,
1228                range: _,
1229                node_index: _,
1230            }) => Self::Compare(ExprCompare {
1231                left: left.into(),
1232                ops: ops.iter().copied().map(Into::into).collect(),
1233                comparators: comparators.iter().map(Into::into).collect(),
1234            }),
1235            ast::Expr::Call(ast::ExprCall {
1236                func,
1237                arguments,
1238                range_start: _,
1239                node_index: _,
1240            }) => Self::Call(ExprCall {
1241                func: func.into(),
1242                arguments: arguments.into(),
1243            }),
1244            ast::Expr::FString(ast::ExprFString {
1245                value,
1246                range: _,
1247                node_index: _,
1248            }) => Self::FString(ExprFString {
1249                value: value.into(),
1250            }),
1251            ast::Expr::TString(ast::ExprTString {
1252                value,
1253                range: _,
1254                node_index: _,
1255            }) => Self::TString(ExprTString {
1256                value: value.into(),
1257            }),
1258            ast::Expr::StringLiteral(ast::ExprStringLiteral {
1259                value,
1260                range: _,
1261                node_index: _,
1262            }) => Self::StringLiteral(ExprStringLiteral {
1263                value: ComparableStringLiteral {
1264                    value: value.to_str(),
1265                },
1266            }),
1267            ast::Expr::BytesLiteral(ast::ExprBytesLiteral {
1268                value,
1269                range: _,
1270                node_index: _,
1271            }) => Self::BytesLiteral(ExprBytesLiteral {
1272                value: ComparableBytesLiteral {
1273                    value: Cow::from(value),
1274                },
1275            }),
1276            ast::Expr::NumberLiteral(ast::ExprNumberLiteral {
1277                value,
1278                range: _,
1279                node_index: _,
1280            }) => Self::NumberLiteral(ExprNumberLiteral {
1281                value: value.into(),
1282            }),
1283            ast::Expr::BooleanLiteral(ast::ExprBooleanLiteral {
1284                value,
1285                range: _,
1286                node_index: _,
1287            }) => Self::BoolLiteral(ExprBoolLiteral { value: *value }),
1288            ast::Expr::NoneLiteral(_) => Self::NoneLiteral,
1289            ast::Expr::EllipsisLiteral(_) => Self::EllipsisLiteral,
1290            ast::Expr::Attribute(ast::ExprAttribute {
1291                value,
1292                attr,
1293                ctx: _,
1294                range: _,
1295                node_index: _,
1296            }) => Self::Attribute(ExprAttribute {
1297                value: value.into(),
1298                attr: attr.as_str(),
1299            }),
1300            ast::Expr::Subscript(ast::ExprSubscript {
1301                value,
1302                slice,
1303                ctx: _,
1304                range: _,
1305                node_index: _,
1306            }) => Self::Subscript(ExprSubscript {
1307                value: value.into(),
1308                slice: slice.into(),
1309            }),
1310            ast::Expr::Starred(ast::ExprStarred {
1311                value,
1312                ctx: _,
1313                range: _,
1314                node_index: _,
1315            }) => Self::Starred(ExprStarred {
1316                value: value.into(),
1317            }),
1318            ast::Expr::Name(name) => name.into(),
1319            ast::Expr::List(ast::ExprList {
1320                elts,
1321                ctx: _,
1322                range: _,
1323                node_index: _,
1324            }) => Self::List(ExprList {
1325                elts: elts.iter().map(Into::into).collect(),
1326            }),
1327            ast::Expr::Tuple(ast::ExprTuple {
1328                elts,
1329                ctx: _,
1330                range: _,
1331                node_index: _,
1332                parenthesized: _,
1333            }) => Self::Tuple(ExprTuple {
1334                elts: elts.iter().map(Into::into).collect(),
1335            }),
1336            ast::Expr::Slice(ast::ExprSlice {
1337                lower,
1338                upper,
1339                step,
1340                range: _,
1341                node_index: _,
1342            }) => Self::Slice(ExprSlice {
1343                lower: lower.as_ref().map(Into::into),
1344                upper: upper.as_ref().map(Into::into),
1345                step: step.as_ref().map(Into::into),
1346            }),
1347            ast::Expr::IpyEscapeCommand(ast::ExprIpyEscapeCommand {
1348                kind,
1349                value,
1350                range: _,
1351                node_index: _,
1352            }) => Self::IpyEscapeCommand(ExprIpyEscapeCommand { kind: *kind, value }),
1353        }
1354    }
1355}
1356
1357impl<'a> From<&'a ast::ExprName> for ComparableExpr<'a> {
1358    fn from(expr: &'a ast::ExprName) -> Self {
1359        Self::Name(ExprName {
1360            id: expr.id.as_str(),
1361        })
1362    }
1363}
1364
1365#[derive(Debug, PartialEq, Eq, Hash)]
1366pub struct StmtFunctionDef<'a> {
1367    is_async: bool,
1368    decorator_list: Vec<ComparableDecorator<'a>>,
1369    name: &'a str,
1370    type_params: Option<ComparableTypeParams<'a>>,
1371    parameters: ComparableParameters<'a>,
1372    returns: Option<ComparableExpr<'a>>,
1373    body: Vec<ComparableStmt<'a>>,
1374}
1375
1376#[derive(Debug, PartialEq, Eq, Hash)]
1377pub struct StmtClassDef<'a> {
1378    decorator_list: Vec<ComparableDecorator<'a>>,
1379    name: &'a str,
1380    type_params: Option<ComparableTypeParams<'a>>,
1381    arguments: ComparableArguments<'a>,
1382    body: Vec<ComparableStmt<'a>>,
1383}
1384
1385#[derive(Debug, PartialEq, Eq, Hash)]
1386pub struct StmtReturn<'a> {
1387    value: Option<ComparableExpr<'a>>,
1388}
1389
1390#[derive(Debug, PartialEq, Eq, Hash)]
1391pub struct StmtDelete<'a> {
1392    targets: Vec<ComparableExpr<'a>>,
1393}
1394
1395#[derive(Debug, PartialEq, Eq, Hash)]
1396pub struct StmtTypeAlias<'a> {
1397    pub name: Box<ComparableExpr<'a>>,
1398    pub type_params: Option<ComparableTypeParams<'a>>,
1399    pub value: Box<ComparableExpr<'a>>,
1400}
1401
1402#[derive(Debug, PartialEq, Eq, Hash)]
1403pub struct ComparableTypeParams<'a> {
1404    pub type_params: Vec<ComparableTypeParam<'a>>,
1405}
1406
1407impl<'a> From<&'a ast::TypeParams> for ComparableTypeParams<'a> {
1408    fn from(type_params: &'a ast::TypeParams) -> Self {
1409        Self {
1410            type_params: type_params.iter().map(Into::into).collect(),
1411        }
1412    }
1413}
1414
1415impl<'a> From<&'a Box<ast::TypeParams>> for ComparableTypeParams<'a> {
1416    fn from(type_params: &'a Box<ast::TypeParams>) -> Self {
1417        type_params.as_ref().into()
1418    }
1419}
1420
1421#[derive(Debug, PartialEq, Eq, Hash)]
1422pub enum ComparableTypeParam<'a> {
1423    TypeVar(TypeParamTypeVar<'a>),
1424    ParamSpec(TypeParamParamSpec<'a>),
1425    TypeVarTuple(TypeParamTypeVarTuple<'a>),
1426}
1427
1428impl<'a> From<&'a ast::TypeParam> for ComparableTypeParam<'a> {
1429    fn from(type_param: &'a ast::TypeParam) -> Self {
1430        match type_param {
1431            ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
1432                name,
1433                bound,
1434                default,
1435                range: _,
1436                node_index: _,
1437            }) => Self::TypeVar(TypeParamTypeVar {
1438                name: name.as_str(),
1439                bound: bound.as_ref().map(Into::into),
1440                default: default.as_ref().map(Into::into),
1441            }),
1442            ast::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple {
1443                name,
1444                default,
1445                range: _,
1446                node_index: _,
1447            }) => Self::TypeVarTuple(TypeParamTypeVarTuple {
1448                name: name.as_str(),
1449                default: default.as_ref().map(Into::into),
1450            }),
1451            ast::TypeParam::ParamSpec(ast::TypeParamParamSpec {
1452                name,
1453                default,
1454                range: _,
1455                node_index: _,
1456            }) => Self::ParamSpec(TypeParamParamSpec {
1457                name: name.as_str(),
1458                default: default.as_ref().map(Into::into),
1459            }),
1460        }
1461    }
1462}
1463
1464#[derive(Debug, PartialEq, Eq, Hash)]
1465pub struct TypeParamTypeVar<'a> {
1466    pub name: &'a str,
1467    pub bound: Option<Box<ComparableExpr<'a>>>,
1468    pub default: Option<Box<ComparableExpr<'a>>>,
1469}
1470
1471#[derive(Debug, PartialEq, Eq, Hash)]
1472pub struct TypeParamParamSpec<'a> {
1473    pub name: &'a str,
1474    pub default: Option<Box<ComparableExpr<'a>>>,
1475}
1476
1477#[derive(Debug, PartialEq, Eq, Hash)]
1478pub struct TypeParamTypeVarTuple<'a> {
1479    pub name: &'a str,
1480    pub default: Option<Box<ComparableExpr<'a>>>,
1481}
1482
1483#[derive(Debug, PartialEq, Eq, Hash)]
1484pub struct StmtAssign<'a> {
1485    targets: Vec<ComparableExpr<'a>>,
1486    value: ComparableExpr<'a>,
1487}
1488
1489#[derive(Debug, PartialEq, Eq, Hash)]
1490pub struct StmtAugAssign<'a> {
1491    target: ComparableExpr<'a>,
1492    op: ComparableOperator,
1493    value: ComparableExpr<'a>,
1494}
1495
1496#[derive(Debug, PartialEq, Eq, Hash)]
1497pub struct StmtAnnAssign<'a> {
1498    target: ComparableExpr<'a>,
1499    annotation: ComparableExpr<'a>,
1500    value: Option<ComparableExpr<'a>>,
1501    simple: bool,
1502}
1503
1504#[derive(Debug, PartialEq, Eq, Hash)]
1505pub struct StmtFor<'a> {
1506    is_async: bool,
1507    target: ComparableExpr<'a>,
1508    iter: ComparableExpr<'a>,
1509    body: Vec<ComparableStmt<'a>>,
1510    orelse: Vec<ComparableStmt<'a>>,
1511}
1512
1513#[derive(Debug, PartialEq, Eq, Hash)]
1514pub struct StmtWhile<'a> {
1515    test: ComparableExpr<'a>,
1516    body: Vec<ComparableStmt<'a>>,
1517    orelse: Vec<ComparableStmt<'a>>,
1518}
1519
1520#[derive(Debug, PartialEq, Eq, Hash)]
1521pub struct StmtIf<'a> {
1522    test: ComparableExpr<'a>,
1523    body: Vec<ComparableStmt<'a>>,
1524    elif_else_clauses: Vec<ComparableElifElseClause<'a>>,
1525}
1526
1527#[derive(Debug, PartialEq, Eq, Hash)]
1528pub struct StmtWith<'a> {
1529    is_async: bool,
1530    items: Vec<ComparableWithItem<'a>>,
1531    body: Vec<ComparableStmt<'a>>,
1532}
1533
1534#[derive(Debug, PartialEq, Eq, Hash)]
1535pub struct StmtMatch<'a> {
1536    subject: ComparableExpr<'a>,
1537    cases: Vec<ComparableMatchCase<'a>>,
1538}
1539
1540#[derive(Debug, PartialEq, Eq, Hash)]
1541pub struct StmtRaise<'a> {
1542    exc: Option<ComparableExpr<'a>>,
1543    cause: Option<ComparableExpr<'a>>,
1544}
1545
1546#[derive(Debug, PartialEq, Eq, Hash)]
1547pub struct StmtTry<'a> {
1548    body: Vec<ComparableStmt<'a>>,
1549    handlers: Vec<ComparableExceptHandler<'a>>,
1550    orelse: Vec<ComparableStmt<'a>>,
1551    finalbody: Vec<ComparableStmt<'a>>,
1552    is_star: bool,
1553}
1554
1555#[derive(Debug, PartialEq, Eq, Hash)]
1556pub struct StmtAssert<'a> {
1557    test: ComparableExpr<'a>,
1558    msg: Option<ComparableExpr<'a>>,
1559}
1560
1561#[derive(Debug, PartialEq, Eq, Hash)]
1562pub struct StmtImport<'a> {
1563    names: Vec<ComparableAlias<'a>>,
1564    is_lazy: bool,
1565}
1566
1567#[derive(Debug, PartialEq, Eq, Hash)]
1568pub struct StmtImportFrom<'a> {
1569    module: Option<&'a str>,
1570    names: Vec<ComparableAlias<'a>>,
1571    level: u32,
1572    is_lazy: bool,
1573}
1574
1575#[derive(Debug, PartialEq, Eq, Hash)]
1576pub struct StmtGlobal<'a> {
1577    names: Vec<&'a str>,
1578}
1579
1580#[derive(Debug, PartialEq, Eq, Hash)]
1581pub struct StmtNonlocal<'a> {
1582    names: Vec<&'a str>,
1583}
1584
1585#[derive(Debug, PartialEq, Eq, Hash)]
1586pub struct StmtExpr<'a> {
1587    value: ComparableExpr<'a>,
1588}
1589
1590#[derive(Debug, PartialEq, Eq, Hash)]
1591pub struct StmtIpyEscapeCommand<'a> {
1592    kind: ast::IpyEscapeKind,
1593    value: &'a str,
1594}
1595
1596#[derive(Debug, PartialEq, Eq, Hash)]
1597pub enum ComparableStmt<'a> {
1598    FunctionDef(StmtFunctionDef<'a>),
1599    ClassDef(StmtClassDef<'a>),
1600    Return(StmtReturn<'a>),
1601    Delete(StmtDelete<'a>),
1602    Assign(StmtAssign<'a>),
1603    AugAssign(StmtAugAssign<'a>),
1604    AnnAssign(StmtAnnAssign<'a>),
1605    For(StmtFor<'a>),
1606    While(StmtWhile<'a>),
1607    If(StmtIf<'a>),
1608    With(StmtWith<'a>),
1609    Match(StmtMatch<'a>),
1610    Raise(StmtRaise<'a>),
1611    Try(StmtTry<'a>),
1612    TypeAlias(StmtTypeAlias<'a>),
1613    Assert(StmtAssert<'a>),
1614    Import(StmtImport<'a>),
1615    ImportFrom(StmtImportFrom<'a>),
1616    Global(StmtGlobal<'a>),
1617    Nonlocal(StmtNonlocal<'a>),
1618    IpyEscapeCommand(StmtIpyEscapeCommand<'a>),
1619    Expr(StmtExpr<'a>),
1620    Pass,
1621    Break,
1622    Continue,
1623}
1624
1625impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> {
1626    fn from(stmt: &'a ast::Stmt) -> Self {
1627        match stmt {
1628            ast::Stmt::FunctionDef(ast::StmtFunctionDef {
1629                is_async,
1630                name,
1631                parameters,
1632                body,
1633                decorator_list,
1634                returns,
1635                type_params,
1636                range: _,
1637                node_index: _,
1638            }) => Self::FunctionDef(StmtFunctionDef {
1639                is_async: *is_async,
1640                name: name.as_str(),
1641                parameters: parameters.into(),
1642                body: body.iter().map(Into::into).collect(),
1643                decorator_list: decorator_list.iter().map(Into::into).collect(),
1644                returns: returns.as_ref().map(Into::into),
1645                type_params: type_params.as_ref().map(Into::into),
1646            }),
1647            ast::Stmt::ClassDef(ast::StmtClassDef {
1648                name,
1649                arguments,
1650                body,
1651                decorator_list,
1652                type_params,
1653                range: _,
1654                node_index: _,
1655            }) => Self::ClassDef(StmtClassDef {
1656                name: name.as_str(),
1657                arguments: arguments.as_ref().map(Into::into).unwrap_or_default(),
1658                body: body.iter().map(Into::into).collect(),
1659                decorator_list: decorator_list.iter().map(Into::into).collect(),
1660                type_params: type_params.as_ref().map(Into::into),
1661            }),
1662            ast::Stmt::Return(ast::StmtReturn {
1663                value,
1664                range: _,
1665                node_index: _,
1666            }) => Self::Return(StmtReturn {
1667                value: value.as_ref().map(Into::into),
1668            }),
1669            ast::Stmt::Delete(ast::StmtDelete {
1670                targets,
1671                range: _,
1672                node_index: _,
1673            }) => Self::Delete(StmtDelete {
1674                targets: targets.iter().map(Into::into).collect(),
1675            }),
1676            ast::Stmt::TypeAlias(ast::StmtTypeAlias {
1677                range: _,
1678                node_index: _,
1679                name,
1680                type_params,
1681                value,
1682            }) => Self::TypeAlias(StmtTypeAlias {
1683                name: name.into(),
1684                type_params: type_params.as_ref().map(Into::into),
1685                value: value.into(),
1686            }),
1687            ast::Stmt::Assign(ast::StmtAssign {
1688                targets,
1689                value,
1690                range: _,
1691                node_index: _,
1692            }) => Self::Assign(StmtAssign {
1693                targets: targets.iter().map(Into::into).collect(),
1694                value: value.into(),
1695            }),
1696            ast::Stmt::AugAssign(ast::StmtAugAssign {
1697                target,
1698                op,
1699                value,
1700                range: _,
1701                node_index: _,
1702            }) => Self::AugAssign(StmtAugAssign {
1703                target: target.into(),
1704                op: (*op).into(),
1705                value: value.into(),
1706            }),
1707            ast::Stmt::AnnAssign(ast::StmtAnnAssign {
1708                target,
1709                annotation,
1710                value,
1711                simple,
1712                range: _,
1713                node_index: _,
1714            }) => Self::AnnAssign(StmtAnnAssign {
1715                target: target.into(),
1716                annotation: annotation.into(),
1717                value: value.as_ref().map(Into::into),
1718                simple: *simple,
1719            }),
1720            ast::Stmt::For(ast::StmtFor {
1721                is_async,
1722                target,
1723                iter,
1724                body,
1725                orelse,
1726                range: _,
1727                node_index: _,
1728            }) => Self::For(StmtFor {
1729                is_async: *is_async,
1730                target: target.into(),
1731                iter: iter.into(),
1732                body: body.iter().map(Into::into).collect(),
1733                orelse: orelse.iter().map(Into::into).collect(),
1734            }),
1735            ast::Stmt::While(ast::StmtWhile {
1736                test,
1737                body,
1738                orelse,
1739                range: _,
1740                node_index: _,
1741            }) => Self::While(StmtWhile {
1742                test: test.into(),
1743                body: body.iter().map(Into::into).collect(),
1744                orelse: orelse.iter().map(Into::into).collect(),
1745            }),
1746            ast::Stmt::If(ast::StmtIf {
1747                test,
1748                body,
1749                elif_else_clauses,
1750                range: _,
1751                node_index: _,
1752            }) => Self::If(StmtIf {
1753                test: test.into(),
1754                body: body.iter().map(Into::into).collect(),
1755                elif_else_clauses: elif_else_clauses.iter().map(Into::into).collect(),
1756            }),
1757            ast::Stmt::With(ast::StmtWith {
1758                is_async,
1759                items,
1760                body,
1761                range: _,
1762                node_index: _,
1763            }) => Self::With(StmtWith {
1764                is_async: *is_async,
1765                items: items.iter().map(Into::into).collect(),
1766                body: body.iter().map(Into::into).collect(),
1767            }),
1768            ast::Stmt::Match(ast::StmtMatch {
1769                subject,
1770                cases,
1771                range: _,
1772                node_index: _,
1773            }) => Self::Match(StmtMatch {
1774                subject: subject.into(),
1775                cases: cases.iter().map(Into::into).collect(),
1776            }),
1777            ast::Stmt::Raise(ast::StmtRaise {
1778                exc,
1779                cause,
1780                range: _,
1781                node_index: _,
1782            }) => Self::Raise(StmtRaise {
1783                exc: exc.as_ref().map(Into::into),
1784                cause: cause.as_ref().map(Into::into),
1785            }),
1786            ast::Stmt::Try(ast::StmtTry {
1787                body,
1788                handlers,
1789                orelse,
1790                finalbody,
1791                is_star,
1792                range: _,
1793                node_index: _,
1794            }) => Self::Try(StmtTry {
1795                body: body.iter().map(Into::into).collect(),
1796                handlers: handlers.iter().map(Into::into).collect(),
1797                orelse: orelse.iter().map(Into::into).collect(),
1798                finalbody: finalbody.iter().map(Into::into).collect(),
1799                is_star: *is_star,
1800            }),
1801            ast::Stmt::Assert(ast::StmtAssert {
1802                test,
1803                msg,
1804                range: _,
1805                node_index: _,
1806            }) => Self::Assert(StmtAssert {
1807                test: test.into(),
1808                msg: msg.as_ref().map(Into::into),
1809            }),
1810            ast::Stmt::Import(ast::StmtImport {
1811                names,
1812                is_lazy,
1813                range: _,
1814                node_index: _,
1815            }) => Self::Import(StmtImport {
1816                names: names.iter().map(Into::into).collect(),
1817                is_lazy: *is_lazy,
1818            }),
1819            ast::Stmt::ImportFrom(ast::StmtImportFrom {
1820                module,
1821                names,
1822                level,
1823                is_lazy,
1824                range: _,
1825                node_index: _,
1826            }) => Self::ImportFrom(StmtImportFrom {
1827                module: module.as_deref(),
1828                names: names.iter().map(Into::into).collect(),
1829                level: *level,
1830                is_lazy: *is_lazy,
1831            }),
1832            ast::Stmt::Global(ast::StmtGlobal {
1833                names,
1834                range: _,
1835                node_index: _,
1836            }) => Self::Global(StmtGlobal {
1837                names: names.iter().map(ast::Identifier::as_str).collect(),
1838            }),
1839            ast::Stmt::Nonlocal(ast::StmtNonlocal {
1840                names,
1841                range: _,
1842                node_index: _,
1843            }) => Self::Nonlocal(StmtNonlocal {
1844                names: names.iter().map(ast::Identifier::as_str).collect(),
1845            }),
1846            ast::Stmt::IpyEscapeCommand(ast::StmtIpyEscapeCommand {
1847                kind,
1848                value,
1849                range: _,
1850                node_index: _,
1851            }) => Self::IpyEscapeCommand(StmtIpyEscapeCommand { kind: *kind, value }),
1852            ast::Stmt::Expr(ast::StmtExpr {
1853                value,
1854                range: _,
1855                node_index: _,
1856            }) => Self::Expr(StmtExpr {
1857                value: value.into(),
1858            }),
1859            ast::Stmt::Pass(_) => Self::Pass,
1860            ast::Stmt::Break(_) => Self::Break,
1861            ast::Stmt::Continue(_) => Self::Continue,
1862        }
1863    }
1864}
1865
1866#[derive(Debug, PartialEq, Eq, Hash)]
1867pub enum ComparableMod<'a> {
1868    Module(ComparableModModule<'a>),
1869    Expression(ComparableModExpression<'a>),
1870}
1871
1872#[derive(Debug, PartialEq, Eq, Hash)]
1873pub struct ComparableModModule<'a> {
1874    body: Vec<ComparableStmt<'a>>,
1875}
1876
1877#[derive(Debug, PartialEq, Eq, Hash)]
1878pub struct ComparableModExpression<'a> {
1879    body: Box<ComparableExpr<'a>>,
1880}
1881
1882impl<'a> From<&'a ast::Mod> for ComparableMod<'a> {
1883    fn from(mod_: &'a ast::Mod) -> Self {
1884        match mod_ {
1885            ast::Mod::Module(module) => Self::Module(module.into()),
1886            ast::Mod::Expression(expr) => Self::Expression(expr.into()),
1887        }
1888    }
1889}
1890
1891impl<'a> From<&'a ast::ModModule> for ComparableModModule<'a> {
1892    fn from(module: &'a ast::ModModule) -> Self {
1893        Self {
1894            body: module.body.iter().map(Into::into).collect(),
1895        }
1896    }
1897}
1898
1899impl<'a> From<&'a ast::ModExpression> for ComparableModExpression<'a> {
1900    fn from(expr: &'a ast::ModExpression) -> Self {
1901        Self {
1902            body: (&expr.body).into(),
1903        }
1904    }
1905}
1906
1907/// Wrapper around [`Expr`] that implements [`Hash`] and [`PartialEq`] according to Python
1908/// semantics:
1909///
1910/// > Values that compare equal (such as 1, 1.0, and True) can be used interchangeably to index the
1911/// > same dictionary entry.
1912///
1913/// For example, considers `True`, `1`, and `1.0` to be equal, as they hash to the same value
1914/// in Python, along with `False`, `0`, and `0.0`.
1915///
1916/// See: <https://docs.python.org/3/library/stdtypes.html#mapping-types-dict>
1917#[derive(Debug, PartialEq, Eq, Hash)]
1918pub struct HashableExpr<'a>(HashableExprKind<'a>);
1919
1920#[derive(Debug, PartialEq, Eq, Hash)]
1921enum HashableExprKind<'a> {
1922    Comparable(ComparableExpr<'a>),
1923    Number(HashableNumber),
1924    NamedExpr {
1925        target: ComparableExpr<'a>,
1926        value: Box<HashableExprKind<'a>>,
1927    },
1928    Tuple(Vec<HashableExprKind<'a>>),
1929}
1930
1931#[derive(Debug, PartialEq, Eq, Hash)]
1932struct HashableNumber {
1933    real: HashableReal,
1934    imag: HashableReal,
1935}
1936
1937impl HashableNumber {
1938    fn real(real: HashableReal) -> Self {
1939        Self {
1940            real,
1941            imag: HashableReal::Integer(0),
1942        }
1943    }
1944
1945    fn complex(real: HashableReal, imag: HashableReal) -> Self {
1946        Self { real, imag }
1947    }
1948
1949    fn negate(mut self) -> Self {
1950        self.real.negate();
1951        self.imag.negate();
1952        self
1953    }
1954
1955    fn into_real(self) -> Option<HashableReal> {
1956        self.imag.is_zero().then_some(self.real)
1957    }
1958}
1959
1960#[derive(Debug, PartialEq, Eq, Hash)]
1961enum HashableReal {
1962    Integer(i128),
1963    Float(u64),
1964}
1965
1966impl HashableReal {
1967    fn from_int(value: &ast::Int) -> Option<Self> {
1968        value.as_u64().map(i128::from).map(Self::Integer)
1969    }
1970
1971    #[expect(
1972        clippy::cast_possible_truncation,
1973        clippy::cast_precision_loss,
1974        clippy::float_cmp,
1975        reason = "the round-trip check guarantees that the float is exactly representable as an integer"
1976    )]
1977    fn from_float(value: f64) -> Self {
1978        if value.is_finite() && value.abs() < U64_EXCLUSIVE_UPPER_BOUND {
1979            let integer = value as i128;
1980            if integer as f64 == value {
1981                return Self::Integer(integer);
1982            }
1983        }
1984        Self::Float(value.to_bits())
1985    }
1986
1987    #[expect(
1988        clippy::cast_precision_loss,
1989        reason = "Python converts real components to floats before complex arithmetic"
1990    )]
1991    fn into_float(self) -> Self {
1992        match self {
1993            Self::Integer(integer) => Self::from_float(integer as f64),
1994            Self::Float(_) => self,
1995        }
1996    }
1997
1998    fn is_zero(&self) -> bool {
1999        matches!(self, Self::Integer(0))
2000    }
2001
2002    fn negate(&mut self) {
2003        match self {
2004            Self::Integer(integer) => *integer = -*integer,
2005            Self::Float(bits) => *bits ^= 1 << 63,
2006        }
2007    }
2008}
2009
2010// `2^64`, the exclusive upper bound for values representable as a `u64`.
2011const U64_EXCLUSIVE_UPPER_BOUND: f64 = 18_446_744_073_709_551_616.0;
2012
2013impl<'a> From<&'a Expr> for HashableExpr<'a> {
2014    fn from(expr: &'a Expr) -> Self {
2015        /// Returns a version of the given expression that can be hashed and compared according to
2016        /// Python  semantics.
2017        fn as_hashable(expr: &Expr) -> HashableExprKind<'_> {
2018            if let Some(constant) = as_hashable_constant(expr) {
2019                return constant;
2020            }
2021
2022            match expr {
2023                Expr::Named(named) => HashableExprKind::NamedExpr {
2024                    target: ComparableExpr::from(&named.target),
2025                    value: Box::new(as_hashable(&named.value)),
2026                },
2027                _ => HashableExprKind::Comparable(ComparableExpr::from(expr)),
2028            }
2029        }
2030
2031        /// Returns a hashable representation if the expression's value is statically known.
2032        fn as_hashable_constant(expr: &Expr) -> Option<HashableExprKind<'_>> {
2033            if let Some(number) = as_number(expr) {
2034                return Some(HashableExprKind::Number(number));
2035            }
2036
2037            let kind = match expr {
2038                Expr::Tuple(tuple) => HashableExprKind::Tuple(
2039                    tuple
2040                        .iter()
2041                        .map(as_hashable_constant)
2042                        .collect::<Option<_>>()?,
2043                ),
2044                _ if expr.is_literal_expr() => {
2045                    HashableExprKind::Comparable(ComparableExpr::from(expr))
2046                }
2047                _ => return None,
2048            };
2049
2050            Some(kind)
2051        }
2052
2053        fn as_number(expr: &Expr) -> Option<HashableNumber> {
2054            match expr {
2055                Expr::BooleanLiteral(boolean) => Some(HashableNumber::real(HashableReal::Integer(
2056                    i128::from(u8::from(boolean.value)),
2057                ))),
2058                Expr::NumberLiteral(number) => match &number.value {
2059                    Number::Int(int) => HashableReal::from_int(int).map(HashableNumber::real),
2060                    Number::Float(float) => {
2061                        Some(HashableNumber::real(HashableReal::from_float(*float)))
2062                    }
2063                    Number::Complex { real, imag } => Some(HashableNumber::complex(
2064                        HashableReal::from_float(*real),
2065                        HashableReal::from_float(*imag),
2066                    )),
2067                },
2068                Expr::UnaryOp(ast::ExprUnaryOp { op, operand, .. }) => match op {
2069                    ast::UnaryOp::UAdd => as_number(operand),
2070                    ast::UnaryOp::USub => as_number(operand).map(HashableNumber::negate),
2071                    ast::UnaryOp::Invert | ast::UnaryOp::Not => None,
2072                },
2073                Expr::BinOp(ast::ExprBinOp {
2074                    left,
2075                    op: op @ (ast::Operator::Add | ast::Operator::Sub),
2076                    right,
2077                    ..
2078                }) => {
2079                    let real = as_number(left)?.into_real()?.into_float();
2080                    let Expr::NumberLiteral(ast::ExprNumberLiteral {
2081                        value:
2082                            Number::Complex {
2083                                real: complex_real,
2084                                imag,
2085                            },
2086                        ..
2087                    }) = right.as_ref()
2088                    else {
2089                        return None;
2090                    };
2091                    let complex_real = HashableReal::from_float(*complex_real);
2092                    if !complex_real.is_zero() {
2093                        return None;
2094                    }
2095                    let mut imag = HashableReal::from_float(*imag);
2096                    if op.is_sub() {
2097                        imag.negate();
2098                    }
2099                    Some(HashableNumber::complex(real, imag))
2100                }
2101                _ => None,
2102            }
2103        }
2104
2105        Self(as_hashable(expr))
2106    }
2107}