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_hir::attrs::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    pub fn scrutinee(&self) -> &'hir Expr<'hir> {
178        match self {
179            Self::Match(scrutinee, _, _) | Self::IfLet(scrutinee, _, _, _, _) => scrutinee,
180        }
181    }
182}
183
184/// An `if` or `if let` expression
185pub struct IfOrIfLet<'hir> {
186    /// `if` condition that is maybe a `let` expression
187    pub cond: &'hir Expr<'hir>,
188    /// `if` then expression
189    pub then: &'hir Expr<'hir>,
190    /// `else` expression
191    pub r#else: Option<&'hir Expr<'hir>>,
192}
193
194impl<'hir> IfOrIfLet<'hir> {
195    #[inline]
196    /// Parses an `if` or `if let` expression
197    pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
198        if let ExprKind::If(cond, then, r#else) = expr.kind {
199            Some(Self { cond, then, r#else })
200        } else {
201            None
202        }
203    }
204}
205
206/// Represent a range akin to `ast::ExprKind::Range`.
207#[derive(Debug, Copy, Clone)]
208pub struct Range<'a> {
209    /// Type of the range, as an enum of only range types.
210    pub ty: RangeTy,
211    /// The lower bound of the range, or `None` for ranges such as `..X`.
212    pub start: Option<&'a Expr<'a>>,
213    /// The upper bound of the range, or `None` for ranges such as `X..`.
214    pub end: Option<&'a Expr<'a>>,
215    pub span: Span,
216}
217
218impl<'a> Range<'a> {
219    /// Higher a `hir` range to something similar to `ast::ExprKind::Range`.
220    pub fn hir(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<Range<'a>> {
221        let span = expr.range_span()?;
222        let (ty, start, end) = match expr.kind {
223            ExprKind::Call(path, [arg1, arg2])
224                if let ExprKind::Path(qpath) = path.kind
225                    && cx.tcx.qpath_is_lang_item(qpath, LangItem::RangeInclusiveNew) =>
226            {
227                (RangeTy::OpsInclusive, Some(arg1), Some(arg2))
228            },
229            ExprKind::Struct(&qpath, fields, StructTailExpr::None) => match (cx.tcx.qpath_lang_item(qpath)?, fields) {
230                (LangItem::RangeFull, []) => (RangeTy::OpsFull, None, None),
231                (LangItem::RangeFrom, [start]) if start.ident.name == sym::start => {
232                    (RangeTy::OpsFrom, Some(start.expr), None)
233                },
234                (LangItem::RangeFromCopy, [start]) if start.ident.name == sym::start => {
235                    (RangeTy::RangeFrom, Some(start.expr), None)
236                },
237                (LangItem::Range, [start, end] | [end, start])
238                    if start.ident.name == sym::start && end.ident.name == sym::end =>
239                {
240                    (RangeTy::OpsRange, Some(start.expr), Some(end.expr))
241                },
242                (LangItem::RangeCopy, [start, end] | [end, start])
243                    if start.ident.name == sym::start && end.ident.name == sym::end =>
244                {
245                    (RangeTy::RangeRange, Some(start.expr), Some(end.expr))
246                },
247                (LangItem::RangeInclusiveCopy, [start, last] | [last, start])
248                    if start.ident.name == sym::start && last.ident.name == sym::last =>
249                {
250                    (RangeTy::RangeInclusive, Some(start.expr), Some(last.expr))
251                },
252                (LangItem::RangeToInclusive, [end]) if end.ident.name == sym::end => {
253                    (RangeTy::OpsToInclusive, None, Some(end.expr))
254                },
255                (LangItem::RangeToInclusiveCopy, [last]) if last.ident.name == sym::last => {
256                    (RangeTy::RangeToInclusive, None, Some(last.expr))
257                },
258                (LangItem::RangeTo, [end]) if end.ident.name == sym::end => (RangeTy::OpsTo, None, Some(end.expr)),
259                _ => return None,
260            },
261            _ => return None,
262        };
263
264        Some(Range { ty, start, end, span })
265    }
266}
267
268/// A type that can appear as the type of a range expression.
269///
270/// This is a component of [`Range`].
271#[derive(Debug, Copy, Clone, Eq, PartialEq)]
272pub enum RangeTy {
273    /// [`core::ops::RangeFrom`]
274    OpsFrom,
275    /// [`core::range::RangeFrom`]
276    RangeFrom,
277
278    /// [`core::ops::RangeFull`]
279    OpsFull,
280
281    /// [`core::ops::Range`]
282    OpsRange,
283    /// [`core::range::Range`]
284    RangeRange,
285
286    /// [`core::ops::RangeInclusive`]
287    OpsInclusive,
288    /// [`core::range::RangeInclusive`]
289    RangeInclusive,
290
291    /// [`core::ops::RangeTo`]
292    OpsTo,
293
294    /// [`core::ops::RangeToInclusive`]
295    OpsToInclusive,
296    /// [`core::range::RangeToInclusive`]
297    RangeToInclusive,
298}
299
300#[expect(clippy::match_same_arms, reason = "regularity over density")]
301impl RangeTy {
302    /// Returns whether this type implements [`IntoIterator`] — that is, whether it is iterable —
303    /// presuming that its element type implements the `Step` trait.
304    pub fn implements_into_iterator(self) -> bool {
305        match self {
306            RangeTy::OpsFrom => true,
307            RangeTy::RangeFrom => true,
308            RangeTy::OpsRange => true,
309            RangeTy::RangeRange => true,
310            RangeTy::OpsInclusive => true,
311            RangeTy::RangeInclusive => true,
312
313            RangeTy::OpsFull => false,
314            RangeTy::OpsTo => false,
315            RangeTy::OpsToInclusive => false,
316            RangeTy::RangeToInclusive => false,
317        }
318    }
319
320    /// Returns whether this type implements [`Iterator`] directly, and [`IntoIterator`] via blanket
321    /// impl, presuming that its element type implements the `Step` trait.
322    pub fn implements_iterator(self) -> bool {
323        match self {
324            RangeTy::OpsFrom => true,
325            RangeTy::OpsRange => true,
326            RangeTy::OpsInclusive => true,
327
328            // New range types don’t implement Iterator, only IntoIterator
329            RangeTy::RangeFrom => false,
330            RangeTy::RangeRange => false,
331            RangeTy::RangeInclusive => false,
332
333            // Non-iterables
334            RangeTy::OpsFull => false,
335            RangeTy::OpsTo => false,
336            RangeTy::OpsToInclusive => false,
337            RangeTy::RangeToInclusive => false,
338        }
339    }
340
341    pub fn limits(self) -> ast::RangeLimits {
342        match self {
343            RangeTy::RangeFrom => ast::RangeLimits::HalfOpen,
344            RangeTy::OpsRange => ast::RangeLimits::HalfOpen,
345            RangeTy::RangeRange => ast::RangeLimits::HalfOpen,
346
347            RangeTy::OpsFrom => ast::RangeLimits::HalfOpen,
348            RangeTy::OpsTo => ast::RangeLimits::HalfOpen,
349            RangeTy::OpsFull => ast::RangeLimits::HalfOpen,
350
351            RangeTy::OpsInclusive => ast::RangeLimits::Closed,
352            RangeTy::RangeInclusive => ast::RangeLimits::Closed,
353            RangeTy::OpsToInclusive => ast::RangeLimits::Closed,
354            RangeTy::RangeToInclusive => ast::RangeLimits::Closed,
355        }
356    }
357}
358
359/// Represents the pre-expansion arguments of a `vec!` invocation.
360pub enum VecArgs<'a> {
361    /// `vec![elem; len]`
362    Repeat(&'a Expr<'a>, &'a Expr<'a>),
363    /// `vec![a, b, c]`
364    Vec(&'a [Expr<'a>]),
365}
366
367impl<'a> VecArgs<'a> {
368    /// Returns the arguments of the `vec!` macro if this expression was expanded
369    /// from `vec!`.
370    pub fn hir(cx: &LateContext<'_>, expr: &'a Expr<'_>) -> Option<VecArgs<'a>> {
371        if let ExprKind::Call(fun, args) = expr.kind
372            && let ExprKind::Path(ref qpath) = fun.kind
373            && is_expn_of(fun.span, sym::vec).is_some()
374            && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
375            && let Some(name) = cx.tcx.get_diagnostic_name(fun_def_id)
376        {
377            return match (name, args) {
378                (sym::vec_from_elem, [elem, size]) => {
379                    // `vec![elem; size]` case
380                    Some(VecArgs::Repeat(elem, size))
381                },
382                (sym::box_assume_init_into_vec_unsafe, [write_box_via_move])
383                    if let ExprKind::Call(_, [_box, elems]) = write_box_via_move.kind
384                        && let ExprKind::Array(elems) = elems.kind =>
385                {
386                    // `vec![a, b, c]` case
387                    Some(VecArgs::Vec(elems))
388                },
389                (sym::vec_new, []) => Some(VecArgs::Vec(&[])),
390                _ => None,
391            };
392        }
393
394        None
395    }
396}
397
398/// A desugared `while` loop
399pub struct While<'hir> {
400    /// `while` loop condition
401    pub condition: &'hir Expr<'hir>,
402    /// `while` loop body
403    pub body: &'hir Expr<'hir>,
404    /// Span of the loop header
405    pub span: Span,
406    pub label: Option<ast::Label>,
407}
408
409impl<'hir> While<'hir> {
410    #[inline]
411    /// Parses a desugared `while` loop
412    pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
413        if let ExprKind::Loop(
414            Block {
415                expr:
416                    Some(Expr {
417                        kind: ExprKind::If(condition, body, _),
418                        ..
419                    }),
420                ..
421            },
422            label,
423            LoopSource::While,
424            span,
425        ) = expr.kind
426            && !has_let_expr(condition)
427        {
428            return Some(Self {
429                condition,
430                body,
431                span,
432                label,
433            });
434        }
435        None
436    }
437}
438
439/// A desugared `while let` loop
440pub struct WhileLet<'hir> {
441    /// `while let` loop item pattern
442    pub let_pat: &'hir Pat<'hir>,
443    /// `while let` loop scrutinee
444    pub let_expr: &'hir Expr<'hir>,
445    /// `while let` loop body
446    pub if_then: &'hir Expr<'hir>,
447    pub label: Option<ast::Label>,
448    /// `while let PAT = EXPR`
449    ///        ^^^^^^^^^^^^^^
450    pub let_span: Span,
451}
452
453impl<'hir> WhileLet<'hir> {
454    #[inline]
455    /// Parses a desugared `while let` loop
456    pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
457        if let ExprKind::Loop(
458            &Block {
459                expr:
460                    Some(&Expr {
461                        kind:
462                            ExprKind::If(
463                                &Expr {
464                                    kind:
465                                        ExprKind::Let(&hir::LetExpr {
466                                            pat: let_pat,
467                                            init: let_expr,
468                                            span: let_span,
469                                            ..
470                                        }),
471                                    ..
472                                },
473                                if_then,
474                                _,
475                            ),
476                        ..
477                    }),
478                ..
479            },
480            label,
481            LoopSource::While,
482            _,
483        ) = expr.kind
484        {
485            return Some(Self {
486                let_pat,
487                let_expr,
488                if_then,
489                label,
490                let_span,
491            });
492        }
493        None
494    }
495}
496
497/// A desugared compound assignment statement, such as in
498/// `(a, b) = expr`.
499pub struct CompoundAssignment<'hir> {
500    /// The individual assignees
501    pub assignees: Vec<&'hir Expr<'hir>>,
502    /// The initializatiojn expression
503    pub init: &'hir Expr<'hir>,
504}
505
506impl<'hir> CompoundAssignment<'hir> {
507    /// Check if `expr` is a block which is an expansion of a compound assignment.
508    #[inline]
509    pub fn hir(expr: &'hir Expr<'_>) -> Option<Self> {
510        // A compound assignment is unsugared into a block which first assigns the RHS subexpressions to
511        // temporaries, then moves those temporaries to the assignment targets. By doing it this
512        // way, and since the moves cannot fail, the compound assignment either succeeds or not take
513        // place at all if, for example, one of the RHS subcomponent diverges.
514        if let ExprKind::Block(
515            Block {
516                stmts: [assign, rest @ ..],
517                expr: None,
518                ..
519            },
520            None,
521        ) = expr.kind
522            && let hir::StmtKind::Let(LetStmt {
523                init: Some(init),
524                source: LocalSource::AssignDesugar,
525                ..
526            }) = assign.kind
527        {
528            let mut assignees = Vec::with_capacity(rest.len());
529            for stmt in rest {
530                if let hir::StmtKind::Expr(expr) = stmt.kind
531                    && let ExprKind::Assign(target, _, _) = expr.kind
532                {
533                    assignees.push(target);
534                } else {
535                    return None;
536                }
537            }
538            Some(CompoundAssignment { assignees, init })
539        } else {
540            None
541        }
542    }
543}
544
545/// Converts a `hir` binary operator to the corresponding `ast` type.
546#[must_use]
547pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
548    match op {
549        hir::BinOpKind::Eq => ast::BinOpKind::Eq,
550        hir::BinOpKind::Ge => ast::BinOpKind::Ge,
551        hir::BinOpKind::Gt => ast::BinOpKind::Gt,
552        hir::BinOpKind::Le => ast::BinOpKind::Le,
553        hir::BinOpKind::Lt => ast::BinOpKind::Lt,
554        hir::BinOpKind::Ne => ast::BinOpKind::Ne,
555        hir::BinOpKind::Or => ast::BinOpKind::Or,
556        hir::BinOpKind::Add => ast::BinOpKind::Add,
557        hir::BinOpKind::And => ast::BinOpKind::And,
558        hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
559        hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
560        hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
561        hir::BinOpKind::Div => ast::BinOpKind::Div,
562        hir::BinOpKind::Mul => ast::BinOpKind::Mul,
563        hir::BinOpKind::Rem => ast::BinOpKind::Rem,
564        hir::BinOpKind::Shl => ast::BinOpKind::Shl,
565        hir::BinOpKind::Shr => ast::BinOpKind::Shr,
566        hir::BinOpKind::Sub => ast::BinOpKind::Sub,
567    }
568}
569
570/// A parsed `Vec` initialization expression
571#[derive(Clone, Copy)]
572pub enum VecInitKind {
573    /// `Vec::new()`
574    New,
575    /// `Vec::default()` or `Default::default()`
576    Default,
577    /// `Vec::with_capacity(123)`
578    WithConstCapacity(u128),
579    /// `Vec::with_capacity(slice.len())`
580    WithExprCapacity(HirId),
581}
582
583/// Checks if the given expression is an initialization of `Vec` and returns its kind.
584pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
585    if let ExprKind::Call(func, args) = expr.kind {
586        match func.kind {
587            ExprKind::Path(QPath::TypeRelative(ty, name))
588                if cx.typeck_results().node_type(ty.hir_id).is_diag_item(cx, sym::Vec) =>
589            {
590                if name.ident.name == sym::new {
591                    return Some(VecInitKind::New);
592                } else if name.ident.name == symbol::kw::Default {
593                    return Some(VecInitKind::Default);
594                } else if name.ident.name == sym::with_capacity {
595                    let arg = args.first()?;
596                    return match ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt()) {
597                        Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
598                        _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
599                    };
600                }
601            },
602            ExprKind::Path(QPath::Resolved(_, path))
603                if cx.tcx.is_diagnostic_item(sym::default_fn, path.res.opt_def_id()?)
604                    && cx.typeck_results().expr_ty(expr).is_diag_item(cx, sym::Vec) =>
605            {
606                return Some(VecInitKind::Default);
607            },
608            _ => (),
609        }
610    }
611    None
612}
613
614/// Checks that a condition doesn't have a `let` expression, to keep `If` and `While` from accepting
615/// `if let` and `while let`.
616pub const fn has_let_expr<'tcx>(cond: &'tcx Expr<'tcx>) -> bool {
617    match &cond.kind {
618        ExprKind::Let(_) => true,
619        ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
620        _ => false,
621    }
622}