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