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::{ConstantValue, 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, PartialEq, Eq, Hash)]
369pub enum ComparableConstantValue<'a> {
370    None,
371    Boolean(bool),
372    Str(&'a str),
373    Bytes(Cow<'a, [u8]>),
374    Integer(&'a str),
375    Tuple(Vec<Self>),
376    Frozenset(Vec<Self>),
377    Float(u64),
378    Complex { real: u64, imag: u64 },
379    Ellipsis,
380}
381
382impl<'a> From<&'a ConstantValue> for ComparableConstantValue<'a> {
383    fn from(value: &'a ConstantValue) -> Self {
384        match value {
385            ConstantValue::None => Self::None,
386            ConstantValue::Boolean(value) => Self::Boolean(*value),
387            ConstantValue::Str(value) => Self::Str(value),
388            ConstantValue::Bytes(value) => Self::Bytes(Cow::from(value.as_ref())),
389            ConstantValue::Integer(value) => Self::Integer(value),
390            ConstantValue::Tuple(elements) => {
391                Self::Tuple(elements.iter().map(Into::into).collect())
392            }
393            ConstantValue::Frozenset(elements) => {
394                Self::Frozenset(elements.iter().map(Into::into).collect())
395            }
396            ConstantValue::Float(value) => Self::Float(value.to_bits()),
397            ConstantValue::Complex { real, imag } => Self::Complex {
398                real: real.to_bits(),
399                imag: imag.to_bits(),
400            },
401            ConstantValue::Ellipsis => Self::Ellipsis,
402        }
403    }
404}
405
406#[derive(Debug, Default, PartialEq, Eq, Hash)]
407pub struct ComparableArguments<'a> {
408    args: Vec<ComparableExpr<'a>>,
409    keywords: Vec<ComparableKeyword<'a>>,
410}
411
412impl<'a> From<&'a ast::Arguments> for ComparableArguments<'a> {
413    fn from(arguments: &'a ast::Arguments) -> Self {
414        Self {
415            args: arguments.args.iter().map(Into::into).collect(),
416            keywords: arguments.keywords.iter().map(Into::into).collect(),
417        }
418    }
419}
420
421impl<'a> From<&'a Box<ast::Arguments>> for ComparableArguments<'a> {
422    fn from(arguments: &'a Box<ast::Arguments>) -> Self {
423        (arguments.as_ref()).into()
424    }
425}
426
427#[derive(Debug, PartialEq, Eq, Hash)]
428pub struct ComparableParameters<'a> {
429    posonlyargs: Vec<ComparableParameterWithDefault<'a>>,
430    args: Vec<ComparableParameterWithDefault<'a>>,
431    vararg: Option<ComparableParameter<'a>>,
432    kwonlyargs: Vec<ComparableParameterWithDefault<'a>>,
433    kwarg: Option<ComparableParameter<'a>>,
434}
435
436impl<'a> From<&'a ast::Parameters> for ComparableParameters<'a> {
437    fn from(parameters: &'a ast::Parameters) -> Self {
438        Self {
439            posonlyargs: parameters.posonlyargs.iter().map(Into::into).collect(),
440            args: parameters.args.iter().map(Into::into).collect(),
441            vararg: parameters.vararg.as_ref().map(Into::into),
442            kwonlyargs: parameters.kwonlyargs.iter().map(Into::into).collect(),
443            kwarg: parameters.kwarg.as_ref().map(Into::into),
444        }
445    }
446}
447
448impl<'a> From<&'a Box<ast::Parameters>> for ComparableParameters<'a> {
449    fn from(parameters: &'a Box<ast::Parameters>) -> Self {
450        (parameters.as_ref()).into()
451    }
452}
453
454impl<'a> From<&'a Box<ast::Parameter>> for ComparableParameter<'a> {
455    fn from(arg: &'a Box<ast::Parameter>) -> Self {
456        (arg.as_ref()).into()
457    }
458}
459
460#[derive(Debug, PartialEq, Eq, Hash)]
461pub struct ComparableParameter<'a> {
462    arg: &'a str,
463    annotation: Option<Box<ComparableExpr<'a>>>,
464}
465
466impl<'a> From<&'a ast::Parameter> for ComparableParameter<'a> {
467    fn from(arg: &'a ast::Parameter) -> Self {
468        Self {
469            arg: arg.name.as_str(),
470            annotation: arg.annotation.as_ref().map(Into::into),
471        }
472    }
473}
474
475#[derive(Debug, PartialEq, Eq, Hash)]
476pub struct ComparableParameterWithDefault<'a> {
477    def: ComparableParameter<'a>,
478    default: Option<ComparableExpr<'a>>,
479}
480
481impl<'a> From<&'a ast::ParameterWithDefault> for ComparableParameterWithDefault<'a> {
482    fn from(arg: &'a ast::ParameterWithDefault) -> Self {
483        Self {
484            def: (&arg.parameter).into(),
485            default: arg.default.as_ref().map(Into::into),
486        }
487    }
488}
489
490#[derive(Debug, PartialEq, Eq, Hash)]
491pub struct ComparableKeyword<'a> {
492    arg: Option<&'a str>,
493    value: ComparableExpr<'a>,
494}
495
496impl<'a> From<&'a ast::Keyword> for ComparableKeyword<'a> {
497    fn from(keyword: &'a ast::Keyword) -> Self {
498        Self {
499            arg: keyword.arg.as_ref().map(ast::Identifier::as_str),
500            value: (&keyword.value).into(),
501        }
502    }
503}
504
505#[derive(Debug, PartialEq, Eq, Hash)]
506pub struct ComparableComprehension<'a> {
507    target: ComparableExpr<'a>,
508    iter: ComparableExpr<'a>,
509    ifs: Vec<ComparableExpr<'a>>,
510    is_async: bool,
511}
512
513impl<'a> From<&'a ast::Comprehension> for ComparableComprehension<'a> {
514    fn from(comprehension: &'a ast::Comprehension) -> Self {
515        Self {
516            target: (&comprehension.target).into(),
517            iter: (&comprehension.iter).into(),
518            ifs: comprehension.ifs.iter().map(Into::into).collect(),
519            is_async: comprehension.is_async,
520        }
521    }
522}
523
524#[derive(Debug, PartialEq, Eq, Hash)]
525pub struct ExceptHandlerExceptHandler<'a> {
526    type_: Option<Box<ComparableExpr<'a>>>,
527    name: Option<&'a str>,
528    body: Vec<ComparableStmt<'a>>,
529}
530
531#[derive(Debug, PartialEq, Eq, Hash)]
532pub enum ComparableExceptHandler<'a> {
533    ExceptHandler(ExceptHandlerExceptHandler<'a>),
534}
535
536impl<'a> From<&'a ast::ExceptHandler> for ComparableExceptHandler<'a> {
537    fn from(except_handler: &'a ast::ExceptHandler) -> Self {
538        let ast::ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
539            type_,
540            name,
541            body,
542            ..
543        }) = except_handler;
544        Self::ExceptHandler(ExceptHandlerExceptHandler {
545            type_: type_.as_ref().map(Into::into),
546            name: name.as_deref(),
547            body: body.iter().map(Into::into).collect(),
548        })
549    }
550}
551
552#[derive(Debug, PartialEq, Eq, Hash)]
553pub enum ComparableInterpolatedStringElement<'a> {
554    Literal(Cow<'a, str>),
555    InterpolatedElement(InterpolatedElement<'a>),
556}
557
558/// Comparable wrapper for [`ast::DebugText`].
559///
560/// Compares the full debug text (leading + expression source + trailing) rather than only the
561/// expression source, because whitespace is part of the f-string's runtime output: `f"{x =}"`
562/// produces `"x =<value>"` while `f"{x=}"` produces `"x=<value>"`, making them distinct
563/// `Literal` types.
564#[derive(Debug, PartialEq, Eq, Hash)]
565pub struct ComparableDebugText<'a> {
566    text: Cow<'a, str>,
567}
568
569impl<'a> From<&'a ast::DebugText> for ComparableDebugText<'a> {
570    fn from(debug_text: &'a ast::DebugText) -> Self {
571        // Normalizing newlines is safe because Python normalizes `\r\n` and `\r` to `\n`
572        // at compile time, so they produce identical runtime values.
573        Self {
574            text: normalize_newlines(debug_text.as_str()),
575        }
576    }
577}
578
579fn normalize_newlines(contents: &str) -> Cow<'_, str> {
580    if contents.contains('\r') {
581        Cow::Owned(contents.replace("\r\n", "\n").replace('\r', "\n"))
582    } else {
583        Cow::Borrowed(contents)
584    }
585}
586
587#[derive(Debug, PartialEq, Eq, Hash)]
588pub struct InterpolatedElement<'a> {
589    expression: ComparableExpr<'a>,
590    debug_text: Option<ComparableDebugText<'a>>,
591    conversion: ast::ConversionFlag,
592    format_spec: Option<Vec<ComparableInterpolatedStringElement<'a>>>,
593}
594
595impl<'a> From<&'a ast::InterpolatedStringElement> for ComparableInterpolatedStringElement<'a> {
596    fn from(interpolated_string_element: &'a ast::InterpolatedStringElement) -> Self {
597        match interpolated_string_element {
598            ast::InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement {
599                value,
600                ..
601            }) => Self::Literal(value.as_ref().into()),
602            ast::InterpolatedStringElement::Interpolation(formatted_value) => {
603                formatted_value.into()
604            }
605        }
606    }
607}
608
609impl<'a> From<&'a ast::InterpolatedElement> for InterpolatedElement<'a> {
610    fn from(interpolated_element: &'a ast::InterpolatedElement) -> Self {
611        let ast::InterpolatedElement {
612            expression,
613            debug_text,
614            conversion,
615            format_spec,
616            ..
617        } = interpolated_element;
618
619        Self {
620            expression: (expression).into(),
621            debug_text: debug_text.as_ref().map(Into::into),
622            conversion: *conversion,
623            format_spec: format_spec
624                .as_ref()
625                .map(|spec| spec.elements.iter().map(Into::into).collect()),
626        }
627    }
628}
629
630impl<'a> From<&'a ast::InterpolatedElement> for ComparableInterpolatedStringElement<'a> {
631    fn from(interpolated_element: &'a ast::InterpolatedElement) -> Self {
632        Self::InterpolatedElement(interpolated_element.into())
633    }
634}
635
636#[derive(Debug, PartialEq, Eq, Hash)]
637pub struct ComparableElifElseClause<'a> {
638    test: Option<ComparableExpr<'a>>,
639    body: Vec<ComparableStmt<'a>>,
640}
641
642impl<'a> From<&'a ast::ElifElseClause> for ComparableElifElseClause<'a> {
643    fn from(elif_else_clause: &'a ast::ElifElseClause) -> Self {
644        let ast::ElifElseClause {
645            range: _,
646            node_index: _,
647            test,
648            body,
649            ..
650        } = elif_else_clause;
651        Self {
652            test: test.as_ref().map(Into::into),
653            body: body.iter().map(Into::into).collect(),
654        }
655    }
656}
657
658#[derive(Debug, PartialEq, Eq, Hash)]
659pub enum ComparableLiteral<'a> {
660    None,
661    Ellipsis,
662    Bool(&'a bool),
663    Str(Vec<ComparableStringLiteral<'a>>),
664    Bytes(Vec<ComparableBytesLiteral<'a>>),
665    Number(ComparableNumber<'a>),
666}
667
668impl<'a> From<ast::LiteralExpressionRef<'a>> for ComparableLiteral<'a> {
669    fn from(literal: ast::LiteralExpressionRef<'a>) -> Self {
670        match literal {
671            ast::LiteralExpressionRef::NoneLiteral(_) => Self::None,
672            ast::LiteralExpressionRef::EllipsisLiteral(_) => Self::Ellipsis,
673            ast::LiteralExpressionRef::BooleanLiteral(ast::ExprBooleanLiteral {
674                value, ..
675            }) => Self::Bool(value),
676            ast::LiteralExpressionRef::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
677                Self::Str(value.iter().map(Into::into).collect())
678            }
679            ast::LiteralExpressionRef::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
680                Self::Bytes(value.iter().map(Into::into).collect())
681            }
682            ast::LiteralExpressionRef::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
683                Self::Number(value.into())
684            }
685        }
686    }
687}
688
689#[derive(Debug, PartialEq, Eq, Hash)]
690pub struct ComparableFString<'a> {
691    elements: Box<[ComparableInterpolatedStringElement<'a>]>,
692}
693
694impl<'a> From<&'a ast::FStringValue> for ComparableFString<'a> {
695    // The approach below is somewhat complicated, so it may
696    // require some justification.
697    //
698    // Suppose given an f-string of the form
699    // `f"{foo!r} one" " and two " f" and three {bar!s}"`
700    // This decomposes as:
701    // - An `FStringPart::FString`, `f"{foo!r} one"` with elements
702    //      - `FStringElement::Expression` encoding `{foo!r}`
703    //      - `FStringElement::Literal` encoding " one"
704    // - An `FStringPart::Literal` capturing `" and two "`
705    // - An `FStringPart::FString`, `f" and three {bar!s}"` with elements
706    //      - `FStringElement::Literal` encoding " and three "
707    //      - `FStringElement::Expression` encoding `{bar!s}`
708    //
709    // We would like to extract from this a vector of (comparable) f-string
710    // _elements_ which alternate between expression elements and literal
711    // elements. In order to do so, we need to concatenate adjacent string
712    // literals. String literals may be separated for two reasons: either
713    // they appear in adjacent string literal parts, or else a string literal
714    // part is adjacent to a string literal _element_ inside of an f-string part.
715    fn from(value: &'a ast::FStringValue) -> Self {
716        #[derive(Default)]
717        struct Collector<'a> {
718            elements: Vec<ComparableInterpolatedStringElement<'a>>,
719        }
720
721        impl<'a> Collector<'a> {
722            // The logic for concatenating adjacent string literals
723            // occurs here, implicitly: when we encounter a sequence
724            // of string literals, the first gets pushed to the
725            // `elements` vector, while subsequent strings
726            // are concatenated onto this top string.
727            fn push_literal(&mut self, literal: &'a str) {
728                if let Some(ComparableInterpolatedStringElement::Literal(existing_literal)) =
729                    self.elements.last_mut()
730                {
731                    existing_literal.to_mut().push_str(literal);
732                } else {
733                    self.elements
734                        .push(ComparableInterpolatedStringElement::Literal(literal.into()));
735                }
736            }
737
738            fn push_expression(&mut self, expression: &'a ast::InterpolatedElement) {
739                self.elements.push(expression.into());
740            }
741        }
742
743        let mut collector = Collector::default();
744
745        for part in value {
746            match part {
747                ast::FStringPart::Literal(string_literal) => {
748                    collector.push_literal(&string_literal.value);
749                }
750                ast::FStringPart::FString(fstring) => {
751                    for element in &fstring.elements {
752                        match element {
753                            ast::InterpolatedStringElement::Literal(literal) => {
754                                collector.push_literal(&literal.value);
755                            }
756                            ast::InterpolatedStringElement::Interpolation(expression) => {
757                                collector.push_expression(expression);
758                            }
759                        }
760                    }
761                }
762            }
763        }
764
765        Self {
766            elements: collector.elements.into_boxed_slice(),
767        }
768    }
769}
770
771#[derive(Debug, PartialEq, Eq, Hash)]
772pub struct ComparableTString<'a> {
773    strings: Box<[ComparableInterpolatedStringElement<'a>]>,
774    interpolations: Box<[InterpolatedElement<'a>]>,
775}
776
777impl<'a> From<&'a ast::TStringValue> for ComparableTString<'a> {
778    // We model a [`ComparableTString`] on the actual
779    // [CPython implementation] of a `string.templatelib.Template` object.
780    //
781    // As in CPython, we must be careful to ensure that the length
782    // of `strings` is always one more than the length of `interpolations` -
783    // that way we can recover the original reading order by interleaving
784    // starting with `strings`. This is how we can tell the
785    // difference between, e.g. `t"{foo}bar"` and `t"bar{foo}"`.
786    //
787    // - [CPython implementation](https://github.com/python/cpython/blob/c91ad5da9d92eac4718e4da8d53689c3cc24535e/Python/codegen.c#L4052-L4103)
788    fn from(value: &'a ast::TStringValue) -> Self {
789        struct Collector<'a> {
790            strings: Vec<ComparableInterpolatedStringElement<'a>>,
791            interpolations: Vec<InterpolatedElement<'a>>,
792        }
793
794        impl Default for Collector<'_> {
795            fn default() -> Self {
796                Self {
797                    strings: vec![ComparableInterpolatedStringElement::Literal("".into())],
798                    interpolations: vec![],
799                }
800            }
801        }
802
803        impl<'a> Collector<'a> {
804            // The logic for concatenating adjacent string literals
805            // occurs here, implicitly: when we encounter a sequence
806            // of string literals, the first gets pushed to the
807            // `strings` vector, while subsequent strings
808            // are concatenated onto this top string.
809            fn push_literal(&mut self, literal: &'a str) {
810                if let Some(ComparableInterpolatedStringElement::Literal(existing_literal)) =
811                    self.strings.last_mut()
812                {
813                    existing_literal.to_mut().push_str(literal);
814                } else {
815                    self.strings
816                        .push(ComparableInterpolatedStringElement::Literal(literal.into()));
817                }
818            }
819
820            fn start_new_literal(&mut self) {
821                self.strings
822                    .push(ComparableInterpolatedStringElement::Literal("".into()));
823            }
824
825            fn push_tstring_interpolation(&mut self, expression: &'a ast::InterpolatedElement) {
826                self.interpolations.push(expression.into());
827                self.start_new_literal();
828            }
829        }
830
831        let mut collector = Collector::default();
832
833        for element in value.elements() {
834            match element {
835                ast::InterpolatedStringElement::Literal(literal) => {
836                    collector.push_literal(&literal.value);
837                }
838                ast::InterpolatedStringElement::Interpolation(interpolation) => {
839                    collector.push_tstring_interpolation(interpolation);
840                }
841            }
842        }
843
844        Self {
845            strings: collector.strings.into_boxed_slice(),
846            interpolations: collector.interpolations.into_boxed_slice(),
847        }
848    }
849}
850
851#[derive(Debug, PartialEq, Eq, Hash)]
852pub struct ComparableStringLiteral<'a> {
853    value: &'a str,
854}
855
856impl<'a> From<&'a ast::StringLiteral> for ComparableStringLiteral<'a> {
857    fn from(string_literal: &'a ast::StringLiteral) -> Self {
858        Self {
859            value: &string_literal.value,
860        }
861    }
862}
863
864#[derive(Debug, PartialEq, Eq, Hash)]
865pub struct ComparableBytesLiteral<'a> {
866    value: Cow<'a, [u8]>,
867}
868
869impl<'a> From<&'a ast::BytesLiteral> for ComparableBytesLiteral<'a> {
870    fn from(bytes_literal: &'a ast::BytesLiteral) -> Self {
871        Self {
872            value: Cow::Borrowed(&bytes_literal.value),
873        }
874    }
875}
876
877#[derive(Debug, PartialEq, Eq, Hash)]
878pub struct ExprBoolOp<'a> {
879    op: ComparableBoolOp,
880    values: Vec<ComparableExpr<'a>>,
881}
882
883#[derive(Debug, PartialEq, Eq, Hash)]
884pub struct ExprNamed<'a> {
885    target: Box<ComparableExpr<'a>>,
886    value: Box<ComparableExpr<'a>>,
887}
888
889#[derive(Debug, PartialEq, Eq, Hash)]
890pub struct ExprBinOp<'a> {
891    left: Box<ComparableExpr<'a>>,
892    op: ComparableOperator,
893    right: Box<ComparableExpr<'a>>,
894}
895
896#[derive(Debug, PartialEq, Eq, Hash)]
897pub struct ExprUnaryOp<'a> {
898    op: ComparableUnaryOp,
899    operand: Box<ComparableExpr<'a>>,
900}
901
902#[derive(Debug, PartialEq, Eq, Hash)]
903pub struct ExprLambda<'a> {
904    parameters: Option<ComparableParameters<'a>>,
905    body: Box<ComparableExpr<'a>>,
906}
907
908#[derive(Debug, PartialEq, Eq, Hash)]
909pub struct ExprIf<'a> {
910    test: Box<ComparableExpr<'a>>,
911    body: Box<ComparableExpr<'a>>,
912    orelse: Box<ComparableExpr<'a>>,
913}
914
915#[derive(Debug, PartialEq, Eq, Hash)]
916pub struct ComparableDictItem<'a> {
917    key: Option<ComparableExpr<'a>>,
918    value: ComparableExpr<'a>,
919}
920
921impl<'a> From<&'a ast::DictItem> for ComparableDictItem<'a> {
922    fn from(ast::DictItem { key, value }: &'a ast::DictItem) -> Self {
923        Self {
924            key: key.as_ref().map(ComparableExpr::from),
925            value: value.into(),
926        }
927    }
928}
929
930#[derive(Debug, PartialEq, Eq, Hash)]
931pub struct ExprDict<'a> {
932    items: Vec<ComparableDictItem<'a>>,
933}
934
935#[derive(Debug, PartialEq, Eq, Hash)]
936pub struct ExprSet<'a> {
937    elts: Vec<ComparableExpr<'a>>,
938}
939
940#[derive(Debug, PartialEq, Eq, Hash)]
941pub struct ExprListComp<'a> {
942    elt: Box<ComparableExpr<'a>>,
943    generators: Vec<ComparableComprehension<'a>>,
944}
945
946#[derive(Debug, PartialEq, Eq, Hash)]
947pub struct ExprSetComp<'a> {
948    elt: Box<ComparableExpr<'a>>,
949    generators: Vec<ComparableComprehension<'a>>,
950}
951
952#[derive(Debug, PartialEq, Eq, Hash)]
953pub struct ExprDictComp<'a> {
954    key: Option<Box<ComparableExpr<'a>>>,
955    value: Box<ComparableExpr<'a>>,
956    generators: Vec<ComparableComprehension<'a>>,
957}
958
959#[derive(Debug, PartialEq, Eq, Hash)]
960pub struct ExprGenerator<'a> {
961    elt: Box<ComparableExpr<'a>>,
962    generators: Vec<ComparableComprehension<'a>>,
963}
964
965#[derive(Debug, PartialEq, Eq, Hash)]
966pub struct ExprAwait<'a> {
967    value: Box<ComparableExpr<'a>>,
968}
969
970#[derive(Debug, PartialEq, Eq, Hash)]
971pub struct ExprYield<'a> {
972    value: Option<Box<ComparableExpr<'a>>>,
973}
974
975#[derive(Debug, PartialEq, Eq, Hash)]
976pub struct ExprYieldFrom<'a> {
977    value: Box<ComparableExpr<'a>>,
978}
979
980#[derive(Debug, PartialEq, Eq, Hash)]
981pub struct ExprCompare<'a> {
982    left: Box<ComparableExpr<'a>>,
983    ops: Vec<ComparableCmpOp>,
984    comparators: Vec<ComparableExpr<'a>>,
985}
986
987#[derive(Debug, PartialEq, Eq, Hash)]
988pub struct ExprCall<'a> {
989    func: Box<ComparableExpr<'a>>,
990    arguments: ComparableArguments<'a>,
991}
992
993#[derive(Debug, PartialEq, Eq, Hash)]
994pub struct ExprInterpolatedElement<'a> {
995    value: Box<ComparableExpr<'a>>,
996    debug_text: Option<ComparableDebugText<'a>>,
997    conversion: ast::ConversionFlag,
998    format_spec: Vec<ComparableInterpolatedStringElement<'a>>,
999}
1000
1001#[derive(Debug, PartialEq, Eq, Hash)]
1002pub struct ExprFString<'a> {
1003    value: ComparableFString<'a>,
1004}
1005
1006#[derive(Debug, PartialEq, Eq, Hash)]
1007pub struct ExprTString<'a> {
1008    value: ComparableTString<'a>,
1009}
1010
1011#[derive(Debug, PartialEq, Eq, Hash)]
1012pub struct ExprStringLiteral<'a> {
1013    value: ComparableStringLiteral<'a>,
1014}
1015
1016#[derive(Debug, PartialEq, Eq, Hash)]
1017pub struct ExprBytesLiteral<'a> {
1018    value: ComparableBytesLiteral<'a>,
1019}
1020
1021#[derive(Debug, PartialEq, Eq, Hash)]
1022pub struct ExprNumberLiteral<'a> {
1023    value: ComparableNumber<'a>,
1024}
1025
1026#[derive(Debug, PartialEq, Eq, Hash)]
1027pub struct ExprConstant<'a> {
1028    value: ComparableConstantValue<'a>,
1029}
1030
1031#[derive(Debug, PartialEq, Eq, Hash)]
1032pub struct ExprBoolLiteral {
1033    value: bool,
1034}
1035
1036#[derive(Debug, PartialEq, Eq, Hash)]
1037pub struct ExprAttribute<'a> {
1038    value: Box<ComparableExpr<'a>>,
1039    attr: &'a str,
1040}
1041
1042#[derive(Debug, PartialEq, Eq, Hash)]
1043pub struct ExprSubscript<'a> {
1044    value: Box<ComparableExpr<'a>>,
1045    slice: Box<ComparableExpr<'a>>,
1046}
1047
1048#[derive(Debug, PartialEq, Eq, Hash)]
1049pub struct ExprStarred<'a> {
1050    value: Box<ComparableExpr<'a>>,
1051}
1052
1053#[derive(Debug, PartialEq, Eq, Hash)]
1054pub struct ExprName<'a> {
1055    id: &'a str,
1056}
1057
1058#[derive(Debug, PartialEq, Eq, Hash)]
1059pub struct ExprList<'a> {
1060    elts: Vec<ComparableExpr<'a>>,
1061}
1062
1063#[derive(Debug, PartialEq, Eq, Hash)]
1064pub struct ExprTuple<'a> {
1065    elts: Vec<ComparableExpr<'a>>,
1066}
1067
1068#[derive(Debug, PartialEq, Eq, Hash)]
1069pub struct ExprSlice<'a> {
1070    lower: Option<Box<ComparableExpr<'a>>>,
1071    upper: Option<Box<ComparableExpr<'a>>>,
1072    step: Option<Box<ComparableExpr<'a>>>,
1073}
1074
1075#[derive(Debug, PartialEq, Eq, Hash)]
1076pub struct ExprIpyEscapeCommand<'a> {
1077    kind: ast::IpyEscapeKind,
1078    value: &'a str,
1079}
1080
1081#[derive(Debug, PartialEq, Eq, Hash)]
1082pub enum ComparableExpr<'a> {
1083    BoolOp(ExprBoolOp<'a>),
1084    NamedExpr(ExprNamed<'a>),
1085    BinOp(ExprBinOp<'a>),
1086    UnaryOp(ExprUnaryOp<'a>),
1087    Lambda(ExprLambda<'a>),
1088    IfExp(ExprIf<'a>),
1089    Dict(ExprDict<'a>),
1090    Set(ExprSet<'a>),
1091    ListComp(ExprListComp<'a>),
1092    SetComp(ExprSetComp<'a>),
1093    DictComp(ExprDictComp<'a>),
1094    GeneratorExp(ExprGenerator<'a>),
1095    Await(ExprAwait<'a>),
1096    Yield(ExprYield<'a>),
1097    YieldFrom(ExprYieldFrom<'a>),
1098    Compare(ExprCompare<'a>),
1099    Call(ExprCall<'a>),
1100    FStringExpressionElement(ExprInterpolatedElement<'a>),
1101    FString(ExprFString<'a>),
1102    TStringInterpolationElement(ExprInterpolatedElement<'a>),
1103    TString(ExprTString<'a>),
1104    StringLiteral(ExprStringLiteral<'a>),
1105    BytesLiteral(ExprBytesLiteral<'a>),
1106    NumberLiteral(ExprNumberLiteral<'a>),
1107    Constant(ExprConstant<'a>),
1108    BoolLiteral(ExprBoolLiteral),
1109    NoneLiteral,
1110    EllipsisLiteral,
1111    Attribute(ExprAttribute<'a>),
1112    Subscript(ExprSubscript<'a>),
1113    Starred(ExprStarred<'a>),
1114    Name(ExprName<'a>),
1115    List(ExprList<'a>),
1116    Tuple(ExprTuple<'a>),
1117    Slice(ExprSlice<'a>),
1118    IpyEscapeCommand(ExprIpyEscapeCommand<'a>),
1119}
1120
1121impl<'a> From<&'a Box<ast::Expr>> for Box<ComparableExpr<'a>> {
1122    fn from(expr: &'a Box<ast::Expr>) -> Self {
1123        Box::new((expr.as_ref()).into())
1124    }
1125}
1126
1127impl<'a> From<&'a Box<ast::Expr>> for ComparableExpr<'a> {
1128    fn from(expr: &'a Box<ast::Expr>) -> Self {
1129        (expr.as_ref()).into()
1130    }
1131}
1132
1133impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> {
1134    fn from(expr: &'a ast::Expr) -> Self {
1135        match expr {
1136            ast::Expr::BoolOp(ast::ExprBoolOp { op, values, .. }) => Self::BoolOp(ExprBoolOp {
1137                op: (*op).into(),
1138                values: values.iter().map(Into::into).collect(),
1139            }),
1140            ast::Expr::Named(ast::ExprNamed { target, value, .. }) => Self::NamedExpr(ExprNamed {
1141                target: target.into(),
1142                value: value.into(),
1143            }),
1144            ast::Expr::BinOp(ast::ExprBinOp {
1145                left, op, right, ..
1146            }) => Self::BinOp(ExprBinOp {
1147                left: left.into(),
1148                op: (*op).into(),
1149                right: right.into(),
1150            }),
1151            ast::Expr::UnaryOp(ast::ExprUnaryOp { op, operand, .. }) => {
1152                Self::UnaryOp(ExprUnaryOp {
1153                    op: (*op).into(),
1154                    operand: operand.into(),
1155                })
1156            }
1157            ast::Expr::Lambda(ast::ExprLambda {
1158                parameters, body, ..
1159            }) => Self::Lambda(ExprLambda {
1160                parameters: parameters.as_ref().map(Into::into),
1161                body: body.into(),
1162            }),
1163            ast::Expr::If(ast::ExprIf {
1164                test, body, orelse, ..
1165            }) => Self::IfExp(ExprIf {
1166                test: test.into(),
1167                body: body.into(),
1168                orelse: orelse.into(),
1169            }),
1170            ast::Expr::Dict(ast::ExprDict { items, .. }) => Self::Dict(ExprDict {
1171                items: items.iter().map(ComparableDictItem::from).collect(),
1172            }),
1173            ast::Expr::Set(ast::ExprSet { elts, .. }) => Self::Set(ExprSet {
1174                elts: elts.iter().map(Into::into).collect(),
1175            }),
1176            ast::Expr::ListComp(ast::ExprListComp {
1177                elt, generators, ..
1178            }) => Self::ListComp(ExprListComp {
1179                elt: elt.into(),
1180                generators: generators.iter().map(Into::into).collect(),
1181            }),
1182            ast::Expr::SetComp(ast::ExprSetComp {
1183                elt, generators, ..
1184            }) => Self::SetComp(ExprSetComp {
1185                elt: elt.into(),
1186                generators: generators.iter().map(Into::into).collect(),
1187            }),
1188            ast::Expr::DictComp(ast::ExprDictComp {
1189                key,
1190                value,
1191                generators,
1192                ..
1193            }) => Self::DictComp(ExprDictComp {
1194                key: key.as_ref().map(Into::into),
1195                value: value.into(),
1196                generators: generators.iter().map(Into::into).collect(),
1197            }),
1198            ast::Expr::Generator(ast::ExprGenerator {
1199                elt, generators, ..
1200            }) => Self::GeneratorExp(ExprGenerator {
1201                elt: elt.into(),
1202                generators: generators.iter().map(Into::into).collect(),
1203            }),
1204            ast::Expr::Await(ast::ExprAwait { value, .. }) => Self::Await(ExprAwait {
1205                value: value.into(),
1206            }),
1207            ast::Expr::Yield(ast::ExprYield { value, .. }) => Self::Yield(ExprYield {
1208                value: value.as_ref().map(Into::into),
1209            }),
1210            ast::Expr::YieldFrom(ast::ExprYieldFrom { value, .. }) => {
1211                Self::YieldFrom(ExprYieldFrom {
1212                    value: value.into(),
1213                })
1214            }
1215            ast::Expr::Compare(ast::ExprCompare {
1216                left,
1217                ops,
1218                comparators,
1219                ..
1220            }) => Self::Compare(ExprCompare {
1221                left: left.into(),
1222                ops: ops.iter().copied().map(Into::into).collect(),
1223                comparators: comparators.iter().map(Into::into).collect(),
1224            }),
1225            ast::Expr::Call(ast::ExprCall {
1226                func, arguments, ..
1227            }) => Self::Call(ExprCall {
1228                func: func.into(),
1229                arguments: arguments.into(),
1230            }),
1231            ast::Expr::FString(ast::ExprFString { value, .. }) => Self::FString(ExprFString {
1232                value: value.into(),
1233            }),
1234            ast::Expr::TString(ast::ExprTString { value, .. }) => Self::TString(ExprTString {
1235                value: value.into(),
1236            }),
1237            ast::Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
1238                Self::StringLiteral(ExprStringLiteral {
1239                    value: ComparableStringLiteral {
1240                        value: value.to_str(),
1241                    },
1242                })
1243            }
1244            ast::Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
1245                Self::BytesLiteral(ExprBytesLiteral {
1246                    value: ComparableBytesLiteral {
1247                        value: Cow::from(value),
1248                    },
1249                })
1250            }
1251            ast::Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => {
1252                Self::NumberLiteral(ExprNumberLiteral {
1253                    value: value.into(),
1254                })
1255            }
1256            ast::Expr::Constant(ast::ExprConstant { value, .. }) => Self::Constant(ExprConstant {
1257                value: value.into(),
1258            }),
1259            ast::Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) => {
1260                Self::BoolLiteral(ExprBoolLiteral { value: *value })
1261            }
1262            ast::Expr::NoneLiteral(_) => Self::NoneLiteral,
1263            ast::Expr::EllipsisLiteral(_) => Self::EllipsisLiteral,
1264            ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
1265                Self::Attribute(ExprAttribute {
1266                    value: value.into(),
1267                    attr: attr.as_str(),
1268                })
1269            }
1270            ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
1271                Self::Subscript(ExprSubscript {
1272                    value: value.into(),
1273                    slice: slice.into(),
1274                })
1275            }
1276            ast::Expr::Starred(ast::ExprStarred { value, .. }) => Self::Starred(ExprStarred {
1277                value: value.into(),
1278            }),
1279            ast::Expr::Name(name) => name.into(),
1280            ast::Expr::List(ast::ExprList { elts, .. }) => Self::List(ExprList {
1281                elts: elts.iter().map(Into::into).collect(),
1282            }),
1283            ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => Self::Tuple(ExprTuple {
1284                elts: elts.iter().map(Into::into).collect(),
1285            }),
1286            ast::Expr::Slice(ast::ExprSlice {
1287                lower, upper, step, ..
1288            }) => Self::Slice(ExprSlice {
1289                lower: lower.as_ref().map(Into::into),
1290                upper: upper.as_ref().map(Into::into),
1291                step: step.as_ref().map(Into::into),
1292            }),
1293            ast::Expr::IpyEscapeCommand(ast::ExprIpyEscapeCommand { kind, value, .. }) => {
1294                Self::IpyEscapeCommand(ExprIpyEscapeCommand { kind: *kind, value })
1295            }
1296        }
1297    }
1298}
1299
1300impl<'a> From<&'a ast::ExprName> for ComparableExpr<'a> {
1301    fn from(expr: &'a ast::ExprName) -> Self {
1302        Self::Name(ExprName {
1303            id: expr.id.as_str(),
1304        })
1305    }
1306}
1307
1308#[derive(Debug, PartialEq, Eq, Hash)]
1309pub struct StmtFunctionDef<'a> {
1310    is_async: bool,
1311    decorator_list: Vec<ComparableDecorator<'a>>,
1312    name: &'a str,
1313    type_params: Option<ComparableTypeParams<'a>>,
1314    parameters: ComparableParameters<'a>,
1315    returns: Option<ComparableExpr<'a>>,
1316    body: Vec<ComparableStmt<'a>>,
1317}
1318
1319#[derive(Debug, PartialEq, Eq, Hash)]
1320pub struct StmtClassDef<'a> {
1321    decorator_list: Vec<ComparableDecorator<'a>>,
1322    name: &'a str,
1323    type_params: Option<ComparableTypeParams<'a>>,
1324    arguments: ComparableArguments<'a>,
1325    body: Vec<ComparableStmt<'a>>,
1326}
1327
1328#[derive(Debug, PartialEq, Eq, Hash)]
1329pub struct StmtReturn<'a> {
1330    value: Option<ComparableExpr<'a>>,
1331}
1332
1333#[derive(Debug, PartialEq, Eq, Hash)]
1334pub struct StmtDelete<'a> {
1335    targets: Vec<ComparableExpr<'a>>,
1336}
1337
1338#[derive(Debug, PartialEq, Eq, Hash)]
1339pub struct StmtTypeAlias<'a> {
1340    pub name: Box<ComparableExpr<'a>>,
1341    pub type_params: Option<ComparableTypeParams<'a>>,
1342    pub value: Box<ComparableExpr<'a>>,
1343}
1344
1345#[derive(Debug, PartialEq, Eq, Hash)]
1346pub struct ComparableTypeParams<'a> {
1347    pub type_params: Vec<ComparableTypeParam<'a>>,
1348}
1349
1350impl<'a> From<&'a ast::TypeParams> for ComparableTypeParams<'a> {
1351    fn from(type_params: &'a ast::TypeParams) -> Self {
1352        Self {
1353            type_params: type_params.iter().map(Into::into).collect(),
1354        }
1355    }
1356}
1357
1358impl<'a> From<&'a Box<ast::TypeParams>> for ComparableTypeParams<'a> {
1359    fn from(type_params: &'a Box<ast::TypeParams>) -> Self {
1360        type_params.as_ref().into()
1361    }
1362}
1363
1364#[derive(Debug, PartialEq, Eq, Hash)]
1365pub enum ComparableTypeParam<'a> {
1366    TypeVar(TypeParamTypeVar<'a>),
1367    ParamSpec(TypeParamParamSpec<'a>),
1368    TypeVarTuple(TypeParamTypeVarTuple<'a>),
1369}
1370
1371impl<'a> From<&'a ast::TypeParam> for ComparableTypeParam<'a> {
1372    fn from(type_param: &'a ast::TypeParam) -> Self {
1373        match type_param {
1374            ast::TypeParam::TypeVar(ast::TypeParamTypeVar {
1375                name,
1376                bound,
1377                default,
1378                ..
1379            }) => Self::TypeVar(TypeParamTypeVar {
1380                name: name.as_str(),
1381                bound: bound.as_ref().map(Into::into),
1382                default: default.as_ref().map(Into::into),
1383            }),
1384            ast::TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple { name, default, .. }) => {
1385                Self::TypeVarTuple(TypeParamTypeVarTuple {
1386                    name: name.as_str(),
1387                    default: default.as_ref().map(Into::into),
1388                })
1389            }
1390            ast::TypeParam::ParamSpec(ast::TypeParamParamSpec { name, default, .. }) => {
1391                Self::ParamSpec(TypeParamParamSpec {
1392                    name: name.as_str(),
1393                    default: default.as_ref().map(Into::into),
1394                })
1395            }
1396        }
1397    }
1398}
1399
1400#[derive(Debug, PartialEq, Eq, Hash)]
1401pub struct TypeParamTypeVar<'a> {
1402    pub name: &'a str,
1403    pub bound: Option<Box<ComparableExpr<'a>>>,
1404    pub default: Option<Box<ComparableExpr<'a>>>,
1405}
1406
1407#[derive(Debug, PartialEq, Eq, Hash)]
1408pub struct TypeParamParamSpec<'a> {
1409    pub name: &'a str,
1410    pub default: Option<Box<ComparableExpr<'a>>>,
1411}
1412
1413#[derive(Debug, PartialEq, Eq, Hash)]
1414pub struct TypeParamTypeVarTuple<'a> {
1415    pub name: &'a str,
1416    pub default: Option<Box<ComparableExpr<'a>>>,
1417}
1418
1419#[derive(Debug, PartialEq, Eq, Hash)]
1420pub struct StmtAssign<'a> {
1421    targets: Vec<ComparableExpr<'a>>,
1422    value: ComparableExpr<'a>,
1423}
1424
1425#[derive(Debug, PartialEq, Eq, Hash)]
1426pub struct StmtAugAssign<'a> {
1427    target: ComparableExpr<'a>,
1428    op: ComparableOperator,
1429    value: ComparableExpr<'a>,
1430}
1431
1432#[derive(Debug, PartialEq, Eq, Hash)]
1433pub struct StmtAnnAssign<'a> {
1434    target: ComparableExpr<'a>,
1435    annotation: ComparableExpr<'a>,
1436    value: Option<ComparableExpr<'a>>,
1437    simple: bool,
1438}
1439
1440#[derive(Debug, PartialEq, Eq, Hash)]
1441pub struct StmtFor<'a> {
1442    is_async: bool,
1443    target: ComparableExpr<'a>,
1444    iter: ComparableExpr<'a>,
1445    body: Vec<ComparableStmt<'a>>,
1446    orelse: Vec<ComparableStmt<'a>>,
1447}
1448
1449#[derive(Debug, PartialEq, Eq, Hash)]
1450pub struct StmtWhile<'a> {
1451    test: ComparableExpr<'a>,
1452    body: Vec<ComparableStmt<'a>>,
1453    orelse: Vec<ComparableStmt<'a>>,
1454}
1455
1456#[derive(Debug, PartialEq, Eq, Hash)]
1457pub struct StmtIf<'a> {
1458    test: ComparableExpr<'a>,
1459    body: Vec<ComparableStmt<'a>>,
1460    elif_else_clauses: Vec<ComparableElifElseClause<'a>>,
1461}
1462
1463#[derive(Debug, PartialEq, Eq, Hash)]
1464pub struct StmtWith<'a> {
1465    is_async: bool,
1466    items: Vec<ComparableWithItem<'a>>,
1467    body: Vec<ComparableStmt<'a>>,
1468}
1469
1470#[derive(Debug, PartialEq, Eq, Hash)]
1471pub struct StmtMatch<'a> {
1472    subject: ComparableExpr<'a>,
1473    cases: Vec<ComparableMatchCase<'a>>,
1474}
1475
1476#[derive(Debug, PartialEq, Eq, Hash)]
1477pub struct StmtRaise<'a> {
1478    exc: Option<ComparableExpr<'a>>,
1479    cause: Option<ComparableExpr<'a>>,
1480}
1481
1482#[derive(Debug, PartialEq, Eq, Hash)]
1483pub struct StmtTry<'a> {
1484    body: Vec<ComparableStmt<'a>>,
1485    handlers: Vec<ComparableExceptHandler<'a>>,
1486    orelse: Vec<ComparableStmt<'a>>,
1487    finalbody: Vec<ComparableStmt<'a>>,
1488    is_star: bool,
1489}
1490
1491#[derive(Debug, PartialEq, Eq, Hash)]
1492pub struct StmtAssert<'a> {
1493    test: ComparableExpr<'a>,
1494    msg: Option<ComparableExpr<'a>>,
1495}
1496
1497#[derive(Debug, PartialEq, Eq, Hash)]
1498pub struct StmtImport<'a> {
1499    names: Vec<ComparableAlias<'a>>,
1500    is_lazy: bool,
1501}
1502
1503#[derive(Debug, PartialEq, Eq, Hash)]
1504pub struct StmtImportFrom<'a> {
1505    module: Option<&'a str>,
1506    names: Vec<ComparableAlias<'a>>,
1507    level: u32,
1508    is_lazy: bool,
1509}
1510
1511#[derive(Debug, PartialEq, Eq, Hash)]
1512pub struct StmtGlobal<'a> {
1513    names: Vec<&'a str>,
1514}
1515
1516#[derive(Debug, PartialEq, Eq, Hash)]
1517pub struct StmtNonlocal<'a> {
1518    names: Vec<&'a str>,
1519}
1520
1521#[derive(Debug, PartialEq, Eq, Hash)]
1522pub struct StmtExpr<'a> {
1523    value: ComparableExpr<'a>,
1524}
1525
1526#[derive(Debug, PartialEq, Eq, Hash)]
1527pub struct StmtIpyEscapeCommand<'a> {
1528    kind: ast::IpyEscapeKind,
1529    value: &'a str,
1530}
1531
1532#[derive(Debug, PartialEq, Eq, Hash)]
1533pub enum ComparableStmt<'a> {
1534    FunctionDef(StmtFunctionDef<'a>),
1535    ClassDef(StmtClassDef<'a>),
1536    Return(StmtReturn<'a>),
1537    Delete(StmtDelete<'a>),
1538    Assign(StmtAssign<'a>),
1539    AugAssign(StmtAugAssign<'a>),
1540    AnnAssign(StmtAnnAssign<'a>),
1541    For(StmtFor<'a>),
1542    While(StmtWhile<'a>),
1543    If(StmtIf<'a>),
1544    With(StmtWith<'a>),
1545    Match(StmtMatch<'a>),
1546    Raise(StmtRaise<'a>),
1547    Try(StmtTry<'a>),
1548    TypeAlias(StmtTypeAlias<'a>),
1549    Assert(StmtAssert<'a>),
1550    Import(StmtImport<'a>),
1551    ImportFrom(StmtImportFrom<'a>),
1552    Global(StmtGlobal<'a>),
1553    Nonlocal(StmtNonlocal<'a>),
1554    IpyEscapeCommand(StmtIpyEscapeCommand<'a>),
1555    Expr(StmtExpr<'a>),
1556    Pass,
1557    Break,
1558    Continue,
1559}
1560
1561impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> {
1562    fn from(stmt: &'a ast::Stmt) -> Self {
1563        match stmt {
1564            ast::Stmt::FunctionDef(ast::StmtFunctionDef {
1565                is_async,
1566                name,
1567                parameters,
1568                body,
1569                decorator_list,
1570                returns,
1571                type_params,
1572                ..
1573            }) => Self::FunctionDef(StmtFunctionDef {
1574                is_async: *is_async,
1575                name: name.as_str(),
1576                parameters: parameters.into(),
1577                body: body.iter().map(Into::into).collect(),
1578                decorator_list: decorator_list.iter().map(Into::into).collect(),
1579                returns: returns.as_ref().map(Into::into),
1580                type_params: type_params.as_ref().map(Into::into),
1581            }),
1582            ast::Stmt::ClassDef(ast::StmtClassDef {
1583                name,
1584                arguments,
1585                body,
1586                decorator_list,
1587                type_params,
1588                ..
1589            }) => Self::ClassDef(StmtClassDef {
1590                name: name.as_str(),
1591                arguments: arguments.as_ref().map(Into::into).unwrap_or_default(),
1592                body: body.iter().map(Into::into).collect(),
1593                decorator_list: decorator_list.iter().map(Into::into).collect(),
1594                type_params: type_params.as_ref().map(Into::into),
1595            }),
1596            ast::Stmt::Return(ast::StmtReturn { value, .. }) => Self::Return(StmtReturn {
1597                value: value.as_ref().map(Into::into),
1598            }),
1599            ast::Stmt::Delete(ast::StmtDelete { targets, .. }) => Self::Delete(StmtDelete {
1600                targets: targets.iter().map(Into::into).collect(),
1601            }),
1602            ast::Stmt::TypeAlias(ast::StmtTypeAlias {
1603                range: _,
1604                node_index: _,
1605                name,
1606                type_params,
1607                value,
1608            }) => Self::TypeAlias(StmtTypeAlias {
1609                name: name.into(),
1610                type_params: type_params.as_ref().map(Into::into),
1611                value: value.into(),
1612            }),
1613            ast::Stmt::Assign(ast::StmtAssign { targets, value, .. }) => Self::Assign(StmtAssign {
1614                targets: targets.iter().map(Into::into).collect(),
1615                value: value.into(),
1616            }),
1617            ast::Stmt::AugAssign(ast::StmtAugAssign {
1618                target, op, value, ..
1619            }) => Self::AugAssign(StmtAugAssign {
1620                target: target.into(),
1621                op: (*op).into(),
1622                value: value.into(),
1623            }),
1624            ast::Stmt::AnnAssign(ast::StmtAnnAssign {
1625                target,
1626                annotation,
1627                value,
1628                simple,
1629                ..
1630            }) => Self::AnnAssign(StmtAnnAssign {
1631                target: target.into(),
1632                annotation: annotation.into(),
1633                value: value.as_ref().map(Into::into),
1634                simple: *simple,
1635            }),
1636            ast::Stmt::For(ast::StmtFor {
1637                is_async,
1638                target,
1639                iter,
1640                body,
1641                orelse,
1642                ..
1643            }) => Self::For(StmtFor {
1644                is_async: *is_async,
1645                target: target.into(),
1646                iter: iter.into(),
1647                body: body.iter().map(Into::into).collect(),
1648                orelse: orelse.iter().map(Into::into).collect(),
1649            }),
1650            ast::Stmt::While(ast::StmtWhile {
1651                test, body, orelse, ..
1652            }) => Self::While(StmtWhile {
1653                test: test.into(),
1654                body: body.iter().map(Into::into).collect(),
1655                orelse: orelse.iter().map(Into::into).collect(),
1656            }),
1657            ast::Stmt::If(ast::StmtIf {
1658                test,
1659                body,
1660                elif_else_clauses,
1661                ..
1662            }) => Self::If(StmtIf {
1663                test: test.into(),
1664                body: body.iter().map(Into::into).collect(),
1665                elif_else_clauses: elif_else_clauses.iter().map(Into::into).collect(),
1666            }),
1667            ast::Stmt::With(ast::StmtWith {
1668                is_async,
1669                items,
1670                body,
1671                ..
1672            }) => Self::With(StmtWith {
1673                is_async: *is_async,
1674                items: items.iter().map(Into::into).collect(),
1675                body: body.iter().map(Into::into).collect(),
1676            }),
1677            ast::Stmt::Match(ast::StmtMatch { subject, cases, .. }) => Self::Match(StmtMatch {
1678                subject: subject.into(),
1679                cases: cases.iter().map(Into::into).collect(),
1680            }),
1681            ast::Stmt::Raise(ast::StmtRaise { exc, cause, .. }) => Self::Raise(StmtRaise {
1682                exc: exc.as_ref().map(Into::into),
1683                cause: cause.as_ref().map(Into::into),
1684            }),
1685            ast::Stmt::Try(ast::StmtTry {
1686                body,
1687                handlers,
1688                orelse,
1689                finalbody,
1690                is_star,
1691                ..
1692            }) => Self::Try(StmtTry {
1693                body: body.iter().map(Into::into).collect(),
1694                handlers: handlers.iter().map(Into::into).collect(),
1695                orelse: orelse.iter().map(Into::into).collect(),
1696                finalbody: finalbody.iter().map(Into::into).collect(),
1697                is_star: *is_star,
1698            }),
1699            ast::Stmt::Assert(ast::StmtAssert { test, msg, .. }) => Self::Assert(StmtAssert {
1700                test: test.into(),
1701                msg: msg.as_ref().map(Into::into),
1702            }),
1703            ast::Stmt::Import(ast::StmtImport { names, is_lazy, .. }) => Self::Import(StmtImport {
1704                names: names.iter().map(Into::into).collect(),
1705                is_lazy: *is_lazy,
1706            }),
1707            ast::Stmt::ImportFrom(ast::StmtImportFrom {
1708                module,
1709                names,
1710                level,
1711                is_lazy,
1712                ..
1713            }) => Self::ImportFrom(StmtImportFrom {
1714                module: module.as_deref(),
1715                names: names.iter().map(Into::into).collect(),
1716                level: *level,
1717                is_lazy: *is_lazy,
1718            }),
1719            ast::Stmt::Global(ast::StmtGlobal { names, .. }) => Self::Global(StmtGlobal {
1720                names: names.iter().map(ast::Identifier::as_str).collect(),
1721            }),
1722            ast::Stmt::Nonlocal(ast::StmtNonlocal { names, .. }) => Self::Nonlocal(StmtNonlocal {
1723                names: names.iter().map(ast::Identifier::as_str).collect(),
1724            }),
1725            ast::Stmt::IpyEscapeCommand(ast::StmtIpyEscapeCommand { kind, value, .. }) => {
1726                Self::IpyEscapeCommand(StmtIpyEscapeCommand { kind: *kind, value })
1727            }
1728            ast::Stmt::Expr(ast::StmtExpr { value, .. }) => Self::Expr(StmtExpr {
1729                value: value.into(),
1730            }),
1731            ast::Stmt::Pass(_) => Self::Pass,
1732            ast::Stmt::Break(_) => Self::Break,
1733            ast::Stmt::Continue(_) => Self::Continue,
1734        }
1735    }
1736}
1737
1738#[derive(Debug, PartialEq, Eq, Hash)]
1739pub enum ComparableMod<'a> {
1740    Module(ComparableModModule<'a>),
1741    Expression(ComparableModExpression<'a>),
1742}
1743
1744#[derive(Debug, PartialEq, Eq, Hash)]
1745pub struct ComparableModModule<'a> {
1746    body: Vec<ComparableStmt<'a>>,
1747}
1748
1749#[derive(Debug, PartialEq, Eq, Hash)]
1750pub struct ComparableModExpression<'a> {
1751    body: Box<ComparableExpr<'a>>,
1752}
1753
1754impl<'a> From<&'a ast::Mod> for ComparableMod<'a> {
1755    fn from(mod_: &'a ast::Mod) -> Self {
1756        match mod_ {
1757            ast::Mod::Module(module) => Self::Module(module.into()),
1758            ast::Mod::Expression(expr) => Self::Expression(expr.into()),
1759        }
1760    }
1761}
1762
1763impl<'a> From<&'a ast::ModModule> for ComparableModModule<'a> {
1764    fn from(module: &'a ast::ModModule) -> Self {
1765        Self {
1766            body: module.body.iter().map(Into::into).collect(),
1767        }
1768    }
1769}
1770
1771impl<'a> From<&'a ast::ModExpression> for ComparableModExpression<'a> {
1772    fn from(expr: &'a ast::ModExpression) -> Self {
1773        Self {
1774            body: (&expr.body).into(),
1775        }
1776    }
1777}
1778
1779/// Wrapper around [`Expr`] that implements [`Hash`] and [`PartialEq`] according to Python
1780/// semantics:
1781///
1782/// > Values that compare equal (such as 1, 1.0, and True) can be used interchangeably to index the
1783/// > same dictionary entry.
1784///
1785/// For example, considers `True`, `1`, and `1.0` to be equal, as they hash to the same value
1786/// in Python, along with `False`, `0`, and `0.0`.
1787///
1788/// See: <https://docs.python.org/3/library/stdtypes.html#mapping-types-dict>
1789#[derive(Debug, PartialEq, Eq, Hash)]
1790pub struct HashableExpr<'a>(HashableExprKind<'a>);
1791
1792#[derive(Debug, PartialEq, Eq, Hash)]
1793enum HashableExprKind<'a> {
1794    Comparable(ComparableExpr<'a>),
1795    Number(HashableNumber),
1796    NamedExpr {
1797        target: ComparableExpr<'a>,
1798        value: Box<HashableExprKind<'a>>,
1799    },
1800    Tuple(Vec<HashableExprKind<'a>>),
1801}
1802
1803#[derive(Debug, PartialEq, Eq, Hash)]
1804struct HashableNumber {
1805    real: HashableReal,
1806    imag: HashableReal,
1807}
1808
1809impl HashableNumber {
1810    fn real(real: HashableReal) -> Self {
1811        Self {
1812            real,
1813            imag: HashableReal::Integer(0),
1814        }
1815    }
1816
1817    fn complex(real: HashableReal, imag: HashableReal) -> Self {
1818        Self { real, imag }
1819    }
1820
1821    fn negate(mut self) -> Self {
1822        self.real.negate();
1823        self.imag.negate();
1824        self
1825    }
1826
1827    fn into_real(self) -> Option<HashableReal> {
1828        self.imag.is_zero().then_some(self.real)
1829    }
1830}
1831
1832#[derive(Debug, PartialEq, Eq, Hash)]
1833enum HashableReal {
1834    Integer(i128),
1835    Float(u64),
1836}
1837
1838impl HashableReal {
1839    fn from_int(value: &ast::Int) -> Option<Self> {
1840        value.as_u64().map(i128::from).map(Self::Integer)
1841    }
1842
1843    #[expect(
1844        clippy::cast_possible_truncation,
1845        clippy::cast_precision_loss,
1846        clippy::float_cmp,
1847        reason = "the round-trip check guarantees that the float is exactly representable as an integer"
1848    )]
1849    fn from_float(value: f64) -> Self {
1850        if value.is_finite() && value.abs() < U64_EXCLUSIVE_UPPER_BOUND {
1851            let integer = value as i128;
1852            if integer as f64 == value {
1853                return Self::Integer(integer);
1854            }
1855        }
1856        Self::Float(value.to_bits())
1857    }
1858
1859    #[expect(
1860        clippy::cast_precision_loss,
1861        reason = "Python converts real components to floats before complex arithmetic"
1862    )]
1863    fn into_float(self) -> Self {
1864        match self {
1865            Self::Integer(integer) => Self::from_float(integer as f64),
1866            Self::Float(_) => self,
1867        }
1868    }
1869
1870    fn is_zero(&self) -> bool {
1871        matches!(self, Self::Integer(0))
1872    }
1873
1874    fn negate(&mut self) {
1875        match self {
1876            Self::Integer(integer) => *integer = -*integer,
1877            Self::Float(bits) => *bits ^= 1 << 63,
1878        }
1879    }
1880}
1881
1882// `2^64`, the exclusive upper bound for values representable as a `u64`.
1883const U64_EXCLUSIVE_UPPER_BOUND: f64 = 18_446_744_073_709_551_616.0;
1884
1885impl<'a> From<&'a Expr> for HashableExpr<'a> {
1886    fn from(expr: &'a Expr) -> Self {
1887        /// Returns a version of the given expression that can be hashed and compared according to
1888        /// Python  semantics.
1889        fn as_hashable(expr: &Expr) -> HashableExprKind<'_> {
1890            if let Some(constant) = as_hashable_constant(expr) {
1891                return constant;
1892            }
1893
1894            match expr {
1895                Expr::Named(named) => HashableExprKind::NamedExpr {
1896                    target: ComparableExpr::from(&named.target),
1897                    value: Box::new(as_hashable(&named.value)),
1898                },
1899                _ => HashableExprKind::Comparable(ComparableExpr::from(expr)),
1900            }
1901        }
1902
1903        /// Returns a hashable representation if the expression's value is statically known.
1904        fn as_hashable_constant(expr: &Expr) -> Option<HashableExprKind<'_>> {
1905            if let Some(number) = as_number(expr) {
1906                return Some(HashableExprKind::Number(number));
1907            }
1908
1909            let kind = match expr {
1910                Expr::Tuple(tuple) => HashableExprKind::Tuple(
1911                    tuple
1912                        .iter()
1913                        .map(as_hashable_constant)
1914                        .collect::<Option<_>>()?,
1915                ),
1916                _ if expr.is_literal_expr() => {
1917                    HashableExprKind::Comparable(ComparableExpr::from(expr))
1918                }
1919                _ => return None,
1920            };
1921
1922            Some(kind)
1923        }
1924
1925        fn as_number(expr: &Expr) -> Option<HashableNumber> {
1926            match expr {
1927                Expr::BooleanLiteral(boolean) => Some(HashableNumber::real(HashableReal::Integer(
1928                    i128::from(u8::from(boolean.value)),
1929                ))),
1930                Expr::Constant(ast::ExprConstant {
1931                    value: ConstantValue::Boolean(value),
1932                    ..
1933                }) => Some(HashableNumber::real(HashableReal::Integer(i128::from(
1934                    u8::from(*value),
1935                )))),
1936                Expr::NumberLiteral(number) => match &number.value {
1937                    Number::Int(int) => HashableReal::from_int(int).map(HashableNumber::real),
1938                    Number::Float(float) => {
1939                        Some(HashableNumber::real(HashableReal::from_float(*float)))
1940                    }
1941                    Number::Complex { real, imag } => Some(HashableNumber::complex(
1942                        HashableReal::from_float(*real),
1943                        HashableReal::from_float(*imag),
1944                    )),
1945                },
1946                Expr::UnaryOp(ast::ExprUnaryOp { op, operand, .. }) => match op {
1947                    ast::UnaryOp::UAdd => as_number(operand),
1948                    ast::UnaryOp::USub => as_number(operand).map(HashableNumber::negate),
1949                    ast::UnaryOp::Invert | ast::UnaryOp::Not => None,
1950                },
1951                Expr::BinOp(ast::ExprBinOp {
1952                    left,
1953                    op: op @ (ast::Operator::Add | ast::Operator::Sub),
1954                    right,
1955                    ..
1956                }) => {
1957                    let real = as_number(left)?.into_real()?.into_float();
1958                    let Expr::NumberLiteral(ast::ExprNumberLiteral {
1959                        value:
1960                            Number::Complex {
1961                                real: complex_real,
1962                                imag,
1963                            },
1964                        ..
1965                    }) = right.as_ref()
1966                    else {
1967                        return None;
1968                    };
1969                    let complex_real = HashableReal::from_float(*complex_real);
1970                    if !complex_real.is_zero() {
1971                        return None;
1972                    }
1973                    let mut imag = HashableReal::from_float(*imag);
1974                    if op.is_sub() {
1975                        imag.negate();
1976                    }
1977                    Some(HashableNumber::complex(real, imag))
1978                }
1979                _ => None,
1980            }
1981        }
1982
1983        Self(as_hashable(expr))
1984    }
1985}