Skip to main content

ruff_python_ast/
helpers.rs

1use std::borrow::Cow;
2use std::path::Path;
3
4use rustc_hash::FxHashMap;
5
6use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer, indentation_at_offset};
7use ruff_source_file::LineRanges;
8use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
9
10use crate::name::{Name, QualifiedName, QualifiedNameBuilder};
11use crate::statement_visitor::StatementVisitor;
12use crate::token::{Tokens, parenthesized_range};
13use crate::visitor::Visitor;
14use crate::{
15    self as ast, Arguments, AtomicNodeIndex, CmpOp, ConstantValue, DictItem, ExceptHandler, Expr,
16    ExprNoneLiteral, InterpolatedStringElement, MatchCase, Operator, Pattern, Stmt, TypeParam,
17};
18use crate::{AnyNodeRef, ExprContext};
19
20/// Return `true` if the `Stmt` is a compound statement (as opposed to a simple statement).
21pub const fn is_compound_statement(stmt: &Stmt) -> bool {
22    matches!(
23        stmt,
24        Stmt::FunctionDef(_)
25            | Stmt::ClassDef(_)
26            | Stmt::While(_)
27            | Stmt::For(_)
28            | Stmt::Match(_)
29            | Stmt::With(_)
30            | Stmt::If(_)
31            | Stmt::Try(_)
32    )
33}
34
35fn is_iterable_initializer<F>(id: &str, is_builtin: F) -> bool
36where
37    F: Fn(&str) -> bool,
38{
39    matches!(id, "list" | "tuple" | "set" | "dict" | "frozenset") && is_builtin(id)
40}
41
42/// Whether an expression has no side effects, may have side effects,
43/// or is assumed to have side effects.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum SideEffect {
46    /// The expression is definitely side-effect-free.
47    Absent,
48    /// The expression may have side effects (e.g., f-string interpolation
49    /// may invoke `__format__` or `__str__`).
50    Possible,
51    /// The expression is assumed to have side effects.
52    Present,
53}
54
55impl SideEffect {
56    pub const fn is_present(self) -> bool {
57        matches!(self, Self::Present)
58    }
59
60    pub const fn is_absent(self) -> bool {
61        matches!(self, Self::Absent)
62    }
63
64    #[must_use]
65    pub const fn merge(self, other: Self) -> Self {
66        match (self, other) {
67            (Self::Present, _) | (_, Self::Present) => Self::Present,
68            (Self::Possible, _) | (_, Self::Possible) => Self::Possible,
69            _ => Self::Absent,
70        }
71    }
72
73    /// Classify a single expression node's side effect.
74    fn from_expr(expr: &Expr, is_builtin: &dyn Fn(&str) -> bool) -> Self {
75        match expr {
76            // Empty initializers for known builtins are side-effect-free.
77            Expr::Call(ast::ExprCall {
78                func, arguments, ..
79            }) if arguments.is_empty() => {
80                if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
81                    if is_iterable_initializer(id.as_str(), |id| is_builtin(id)) {
82                        return Self::Absent;
83                    }
84                }
85                Self::Present
86            }
87
88            // Overloaded operators: only side-effect-free if both sides are literals.
89            Expr::BinOp(ast::ExprBinOp { left, right, .. }) => {
90                if is_known_safe_binop_operand(left) && is_known_safe_binop_operand(right) {
91                    Self::Absent
92                } else {
93                    Self::Present
94                }
95            }
96
97            // Non-literal f-string interpolation may invoke `__format__`/`__str__`.
98            Expr::FString(ast::ExprFString { value, .. }) => {
99                if value.elements().any(has_uncertain_interpolation) {
100                    Self::Possible
101                } else {
102                    Self::Absent
103                }
104            }
105            Expr::TString(ast::ExprTString { value, .. }) => {
106                if value.elements().any(has_uncertain_interpolation) {
107                    Self::Possible
108                } else {
109                    Self::Absent
110                }
111            }
112
113            // Named expressions (walrus operator) are assignments.
114            Expr::Named(_) => Self::Present,
115
116            // Complex expressions that are assumed to have side effects.
117            Expr::Await(_)
118            | Expr::Call(_)
119            | Expr::DictComp(_)
120            | Expr::Generator(_)
121            | Expr::ListComp(_)
122            | Expr::SetComp(_)
123            | Expr::Subscript(_)
124            | Expr::Yield(_)
125            | Expr::YieldFrom(_)
126            | Expr::IpyEscapeCommand(_) => Self::Present,
127
128            // Side-effect-free expressions — continue walking child nodes.
129            Expr::BoolOp(_)
130            | Expr::Compare(_)
131            | Expr::Dict(_)
132            | Expr::If(_)
133            | Expr::Lambda(_)
134            | Expr::List(_)
135            | Expr::Set(_)
136            | Expr::Slice(_)
137            | Expr::Starred(_)
138            | Expr::Tuple(_)
139            | Expr::UnaryOp(_)
140            | Expr::Attribute(_)
141            | Expr::Name(_)
142            | Expr::StringLiteral(_)
143            | Expr::BytesLiteral(_)
144            | Expr::NumberLiteral(_)
145            | Expr::Constant(_)
146            | Expr::BooleanLiteral(_)
147            | Expr::NoneLiteral(_)
148            | Expr::EllipsisLiteral(_) => Self::Absent,
149        }
150    }
151}
152
153const fn is_known_safe_binop_operand(expr: &Expr) -> bool {
154    match expr {
155        Expr::StringLiteral(_)
156        | Expr::BytesLiteral(_)
157        | Expr::NumberLiteral(_)
158        | Expr::Constant(_)
159        | Expr::BooleanLiteral(_)
160        | Expr::NoneLiteral(_)
161        | Expr::EllipsisLiteral(_)
162        | Expr::FString(_)
163        | Expr::List(_)
164        | Expr::Tuple(_)
165        | Expr::Set(_)
166        | Expr::Dict(_)
167        | Expr::ListComp(_)
168        | Expr::SetComp(_)
169        | Expr::DictComp(_) => true,
170
171        Expr::BoolOp(_)
172        | Expr::Named(_)
173        | Expr::BinOp(_)
174        | Expr::UnaryOp(_)
175        | Expr::Lambda(_)
176        | Expr::If(_)
177        | Expr::Compare(_)
178        | Expr::Call(_)
179        | Expr::Generator(_)
180        | Expr::Await(_)
181        | Expr::Yield(_)
182        | Expr::YieldFrom(_)
183        | Expr::Attribute(_)
184        | Expr::Subscript(_)
185        | Expr::Starred(_)
186        | Expr::Name(_)
187        | Expr::Slice(_)
188        | Expr::IpyEscapeCommand(_)
189        | Expr::TString(_) => false,
190    }
191}
192
193fn is_definitely_side_effect_free_interpolation_expr(expr: &Expr) -> bool {
194    matches!(
195        expr,
196        Expr::NumberLiteral(_)
197            | Expr::Constant(_)
198            | Expr::BooleanLiteral(_)
199            | Expr::NoneLiteral(_)
200            | Expr::EllipsisLiteral(_)
201            | Expr::StringLiteral(_)
202            | Expr::BytesLiteral(_)
203    )
204}
205
206fn has_uncertain_interpolation(element: &InterpolatedStringElement) -> bool {
207    match element {
208        InterpolatedStringElement::Literal(_) => false,
209        InterpolatedStringElement::Interpolation(interp) => {
210            !is_definitely_side_effect_free_interpolation_expr(&interp.expression)
211                || interp
212                    .format_spec
213                    .as_ref()
214                    .is_some_and(|spec| spec.elements.iter().any(has_uncertain_interpolation))
215        }
216    }
217}
218
219/// Return `true` if the `Expr` contains an expression that appears to include a
220/// side-effect (like a function call).
221///
222/// Accepts a closure that determines whether a given name (e.g., `"list"`) is a Python builtin.
223pub fn contains_effect<F>(expr: &Expr, is_builtin: F) -> bool
224where
225    F: Fn(&str) -> bool,
226{
227    side_effect(expr, is_builtin).is_present()
228}
229
230/// Return whether `expr` has no side effects, maybe has side effects, or definitely
231/// has side effects.
232///
233/// Unlike [`contains_effect`], which returns a simple `bool`, this function distinguishes
234/// between expressions that are definitely side-effect-free, definitely side-effectful,
235/// and those that may invoke user-defined code (e.g., formatting a non-literal f-string
236/// interpolation can call `__format__` or `__str__`).
237pub fn side_effect<F>(expr: &Expr, is_builtin: F) -> SideEffect
238where
239    F: Fn(&str) -> bool,
240{
241    let mut effect = SideEffect::Absent;
242    any_over_expr(expr, |expr| {
243        match SideEffect::from_expr(expr, &is_builtin) {
244            SideEffect::Present => {
245                effect = SideEffect::Present;
246                true
247            }
248            SideEffect::Possible => {
249                effect = effect.merge(SideEffect::Possible);
250                false
251            }
252            SideEffect::Absent => false,
253        }
254    });
255    effect
256}
257
258/// Call `func` over every `Expr` in `expr`, returning `true` if any expression
259/// returns `true`..
260pub fn any_over_expr<F>(expr: &Expr, mut func: F) -> bool
261where
262    F: FnMut(&Expr) -> bool,
263{
264    fn inner(expr: &Expr, func: &mut dyn FnMut(&Expr) -> bool) -> bool {
265        if func(expr) {
266            return true;
267        }
268        match expr {
269            Expr::BoolOp(ast::ExprBoolOp { values, .. }) => {
270                values.iter().any(|expr| any_over_expr(expr, &mut *func))
271            }
272            Expr::FString(ast::ExprFString { value, .. }) => value
273                .elements()
274                .any(|expr| any_over_interpolated_string_element(expr, &mut *func)),
275            Expr::TString(ast::ExprTString { value, .. }) => value
276                .elements()
277                .any(|expr| any_over_interpolated_string_element(expr, &mut *func)),
278            Expr::Named(ast::ExprNamed {
279                target,
280                value,
281                range: _,
282                node_index: _,
283            }) => any_over_expr(target, &mut *func) || any_over_expr(value, &mut *func),
284            Expr::BinOp(ast::ExprBinOp { left, right, .. }) => {
285                any_over_expr(left, &mut *func) || any_over_expr(right, &mut *func)
286            }
287            Expr::UnaryOp(ast::ExprUnaryOp { operand, .. }) => any_over_expr(operand, func),
288            Expr::Lambda(ast::ExprLambda {
289                body, parameters, ..
290            }) => {
291                parameters
292                    .iter()
293                    .flat_map(|parameters| parameters.iter_non_variadic_params())
294                    .filter_map(|parameter| parameter.default.as_deref())
295                    .any(|default| any_over_expr(default, &mut *func))
296                    || any_over_expr(body, func)
297            }
298            Expr::If(ast::ExprIf {
299                test,
300                body,
301                orelse,
302                range: _,
303                node_index: _,
304            }) => {
305                any_over_expr(test, &mut *func)
306                    || any_over_expr(body, &mut *func)
307                    || any_over_expr(orelse, &mut *func)
308            }
309            Expr::Dict(ast::ExprDict { items, .. }) => {
310                items.iter().any(|ast::DictItem { key, value }| {
311                    any_over_expr(value, &mut *func)
312                        || key
313                            .as_ref()
314                            .is_some_and(|key| any_over_expr(key, &mut *func))
315                })
316            }
317            Expr::Set(ast::ExprSet { elts, .. })
318            | Expr::List(ast::ExprList { elts, .. })
319            | Expr::Tuple(ast::ExprTuple { elts, .. }) => {
320                elts.iter().any(|expr| any_over_expr(expr, &mut *func))
321            }
322            Expr::ListComp(ast::ExprListComp {
323                elt,
324                generators,
325                range: _,
326                node_index: _,
327            })
328            | Expr::SetComp(ast::ExprSetComp {
329                elt,
330                generators,
331                range: _,
332                node_index: _,
333            })
334            | Expr::Generator(ast::ExprGenerator {
335                elt,
336                generators,
337                range: _,
338                node_index: _,
339                parenthesized: _,
340            }) => {
341                any_over_expr(elt, &mut *func)
342                    || generators.iter().any(|generator| {
343                        any_over_expr(&generator.target, &mut *func)
344                            || any_over_expr(&generator.iter, &mut *func)
345                            || generator
346                                .ifs
347                                .iter()
348                                .any(|expr| any_over_expr(expr, &mut *func))
349                    })
350            }
351            Expr::DictComp(ast::ExprDictComp {
352                key,
353                value,
354                generators,
355                range: _,
356                node_index: _,
357            }) => {
358                key.as_deref()
359                    .is_some_and(|key| any_over_expr(key, &mut *func))
360                    || any_over_expr(value, &mut *func)
361                    || generators.iter().any(|generator| {
362                        any_over_expr(&generator.target, &mut *func)
363                            || any_over_expr(&generator.iter, &mut *func)
364                            || generator
365                                .ifs
366                                .iter()
367                                .any(|expr| any_over_expr(expr, &mut *func))
368                    })
369            }
370            Expr::Await(ast::ExprAwait {
371                value,
372                range: _,
373                node_index: _,
374            })
375            | Expr::YieldFrom(ast::ExprYieldFrom {
376                value,
377                range: _,
378                node_index: _,
379            })
380            | Expr::Attribute(ast::ExprAttribute { value, .. })
381            | Expr::Starred(ast::ExprStarred { value, .. }) => any_over_expr(value, func),
382            Expr::Yield(ast::ExprYield {
383                value,
384                range: _,
385                node_index: _,
386            }) => value
387                .as_ref()
388                .is_some_and(|value| any_over_expr(value, func)),
389            Expr::Compare(ast::ExprCompare { operands, .. }) => {
390                operands.iter().any(|expr| any_over_expr(expr, &mut *func))
391            }
392            Expr::Call(ast::ExprCall {
393                func: call_func,
394                arguments,
395                range_start: _,
396                node_index: _,
397            }) => {
398                // Note that this is the evaluation order but not necessarily the declaration order
399                // (e.g. for `f(*args, a=2, *args2, **kwargs)` it's not)
400                any_over_expr(call_func, &mut *func)
401                    || arguments
402                        .args
403                        .iter()
404                        .any(|expr| any_over_expr(expr, &mut *func))
405                    || arguments
406                        .keywords
407                        .iter()
408                        .any(|keyword| any_over_expr(&keyword.value, &mut *func))
409            }
410            Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
411                any_over_expr(value, &mut *func) || any_over_expr(slice, &mut *func)
412            }
413            Expr::Slice(ast::ExprSlice {
414                lower,
415                upper,
416                step,
417                range: _,
418                node_index: _,
419            }) => {
420                lower
421                    .as_ref()
422                    .is_some_and(|value| any_over_expr(value, &mut *func))
423                    || upper
424                        .as_ref()
425                        .is_some_and(|value| any_over_expr(value, &mut *func))
426                    || step
427                        .as_ref()
428                        .is_some_and(|value| any_over_expr(value, &mut *func))
429            }
430            Expr::Name(_)
431            | Expr::StringLiteral(_)
432            | Expr::BytesLiteral(_)
433            | Expr::NumberLiteral(_)
434            | Expr::Constant(_)
435            | Expr::BooleanLiteral(_)
436            | Expr::NoneLiteral(_)
437            | Expr::EllipsisLiteral(_)
438            | Expr::IpyEscapeCommand(_) => false,
439        }
440    }
441
442    inner(expr, &mut func)
443}
444
445fn any_over_type_param(type_param: &TypeParam, func: &mut dyn FnMut(&Expr) -> bool) -> bool {
446    match type_param {
447        TypeParam::TypeVar(ast::TypeParamTypeVar { bound, default, .. }) => {
448            bound
449                .as_ref()
450                .is_some_and(|value| any_over_expr(value, &mut *func))
451                || default
452                    .as_ref()
453                    .is_some_and(|value| any_over_expr(value, &mut *func))
454        }
455        TypeParam::TypeVarTuple(ast::TypeParamTypeVarTuple { default, .. }) => default
456            .as_ref()
457            .is_some_and(|value| any_over_expr(value, &mut *func)),
458        TypeParam::ParamSpec(ast::TypeParamParamSpec { default, .. }) => default
459            .as_ref()
460            .is_some_and(|value| any_over_expr(value, &mut *func)),
461    }
462}
463
464fn any_over_pattern(pattern: &Pattern, func: &mut dyn FnMut(&Expr) -> bool) -> bool {
465    match pattern {
466        Pattern::MatchValue(ast::PatternMatchValue {
467            value,
468            range: _,
469            node_index: _,
470        }) => any_over_expr(value, func),
471        Pattern::MatchSingleton(_) => false,
472        Pattern::MatchSequence(ast::PatternMatchSequence { patterns, .. }) => patterns
473            .iter()
474            .any(|pattern| any_over_pattern(pattern, &mut *func)),
475        Pattern::MatchMapping(ast::PatternMatchMapping { keys, patterns, .. }) => {
476            keys.iter().any(|key| any_over_expr(key, &mut *func))
477                || patterns
478                    .iter()
479                    .any(|pattern| any_over_pattern(pattern, &mut *func))
480        }
481        Pattern::MatchClass(ast::PatternMatchClass { cls, arguments, .. }) => {
482            any_over_expr(cls, &mut *func)
483                || arguments
484                    .patterns
485                    .iter()
486                    .any(|pattern| any_over_pattern(pattern, &mut *func))
487                || arguments
488                    .keywords
489                    .iter()
490                    .any(|keyword| any_over_pattern(&keyword.pattern, &mut *func))
491        }
492        Pattern::MatchStar(_) => false,
493        Pattern::MatchAs(ast::PatternMatchAs { pattern, .. }) => pattern
494            .as_ref()
495            .is_some_and(|pattern| any_over_pattern(pattern, func)),
496        Pattern::MatchOr(ast::PatternMatchOr { patterns, .. }) => patterns
497            .iter()
498            .any(|pattern| any_over_pattern(pattern, &mut *func)),
499    }
500}
501
502fn any_over_interpolated_string_element(
503    element: &ast::InterpolatedStringElement,
504    func: &mut dyn FnMut(&Expr) -> bool,
505) -> bool {
506    match element {
507        ast::InterpolatedStringElement::Literal(_) => false,
508        ast::InterpolatedStringElement::Interpolation(ast::InterpolatedElement {
509            expression,
510            format_spec,
511            ..
512        }) => {
513            any_over_expr(expression, &mut *func)
514                || format_spec.as_ref().is_some_and(|spec| {
515                    spec.elements.iter().any(|spec_element| {
516                        any_over_interpolated_string_element(spec_element, &mut *func)
517                    })
518                })
519        }
520    }
521}
522
523fn any_over_stmt<F>(stmt: &Stmt, mut func: F) -> bool
524where
525    F: FnMut(&Expr) -> bool,
526{
527    fn inner(stmt: &Stmt, func: &mut dyn FnMut(&Expr) -> bool) -> bool {
528        match stmt {
529            Stmt::FunctionDef(ast::StmtFunctionDef {
530                parameters,
531                type_params,
532                body,
533                decorator_list,
534                returns,
535                ..
536            }) => {
537                parameters.iter().any(|param| {
538                    param
539                        .default()
540                        .is_some_and(|default| any_over_expr(default, &mut *func))
541                        || param
542                            .annotation()
543                            .is_some_and(|annotation| any_over_expr(annotation, &mut *func))
544                }) || type_params.as_ref().is_some_and(|type_params| {
545                    type_params
546                        .iter()
547                        .any(|type_param| any_over_type_param(type_param, &mut *func))
548                }) || body.iter().any(|stmt| any_over_stmt(stmt, &mut *func))
549                    || decorator_list
550                        .iter()
551                        .any(|decorator| any_over_expr(&decorator.expression, &mut *func))
552                    || returns
553                        .as_ref()
554                        .is_some_and(|value| any_over_expr(value, func))
555            }
556            Stmt::ClassDef(ast::StmtClassDef {
557                arguments,
558                type_params,
559                body,
560                decorator_list,
561                ..
562            }) => {
563                // Note that e.g. `class A(*args, a=2, *args2, **kwargs): pass` is a valid class
564                // definition
565                arguments
566                    .as_deref()
567                    .is_some_and(|Arguments { args, keywords, .. }| {
568                        args.iter().any(|expr| any_over_expr(expr, &mut *func))
569                            || keywords
570                                .iter()
571                                .any(|keyword| any_over_expr(&keyword.value, &mut *func))
572                    })
573                    || type_params.as_ref().is_some_and(|type_params| {
574                        type_params
575                            .iter()
576                            .any(|type_param| any_over_type_param(type_param, &mut *func))
577                    })
578                    || body.iter().any(|stmt| any_over_stmt(stmt, &mut *func))
579                    || decorator_list
580                        .iter()
581                        .any(|decorator| any_over_expr(&decorator.expression, &mut *func))
582            }
583            Stmt::Return(ast::StmtReturn {
584                value,
585                range: _,
586                node_index: _,
587            }) => value
588                .as_ref()
589                .is_some_and(|value| any_over_expr(value, func)),
590            Stmt::Delete(ast::StmtDelete { targets, .. }) => {
591                targets.iter().any(|expr| any_over_expr(expr, &mut *func))
592            }
593            Stmt::TypeAlias(ast::StmtTypeAlias {
594                name,
595                type_params,
596                value,
597                ..
598            }) => {
599                any_over_expr(name, &mut *func)
600                    || type_params.as_ref().is_some_and(|type_params| {
601                        type_params
602                            .iter()
603                            .any(|type_param| any_over_type_param(type_param, &mut *func))
604                    })
605                    || any_over_expr(value, func)
606            }
607            Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
608                targets.iter().any(|expr| any_over_expr(expr, &mut *func))
609                    || any_over_expr(value, func)
610            }
611            Stmt::AugAssign(ast::StmtAugAssign { target, value, .. }) => {
612                any_over_expr(target, &mut *func) || any_over_expr(value, &mut *func)
613            }
614            Stmt::AnnAssign(ast::StmtAnnAssign {
615                target,
616                annotation,
617                value,
618                ..
619            }) => {
620                any_over_expr(target, &mut *func)
621                    || any_over_expr(annotation, &mut *func)
622                    || value
623                        .as_ref()
624                        .is_some_and(|value| any_over_expr(value, &mut *func))
625            }
626            Stmt::For(ast::StmtFor {
627                target,
628                iter,
629                body,
630                orelse,
631                ..
632            }) => {
633                any_over_expr(target, &mut *func)
634                    || any_over_expr(iter, &mut *func)
635                    || any_over_body(body, &mut *func)
636                    || any_over_body(orelse, &mut *func)
637            }
638            Stmt::While(ast::StmtWhile {
639                test, body, orelse, ..
640            }) => {
641                any_over_expr(test, &mut *func)
642                    || any_over_body(body, &mut *func)
643                    || any_over_body(orelse, &mut *func)
644            }
645            Stmt::If(ast::StmtIf {
646                test,
647                body,
648                elif_else_clauses,
649                ..
650            }) => {
651                any_over_expr(test, &mut *func)
652                    || any_over_body(body, &mut *func)
653                    || elif_else_clauses.iter().any(|clause| {
654                        clause
655                            .test
656                            .as_ref()
657                            .is_some_and(|test| any_over_expr(test, &mut *func))
658                            || any_over_body(&clause.body, &mut *func)
659                    })
660            }
661            Stmt::With(ast::StmtWith { items, body, .. }) => {
662                items.iter().any(|with_item| {
663                    any_over_expr(&with_item.context_expr, &mut *func)
664                        || with_item
665                            .optional_vars
666                            .as_ref()
667                            .is_some_and(|expr| any_over_expr(expr, &mut *func))
668                }) || any_over_body(body, &mut *func)
669            }
670            Stmt::Raise(ast::StmtRaise {
671                exc,
672                cause,
673                range: _,
674                node_index: _,
675            }) => {
676                exc.as_ref()
677                    .is_some_and(|value| any_over_expr(value, &mut *func))
678                    || cause
679                        .as_ref()
680                        .is_some_and(|value| any_over_expr(value, &mut *func))
681            }
682            Stmt::Try(ast::StmtTry {
683                body,
684                handlers,
685                orelse,
686                finalbody,
687                ..
688            }) => {
689                any_over_body(body, &mut *func)
690                    || handlers.iter().any(|handler| {
691                        let ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler {
692                            type_,
693                            body,
694                            ..
695                        }) = handler;
696                        type_
697                            .as_ref()
698                            .is_some_and(|expr| any_over_expr(expr, &mut *func))
699                            || any_over_body(body, &mut *func)
700                    })
701                    || any_over_body(orelse, &mut *func)
702                    || any_over_body(finalbody, &mut *func)
703            }
704            Stmt::Assert(ast::StmtAssert {
705                test,
706                msg,
707                range: _,
708                node_index: _,
709            }) => {
710                any_over_expr(test, &mut *func)
711                    || msg
712                        .as_ref()
713                        .is_some_and(|value| any_over_expr(value, &mut *func))
714            }
715            Stmt::Match(ast::StmtMatch {
716                subject,
717                cases,
718                range: _,
719                node_index: _,
720            }) => {
721                any_over_expr(subject, &mut *func)
722                    || cases.iter().any(|case| {
723                        let MatchCase {
724                            pattern,
725                            guard,
726                            body,
727                            ..
728                        } = case;
729                        any_over_pattern(pattern, &mut *func)
730                            || guard
731                                .as_ref()
732                                .is_some_and(|expr| any_over_expr(expr, &mut *func))
733                            || any_over_body(body, &mut *func)
734                    })
735            }
736            Stmt::Import(_) => false,
737            Stmt::ImportFrom(_) => false,
738            Stmt::Global(_) => false,
739            Stmt::Nonlocal(_) => false,
740            Stmt::Expr(ast::StmtExpr {
741                value,
742                range: _,
743                node_index: _,
744            }) => any_over_expr(value, func),
745            Stmt::Pass(_) | Stmt::Break(_) | Stmt::Continue(_) => false,
746            Stmt::IpyEscapeCommand(_) => false,
747        }
748    }
749
750    inner(stmt, &mut func)
751}
752
753pub fn any_over_body<F>(body: &[Stmt], mut func: F) -> bool
754where
755    F: FnMut(&Expr) -> bool,
756{
757    body.iter().any(|stmt| any_over_stmt(stmt, &mut func))
758}
759
760pub fn is_dunder(id: &str) -> bool {
761    id.starts_with("__") && id.ends_with("__")
762}
763
764/// Whether a name starts and ends with a single underscore.
765///
766/// `_a__` is considered neither a dunder nor a sunder name.
767pub fn is_sunder(id: &str) -> bool {
768    id.starts_with('_') && id.ends_with('_') && !id.starts_with("__") && !id.ends_with("__")
769}
770
771/// Return `true` if the [`Stmt`] is an assignment to a dunder (like `__all__`).
772pub fn is_assignment_to_a_dunder(stmt: &Stmt) -> bool {
773    // Check whether it's an assignment to a dunder, with or without a type
774    // annotation. This is what pycodestyle (as of 2.9.1) does.
775    match stmt {
776        Stmt::Assign(ast::StmtAssign { targets, .. }) => {
777            if let [Expr::Name(ast::ExprName { id, .. })] = targets.as_slice() {
778                is_dunder(id)
779            } else {
780                false
781            }
782        }
783        Stmt::AnnAssign(ast::StmtAnnAssign { target, .. }) => {
784            if let Expr::Name(ast::ExprName { id, .. }) = target.as_ref() {
785                is_dunder(id)
786            } else {
787                false
788            }
789        }
790        _ => false,
791    }
792}
793
794/// Return `true` if the [`Expr`] is a singleton (`None`, `True`, `False`, or
795/// `...`).
796const fn is_singleton(expr: &Expr) -> bool {
797    matches!(
798        expr,
799        Expr::NoneLiteral(_)
800            | Expr::BooleanLiteral(_)
801            | Expr::EllipsisLiteral(_)
802            | Expr::Constant(ast::ExprConstant {
803                value: ConstantValue::None | ConstantValue::Boolean(_) | ConstantValue::Ellipsis,
804                ..
805            })
806    )
807}
808
809/// Return `true` if the [`Expr`] is a literal or tuple of literals.
810pub fn is_constant(expr: &Expr) -> bool {
811    if let Expr::Tuple(tuple) = expr {
812        tuple.iter().all(is_constant)
813    } else {
814        expr.is_literal_expr()
815    }
816}
817
818/// Return `true` if the [`Expr`] is a non-singleton constant.
819pub fn is_constant_non_singleton(expr: &Expr) -> bool {
820    is_constant(expr) && !is_singleton(expr)
821}
822
823/// Return `true` if an [`Expr`] is a literal `True`.
824pub const fn is_const_true(expr: &Expr) -> bool {
825    matches!(
826        expr,
827        Expr::BooleanLiteral(ast::ExprBooleanLiteral { value: true, .. }),
828    )
829}
830
831/// Return `true` if an [`Expr`] is a literal `False`.
832pub const fn is_const_false(expr: &Expr) -> bool {
833    matches!(
834        expr,
835        Expr::BooleanLiteral(ast::ExprBooleanLiteral { value: false, .. }),
836    )
837}
838
839/// Return `true` if the [`Expr`] is a mutable iterable initializer, like `{}` or `[]`.
840pub const fn is_mutable_iterable_initializer(expr: &Expr) -> bool {
841    matches!(
842        expr,
843        Expr::Set(_)
844            | Expr::SetComp(_)
845            | Expr::List(_)
846            | Expr::ListComp(_)
847            | Expr::Dict(_)
848            | Expr::DictComp(_)
849    )
850}
851
852/// Extract the names of all handled exceptions.
853pub fn extract_handled_exceptions(handlers: &[ExceptHandler]) -> Vec<&Expr> {
854    let mut handled_exceptions = Vec::new();
855    for handler in handlers {
856        match handler {
857            ExceptHandler::ExceptHandler(ast::ExceptHandlerExceptHandler { type_, .. }) => {
858                if let Some(type_) = type_ {
859                    if let Expr::Tuple(tuple) = &**type_ {
860                        for type_ in tuple {
861                            handled_exceptions.push(type_);
862                        }
863                    } else {
864                        handled_exceptions.push(type_);
865                    }
866                }
867            }
868        }
869    }
870    handled_exceptions
871}
872
873/// Given an [`Expr`] that can be callable or not (like a decorator, which could
874/// be used with or without explicit call syntax), return the underlying
875/// callable.
876pub fn map_callable(decorator: &Expr) -> &Expr {
877    if let Expr::Call(ast::ExprCall { func, .. }) = decorator {
878        // Ex) `@decorator()`
879        func
880    } else {
881        // Ex) `@decorator`
882        decorator
883    }
884}
885
886/// Given an [`Expr`] that can be a [`ExprSubscript`][ast::ExprSubscript] or not
887/// (like an annotation that may be generic or not), return the underlying expr.
888pub fn map_subscript(expr: &Expr) -> &Expr {
889    if let Expr::Subscript(ast::ExprSubscript { value, .. }) = expr {
890        // Ex) `Iterable[T]`  => return `Iterable`
891        value
892    } else {
893        // Ex) `Iterable`  => return `Iterable`
894        expr
895    }
896}
897
898/// Given an [`Expr`] that can be starred, return the underlying starred expression.
899pub fn map_starred(expr: &Expr) -> &Expr {
900    if let Expr::Starred(ast::ExprStarred { value, .. }) = expr {
901        // Ex) `*args`
902        value
903    } else {
904        // Ex) `args`
905        expr
906    }
907}
908
909/// Return `true` if the body uses `locals()`, `globals()`, `vars()`, `eval()`.
910///
911/// Accepts a closure that determines whether a given name (e.g., `"list"`) is a Python builtin.
912pub fn uses_magic_variable_access<F>(body: &[Stmt], is_builtin: F) -> bool
913where
914    F: Fn(&str) -> bool,
915{
916    any_over_body(body, |expr| {
917        if let Expr::Call(ast::ExprCall { func, .. }) = expr {
918            if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
919                if matches!(id.as_str(), "locals" | "globals" | "vars" | "exec" | "eval") {
920                    if is_builtin(id.as_str()) {
921                        return true;
922                    }
923                }
924            }
925        }
926        false
927    })
928}
929
930/// Format the module reference name for a relative import.
931///
932/// # Examples
933///
934/// ```rust
935/// # use ruff_python_ast::helpers::format_import_from;
936///
937/// assert_eq!(format_import_from(0, None), "".to_string());
938/// assert_eq!(format_import_from(1, None), ".".to_string());
939/// assert_eq!(format_import_from(1, Some("foo")), ".foo".to_string());
940/// ```
941pub fn format_import_from(level: u32, module: Option<&str>) -> Cow<'_, str> {
942    match (level, module) {
943        (0, Some(module)) => Cow::Borrowed(module),
944        (level, module) => {
945            let mut module_name =
946                String::with_capacity((level as usize) + module.map_or(0, str::len));
947            for _ in 0..level {
948                module_name.push('.');
949            }
950            if let Some(module) = module {
951                module_name.push_str(module);
952            }
953            Cow::Owned(module_name)
954        }
955    }
956}
957
958/// Format the member reference name for a relative import.
959///
960/// # Examples
961///
962/// ```rust
963/// # use ruff_python_ast::helpers::format_import_from_member;
964///
965/// assert_eq!(format_import_from_member(0, None, "bar"), "bar".to_string());
966/// assert_eq!(format_import_from_member(1, None, "bar"), ".bar".to_string());
967/// assert_eq!(format_import_from_member(1, Some("foo"), "bar"), ".foo.bar".to_string());
968/// ```
969pub fn format_import_from_member(level: u32, module: Option<&str>, member: &str) -> String {
970    let mut qualified_name =
971        String::with_capacity((level as usize) + module.map_or(0, str::len) + 1 + member.len());
972    if level > 0 {
973        for _ in 0..level {
974            qualified_name.push('.');
975        }
976    }
977    if let Some(module) = module {
978        qualified_name.push_str(module);
979        qualified_name.push('.');
980    }
981    qualified_name.push_str(member);
982    qualified_name
983}
984
985/// Create a module path from a (package, path) pair.
986///
987/// For example, if the package is `foo/bar` and the path is `foo/bar/baz.py`,
988/// the call path is `["baz"]`.
989pub fn to_module_path(package: &Path, path: &Path) -> Option<Vec<String>> {
990    path.strip_prefix(package.parent()?)
991        .ok()?
992        .iter()
993        .map(Path::new)
994        .map(Path::file_stem)
995        .map(|path| path.and_then(|path| path.to_os_string().into_string().ok()))
996        .collect::<Option<Vec<String>>>()
997}
998
999/// Format the call path for a relative import.
1000///
1001/// # Examples
1002///
1003/// ```rust
1004/// # use ruff_python_ast::helpers::collect_import_from_member;
1005///
1006/// assert_eq!(collect_import_from_member(0, None, "bar").segments(), ["bar"]);
1007/// assert_eq!(collect_import_from_member(1, None, "bar").segments(), [".", "bar"]);
1008/// assert_eq!(collect_import_from_member(1, Some("foo"), "bar").segments(), [".", "foo", "bar"]);
1009/// ```
1010pub fn collect_import_from_member<'a>(
1011    level: u32,
1012    module: Option<&'a str>,
1013    member: &'a str,
1014) -> QualifiedName<'a> {
1015    let mut qualified_name_builder = QualifiedNameBuilder::with_capacity(
1016        level as usize
1017            + module
1018                .map(|module| module.split('.').count())
1019                .unwrap_or_default()
1020            + 1,
1021    );
1022
1023    // Include the dots as standalone segments.
1024    if level > 0 {
1025        for _ in 0..level {
1026            qualified_name_builder.push(".");
1027        }
1028    }
1029
1030    // Add the remaining segments.
1031    if let Some(module) = module {
1032        qualified_name_builder.extend(module.split('.'));
1033    }
1034
1035    // Add the member.
1036    qualified_name_builder.push(member);
1037
1038    qualified_name_builder.build()
1039}
1040
1041/// Format the call path for a relative import, or `None` if the relative import extends beyond
1042/// the root module.
1043pub fn from_relative_import<'a>(
1044    // The path from which the import is relative.
1045    module: &'a [String],
1046    // The path of the import itself (e.g., given `from ..foo import bar`, `[".", ".", "foo", "bar]`).
1047    import: &[&'a str],
1048    // The remaining segments to the call path (e.g., given `bar.baz`, `["baz"]`).
1049    tail: &[&'a str],
1050) -> Option<QualifiedName<'a>> {
1051    let mut qualified_name_builder =
1052        QualifiedNameBuilder::with_capacity(module.len() + import.len() + tail.len());
1053
1054    // Start with the module path.
1055    qualified_name_builder.extend(module.iter().map(String::as_str));
1056
1057    // Remove segments based on the number of dots.
1058    for segment in import {
1059        if *segment == "." {
1060            if qualified_name_builder.is_empty() {
1061                return None;
1062            }
1063            qualified_name_builder.pop();
1064        } else {
1065            qualified_name_builder.push(segment);
1066        }
1067    }
1068
1069    // Add the remaining segments.
1070    qualified_name_builder.extend_from_slice(tail);
1071
1072    Some(qualified_name_builder.build())
1073}
1074
1075/// Given an imported module (based on its relative import level and module name), return the
1076/// fully-qualified module path.
1077pub fn resolve_imported_module_path<'a>(
1078    level: u32,
1079    module: Option<&'a str>,
1080    module_path: Option<&[String]>,
1081) -> Option<Cow<'a, str>> {
1082    if level == 0 {
1083        return Some(Cow::Borrowed(module.unwrap_or("")));
1084    }
1085
1086    let module_path = module_path?;
1087
1088    if level as usize >= module_path.len() {
1089        return None;
1090    }
1091
1092    let mut qualified_path = module_path[..module_path.len() - level as usize].join(".");
1093    if let Some(module) = module {
1094        if !qualified_path.is_empty() {
1095            qualified_path.push('.');
1096        }
1097        qualified_path.push_str(module);
1098    }
1099    Some(Cow::Owned(qualified_path))
1100}
1101
1102/// A [`Visitor`] to collect all [`Expr::Name`] nodes in an AST.
1103#[derive(Debug, Default)]
1104pub struct NameFinder<'a> {
1105    /// A map from identifier to defining expression.
1106    pub names: FxHashMap<&'a str, &'a ast::ExprName>,
1107}
1108
1109impl<'a> Visitor<'a> for NameFinder<'a> {
1110    fn visit_expr(&mut self, expr: &'a Expr) {
1111        if let Expr::Name(name) = expr {
1112            self.names.insert(&name.id, name);
1113        }
1114        crate::visitor::walk_expr(self, expr);
1115    }
1116}
1117
1118/// A [`Visitor`] to collect all stored [`Expr::Name`] nodes in an AST.
1119#[derive(Debug, Default)]
1120pub struct StoredNameFinder<'a> {
1121    /// A map from identifier to defining expression.
1122    pub names: FxHashMap<&'a str, &'a ast::ExprName>,
1123}
1124
1125impl<'a> Visitor<'a> for StoredNameFinder<'a> {
1126    fn visit_expr(&mut self, expr: &'a Expr) {
1127        if let Expr::Name(name) = expr {
1128            if name.ctx.is_store() {
1129                self.names.insert(&name.id, name);
1130            }
1131        }
1132        crate::visitor::walk_expr(self, expr);
1133    }
1134}
1135
1136/// A [`Visitor`] that collects all `return` statements in a function or method.
1137#[derive(Default)]
1138pub struct ReturnStatementVisitor<'a> {
1139    pub returns: Vec<&'a ast::StmtReturn>,
1140    pub is_generator: bool,
1141}
1142
1143impl<'a> Visitor<'a> for ReturnStatementVisitor<'a> {
1144    fn visit_stmt(&mut self, stmt: &'a Stmt) {
1145        match stmt {
1146            Stmt::FunctionDef(_) | Stmt::ClassDef(_) => {
1147                // Don't recurse.
1148            }
1149            Stmt::Return(stmt) => self.returns.push(stmt),
1150            _ => crate::visitor::walk_stmt(self, stmt),
1151        }
1152    }
1153
1154    fn visit_expr(&mut self, expr: &'a Expr) {
1155        if let Expr::Yield(_) | Expr::YieldFrom(_) = expr {
1156            self.is_generator = true;
1157        } else {
1158            crate::visitor::walk_expr(self, expr);
1159        }
1160    }
1161}
1162
1163/// A [`StatementVisitor`] that collects all `raise` statements in a function or method.
1164#[derive(Default)]
1165pub struct RaiseStatementVisitor<'a> {
1166    pub raises: Vec<(TextRange, Option<&'a Expr>, Option<&'a Expr>)>,
1167}
1168
1169impl<'a> StatementVisitor<'a> for RaiseStatementVisitor<'a> {
1170    fn visit_stmt(&mut self, stmt: &'a Stmt) {
1171        match stmt {
1172            Stmt::Raise(ast::StmtRaise {
1173                exc,
1174                cause,
1175                range: _,
1176                node_index: _,
1177            }) => {
1178                self.raises
1179                    .push((stmt.range(), exc.as_deref(), cause.as_deref()));
1180            }
1181            Stmt::ClassDef(_) | Stmt::FunctionDef(_) | Stmt::Try(_) => {}
1182            Stmt::If(ast::StmtIf {
1183                body,
1184                elif_else_clauses,
1185                ..
1186            }) => {
1187                crate::statement_visitor::walk_body(self, body);
1188                for clause in elif_else_clauses {
1189                    self.visit_elif_else_clause(clause);
1190                }
1191            }
1192            Stmt::While(ast::StmtWhile { body, .. })
1193            | Stmt::With(ast::StmtWith { body, .. })
1194            | Stmt::For(ast::StmtFor { body, .. }) => {
1195                crate::statement_visitor::walk_body(self, body);
1196            }
1197            Stmt::Match(ast::StmtMatch { cases, .. }) => {
1198                for case in cases {
1199                    crate::statement_visitor::walk_body(self, &case.body);
1200                }
1201            }
1202            _ => {}
1203        }
1204    }
1205}
1206
1207/// A [`Visitor`] that detects the presence of `await` expressions in the current scope.
1208#[derive(Debug, Default)]
1209pub struct AwaitVisitor {
1210    pub seen_await: bool,
1211}
1212
1213impl Visitor<'_> for AwaitVisitor {
1214    fn visit_stmt(&mut self, stmt: &Stmt) {
1215        match stmt {
1216            Stmt::FunctionDef(_) | Stmt::ClassDef(_) => (),
1217            Stmt::With(ast::StmtWith { is_async: true, .. }) => {
1218                self.seen_await = true;
1219            }
1220            Stmt::For(ast::StmtFor { is_async: true, .. }) => {
1221                self.seen_await = true;
1222            }
1223            _ => crate::visitor::walk_stmt(self, stmt),
1224        }
1225    }
1226
1227    fn visit_expr(&mut self, expr: &Expr) {
1228        if let Expr::Await(ast::ExprAwait { .. }) = expr {
1229            self.seen_await = true;
1230        } else {
1231            crate::visitor::walk_expr(self, expr);
1232        }
1233    }
1234
1235    fn visit_comprehension(&mut self, comprehension: &'_ crate::Comprehension) {
1236        if comprehension.is_async {
1237            self.seen_await = true;
1238        } else {
1239            crate::visitor::walk_comprehension(self, comprehension);
1240        }
1241    }
1242}
1243
1244/// Return `true` if a `Stmt` is a docstring.
1245pub fn is_docstring_stmt(stmt: &Stmt) -> bool {
1246    if let Stmt::Expr(ast::StmtExpr {
1247        value,
1248        range: _,
1249        node_index: _,
1250    }) = stmt
1251    {
1252        value.is_string_literal_expr()
1253    } else {
1254        false
1255    }
1256}
1257
1258/// Returns `true` if all statements in `body` are `pass` or `...` (ellipsis)
1259///
1260/// An empty body (`[]`) returns `false`
1261pub fn is_stub_body(body: &[Stmt]) -> bool {
1262    !body.is_empty()
1263        && body.iter().all(|stmt| match stmt {
1264            Stmt::Pass(_) => true,
1265            Stmt::Expr(ast::StmtExpr { value, .. }) => value.is_ellipsis_literal_expr(),
1266            _ => false,
1267        })
1268}
1269
1270/// Returns `body` without its leading docstring statement, if present.
1271pub fn body_without_leading_docstring(body: &[Stmt]) -> &[Stmt] {
1272    match body.split_first() {
1273        Some((first, rest)) if is_docstring_stmt(first) => rest,
1274        _ => body,
1275    }
1276}
1277
1278/// Check if a node is part of a conditional branch.
1279pub fn on_conditional_branch<'a>(parents: &mut impl Iterator<Item = &'a Stmt>) -> bool {
1280    parents.any(|parent| {
1281        if matches!(parent, Stmt::If(_) | Stmt::While(_) | Stmt::Match(_)) {
1282            return true;
1283        }
1284        if let Stmt::Expr(ast::StmtExpr {
1285            value,
1286            range: _,
1287            node_index: _,
1288        }) = parent
1289        {
1290            if value.is_if_expr() {
1291                return true;
1292            }
1293        }
1294        false
1295    })
1296}
1297
1298/// Check if a node is in a nested block.
1299pub fn in_nested_block<'a>(mut parents: impl Iterator<Item = &'a Stmt>) -> bool {
1300    parents.any(|parent| {
1301        matches!(
1302            parent,
1303            Stmt::Try(_) | Stmt::If(_) | Stmt::With(_) | Stmt::Match(_)
1304        )
1305    })
1306}
1307
1308/// Check if a node represents an unpacking assignment.
1309pub fn is_unpacking_assignment(parent: &Stmt, child: &Expr) -> bool {
1310    match parent {
1311        Stmt::With(ast::StmtWith { items, .. }) => items.iter().any(|item| {
1312            if let Some(optional_vars) = &item.optional_vars {
1313                if optional_vars.is_tuple_expr() {
1314                    if any_over_expr(optional_vars, |expr| expr == child) {
1315                        return true;
1316                    }
1317                }
1318            }
1319            false
1320        }),
1321        Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
1322            // In `(a, b) = (1, 2)`, `(1, 2)` is the target, and it is a tuple.
1323            let value_is_tuple = matches!(
1324                value.as_ref(),
1325                Expr::Set(_) | Expr::List(_) | Expr::Tuple(_)
1326            );
1327            // In `(a, b) = coords = (1, 2)`, `(a, b)` and `coords` are the targets, and
1328            // `(a, b)` is a tuple. (We use "tuple" as a placeholder for any
1329            // unpackable expression.)
1330            let targets_are_tuples = targets
1331                .iter()
1332                .all(|item| matches!(item, Expr::Set(_) | Expr::List(_) | Expr::Tuple(_)));
1333            // If we're looking at `a` in `(a, b) = coords = (1, 2)`, then we should
1334            // identify that the current expression is in a tuple.
1335            let child_in_tuple = targets_are_tuples
1336                || targets.iter().any(|item| {
1337                    matches!(item, Expr::Set(_) | Expr::List(_) | Expr::Tuple(_))
1338                        && any_over_expr(item, |expr| expr == child)
1339                });
1340
1341            // If our child is a tuple, and value is not, it's always an unpacking
1342            // expression. Ex) `x, y = tup`
1343            if child_in_tuple && !value_is_tuple {
1344                return true;
1345            }
1346
1347            // If our child isn't a tuple, but value is, it's never an unpacking expression.
1348            // Ex) `coords = (1, 2)`
1349            if !child_in_tuple && value_is_tuple {
1350                return false;
1351            }
1352
1353            // If our target and the value are both tuples, then it's an unpacking
1354            // expression assuming there's at least one non-tuple child.
1355            // Ex) Given `(x, y) = coords = 1, 2`, `(x, y)` is considered an unpacking
1356            // expression. Ex) Given `(x, y) = (a, b) = 1, 2`, `(x, y)` isn't
1357            // considered an unpacking expression.
1358            if child_in_tuple && value_is_tuple {
1359                return !targets_are_tuples;
1360            }
1361
1362            false
1363        }
1364        _ => false,
1365    }
1366}
1367
1368#[derive(Copy, Clone, Debug, PartialEq, is_macro::Is)]
1369pub enum Truthiness {
1370    /// The expression is `True`.
1371    True,
1372    /// The expression is `False`.
1373    False,
1374    /// The expression evaluates to a `False`-like value (e.g., `None`, `0`, `[]`, `""`).
1375    Falsey,
1376    /// The expression evaluates to a `True`-like value (e.g., `1`, `"foo"`).
1377    Truthy,
1378    /// The expression evaluates to `None`.
1379    None,
1380    /// The expression evaluates to an unknown value (e.g., a variable `x` of unknown type).
1381    Unknown,
1382}
1383
1384impl Truthiness {
1385    /// Return the truthiness of an expression.
1386    pub fn from_expr<F>(expr: &Expr, is_builtin: F) -> Self
1387    where
1388        F: Fn(&str) -> bool,
1389    {
1390        match expr {
1391            Expr::Lambda(_) => Self::Truthy,
1392            Expr::Generator(_) => Self::Truthy,
1393            Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
1394                if value.is_empty() {
1395                    Self::Falsey
1396                } else {
1397                    Self::Truthy
1398                }
1399            }
1400            Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
1401                if value.is_empty() {
1402                    Self::Falsey
1403                } else {
1404                    Self::Truthy
1405                }
1406            }
1407            Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => match value {
1408                ast::Number::Int(int) => {
1409                    if *int == 0 {
1410                        Self::Falsey
1411                    } else {
1412                        Self::Truthy
1413                    }
1414                }
1415                ast::Number::Float(float) => {
1416                    if *float == 0.0 {
1417                        Self::Falsey
1418                    } else {
1419                        Self::Truthy
1420                    }
1421                }
1422                ast::Number::Complex { real, imag, .. } => {
1423                    if *real == 0.0 && *imag == 0.0 {
1424                        Self::Falsey
1425                    } else {
1426                        Self::Truthy
1427                    }
1428                }
1429            },
1430            Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) => {
1431                if *value {
1432                    Self::True
1433                } else {
1434                    Self::False
1435                }
1436            }
1437            Expr::NoneLiteral(_) => Self::None,
1438            Expr::EllipsisLiteral(_) => Self::Truthy,
1439            Expr::FString(f_string) => {
1440                if is_empty_f_string(f_string) {
1441                    Self::Falsey
1442                } else if is_non_empty_f_string(f_string) {
1443                    Self::Truthy
1444                } else {
1445                    Self::Unknown
1446                }
1447            }
1448            Expr::TString(_) => Self::Truthy,
1449            Expr::List(ast::ExprList { elts, .. })
1450            | Expr::Set(ast::ExprSet { elts, .. })
1451            | Expr::Tuple(ast::ExprTuple { elts, .. }) => {
1452                if elts.is_empty() {
1453                    return Self::Falsey;
1454                }
1455
1456                if elts.iter().all(Expr::is_starred_expr) {
1457                    // [*foo] / [*foo, *bar]
1458                    Self::Unknown
1459                } else {
1460                    Self::Truthy
1461                }
1462            }
1463            Expr::Dict(dict) => {
1464                if dict.is_empty() {
1465                    return Self::Falsey;
1466                }
1467
1468                // If the dict consists only of double-starred items (e.g., {**x, **y}),
1469                // consider its truthiness unknown. This matches lists/sets/tuples containing
1470                // only starred elements, which are also Unknown.
1471                if dict
1472                    .items
1473                    .iter()
1474                    .all(|item| matches!(item, DictItem { key: None, .. }))
1475                {
1476                    // {**foo} / {**foo, **bar}
1477                    Self::Unknown
1478                } else {
1479                    Self::Truthy
1480                }
1481            }
1482            Expr::Call(ast::ExprCall {
1483                func, arguments, ..
1484            }) => {
1485                if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
1486                    if is_iterable_initializer(id.as_str(), |id| is_builtin(id)) {
1487                        if arguments.is_empty() {
1488                            // Ex) `list()`
1489                            Self::Falsey
1490                        } else if let [argument] = &*arguments.args
1491                            && arguments.keywords.is_empty()
1492                        {
1493                            // Ex) `list([1, 2, 3])`
1494                            match argument {
1495                                // Return Unknown for types with definite truthiness that might
1496                                // result in empty iterables (t-strings and generators) or will
1497                                // raise a type error (non-iterable types like numbers, booleans,
1498                                // None, etc.).
1499                                Expr::NumberLiteral(_)
1500                                | Expr::BooleanLiteral(_)
1501                                | Expr::NoneLiteral(_)
1502                                | Expr::EllipsisLiteral(_)
1503                                | Expr::TString(_)
1504                                | Expr::Lambda(_)
1505                                | Expr::Generator(_) => Self::Unknown,
1506                                // Recurse for all other types - collections, comprehensions, variables, etc.
1507                                // StringLiteral, FString, and BytesLiteral recurse because Self::from_expr
1508                                // correctly handles their truthiness (checking if empty or not).
1509                                _ => Self::from_expr(argument, is_builtin),
1510                            }
1511                        } else {
1512                            Self::Unknown
1513                        }
1514                    } else {
1515                        Self::Unknown
1516                    }
1517                } else {
1518                    Self::Unknown
1519                }
1520            }
1521            _ => Self::Unknown,
1522        }
1523    }
1524
1525    pub fn into_bool(self) -> Option<bool> {
1526        match self {
1527            Self::True | Self::Truthy => Some(true),
1528            Self::False | Self::Falsey => Some(false),
1529            Self::None => Some(false),
1530            Self::Unknown => None,
1531        }
1532    }
1533}
1534
1535/// Returns `true` if the expression definitely resolves to a non-empty string, when used as an
1536/// f-string expression, or `false` if the expression may resolve to an empty string.
1537fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool {
1538    fn inner(expr: &Expr) -> bool {
1539        match expr {
1540            // When stringified, these expressions are always non-empty.
1541            Expr::Lambda(_) => true,
1542            Expr::Dict(_) => true,
1543            Expr::Set(_) => true,
1544            Expr::ListComp(_) => true,
1545            Expr::SetComp(_) => true,
1546            Expr::DictComp(_) => true,
1547            Expr::NumberLiteral(_) => true,
1548            Expr::Constant(_) => true,
1549            Expr::BooleanLiteral(_) => true,
1550            Expr::NoneLiteral(_) => true,
1551            Expr::EllipsisLiteral(_) => true,
1552            Expr::List(_) => true,
1553            Expr::Tuple(_) => true,
1554            Expr::TString(_) => true,
1555
1556            // These expressions must resolve to the inner expression.
1557            Expr::If(ast::ExprIf { body, orelse, .. }) => inner(body) && inner(orelse),
1558            Expr::Named(ast::ExprNamed { value, .. }) => inner(value),
1559
1560            // These expressions are complex. We can't determine whether they're empty or not.
1561            Expr::BoolOp(ast::ExprBoolOp { .. }) => false,
1562            Expr::BinOp(ast::ExprBinOp { .. }) => false,
1563            Expr::UnaryOp(ast::ExprUnaryOp { .. }) => false,
1564            // Rich comparison methods can return arbitrary objects.
1565            Expr::Compare(_) => false,
1566            Expr::Generator(_) => false,
1567            Expr::Await(_) => false,
1568            Expr::Yield(_) => false,
1569            Expr::YieldFrom(_) => false,
1570            Expr::Call(_) => false,
1571            Expr::Attribute(_) => false,
1572            Expr::Subscript(_) => false,
1573            Expr::Starred(_) => false,
1574            Expr::Name(_) => false,
1575            Expr::Slice(_) => false,
1576            Expr::IpyEscapeCommand(_) => false,
1577
1578            // These literals may or may not be empty.
1579            Expr::FString(f_string) => is_non_empty_f_string(f_string),
1580            // These literals may or may not be empty.
1581            Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => !value.is_empty(),
1582            // Confusingly, f"{b""}" renders as the string 'b""', which is non-empty.
1583            // Therefore, any bytes interpolation is guaranteed non-empty when stringified.
1584            Expr::BytesLiteral(_) => true,
1585        }
1586    }
1587
1588    expr.value.iter().any(|part| match part {
1589        ast::FStringPartRef::Literal(string_literal) => !string_literal.is_empty(),
1590        ast::FStringPartRef::FString(f_string) => {
1591            // The part is a concatenation of elements, so it's guaranteed non-empty if any element is
1592            f_string.elements.iter().any(|element| match element {
1593                InterpolatedStringElement::Literal(string_literal) => !string_literal.is_empty(),
1594                InterpolatedStringElement::Interpolation(f_string) => {
1595                    f_string.debug_text.is_some()
1596                        || (f_string.format_spec.is_none() && inner(&f_string.expression))
1597                }
1598            })
1599        }
1600    })
1601}
1602
1603/// Returns `true` if the expression definitely resolves to the empty string, when used as an f-string
1604/// expression.
1605pub fn is_empty_f_string(expr: &ast::ExprFString) -> bool {
1606    fn inner(expr: &Expr) -> bool {
1607        match expr {
1608            Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.is_empty(),
1609            // Confusingly, `bool(f"{b""}") == True` even though
1610            // `bool(b"") == False`. This is because `f"{b""}"`
1611            // evaluates as the string `'b""'` of length 3.
1612            Expr::BytesLiteral(_) => false,
1613            Expr::FString(ast::ExprFString { value, .. }) => {
1614                is_empty_interpolated_elements(value.elements())
1615            }
1616            _ => false,
1617        }
1618    }
1619
1620    fn is_empty_interpolated_elements<'a>(
1621        mut elements: impl Iterator<Item = &'a InterpolatedStringElement>,
1622    ) -> bool {
1623        elements.all(|element| match element {
1624            InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement {
1625                value,
1626                ..
1627            }) => value.is_empty(),
1628            InterpolatedStringElement::Interpolation(f_string) => {
1629                f_string.debug_text.is_none()
1630                    && f_string.conversion.is_none()
1631                    && f_string.format_spec.is_none()
1632                    && inner(&f_string.expression)
1633            }
1634        })
1635    }
1636
1637    expr.value.iter().all(|part| match part {
1638        ast::FStringPartRef::Literal(string_literal) => string_literal.is_empty(),
1639        ast::FStringPartRef::FString(f_string) => {
1640            is_empty_interpolated_elements(f_string.elements.iter())
1641        }
1642    })
1643}
1644
1645pub fn generate_comparison(
1646    left: &Expr,
1647    ops: &[CmpOp],
1648    comparators: &[Expr],
1649    parent: AnyNodeRef,
1650    tokens: &Tokens,
1651    source: &str,
1652) -> String {
1653    let start = left.start();
1654    let end = comparators.last().map_or_else(|| left.end(), Ranged::end);
1655    let mut contents = String::with_capacity(usize::from(end - start));
1656
1657    // Add the left side of the comparison.
1658    contents.push_str(
1659        &source[parenthesized_range(left.into(), parent, tokens).unwrap_or(left.range())],
1660    );
1661
1662    for (op, comparator) in ops.iter().zip(comparators) {
1663        // Add the operator.
1664        contents.push_str(match op {
1665            CmpOp::Eq => " == ",
1666            CmpOp::NotEq => " != ",
1667            CmpOp::Lt => " < ",
1668            CmpOp::LtE => " <= ",
1669            CmpOp::Gt => " > ",
1670            CmpOp::GtE => " >= ",
1671            CmpOp::In => " in ",
1672            CmpOp::NotIn => " not in ",
1673            CmpOp::Is => " is ",
1674            CmpOp::IsNot => " is not ",
1675        });
1676
1677        // Add the right side of the comparison.
1678        contents.push_str(
1679            &source[parenthesized_range(comparator.into(), parent, tokens)
1680                .unwrap_or(comparator.range())],
1681        );
1682    }
1683
1684    contents
1685}
1686
1687/// Format the expression as a PEP 604-style optional.
1688pub fn pep_604_optional(expr: &Expr) -> Expr {
1689    ast::ExprBinOp {
1690        left: Box::new(expr.clone()),
1691        op: Operator::BitOr,
1692        right: Box::new(Expr::NoneLiteral(ExprNoneLiteral::default())),
1693        range: TextRange::default(),
1694        node_index: AtomicNodeIndex::NONE,
1695    }
1696    .into()
1697}
1698
1699/// Format the expressions as a PEP 604-style union.
1700pub fn pep_604_union(elts: &[Expr]) -> Expr {
1701    match elts {
1702        [] => Expr::Tuple(ast::ExprTuple {
1703            elts: vec![],
1704            ctx: ExprContext::Load,
1705            range: TextRange::default(),
1706            node_index: AtomicNodeIndex::NONE,
1707            parenthesized: true,
1708            runtime_elts: None,
1709        }),
1710        [Expr::Tuple(ast::ExprTuple { elts, .. })] => pep_604_union(elts),
1711        [elt] => elt.clone(),
1712        [rest @ .., elt] => Expr::BinOp(ast::ExprBinOp {
1713            left: Box::new(pep_604_union(rest)),
1714            op: Operator::BitOr,
1715            right: Box::new(pep_604_union(std::slice::from_ref(elt))),
1716            range: TextRange::default(),
1717            node_index: AtomicNodeIndex::NONE,
1718        }),
1719    }
1720}
1721
1722/// Format the expression as a `typing.Optional`-style optional.
1723pub fn typing_optional(elt: Expr, binding: Name) -> Expr {
1724    Expr::Subscript(ast::ExprSubscript {
1725        value: Box::new(Expr::Name(ast::ExprName {
1726            id: binding,
1727            range: TextRange::default(),
1728            node_index: AtomicNodeIndex::NONE,
1729            ctx: ExprContext::Load,
1730        })),
1731        slice: Box::new(elt),
1732        ctx: ExprContext::Load,
1733        range: TextRange::default(),
1734        node_index: AtomicNodeIndex::NONE,
1735    })
1736}
1737
1738/// Format the expressions as a `typing.Union`-style union.
1739///
1740/// Note: It is a syntax error to have `Union[]` so the caller
1741/// should ensure that the `elts` argument is nonempty.
1742pub fn typing_union(elts: &[Expr], binding: Name) -> Expr {
1743    Expr::Subscript(ast::ExprSubscript {
1744        value: Box::new(Expr::Name(ast::ExprName {
1745            id: binding,
1746            range: TextRange::default(),
1747            node_index: AtomicNodeIndex::NONE,
1748            ctx: ExprContext::Load,
1749        })),
1750        slice: Box::new(Expr::Tuple(ast::ExprTuple {
1751            range: TextRange::default(),
1752            node_index: AtomicNodeIndex::NONE,
1753            elts: elts.to_vec(),
1754            ctx: ExprContext::Load,
1755            parenthesized: false,
1756            runtime_elts: None,
1757        })),
1758        ctx: ExprContext::Load,
1759        range: TextRange::default(),
1760        node_index: AtomicNodeIndex::NONE,
1761    })
1762}
1763
1764/// Determine the indentation level of an own-line comment, defined as the minimum indentation of
1765/// all comments between the preceding node and the comment, including the comment itself. In
1766/// other words, we don't allow successive comments to ident _further_ than any preceding comments.
1767///
1768/// For example, given:
1769/// ```python
1770/// if True:
1771///     pass
1772///     # comment
1773/// ```
1774///
1775/// The indentation would be 4, as the comment is indented by 4 spaces.
1776///
1777/// Given:
1778/// ```python
1779/// if True:
1780///     pass
1781/// # comment
1782/// else:
1783///     pass
1784/// ```
1785///
1786/// The indentation would be 0, as the comment is not indented at all.
1787///
1788/// Given:
1789/// ```python
1790/// if True:
1791///     pass
1792///     # comment
1793///         # comment
1794/// ```
1795///
1796/// Both comments would be marked as indented at 4 spaces, as the indentation of the first comment
1797/// is used for the second comment.
1798///
1799/// This logic avoids pathological cases like:
1800/// ```python
1801/// try:
1802///     if True:
1803///         if True:
1804///             pass
1805///
1806///         # a
1807///             # b
1808///         # c
1809/// except Exception:
1810///     pass
1811/// ```
1812///
1813/// If we don't use the minimum indentation of any preceding comments, we would mark `# b` as
1814/// indented to the same depth as `pass`, which could in turn lead to us treating it as a trailing
1815/// comment of `pass`, despite there being a comment between them that "resets" the indentation.
1816pub fn comment_indentation_after(
1817    preceding: AnyNodeRef,
1818    comment_range: TextRange,
1819    source: &str,
1820) -> TextSize {
1821    let tokenizer = SimpleTokenizer::new(
1822        source,
1823        TextRange::new(source.full_line_end(preceding.end()), comment_range.end()),
1824    );
1825
1826    tokenizer
1827        .filter_map(|token| {
1828            if token.kind() == SimpleTokenKind::Comment {
1829                indentation_at_offset(token.start(), source).map(TextLen::text_len)
1830            } else {
1831                None
1832            }
1833        })
1834        .min()
1835        .unwrap_or_default()
1836}
1837
1838pub fn is_dotted_name(expr: &ast::Expr) -> bool {
1839    match expr {
1840        ast::Expr::Name(_) => true,
1841        ast::Expr::Attribute(ast::ExprAttribute { value, .. }) => is_dotted_name(value),
1842        _ => false,
1843    }
1844}
1845
1846#[cfg(test)]
1847mod tests {
1848    use std::borrow::Cow;
1849    use std::cell::RefCell;
1850    use std::vec;
1851
1852    use ruff_text_size::TextRange;
1853
1854    use crate::helpers::{any_over_stmt, any_over_type_param, resolve_imported_module_path};
1855    use crate::{
1856        AtomicNodeIndex, Expr, ExprContext, ExprName, ExprNumberLiteral, Identifier, Int, Number,
1857        Stmt, StmtTypeAlias, TypeParam, TypeParamParamSpec, TypeParamTypeVar,
1858        TypeParamTypeVarTuple, TypeParams,
1859    };
1860
1861    #[test]
1862    fn resolve_import() {
1863        // Return the module directly.
1864        assert_eq!(
1865            resolve_imported_module_path(0, Some("foo"), None),
1866            Some(Cow::Borrowed("foo"))
1867        );
1868
1869        // Construct the module path from the calling module's path.
1870        assert_eq!(
1871            resolve_imported_module_path(
1872                1,
1873                Some("foo"),
1874                Some(&["bar".to_string(), "baz".to_string()])
1875            ),
1876            Some(Cow::Owned("bar.foo".to_string()))
1877        );
1878
1879        // We can't return the module if it's a relative import, and we don't know the calling
1880        // module's path.
1881        assert_eq!(resolve_imported_module_path(1, Some("foo"), None), None);
1882
1883        // We can't return the module if it's a relative import, and the path goes beyond the
1884        // calling module's path.
1885        assert_eq!(
1886            resolve_imported_module_path(1, Some("foo"), Some(&["bar".to_string()])),
1887            None,
1888        );
1889        assert_eq!(
1890            resolve_imported_module_path(2, Some("foo"), Some(&["bar".to_string()])),
1891            None
1892        );
1893    }
1894
1895    #[test]
1896    fn any_over_stmt_type_alias() {
1897        let seen = RefCell::new(Vec::new());
1898        let name = Expr::Name(ExprName {
1899            id: "x".into(),
1900            range: TextRange::default(),
1901            node_index: AtomicNodeIndex::NONE,
1902            ctx: ExprContext::Load,
1903        });
1904        let constant_one = Expr::NumberLiteral(ExprNumberLiteral {
1905            value: Number::Int(Int::from(1u8)),
1906            range: TextRange::default(),
1907            node_index: AtomicNodeIndex::NONE,
1908        });
1909        let constant_two = Expr::NumberLiteral(ExprNumberLiteral {
1910            value: Number::Int(Int::from(2u8)),
1911            range: TextRange::default(),
1912            node_index: AtomicNodeIndex::NONE,
1913        });
1914        let constant_three = Expr::NumberLiteral(ExprNumberLiteral {
1915            value: Number::Int(Int::from(3u8)),
1916            range: TextRange::default(),
1917            node_index: AtomicNodeIndex::NONE,
1918        });
1919        let type_var_one = TypeParam::TypeVar(TypeParamTypeVar {
1920            range: TextRange::default(),
1921            node_index: AtomicNodeIndex::NONE,
1922            bound: Some(Box::new(constant_one.clone())),
1923            default: None,
1924            name: Identifier::new("x", TextRange::default()),
1925        });
1926        let type_var_two = TypeParam::TypeVar(TypeParamTypeVar {
1927            range: TextRange::default(),
1928            node_index: AtomicNodeIndex::NONE,
1929            bound: None,
1930            default: Some(Box::new(constant_two.clone())),
1931            name: Identifier::new("x", TextRange::default()),
1932        });
1933        let type_alias = Stmt::TypeAlias(StmtTypeAlias {
1934            name: Box::new(name.clone()),
1935            type_params: Some(Box::new(TypeParams {
1936                type_params: vec![type_var_one, type_var_two],
1937                range: TextRange::default(),
1938                node_index: AtomicNodeIndex::NONE,
1939                runtime_type_params: None,
1940            })),
1941            value: Box::new(constant_three.clone()),
1942            range: TextRange::default(),
1943            node_index: AtomicNodeIndex::NONE,
1944        });
1945        assert!(!any_over_stmt(&type_alias, |expr| {
1946            seen.borrow_mut().push(expr.clone());
1947            false
1948        }));
1949        assert_eq!(
1950            seen.take(),
1951            vec![name, constant_one, constant_two, constant_three]
1952        );
1953    }
1954
1955    #[test]
1956    fn any_over_type_param_type_var() {
1957        let type_var_no_bound = TypeParam::TypeVar(TypeParamTypeVar {
1958            range: TextRange::default(),
1959            node_index: AtomicNodeIndex::NONE,
1960            bound: None,
1961            default: None,
1962            name: Identifier::new("x", TextRange::default()),
1963        });
1964        assert!(!any_over_type_param(&type_var_no_bound, &mut |_expr| true));
1965
1966        let constant = Expr::NumberLiteral(ExprNumberLiteral {
1967            value: Number::Int(Int::ONE),
1968            range: TextRange::default(),
1969            node_index: AtomicNodeIndex::NONE,
1970        });
1971
1972        let type_var_with_bound = TypeParam::TypeVar(TypeParamTypeVar {
1973            range: TextRange::default(),
1974            node_index: AtomicNodeIndex::NONE,
1975            bound: Some(Box::new(constant.clone())),
1976            default: None,
1977            name: Identifier::new("x", TextRange::default()),
1978        });
1979        assert!(
1980            any_over_type_param(&type_var_with_bound, &mut |expr| {
1981                assert_eq!(
1982                    *expr, constant,
1983                    "the received expression should be the unwrapped bound"
1984                );
1985                true
1986            }),
1987            "if true is returned from `func` it should be respected"
1988        );
1989
1990        let type_var_with_default = TypeParam::TypeVar(TypeParamTypeVar {
1991            range: TextRange::default(),
1992            node_index: AtomicNodeIndex::NONE,
1993            default: Some(Box::new(constant.clone())),
1994            bound: None,
1995            name: Identifier::new("x", TextRange::default()),
1996        });
1997        assert!(
1998            any_over_type_param(&type_var_with_default, &mut |expr| {
1999                assert_eq!(
2000                    *expr, constant,
2001                    "the received expression should be the unwrapped default"
2002                );
2003                true
2004            }),
2005            "if true is returned from `func` it should be respected"
2006        );
2007    }
2008
2009    #[test]
2010    fn any_over_type_param_type_var_tuple() {
2011        let type_var_tuple = TypeParam::TypeVarTuple(TypeParamTypeVarTuple {
2012            range: TextRange::default(),
2013            node_index: AtomicNodeIndex::NONE,
2014            name: Identifier::new("x", TextRange::default()),
2015            default: None,
2016        });
2017        assert!(
2018            !any_over_type_param(&type_var_tuple, &mut |_expr| true),
2019            "this TypeVarTuple has no expressions to visit"
2020        );
2021
2022        let constant = Expr::NumberLiteral(ExprNumberLiteral {
2023            value: Number::Int(Int::ONE),
2024            range: TextRange::default(),
2025            node_index: AtomicNodeIndex::NONE,
2026        });
2027
2028        let type_var_tuple_with_default = TypeParam::TypeVarTuple(TypeParamTypeVarTuple {
2029            range: TextRange::default(),
2030            node_index: AtomicNodeIndex::NONE,
2031            default: Some(Box::new(constant.clone())),
2032            name: Identifier::new("x", TextRange::default()),
2033        });
2034        assert!(
2035            any_over_type_param(&type_var_tuple_with_default, &mut |expr| {
2036                assert_eq!(
2037                    *expr, constant,
2038                    "the received expression should be the unwrapped default"
2039                );
2040                true
2041            }),
2042            "if true is returned from `func` it should be respected"
2043        );
2044    }
2045
2046    #[test]
2047    fn any_over_type_param_param_spec() {
2048        let type_param_spec = TypeParam::ParamSpec(TypeParamParamSpec {
2049            range: TextRange::default(),
2050            node_index: AtomicNodeIndex::NONE,
2051            name: Identifier::new("x", TextRange::default()),
2052            default: None,
2053        });
2054        assert!(
2055            !any_over_type_param(&type_param_spec, &mut |_expr| true),
2056            "this ParamSpec has no expressions to visit"
2057        );
2058
2059        let constant = Expr::NumberLiteral(ExprNumberLiteral {
2060            value: Number::Int(Int::ONE),
2061            range: TextRange::default(),
2062            node_index: AtomicNodeIndex::NONE,
2063        });
2064
2065        let param_spec_with_default = TypeParam::TypeVarTuple(TypeParamTypeVarTuple {
2066            range: TextRange::default(),
2067            node_index: AtomicNodeIndex::NONE,
2068            default: Some(Box::new(constant.clone())),
2069            name: Identifier::new("x", TextRange::default()),
2070        });
2071        assert!(
2072            any_over_type_param(&param_spec_with_default, &mut |expr| {
2073                assert_eq!(
2074                    *expr, constant,
2075                    "the received expression should be the unwrapped default"
2076                );
2077                true
2078            }),
2079            "if true is returned from `func` it should be respected"
2080        );
2081    }
2082}