Skip to main content

clippy_utils/
higher.rs

1//! This module contains functions that retrieve specific elements.
2
3#![deny(clippy::missing_docs_in_private_items)]
4
5use crate::consts::{ConstEvalCtxt, Constant};
6use crate::res::MaybeDef as _;
7use crate::{is_expn_of, sym};
8
9use rustc_ast::ast;
10use rustc_attr_ir::lang_items::LangItem;
11use rustc_hir::{
12    self as hir, Arm, Block, Expr, ExprKind, HirId, LetStmt, LocalSource, LoopSource, MatchSource, Node, Pat, QPath,
13    StructTailExpr,
14};
15use rustc_lint::LateContext;
16use rustc_span::{Span, symbol};
17
18/// The essential nodes of a desugared for loop as well as the entire span:
19/// `for pat in arg { body }` becomes `(pat, arg, body)`. Returns `(pat, arg, body, span)`.
20#[derive(Debug)]
21pub struct ForLoop<'tcx> {
22    /// `for` loop item
23    pub pat: &'tcx Pat<'tcx>,
24    /// `IntoIterator` argument
25    pub arg: &'tcx Expr<'tcx>,
26    /// `for` loop body
27    pub body: &'tcx Expr<'tcx>,
28    /// Compare this against `hir::Destination.target`
29    pub loop_id: HirId,
30    /// entire `for` loop span
31    pub span: Span,
32    /// label
33    pub label: Option<ast::Label>,
34}
35
36impl<'tcx> ForLoop<'tcx> {
37    /// Parses a desugared `for` loop
38    pub fn hir(expr: &Expr<'tcx>) -> Option<Self> {
39        if let ExprKind::DropTemps(e) = expr.kind
40            && let ExprKind::Match(iterexpr, [arm], MatchSource::ForLoopDesugar) = e.kind
41            && let ExprKind::Call(_, [arg]) = iterexpr.kind
42            && let ExprKind::Loop(block, label, ..) = arm.body.kind
43            && let [stmt] = block.stmts
44            && let hir::StmtKind::Expr(e) = stmt.kind
45            && let ExprKind::Match(_, [_, some_arm], _) = e.kind
46            && let hir::PatKind::Struct(_, [field], _) = some_arm.pat.kind
47        {
48            return Some(Self {
49                pat: field.pat,
50                arg,
51                body: some_arm.body,
52                loop_id: arm.body.hir_id,
53                span: expr.span.ctxt().outer_expn_data().call_site,
54                label,
55            });
56        }
57        None
58    }
59}
60
61/// An `if` expression without `let`
62pub struct If<'hir> {
63    /// `if` condition
64    pub cond: &'hir Expr<'hir>,
65    /// `if` then expression
66    pub then: &'hir Expr<'hir>,
67    /// `else` expression
68    pub r#else: Option<&'hir Expr<'hir>>,
69}
70
71impl<'hir> If<'hir> {
72    #[inline]
73    /// Parses an `if` expression without `let`
74    pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
75        if let ExprKind::If(cond, then, r#else) = expr.kind
76            && !has_let_expr(cond)
77        {
78            Some(Self { cond, then, r#else })
79        } else {
80            None
81        }
82    }
83}
84
85/// An `if let` expression
86pub struct IfLet<'hir> {
87    /// `if let` pattern
88    pub let_pat: &'hir Pat<'hir>,
89    /// `if let` scrutinee
90    pub let_expr: &'hir Expr<'hir>,
91    /// `if let` then expression
92    pub if_then: &'hir Expr<'hir>,
93    /// `if let` else expression
94    pub if_else: Option<&'hir Expr<'hir>>,
95    /// `if let PAT = EXPR`
96    ///     ^^^^^^^^^^^^^^
97    pub let_span: Span,
98}
99
100impl<'hir> IfLet<'hir> {
101    /// Parses an `if let` expression
102    pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
103        if let ExprKind::If(
104            &Expr {
105                kind:
106                    ExprKind::Let(&hir::LetExpr {
107                        pat: let_pat,
108                        init: let_expr,
109                        span: let_span,
110                        ..
111                    }),
112                ..
113            },
114            if_then,
115            if_else,
116        ) = expr.kind
117        {
118            let mut iter = cx.tcx.hir_parent_iter(expr.hir_id);
119            if let Some((_, Node::Block(Block { stmts: [], .. }))) = iter.next()
120                && let Some((
121                    _,
122                    Node::Expr(Expr {
123                        kind: ExprKind::Loop(_, _, LoopSource::While, _),
124                        ..
125                    }),
126                )) = iter.next()
127            {
128                // while loop desugar
129                return None;
130            }
131            return Some(Self {
132                let_pat,
133                let_expr,
134                if_then,
135                if_else,
136                let_span,
137            });
138        }
139        None
140    }
141}
142
143/// An `if let` or `match` expression. Useful for lints that trigger on one or the other.
144#[derive(Debug)]
145pub enum IfLetOrMatch<'hir> {
146    /// Any `match` expression
147    Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource),
148    /// scrutinee, pattern, then block, else block
149    IfLet(
150        &'hir Expr<'hir>,
151        &'hir Pat<'hir>,
152        &'hir Expr<'hir>,
153        Option<&'hir Expr<'hir>>,
154        /// `if let PAT = EXPR`
155        ///     ^^^^^^^^^^^^^^
156        Span,
157    ),
158}
159
160impl<'hir> IfLetOrMatch<'hir> {
161    /// Parses an `if let` or `match` expression
162    pub fn parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
163        match expr.kind {
164            ExprKind::Match(expr, arms, source) => Some(Self::Match(expr, arms, source)),
165            _ => IfLet::hir(cx, expr).map(
166                |IfLet {
167                     let_expr,
168                     let_pat,
169                     if_then,
170                     if_else,
171                     let_span,
172                 }| { Self::IfLet(let_expr, let_pat, if_then, if_else, let_span) },
173            ),
174        }
175    }
176
177    /// Returns the [expression](Expr) scrutinized by this `if let` or `match` expression.
178    pub fn scrutinee(&self) -> &'hir Expr<'hir> {
179        match self {
180            Self::Match(scrutinee, _, _) | Self::IfLet(scrutinee, _, _, _, _) => scrutinee,
181        }
182    }
183}
184
185/// An `if` or `if let` expression
186pub struct IfOrIfLet<'hir> {
187    /// `if` condition that is maybe a `let` expression
188    pub cond: &'hir Expr<'hir>,
189    /// `if` then expression
190    pub then: &'hir Expr<'hir>,
191    /// `else` expression
192    pub r#else: Option<&'hir Expr<'hir>>,
193}
194
195impl<'hir> IfOrIfLet<'hir> {
196    #[inline]
197    /// Parses an `if` or `if let` expression
198    pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
199        if let ExprKind::If(cond, then, r#else) = expr.kind {
200            Some(Self { cond, then, r#else })
201        } else {
202            None
203        }
204    }
205}
206
207/// Represent a range akin to `ast::ExprKind::Range`.
208#[derive(Debug, Copy, Clone)]
209pub struct Range<'a> {
210    /// Type of the range, as an enum of only range types.
211    pub ty: RangeTy,
212    /// The lower bound of the range, or `None` for ranges such as `..X`.
213    pub start: Option<&'a Expr<'a>>,
214    /// The upper bound of the range, or `None` for ranges such as `X..`.
215    pub end: Option<&'a Expr<'a>>,
216    /// The span of the range.
217    pub span: Span,
218}
219
220impl<'a> Range<'a> {
221    /// Higher a `hir` range to something similar to `ast::ExprKind::Range`.
222    pub fn hir(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<Range<'a>> {
223        let span = expr.range_span()?;
224        let (ty, start, end) = match expr.kind {
225            ExprKind::Call(path, [arg1, arg2])
226                if let ExprKind::Path(qpath) = path.kind
227                    && cx.tcx.qpath_is_lang_item(qpath, LangItem::RangeInclusiveNew) =>
228            {
229                (RangeTy::OpsInclusive, Some(arg1), Some(arg2))
230            },
231            ExprKind::Struct(&qpath, fields, StructTailExpr::None) => match (cx.tcx.qpath_lang_item(qpath)?, fields) {
232                (LangItem::RangeFull, []) => (RangeTy::OpsFull, None, None),
233                (LangItem::RangeFrom, [start]) if start.ident.name == sym::start => {
234                    (RangeTy::OpsFrom, Some(start.expr), None)
235                },
236                (LangItem::RangeFromCopy, [start]) if start.ident.name == sym::start => {
237                    (RangeTy::RangeFrom, Some(start.expr), None)
238                },
239                (LangItem::Range, [start, end] | [end, start])
240                    if start.ident.name == sym::start && end.ident.name == sym::end =>
241                {
242                    (RangeTy::OpsRange, Some(start.expr), Some(end.expr))
243                },
244                (LangItem::RangeCopy, [start, end] | [end, start])
245                    if start.ident.name == sym::start && end.ident.name == sym::end =>
246                {
247                    (RangeTy::RangeRange, Some(start.expr), Some(end.expr))
248                },
249                (LangItem::RangeInclusiveCopy, [start, last] | [last, start])
250                    if start.ident.name == sym::start && last.ident.name == sym::last =>
251                {
252                    (RangeTy::RangeInclusive, Some(start.expr), Some(last.expr))
253                },
254                (LangItem::RangeToInclusive, [end]) if end.ident.name == sym::end => {
255                    (RangeTy::OpsToInclusive, None, Some(end.expr))
256                },
257                (LangItem::RangeToInclusiveCopy, [last]) if last.ident.name == sym::last => {
258                    (RangeTy::RangeToInclusive, None, Some(last.expr))
259                },
260                (LangItem::RangeTo, [end]) if end.ident.name == sym::end => (RangeTy::OpsTo, None, Some(end.expr)),
261                _ => return None,
262            },
263            _ => return None,
264        };
265
266        Some(Range { ty, start, end, span })
267    }
268}
269
270/// A type that can appear as the type of a range expression.
271///
272/// This is a component of [`Range`].
273#[derive(Debug, Copy, Clone, Eq, PartialEq)]
274pub enum RangeTy {
275    /// [`core::ops::RangeFrom`]
276    OpsFrom,
277    /// [`core::range::RangeFrom`]
278    RangeFrom,
279
280    /// [`core::ops::RangeFull`]
281    OpsFull,
282
283    /// [`core::ops::Range`]
284    OpsRange,
285    /// [`core::range::Range`]
286    RangeRange,
287
288    /// [`core::ops::RangeInclusive`]
289    OpsInclusive,
290    /// [`core::range::RangeInclusive`]
291    RangeInclusive,
292
293    /// [`core::ops::RangeTo`]
294    OpsTo,
295
296    /// [`core::ops::RangeToInclusive`]
297    OpsToInclusive,
298    /// [`core::range::RangeToInclusive`]
299    RangeToInclusive,
300}
301
302#[expect(clippy::match_same_arms, reason = "regularity over density")]
303impl RangeTy {
304    /// Returns whether this type implements [`IntoIterator`] — that is, whether it is iterable —
305    /// presuming that its element type implements the `Step` trait.
306    pub fn implements_into_iterator(self) -> bool {
307        match self {
308            RangeTy::OpsFrom => true,
309            RangeTy::RangeFrom => true,
310            RangeTy::OpsRange => true,
311            RangeTy::RangeRange => true,
312            RangeTy::OpsInclusive => true,
313            RangeTy::RangeInclusive => true,
314
315            RangeTy::OpsFull => false,
316            RangeTy::OpsTo => false,
317            RangeTy::OpsToInclusive => false,
318            RangeTy::RangeToInclusive => false,
319        }
320    }
321
322    /// Returns whether this type implements [`Iterator`] directly, and [`IntoIterator`] via blanket
323    /// impl, presuming that its element type implements the `Step` trait.
324    pub fn implements_iterator(self) -> bool {
325        match self {
326            RangeTy::OpsFrom => true,
327            RangeTy::OpsRange => true,
328            RangeTy::OpsInclusive => true,
329
330            // New range types don’t implement Iterator, only IntoIterator
331            RangeTy::RangeFrom => false,
332            RangeTy::RangeRange => false,
333            RangeTy::RangeInclusive => false,
334
335            // Non-iterables
336            RangeTy::OpsFull => false,
337            RangeTy::OpsTo => false,
338            RangeTy::OpsToInclusive => false,
339            RangeTy::RangeToInclusive => false,
340        }
341    }
342
343    /// Converts the current [range type](RangeTy) into its equivalent [`ast::RangeLimits`].
344    pub fn limits(self) -> ast::RangeLimits {
345        match self {
346            RangeTy::RangeFrom => ast::RangeLimits::HalfOpen,
347            RangeTy::OpsRange => ast::RangeLimits::HalfOpen,
348            RangeTy::RangeRange => ast::RangeLimits::HalfOpen,
349
350            RangeTy::OpsFrom => ast::RangeLimits::HalfOpen,
351            RangeTy::OpsTo => ast::RangeLimits::HalfOpen,
352            RangeTy::OpsFull => ast::RangeLimits::HalfOpen,
353
354            RangeTy::OpsInclusive => ast::RangeLimits::Closed,
355            RangeTy::RangeInclusive => ast::RangeLimits::Closed,
356            RangeTy::OpsToInclusive => ast::RangeLimits::Closed,
357            RangeTy::RangeToInclusive => ast::RangeLimits::Closed,
358        }
359    }
360}
361
362/// Represents the pre-expansion arguments of a `vec!` invocation.
363pub enum VecArgs<'a> {
364    /// `vec![elem; len]`
365    Repeat(&'a Expr<'a>, &'a Expr<'a>),
366    /// `vec![a, b, c]`
367    Vec(&'a [Expr<'a>]),
368}
369
370impl<'a> VecArgs<'a> {
371    /// Returns the arguments of the `vec!` macro if this expression was expanded
372    /// from `vec!`.
373    pub fn hir(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<VecArgs<'a>> {
374        if let ExprKind::Call(fun, args) = expr.kind
375            && let ExprKind::Path(ref qpath) = fun.kind
376            && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
377            && let Some(name) = cx.tcx.get_diagnostic_name(fun_def_id)
378            && matches!(
379                name,
380                sym::vec_from_elem | sym::box_assume_init_into_vec_unsafe | sym::vec_new
381            )
382            // Do the cheap checks first, since `is_expn_of` walks the whole expansion chain.
383            && is_expn_of(fun.span, sym::vec).is_some()
384        {
385            return match (name, args) {
386                (sym::vec_from_elem, [elem, size]) => {
387                    // `vec![elem; size]` case
388                    Some(VecArgs::Repeat(elem, size))
389                },
390                (sym::box_assume_init_into_vec_unsafe, [write_box_via_move])
391                    if let ExprKind::Call(_, [_box, elems]) = write_box_via_move.kind
392                        && let ExprKind::Array(elems) = elems.kind =>
393                {
394                    // `vec![a, b, c]` case
395                    Some(VecArgs::Vec(elems))
396                },
397                (sym::vec_new, []) => Some(VecArgs::Vec(&[])),
398                _ => None,
399            };
400        }
401
402        None
403    }
404}
405
406/// A desugared `while` loop
407pub struct While<'hir> {
408    /// `while` loop condition
409    pub condition: &'hir Expr<'hir>,
410    /// `while` loop body
411    pub body: &'hir Expr<'hir>,
412    /// Span of the loop header
413    pub span: Span,
414    /// The loop's label, if present
415    pub label: Option<ast::Label>,
416}
417
418impl<'hir> While<'hir> {
419    #[inline]
420    /// Parses a desugared `while` loop
421    pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
422        if let ExprKind::Loop(
423            Block {
424                expr:
425                    Some(Expr {
426                        kind: ExprKind::If(condition, body, _),
427                        ..
428                    }),
429                ..
430            },
431            label,
432            LoopSource::While,
433            span,
434        ) = expr.kind
435            && !has_let_expr(condition)
436        {
437            return Some(Self {
438                condition,
439                body,
440                span,
441                label,
442            });
443        }
444        None
445    }
446}
447
448/// A desugared `while let` loop
449pub struct WhileLet<'hir> {
450    /// `while let` loop item pattern
451    pub let_pat: &'hir Pat<'hir>,
452    /// `while let` loop scrutinee
453    pub let_expr: &'hir Expr<'hir>,
454    /// `while let` loop body
455    pub if_then: &'hir Expr<'hir>,
456    /// The loop's label, if present
457    pub label: Option<ast::Label>,
458    /// `while let PAT = EXPR`
459    ///        ^^^^^^^^^^^^^^
460    pub let_span: Span,
461}
462
463impl<'hir> WhileLet<'hir> {
464    #[inline]
465    /// Parses a desugared `while let` loop
466    pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
467        if let ExprKind::Loop(
468            &Block {
469                expr:
470                    Some(&Expr {
471                        kind:
472                            ExprKind::If(
473                                &Expr {
474                                    kind:
475                                        ExprKind::Let(&hir::LetExpr {
476                                            pat: let_pat,
477                                            init: let_expr,
478                                            span: let_span,
479                                            ..
480                                        }),
481                                    ..
482                                },
483                                if_then,
484                                _,
485                            ),
486                        ..
487                    }),
488                ..
489            },
490            label,
491            LoopSource::While,
492            _,
493        ) = expr.kind
494        {
495            return Some(Self {
496                let_pat,
497                let_expr,
498                if_then,
499                label,
500                let_span,
501            });
502        }
503        None
504    }
505}
506
507/// A desugared compound assignment statement, such as in
508/// `(a, b) = expr`.
509pub struct CompoundAssignment<'hir> {
510    /// The individual assignees
511    pub assignees: Vec<&'hir Expr<'hir>>,
512    /// The initializatiojn expression
513    pub init: &'hir Expr<'hir>,
514}
515
516impl<'hir> CompoundAssignment<'hir> {
517    /// Check if `expr` is a block which is an expansion of a compound assignment.
518    #[inline]
519    pub fn hir(expr: &'hir Expr<'_>) -> Option<Self> {
520        // A compound assignment is unsugared into a block which first assigns the RHS subexpressions to
521        // temporaries, then moves those temporaries to the assignment targets. By doing it this
522        // way, and since the moves cannot fail, the compound assignment either succeeds or not take
523        // place at all if, for example, one of the RHS subcomponent diverges.
524        if let ExprKind::Block(
525            Block {
526                stmts: [assign, rest @ ..],
527                expr: None,
528                ..
529            },
530            None,
531        ) = expr.kind
532            && let hir::StmtKind::Let(LetStmt {
533                init: Some(init),
534                source: LocalSource::AssignDesugar,
535                ..
536            }) = assign.kind
537        {
538            let mut assignees = Vec::with_capacity(rest.len());
539            for stmt in rest {
540                if let hir::StmtKind::Expr(expr) = stmt.kind
541                    && let ExprKind::Assign(target, _, _) = expr.kind
542                {
543                    assignees.push(target);
544                } else {
545                    return None;
546                }
547            }
548            Some(CompoundAssignment { assignees, init })
549        } else {
550            None
551        }
552    }
553}
554
555/// Converts a `hir` binary operator to the corresponding `ast` type.
556#[must_use]
557pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
558    match op {
559        hir::BinOpKind::Eq => ast::BinOpKind::Eq,
560        hir::BinOpKind::Ge => ast::BinOpKind::Ge,
561        hir::BinOpKind::Gt => ast::BinOpKind::Gt,
562        hir::BinOpKind::Le => ast::BinOpKind::Le,
563        hir::BinOpKind::Lt => ast::BinOpKind::Lt,
564        hir::BinOpKind::Ne => ast::BinOpKind::Ne,
565        hir::BinOpKind::Or => ast::BinOpKind::Or,
566        hir::BinOpKind::Add => ast::BinOpKind::Add,
567        hir::BinOpKind::And => ast::BinOpKind::And,
568        hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
569        hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
570        hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
571        hir::BinOpKind::Div => ast::BinOpKind::Div,
572        hir::BinOpKind::Mul => ast::BinOpKind::Mul,
573        hir::BinOpKind::Rem => ast::BinOpKind::Rem,
574        hir::BinOpKind::Shl => ast::BinOpKind::Shl,
575        hir::BinOpKind::Shr => ast::BinOpKind::Shr,
576        hir::BinOpKind::Sub => ast::BinOpKind::Sub,
577    }
578}
579
580/// A parsed `Vec` initialization expression
581#[derive(Clone, Copy)]
582pub enum VecInitKind {
583    /// `Vec::new()`
584    New,
585    /// `Vec::default()` or `Default::default()`
586    Default,
587    /// `Vec::with_capacity(123)`
588    WithConstCapacity(u128),
589    /// `Vec::with_capacity(slice.len())`
590    WithExprCapacity(HirId),
591}
592
593/// Checks if the given expression is an initialization of `Vec` and returns its kind.
594pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
595    if let ExprKind::Call(func, args) = expr.kind {
596        match func.kind {
597            ExprKind::Path(QPath::TypeRelative(ty, name))
598                if cx.typeck_results().node_type(ty.hir_id).is_diag_item(cx, sym::Vec) =>
599            {
600                if name.ident.name == sym::new {
601                    return Some(VecInitKind::New);
602                } else if name.ident.name == symbol::kw::Default {
603                    return Some(VecInitKind::Default);
604                } else if name.ident.name == sym::with_capacity {
605                    let arg = args.first()?;
606                    return match ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt()) {
607                        Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
608                        _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
609                    };
610                }
611            },
612            ExprKind::Path(QPath::Resolved(_, path))
613                if cx.tcx.is_diagnostic_item(sym::default_fn, path.res.opt_def_id()?)
614                    && cx.typeck_results().expr_ty(expr).is_diag_item(cx, sym::Vec) =>
615            {
616                return Some(VecInitKind::Default);
617            },
618            _ => (),
619        }
620    }
621    None
622}
623
624/// Checks that a condition doesn't have a `let` expression, to keep `If` and `While` from accepting
625/// `if let` and `while let`.
626pub const fn has_let_expr<'tcx>(cond: &'tcx Expr<'tcx>) -> bool {
627    match &cond.kind {
628        ExprKind::Let(_) => true,
629        ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
630        _ => false,
631    }
632}