Skip to main content

rustpython_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                if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
63                    if !is_iterable_initializer(id.as_str(), |id| is_builtin(id)) {
64                        return true;
65                    }
66                    return false;
67                }
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            if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
771                if matches!(id.as_str(), "locals" | "globals" | "vars" | "exec" | "eval") {
772                    if is_builtin(id.as_str()) {
773                        return true;
774                    }
775                }
776            }
777        }
778        false
779    })
780}
781
782/// Format the module reference name for a relative import.
783///
784/// # Examples
785///
786/// ```rust
787/// # use ruff_python_ast::helpers::format_import_from;
788///
789/// assert_eq!(format_import_from(0, None), "".to_string());
790/// assert_eq!(format_import_from(1, None), ".".to_string());
791/// assert_eq!(format_import_from(1, Some("foo")), ".foo".to_string());
792/// ```
793pub fn format_import_from(level: u32, module: Option<&str>) -> Cow<'_, str> {
794    match (level, module) {
795        (0, Some(module)) => Cow::Borrowed(module),
796        (level, module) => {
797            let mut module_name =
798                String::with_capacity((level as usize) + module.map_or(0, str::len));
799            for _ in 0..level {
800                module_name.push('.');
801            }
802            if let Some(module) = module {
803                module_name.push_str(module);
804            }
805            Cow::Owned(module_name)
806        }
807    }
808}
809
810/// Format the member reference name for a relative import.
811///
812/// # Examples
813///
814/// ```rust
815/// # use ruff_python_ast::helpers::format_import_from_member;
816///
817/// assert_eq!(format_import_from_member(0, None, "bar"), "bar".to_string());
818/// assert_eq!(format_import_from_member(1, None, "bar"), ".bar".to_string());
819/// assert_eq!(format_import_from_member(1, Some("foo"), "bar"), ".foo.bar".to_string());
820/// ```
821pub fn format_import_from_member(level: u32, module: Option<&str>, member: &str) -> String {
822    let mut qualified_name =
823        String::with_capacity((level as usize) + module.map_or(0, str::len) + 1 + member.len());
824    if level > 0 {
825        for _ in 0..level {
826            qualified_name.push('.');
827        }
828    }
829    if let Some(module) = module {
830        qualified_name.push_str(module);
831        qualified_name.push('.');
832    }
833    qualified_name.push_str(member);
834    qualified_name
835}
836
837/// Create a module path from a (package, path) pair.
838///
839/// For example, if the package is `foo/bar` and the path is `foo/bar/baz.py`,
840/// the call path is `["baz"]`.
841pub fn to_module_path(package: &Path, path: &Path) -> Option<Vec<String>> {
842    path.strip_prefix(package.parent()?)
843        .ok()?
844        .iter()
845        .map(Path::new)
846        .map(Path::file_stem)
847        .map(|path| path.and_then(|path| path.to_os_string().into_string().ok()))
848        .collect::<Option<Vec<String>>>()
849}
850
851/// Format the call path for a relative import.
852///
853/// # Examples
854///
855/// ```rust
856/// # use ruff_python_ast::helpers::collect_import_from_member;
857///
858/// assert_eq!(collect_import_from_member(0, None, "bar").segments(), ["bar"]);
859/// assert_eq!(collect_import_from_member(1, None, "bar").segments(), [".", "bar"]);
860/// assert_eq!(collect_import_from_member(1, Some("foo"), "bar").segments(), [".", "foo", "bar"]);
861/// ```
862pub fn collect_import_from_member<'a>(
863    level: u32,
864    module: Option<&'a str>,
865    member: &'a str,
866) -> QualifiedName<'a> {
867    let mut qualified_name_builder = QualifiedNameBuilder::with_capacity(
868        level as usize
869            + module
870                .map(|module| module.split('.').count())
871                .unwrap_or_default()
872            + 1,
873    );
874
875    // Include the dots as standalone segments.
876    if level > 0 {
877        for _ in 0..level {
878            qualified_name_builder.push(".");
879        }
880    }
881
882    // Add the remaining segments.
883    if let Some(module) = module {
884        qualified_name_builder.extend(module.split('.'));
885    }
886
887    // Add the member.
888    qualified_name_builder.push(member);
889
890    qualified_name_builder.build()
891}
892
893/// Format the call path for a relative import, or `None` if the relative import extends beyond
894/// the root module.
895pub fn from_relative_import<'a>(
896    // The path from which the import is relative.
897    module: &'a [String],
898    // The path of the import itself (e.g., given `from ..foo import bar`, `[".", ".", "foo", "bar]`).
899    import: &[&'a str],
900    // The remaining segments to the call path (e.g., given `bar.baz`, `["baz"]`).
901    tail: &[&'a str],
902) -> Option<QualifiedName<'a>> {
903    let mut qualified_name_builder =
904        QualifiedNameBuilder::with_capacity(module.len() + import.len() + tail.len());
905
906    // Start with the module path.
907    qualified_name_builder.extend(module.iter().map(String::as_str));
908
909    // Remove segments based on the number of dots.
910    for segment in import {
911        if *segment == "." {
912            if qualified_name_builder.is_empty() {
913                return None;
914            }
915            qualified_name_builder.pop();
916        } else {
917            qualified_name_builder.push(segment);
918        }
919    }
920
921    // Add the remaining segments.
922    qualified_name_builder.extend_from_slice(tail);
923
924    Some(qualified_name_builder.build())
925}
926
927/// Given an imported module (based on its relative import level and module name), return the
928/// fully-qualified module path.
929pub fn resolve_imported_module_path<'a>(
930    level: u32,
931    module: Option<&'a str>,
932    module_path: Option<&[String]>,
933) -> Option<Cow<'a, str>> {
934    if level == 0 {
935        return Some(Cow::Borrowed(module.unwrap_or("")));
936    }
937
938    let module_path = module_path?;
939
940    if level as usize >= module_path.len() {
941        return None;
942    }
943
944    let mut qualified_path = module_path[..module_path.len() - level as usize].join(".");
945    if let Some(module) = module {
946        if !qualified_path.is_empty() {
947            qualified_path.push('.');
948        }
949        qualified_path.push_str(module);
950    }
951    Some(Cow::Owned(qualified_path))
952}
953
954/// A [`Visitor`] to collect all [`Expr::Name`] nodes in an AST.
955#[derive(Debug, Default)]
956pub struct NameFinder<'a> {
957    /// A map from identifier to defining expression.
958    pub names: FxHashMap<&'a str, &'a ast::ExprName>,
959}
960
961impl<'a> Visitor<'a> for NameFinder<'a> {
962    fn visit_expr(&mut self, expr: &'a Expr) {
963        if let Expr::Name(name) = expr {
964            self.names.insert(&name.id, name);
965        }
966        crate::visitor::walk_expr(self, expr);
967    }
968}
969
970/// A [`Visitor`] to collect all stored [`Expr::Name`] nodes in an AST.
971#[derive(Debug, Default)]
972pub struct StoredNameFinder<'a> {
973    /// A map from identifier to defining expression.
974    pub names: FxHashMap<&'a str, &'a ast::ExprName>,
975}
976
977impl<'a> Visitor<'a> for StoredNameFinder<'a> {
978    fn visit_expr(&mut self, expr: &'a Expr) {
979        if let Expr::Name(name) = expr {
980            if name.ctx.is_store() {
981                self.names.insert(&name.id, name);
982            }
983        }
984        crate::visitor::walk_expr(self, expr);
985    }
986}
987
988/// A [`Visitor`] that collects all `return` statements in a function or method.
989#[derive(Default)]
990pub struct ReturnStatementVisitor<'a> {
991    pub returns: Vec<&'a ast::StmtReturn>,
992    pub is_generator: bool,
993}
994
995impl<'a> Visitor<'a> for ReturnStatementVisitor<'a> {
996    fn visit_stmt(&mut self, stmt: &'a Stmt) {
997        match stmt {
998            Stmt::FunctionDef(_) | Stmt::ClassDef(_) => {
999                // Don't recurse.
1000            }
1001            Stmt::Return(stmt) => self.returns.push(stmt),
1002            _ => crate::visitor::walk_stmt(self, stmt),
1003        }
1004    }
1005
1006    fn visit_expr(&mut self, expr: &'a Expr) {
1007        if let Expr::Yield(_) | Expr::YieldFrom(_) = expr {
1008            self.is_generator = true;
1009        } else {
1010            crate::visitor::walk_expr(self, expr);
1011        }
1012    }
1013}
1014
1015/// A [`StatementVisitor`] that collects all `raise` statements in a function or method.
1016#[derive(Default)]
1017pub struct RaiseStatementVisitor<'a> {
1018    pub raises: Vec<(TextRange, Option<&'a Expr>, Option<&'a Expr>)>,
1019}
1020
1021impl<'a> StatementVisitor<'a> for RaiseStatementVisitor<'a> {
1022    fn visit_stmt(&mut self, stmt: &'a Stmt) {
1023        match stmt {
1024            Stmt::Raise(ast::StmtRaise {
1025                exc,
1026                cause,
1027                range: _,
1028                node_index: _,
1029            }) => {
1030                self.raises
1031                    .push((stmt.range(), exc.as_deref(), cause.as_deref()));
1032            }
1033            Stmt::ClassDef(_) | Stmt::FunctionDef(_) | Stmt::Try(_) => {}
1034            Stmt::If(ast::StmtIf {
1035                body,
1036                elif_else_clauses,
1037                ..
1038            }) => {
1039                crate::statement_visitor::walk_body(self, body);
1040                for clause in elif_else_clauses {
1041                    self.visit_elif_else_clause(clause);
1042                }
1043            }
1044            Stmt::While(ast::StmtWhile { body, .. })
1045            | Stmt::With(ast::StmtWith { body, .. })
1046            | Stmt::For(ast::StmtFor { body, .. }) => {
1047                crate::statement_visitor::walk_body(self, body);
1048            }
1049            Stmt::Match(ast::StmtMatch { cases, .. }) => {
1050                for case in cases {
1051                    crate::statement_visitor::walk_body(self, &case.body);
1052                }
1053            }
1054            _ => {}
1055        }
1056    }
1057}
1058
1059/// A [`Visitor`] that detects the presence of `await` expressions in the current scope.
1060#[derive(Debug, Default)]
1061pub struct AwaitVisitor {
1062    pub seen_await: bool,
1063}
1064
1065impl Visitor<'_> for AwaitVisitor {
1066    fn visit_stmt(&mut self, stmt: &Stmt) {
1067        match stmt {
1068            Stmt::FunctionDef(_) | Stmt::ClassDef(_) => (),
1069            Stmt::With(ast::StmtWith { is_async: true, .. }) => {
1070                self.seen_await = true;
1071            }
1072            Stmt::For(ast::StmtFor { is_async: true, .. }) => {
1073                self.seen_await = true;
1074            }
1075            _ => crate::visitor::walk_stmt(self, stmt),
1076        }
1077    }
1078
1079    fn visit_expr(&mut self, expr: &Expr) {
1080        if let Expr::Await(ast::ExprAwait { .. }) = expr {
1081            self.seen_await = true;
1082        } else {
1083            crate::visitor::walk_expr(self, expr);
1084        }
1085    }
1086
1087    fn visit_comprehension(&mut self, comprehension: &'_ crate::Comprehension) {
1088        if comprehension.is_async {
1089            self.seen_await = true;
1090        } else {
1091            crate::visitor::walk_comprehension(self, comprehension);
1092        }
1093    }
1094}
1095
1096/// Return `true` if a `Stmt` is a docstring.
1097pub fn is_docstring_stmt(stmt: &Stmt) -> bool {
1098    if let Stmt::Expr(ast::StmtExpr {
1099        value,
1100        range: _,
1101        node_index: _,
1102    }) = stmt
1103    {
1104        value.is_string_literal_expr()
1105    } else {
1106        false
1107    }
1108}
1109
1110/// Returns `true` if all statements in `body` are `pass` or `...` (ellipsis)
1111///
1112/// An empty body (`[]`) returns `false`
1113pub fn is_stub_body(body: &[Stmt]) -> bool {
1114    !body.is_empty()
1115        && body.iter().all(|stmt| match stmt {
1116            Stmt::Pass(_) => true,
1117            Stmt::Expr(ast::StmtExpr { value, .. }) => value.is_ellipsis_literal_expr(),
1118            _ => false,
1119        })
1120}
1121
1122/// Check if a node is part of a conditional branch.
1123pub fn on_conditional_branch<'a>(parents: &mut impl Iterator<Item = &'a Stmt>) -> bool {
1124    parents.any(|parent| {
1125        if matches!(parent, Stmt::If(_) | Stmt::While(_) | Stmt::Match(_)) {
1126            return true;
1127        }
1128        if let Stmt::Expr(ast::StmtExpr {
1129            value,
1130            range: _,
1131            node_index: _,
1132        }) = parent
1133        {
1134            if value.is_if_expr() {
1135                return true;
1136            }
1137        }
1138        false
1139    })
1140}
1141
1142/// Check if a node is in a nested block.
1143pub fn in_nested_block<'a>(mut parents: impl Iterator<Item = &'a Stmt>) -> bool {
1144    parents.any(|parent| {
1145        matches!(
1146            parent,
1147            Stmt::Try(_) | Stmt::If(_) | Stmt::With(_) | Stmt::Match(_)
1148        )
1149    })
1150}
1151
1152/// Check if a node represents an unpacking assignment.
1153pub fn is_unpacking_assignment(parent: &Stmt, child: &Expr) -> bool {
1154    match parent {
1155        Stmt::With(ast::StmtWith { items, .. }) => items.iter().any(|item| {
1156            if let Some(optional_vars) = &item.optional_vars {
1157                if optional_vars.is_tuple_expr() {
1158                    if any_over_expr(optional_vars, &|expr| expr == child) {
1159                        return true;
1160                    }
1161                }
1162            }
1163            false
1164        }),
1165        Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
1166            // In `(a, b) = (1, 2)`, `(1, 2)` is the target, and it is a tuple.
1167            let value_is_tuple = matches!(
1168                value.as_ref(),
1169                Expr::Set(_) | Expr::List(_) | Expr::Tuple(_)
1170            );
1171            // In `(a, b) = coords = (1, 2)`, `(a, b)` and `coords` are the targets, and
1172            // `(a, b)` is a tuple. (We use "tuple" as a placeholder for any
1173            // unpackable expression.)
1174            let targets_are_tuples = targets
1175                .iter()
1176                .all(|item| matches!(item, Expr::Set(_) | Expr::List(_) | Expr::Tuple(_)));
1177            // If we're looking at `a` in `(a, b) = coords = (1, 2)`, then we should
1178            // identify that the current expression is in a tuple.
1179            let child_in_tuple = targets_are_tuples
1180                || targets.iter().any(|item| {
1181                    matches!(item, Expr::Set(_) | Expr::List(_) | Expr::Tuple(_))
1182                        && any_over_expr(item, &|expr| expr == child)
1183                });
1184
1185            // If our child is a tuple, and value is not, it's always an unpacking
1186            // expression. Ex) `x, y = tup`
1187            if child_in_tuple && !value_is_tuple {
1188                return true;
1189            }
1190
1191            // If our child isn't a tuple, but value is, it's never an unpacking expression.
1192            // Ex) `coords = (1, 2)`
1193            if !child_in_tuple && value_is_tuple {
1194                return false;
1195            }
1196
1197            // If our target and the value are both tuples, then it's an unpacking
1198            // expression assuming there's at least one non-tuple child.
1199            // Ex) Given `(x, y) = coords = 1, 2`, `(x, y)` is considered an unpacking
1200            // expression. Ex) Given `(x, y) = (a, b) = 1, 2`, `(x, y)` isn't
1201            // considered an unpacking expression.
1202            if child_in_tuple && value_is_tuple {
1203                return !targets_are_tuples;
1204            }
1205
1206            false
1207        }
1208        _ => false,
1209    }
1210}
1211
1212#[derive(Copy, Clone, Debug, PartialEq, is_macro::Is)]
1213pub enum Truthiness {
1214    /// The expression is `True`.
1215    True,
1216    /// The expression is `False`.
1217    False,
1218    /// The expression evaluates to a `False`-like value (e.g., `None`, `0`, `[]`, `""`).
1219    Falsey,
1220    /// The expression evaluates to a `True`-like value (e.g., `1`, `"foo"`).
1221    Truthy,
1222    /// The expression evaluates to `None`.
1223    None,
1224    /// The expression evaluates to an unknown value (e.g., a variable `x` of unknown type).
1225    Unknown,
1226}
1227
1228impl Truthiness {
1229    /// Return the truthiness of an expression.
1230    pub fn from_expr<F>(expr: &Expr, is_builtin: F) -> Self
1231    where
1232        F: Fn(&str) -> bool,
1233    {
1234        match expr {
1235            Expr::Lambda(_) => Self::Truthy,
1236            Expr::Generator(_) => Self::Truthy,
1237            Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => {
1238                if value.is_empty() {
1239                    Self::Falsey
1240                } else {
1241                    Self::Truthy
1242                }
1243            }
1244            Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. }) => {
1245                if value.is_empty() {
1246                    Self::Falsey
1247                } else {
1248                    Self::Truthy
1249                }
1250            }
1251            Expr::NumberLiteral(ast::ExprNumberLiteral { value, .. }) => match value {
1252                ast::Number::Int(int) => {
1253                    if *int == 0 {
1254                        Self::Falsey
1255                    } else {
1256                        Self::Truthy
1257                    }
1258                }
1259                ast::Number::Float(float) => {
1260                    if *float == 0.0 {
1261                        Self::Falsey
1262                    } else {
1263                        Self::Truthy
1264                    }
1265                }
1266                ast::Number::Complex { real, imag, .. } => {
1267                    if *real == 0.0 && *imag == 0.0 {
1268                        Self::Falsey
1269                    } else {
1270                        Self::Truthy
1271                    }
1272                }
1273            },
1274            Expr::BooleanLiteral(ast::ExprBooleanLiteral { value, .. }) => {
1275                if *value {
1276                    Self::True
1277                } else {
1278                    Self::False
1279                }
1280            }
1281            Expr::NoneLiteral(_) => Self::None,
1282            Expr::EllipsisLiteral(_) => Self::Truthy,
1283            Expr::FString(f_string) => {
1284                if is_empty_f_string(f_string) {
1285                    Self::Falsey
1286                } else if is_non_empty_f_string(f_string) {
1287                    Self::Truthy
1288                } else {
1289                    Self::Unknown
1290                }
1291            }
1292            Expr::TString(_) => Self::Truthy,
1293            Expr::List(ast::ExprList { elts, .. })
1294            | Expr::Set(ast::ExprSet { elts, .. })
1295            | Expr::Tuple(ast::ExprTuple { elts, .. }) => {
1296                if elts.is_empty() {
1297                    return Self::Falsey;
1298                }
1299
1300                if elts.iter().all(Expr::is_starred_expr) {
1301                    // [*foo] / [*foo, *bar]
1302                    Self::Unknown
1303                } else {
1304                    Self::Truthy
1305                }
1306            }
1307            Expr::Dict(dict) => {
1308                if dict.is_empty() {
1309                    return Self::Falsey;
1310                }
1311
1312                // If the dict consists only of double-starred items (e.g., {**x, **y}),
1313                // consider its truthiness unknown. This matches lists/sets/tuples containing
1314                // only starred elements, which are also Unknown.
1315                if dict
1316                    .items
1317                    .iter()
1318                    .all(|item| matches!(item, DictItem { key: None, .. }))
1319                {
1320                    // {**foo} / {**foo, **bar}
1321                    Self::Unknown
1322                } else {
1323                    Self::Truthy
1324                }
1325            }
1326            Expr::Call(ast::ExprCall {
1327                func, arguments, ..
1328            }) => {
1329                if let Expr::Name(ast::ExprName { id, .. }) = func.as_ref() {
1330                    if is_iterable_initializer(id.as_str(), |id| is_builtin(id)) {
1331                        if arguments.is_empty() {
1332                            // Ex) `list()`
1333                            Self::Falsey
1334                        } else if let [argument] = &*arguments.args
1335                            && arguments.keywords.is_empty()
1336                        {
1337                            // Ex) `list([1, 2, 3])`
1338                            match argument {
1339                                // Return Unknown for types with definite truthiness that might
1340                                // result in empty iterables (t-strings and generators) or will
1341                                // raise a type error (non-iterable types like numbers, booleans,
1342                                // None, etc.).
1343                                Expr::NumberLiteral(_)
1344                                | Expr::BooleanLiteral(_)
1345                                | Expr::NoneLiteral(_)
1346                                | Expr::EllipsisLiteral(_)
1347                                | Expr::TString(_)
1348                                | Expr::Lambda(_)
1349                                | Expr::Generator(_) => Self::Unknown,
1350                                // Recurse for all other types - collections, comprehensions, variables, etc.
1351                                // StringLiteral, FString, and BytesLiteral recurse because Self::from_expr
1352                                // correctly handles their truthiness (checking if empty or not).
1353                                _ => Self::from_expr(argument, is_builtin),
1354                            }
1355                        } else {
1356                            Self::Unknown
1357                        }
1358                    } else {
1359                        Self::Unknown
1360                    }
1361                } else {
1362                    Self::Unknown
1363                }
1364            }
1365            _ => Self::Unknown,
1366        }
1367    }
1368
1369    pub fn into_bool(self) -> Option<bool> {
1370        match self {
1371            Self::True | Self::Truthy => Some(true),
1372            Self::False | Self::Falsey => Some(false),
1373            Self::None => Some(false),
1374            Self::Unknown => None,
1375        }
1376    }
1377}
1378
1379/// Returns `true` if the expression definitely resolves to a non-empty string, when used as an
1380/// f-string expression, or `false` if the expression may resolve to an empty string.
1381fn is_non_empty_f_string(expr: &ast::ExprFString) -> bool {
1382    fn inner(expr: &Expr) -> bool {
1383        match expr {
1384            // When stringified, these expressions are always non-empty.
1385            Expr::Lambda(_) => true,
1386            Expr::Dict(_) => true,
1387            Expr::Set(_) => true,
1388            Expr::ListComp(_) => true,
1389            Expr::SetComp(_) => true,
1390            Expr::DictComp(_) => true,
1391            Expr::Compare(_) => true,
1392            Expr::NumberLiteral(_) => true,
1393            Expr::BooleanLiteral(_) => true,
1394            Expr::NoneLiteral(_) => true,
1395            Expr::EllipsisLiteral(_) => true,
1396            Expr::List(_) => true,
1397            Expr::Tuple(_) => true,
1398            Expr::TString(_) => true,
1399
1400            // These expressions must resolve to the inner expression.
1401            Expr::If(ast::ExprIf { body, orelse, .. }) => inner(body) && inner(orelse),
1402            Expr::Named(ast::ExprNamed { value, .. }) => inner(value),
1403
1404            // These expressions are complex. We can't determine whether they're empty or not.
1405            Expr::BoolOp(ast::ExprBoolOp { .. }) => false,
1406            Expr::BinOp(ast::ExprBinOp { .. }) => false,
1407            Expr::UnaryOp(ast::ExprUnaryOp { .. }) => false,
1408            Expr::Generator(_) => false,
1409            Expr::Await(_) => false,
1410            Expr::Yield(_) => false,
1411            Expr::YieldFrom(_) => false,
1412            Expr::Call(_) => false,
1413            Expr::Attribute(_) => false,
1414            Expr::Subscript(_) => false,
1415            Expr::Starred(_) => false,
1416            Expr::Name(_) => false,
1417            Expr::Slice(_) => false,
1418            Expr::IpyEscapeCommand(_) => false,
1419
1420            // These literals may or may not be empty.
1421            Expr::FString(f_string) => is_non_empty_f_string(f_string),
1422            // These literals may or may not be empty.
1423            Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => !value.is_empty(),
1424            // Confusingly, f"{b""}" renders as the string 'b""', which is non-empty.
1425            // Therefore, any bytes interpolation is guaranteed non-empty when stringified.
1426            Expr::BytesLiteral(_) => true,
1427        }
1428    }
1429
1430    expr.value.iter().any(|part| match part {
1431        ast::FStringPart::Literal(string_literal) => !string_literal.is_empty(),
1432        ast::FStringPart::FString(f_string) => {
1433            f_string.elements.iter().all(|element| match element {
1434                InterpolatedStringElement::Literal(string_literal) => !string_literal.is_empty(),
1435                InterpolatedStringElement::Interpolation(f_string) => {
1436                    f_string.debug_text.is_some() || inner(&f_string.expression)
1437                }
1438            })
1439        }
1440    })
1441}
1442
1443/// Returns `true` if the expression definitely resolves to the empty string, when used as an f-string
1444/// expression.
1445pub fn is_empty_f_string(expr: &ast::ExprFString) -> bool {
1446    fn inner(expr: &Expr) -> bool {
1447        match expr {
1448            Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => value.is_empty(),
1449            // Confusingly, `bool(f"{b""}") == True` even though
1450            // `bool(b"") == False`. This is because `f"{b""}"`
1451            // evaluates as the string `'b""'` of length 3.
1452            Expr::BytesLiteral(_) => false,
1453            Expr::FString(ast::ExprFString { value, .. }) => {
1454                is_empty_interpolated_elements(value.elements())
1455            }
1456            _ => false,
1457        }
1458    }
1459
1460    fn is_empty_interpolated_elements<'a>(
1461        mut elements: impl Iterator<Item = &'a InterpolatedStringElement>,
1462    ) -> bool {
1463        elements.all(|element| match element {
1464            InterpolatedStringElement::Literal(ast::InterpolatedStringLiteralElement {
1465                value,
1466                ..
1467            }) => value.is_empty(),
1468            InterpolatedStringElement::Interpolation(f_string) => {
1469                f_string.debug_text.is_none()
1470                    && f_string.conversion.is_none()
1471                    && f_string.format_spec.is_none()
1472                    && inner(&f_string.expression)
1473            }
1474        })
1475    }
1476
1477    expr.value.iter().all(|part| match part {
1478        ast::FStringPart::Literal(string_literal) => string_literal.is_empty(),
1479        ast::FStringPart::FString(f_string) => {
1480            is_empty_interpolated_elements(f_string.elements.iter())
1481        }
1482    })
1483}
1484
1485pub fn generate_comparison(
1486    left: &Expr,
1487    ops: &[CmpOp],
1488    comparators: &[Expr],
1489    parent: AnyNodeRef,
1490    tokens: &Tokens,
1491    source: &str,
1492) -> String {
1493    let start = left.start();
1494    let end = comparators.last().map_or_else(|| left.end(), Ranged::end);
1495    let mut contents = String::with_capacity(usize::from(end - start));
1496
1497    // Add the left side of the comparison.
1498    contents.push_str(
1499        &source[parenthesized_range(left.into(), parent, tokens).unwrap_or(left.range())],
1500    );
1501
1502    for (op, comparator) in ops.iter().zip(comparators) {
1503        // Add the operator.
1504        contents.push_str(match op {
1505            CmpOp::Eq => " == ",
1506            CmpOp::NotEq => " != ",
1507            CmpOp::Lt => " < ",
1508            CmpOp::LtE => " <= ",
1509            CmpOp::Gt => " > ",
1510            CmpOp::GtE => " >= ",
1511            CmpOp::In => " in ",
1512            CmpOp::NotIn => " not in ",
1513            CmpOp::Is => " is ",
1514            CmpOp::IsNot => " is not ",
1515        });
1516
1517        // Add the right side of the comparison.
1518        contents.push_str(
1519            &source[parenthesized_range(comparator.into(), parent, tokens)
1520                .unwrap_or(comparator.range())],
1521        );
1522    }
1523
1524    contents
1525}
1526
1527/// Format the expression as a PEP 604-style optional.
1528pub fn pep_604_optional(expr: &Expr) -> Expr {
1529    ast::ExprBinOp {
1530        left: Box::new(expr.clone()),
1531        op: Operator::BitOr,
1532        right: Box::new(Expr::NoneLiteral(ExprNoneLiteral::default())),
1533        range: TextRange::default(),
1534        node_index: AtomicNodeIndex::NONE,
1535    }
1536    .into()
1537}
1538
1539/// Format the expressions as a PEP 604-style union.
1540pub fn pep_604_union(elts: &[Expr]) -> Expr {
1541    match elts {
1542        [] => Expr::Tuple(ast::ExprTuple {
1543            elts: vec![],
1544            ctx: ExprContext::Load,
1545            range: TextRange::default(),
1546            node_index: AtomicNodeIndex::NONE,
1547            parenthesized: true,
1548        }),
1549        [Expr::Tuple(ast::ExprTuple { elts, .. })] => pep_604_union(elts),
1550        [elt] => elt.clone(),
1551        [rest @ .., elt] => Expr::BinOp(ast::ExprBinOp {
1552            left: Box::new(pep_604_union(rest)),
1553            op: Operator::BitOr,
1554            right: Box::new(pep_604_union(std::slice::from_ref(elt))),
1555            range: TextRange::default(),
1556            node_index: AtomicNodeIndex::NONE,
1557        }),
1558    }
1559}
1560
1561/// Format the expression as a `typing.Optional`-style optional.
1562pub fn typing_optional(elt: Expr, binding: Name) -> Expr {
1563    Expr::Subscript(ast::ExprSubscript {
1564        value: Box::new(Expr::Name(ast::ExprName {
1565            id: binding,
1566            range: TextRange::default(),
1567            node_index: AtomicNodeIndex::NONE,
1568            ctx: ExprContext::Load,
1569        })),
1570        slice: Box::new(elt),
1571        ctx: ExprContext::Load,
1572        range: TextRange::default(),
1573        node_index: AtomicNodeIndex::NONE,
1574    })
1575}
1576
1577/// Format the expressions as a `typing.Union`-style union.
1578///
1579/// Note: It is a syntax error to have `Union[]` so the caller
1580/// should ensure that the `elts` argument is nonempty.
1581pub fn typing_union(elts: &[Expr], binding: Name) -> Expr {
1582    Expr::Subscript(ast::ExprSubscript {
1583        value: Box::new(Expr::Name(ast::ExprName {
1584            id: binding,
1585            range: TextRange::default(),
1586            node_index: AtomicNodeIndex::NONE,
1587            ctx: ExprContext::Load,
1588        })),
1589        slice: Box::new(Expr::Tuple(ast::ExprTuple {
1590            range: TextRange::default(),
1591            node_index: AtomicNodeIndex::NONE,
1592            elts: elts.to_vec(),
1593            ctx: ExprContext::Load,
1594            parenthesized: false,
1595        })),
1596        ctx: ExprContext::Load,
1597        range: TextRange::default(),
1598        node_index: AtomicNodeIndex::NONE,
1599    })
1600}
1601
1602/// Determine the indentation level of an own-line comment, defined as the minimum indentation of
1603/// all comments between the preceding node and the comment, including the comment itself. In
1604/// other words, we don't allow successive comments to ident _further_ than any preceding comments.
1605///
1606/// For example, given:
1607/// ```python
1608/// if True:
1609///     pass
1610///     # comment
1611/// ```
1612///
1613/// The indentation would be 4, as the comment is indented by 4 spaces.
1614///
1615/// Given:
1616/// ```python
1617/// if True:
1618///     pass
1619/// # comment
1620/// else:
1621///     pass
1622/// ```
1623///
1624/// The indentation would be 0, as the comment is not indented at all.
1625///
1626/// Given:
1627/// ```python
1628/// if True:
1629///     pass
1630///     # comment
1631///         # comment
1632/// ```
1633///
1634/// Both comments would be marked as indented at 4 spaces, as the indentation of the first comment
1635/// is used for the second comment.
1636///
1637/// This logic avoids pathological cases like:
1638/// ```python
1639/// try:
1640///     if True:
1641///         if True:
1642///             pass
1643///
1644///         # a
1645///             # b
1646///         # c
1647/// except Exception:
1648///     pass
1649/// ```
1650///
1651/// If we don't use the minimum indentation of any preceding comments, we would mark `# b` as
1652/// indented to the same depth as `pass`, which could in turn lead to us treating it as a trailing
1653/// comment of `pass`, despite there being a comment between them that "resets" the indentation.
1654pub fn comment_indentation_after(
1655    preceding: AnyNodeRef,
1656    comment_range: TextRange,
1657    source: &str,
1658) -> TextSize {
1659    let tokenizer = SimpleTokenizer::new(
1660        source,
1661        TextRange::new(source.full_line_end(preceding.end()), comment_range.end()),
1662    );
1663
1664    tokenizer
1665        .filter_map(|token| {
1666            if token.kind() == SimpleTokenKind::Comment {
1667                indentation_at_offset(token.start(), source).map(TextLen::text_len)
1668            } else {
1669                None
1670            }
1671        })
1672        .min()
1673        .unwrap_or_default()
1674}
1675
1676#[cfg(test)]
1677mod tests {
1678    use std::borrow::Cow;
1679    use std::cell::RefCell;
1680    use std::vec;
1681
1682    use ruff_text_size::TextRange;
1683
1684    use crate::helpers::{any_over_stmt, any_over_type_param, resolve_imported_module_path};
1685    use crate::{
1686        AtomicNodeIndex, Expr, ExprContext, ExprName, ExprNumberLiteral, Identifier, Int, Number,
1687        Stmt, StmtTypeAlias, TypeParam, TypeParamParamSpec, TypeParamTypeVar,
1688        TypeParamTypeVarTuple, TypeParams,
1689    };
1690
1691    #[test]
1692    fn resolve_import() {
1693        // Return the module directly.
1694        assert_eq!(
1695            resolve_imported_module_path(0, Some("foo"), None),
1696            Some(Cow::Borrowed("foo"))
1697        );
1698
1699        // Construct the module path from the calling module's path.
1700        assert_eq!(
1701            resolve_imported_module_path(
1702                1,
1703                Some("foo"),
1704                Some(&["bar".to_string(), "baz".to_string()])
1705            ),
1706            Some(Cow::Owned("bar.foo".to_string()))
1707        );
1708
1709        // We can't return the module if it's a relative import, and we don't know the calling
1710        // module's path.
1711        assert_eq!(resolve_imported_module_path(1, Some("foo"), None), None);
1712
1713        // We can't return the module if it's a relative import, and the path goes beyond the
1714        // calling module's path.
1715        assert_eq!(
1716            resolve_imported_module_path(1, Some("foo"), Some(&["bar".to_string()])),
1717            None,
1718        );
1719        assert_eq!(
1720            resolve_imported_module_path(2, Some("foo"), Some(&["bar".to_string()])),
1721            None
1722        );
1723    }
1724
1725    #[test]
1726    fn any_over_stmt_type_alias() {
1727        let seen = RefCell::new(Vec::new());
1728        let name = Expr::Name(ExprName {
1729            id: "x".into(),
1730            range: TextRange::default(),
1731            node_index: AtomicNodeIndex::NONE,
1732            ctx: ExprContext::Load,
1733        });
1734        let constant_one = Expr::NumberLiteral(ExprNumberLiteral {
1735            value: Number::Int(Int::from(1u8)),
1736            range: TextRange::default(),
1737            node_index: AtomicNodeIndex::NONE,
1738        });
1739        let constant_two = Expr::NumberLiteral(ExprNumberLiteral {
1740            value: Number::Int(Int::from(2u8)),
1741            range: TextRange::default(),
1742            node_index: AtomicNodeIndex::NONE,
1743        });
1744        let constant_three = Expr::NumberLiteral(ExprNumberLiteral {
1745            value: Number::Int(Int::from(3u8)),
1746            range: TextRange::default(),
1747            node_index: AtomicNodeIndex::NONE,
1748        });
1749        let type_var_one = TypeParam::TypeVar(TypeParamTypeVar {
1750            range: TextRange::default(),
1751            node_index: AtomicNodeIndex::NONE,
1752            bound: Some(Box::new(constant_one.clone())),
1753            default: None,
1754            name: Identifier::new("x", TextRange::default()),
1755        });
1756        let type_var_two = TypeParam::TypeVar(TypeParamTypeVar {
1757            range: TextRange::default(),
1758            node_index: AtomicNodeIndex::NONE,
1759            bound: None,
1760            default: Some(Box::new(constant_two.clone())),
1761            name: Identifier::new("x", TextRange::default()),
1762        });
1763        let type_alias = Stmt::TypeAlias(StmtTypeAlias {
1764            name: Box::new(name.clone()),
1765            type_params: Some(Box::new(TypeParams {
1766                type_params: vec![type_var_one, type_var_two],
1767                range: TextRange::default(),
1768                node_index: AtomicNodeIndex::NONE,
1769            })),
1770            value: Box::new(constant_three.clone()),
1771            range: TextRange::default(),
1772            node_index: AtomicNodeIndex::NONE,
1773        });
1774        assert!(!any_over_stmt(&type_alias, &|expr| {
1775            seen.borrow_mut().push(expr.clone());
1776            false
1777        }));
1778        assert_eq!(
1779            seen.take(),
1780            vec![name, constant_one, constant_two, constant_three]
1781        );
1782    }
1783
1784    #[test]
1785    fn any_over_type_param_type_var() {
1786        let type_var_no_bound = TypeParam::TypeVar(TypeParamTypeVar {
1787            range: TextRange::default(),
1788            node_index: AtomicNodeIndex::NONE,
1789            bound: None,
1790            default: None,
1791            name: Identifier::new("x", TextRange::default()),
1792        });
1793        assert!(!any_over_type_param(&type_var_no_bound, &|_expr| true));
1794
1795        let constant = Expr::NumberLiteral(ExprNumberLiteral {
1796            value: Number::Int(Int::ONE),
1797            range: TextRange::default(),
1798            node_index: AtomicNodeIndex::NONE,
1799        });
1800
1801        let type_var_with_bound = TypeParam::TypeVar(TypeParamTypeVar {
1802            range: TextRange::default(),
1803            node_index: AtomicNodeIndex::NONE,
1804            bound: Some(Box::new(constant.clone())),
1805            default: None,
1806            name: Identifier::new("x", TextRange::default()),
1807        });
1808        assert!(
1809            any_over_type_param(&type_var_with_bound, &|expr| {
1810                assert_eq!(
1811                    *expr, constant,
1812                    "the received expression should be the unwrapped bound"
1813                );
1814                true
1815            }),
1816            "if true is returned from `func` it should be respected"
1817        );
1818
1819        let type_var_with_default = TypeParam::TypeVar(TypeParamTypeVar {
1820            range: TextRange::default(),
1821            node_index: AtomicNodeIndex::NONE,
1822            default: Some(Box::new(constant.clone())),
1823            bound: None,
1824            name: Identifier::new("x", TextRange::default()),
1825        });
1826        assert!(
1827            any_over_type_param(&type_var_with_default, &|expr| {
1828                assert_eq!(
1829                    *expr, constant,
1830                    "the received expression should be the unwrapped default"
1831                );
1832                true
1833            }),
1834            "if true is returned from `func` it should be respected"
1835        );
1836    }
1837
1838    #[test]
1839    fn any_over_type_param_type_var_tuple() {
1840        let type_var_tuple = TypeParam::TypeVarTuple(TypeParamTypeVarTuple {
1841            range: TextRange::default(),
1842            node_index: AtomicNodeIndex::NONE,
1843            name: Identifier::new("x", TextRange::default()),
1844            default: None,
1845        });
1846        assert!(
1847            !any_over_type_param(&type_var_tuple, &|_expr| true),
1848            "this TypeVarTuple has no expressions to visit"
1849        );
1850
1851        let constant = Expr::NumberLiteral(ExprNumberLiteral {
1852            value: Number::Int(Int::ONE),
1853            range: TextRange::default(),
1854            node_index: AtomicNodeIndex::NONE,
1855        });
1856
1857        let type_var_tuple_with_default = TypeParam::TypeVarTuple(TypeParamTypeVarTuple {
1858            range: TextRange::default(),
1859            node_index: AtomicNodeIndex::NONE,
1860            default: Some(Box::new(constant.clone())),
1861            name: Identifier::new("x", TextRange::default()),
1862        });
1863        assert!(
1864            any_over_type_param(&type_var_tuple_with_default, &|expr| {
1865                assert_eq!(
1866                    *expr, constant,
1867                    "the received expression should be the unwrapped default"
1868                );
1869                true
1870            }),
1871            "if true is returned from `func` it should be respected"
1872        );
1873    }
1874
1875    #[test]
1876    fn any_over_type_param_param_spec() {
1877        let type_param_spec = TypeParam::ParamSpec(TypeParamParamSpec {
1878            range: TextRange::default(),
1879            node_index: AtomicNodeIndex::NONE,
1880            name: Identifier::new("x", TextRange::default()),
1881            default: None,
1882        });
1883        assert!(
1884            !any_over_type_param(&type_param_spec, &|_expr| true),
1885            "this ParamSpec has no expressions to visit"
1886        );
1887
1888        let constant = Expr::NumberLiteral(ExprNumberLiteral {
1889            value: Number::Int(Int::ONE),
1890            range: TextRange::default(),
1891            node_index: AtomicNodeIndex::NONE,
1892        });
1893
1894        let param_spec_with_default = TypeParam::TypeVarTuple(TypeParamTypeVarTuple {
1895            range: TextRange::default(),
1896            node_index: AtomicNodeIndex::NONE,
1897            default: Some(Box::new(constant.clone())),
1898            name: Identifier::new("x", TextRange::default()),
1899        });
1900        assert!(
1901            any_over_type_param(&param_spec_with_default, &|expr| {
1902                assert_eq!(
1903                    *expr, constant,
1904                    "the received expression should be the unwrapped default"
1905                );
1906                true
1907            }),
1908            "if true is returned from `func` it should be respected"
1909        );
1910    }
1911}