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