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            && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id()
374            && let Some(name) = cx.tcx.get_diagnostic_name(fun_def_id)
375            && matches!(
376                name,
377                sym::vec_from_elem | sym::box_assume_init_into_vec_unsafe | sym::vec_new
378            )
379            // Do the cheap checks first, since `is_expn_of` walks the whole expansion chain.
380            && is_expn_of(fun.span, sym::vec).is_some()
381        {
382            return match (name, args) {
383                (sym::vec_from_elem, [elem, size]) => {
384                    // `vec![elem; size]` case
385                    Some(VecArgs::Repeat(elem, size))
386                },
387                (sym::box_assume_init_into_vec_unsafe, [write_box_via_move])
388                    if let ExprKind::Call(_, [_box, elems]) = write_box_via_move.kind
389                        && let ExprKind::Array(elems) = elems.kind =>
390                {
391                    // `vec![a, b, c]` case
392                    Some(VecArgs::Vec(elems))
393                },
394                (sym::vec_new, []) => Some(VecArgs::Vec(&[])),
395                _ => None,
396            };
397        }
398
399        None
400    }
401}
402
403/// A desugared `while` loop
404pub struct While<'hir> {
405    /// `while` loop condition
406    pub condition: &'hir Expr<'hir>,
407    /// `while` loop body
408    pub body: &'hir Expr<'hir>,
409    /// Span of the loop header
410    pub span: Span,
411    pub label: Option<ast::Label>,
412}
413
414impl<'hir> While<'hir> {
415    #[inline]
416    /// Parses a desugared `while` loop
417    pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
418        if let ExprKind::Loop(
419            Block {
420                expr:
421                    Some(Expr {
422                        kind: ExprKind::If(condition, body, _),
423                        ..
424                    }),
425                ..
426            },
427            label,
428            LoopSource::While,
429            span,
430        ) = expr.kind
431            && !has_let_expr(condition)
432        {
433            return Some(Self {
434                condition,
435                body,
436                span,
437                label,
438            });
439        }
440        None
441    }
442}
443
444/// A desugared `while let` loop
445pub struct WhileLet<'hir> {
446    /// `while let` loop item pattern
447    pub let_pat: &'hir Pat<'hir>,
448    /// `while let` loop scrutinee
449    pub let_expr: &'hir Expr<'hir>,
450    /// `while let` loop body
451    pub if_then: &'hir Expr<'hir>,
452    pub label: Option<ast::Label>,
453    /// `while let PAT = EXPR`
454    ///        ^^^^^^^^^^^^^^
455    pub let_span: Span,
456}
457
458impl<'hir> WhileLet<'hir> {
459    #[inline]
460    /// Parses a desugared `while let` loop
461    pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
462        if let ExprKind::Loop(
463            &Block {
464                expr:
465                    Some(&Expr {
466                        kind:
467                            ExprKind::If(
468                                &Expr {
469                                    kind:
470                                        ExprKind::Let(&hir::LetExpr {
471                                            pat: let_pat,
472                                            init: let_expr,
473                                            span: let_span,
474                                            ..
475                                        }),
476                                    ..
477                                },
478                                if_then,
479                                _,
480                            ),
481                        ..
482                    }),
483                ..
484            },
485            label,
486            LoopSource::While,
487            _,
488        ) = expr.kind
489        {
490            return Some(Self {
491                let_pat,
492                let_expr,
493                if_then,
494                label,
495                let_span,
496            });
497        }
498        None
499    }
500}
501
502/// A desugared compound assignment statement, such as in
503/// `(a, b) = expr`.
504pub struct CompoundAssignment<'hir> {
505    /// The individual assignees
506    pub assignees: Vec<&'hir Expr<'hir>>,
507    /// The initializatiojn expression
508    pub init: &'hir Expr<'hir>,
509}
510
511impl<'hir> CompoundAssignment<'hir> {
512    /// Check if `expr` is a block which is an expansion of a compound assignment.
513    #[inline]
514    pub fn hir(expr: &'hir Expr<'_>) -> Option<Self> {
515        // A compound assignment is unsugared into a block which first assigns the RHS subexpressions to
516        // temporaries, then moves those temporaries to the assignment targets. By doing it this
517        // way, and since the moves cannot fail, the compound assignment either succeeds or not take
518        // place at all if, for example, one of the RHS subcomponent diverges.
519        if let ExprKind::Block(
520            Block {
521                stmts: [assign, rest @ ..],
522                expr: None,
523                ..
524            },
525            None,
526        ) = expr.kind
527            && let hir::StmtKind::Let(LetStmt {
528                init: Some(init),
529                source: LocalSource::AssignDesugar,
530                ..
531            }) = assign.kind
532        {
533            let mut assignees = Vec::with_capacity(rest.len());
534            for stmt in rest {
535                if let hir::StmtKind::Expr(expr) = stmt.kind
536                    && let ExprKind::Assign(target, _, _) = expr.kind
537                {
538                    assignees.push(target);
539                } else {
540                    return None;
541                }
542            }
543            Some(CompoundAssignment { assignees, init })
544        } else {
545            None
546        }
547    }
548}
549
550/// Converts a `hir` binary operator to the corresponding `ast` type.
551#[must_use]
552pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
553    match op {
554        hir::BinOpKind::Eq => ast::BinOpKind::Eq,
555        hir::BinOpKind::Ge => ast::BinOpKind::Ge,
556        hir::BinOpKind::Gt => ast::BinOpKind::Gt,
557        hir::BinOpKind::Le => ast::BinOpKind::Le,
558        hir::BinOpKind::Lt => ast::BinOpKind::Lt,
559        hir::BinOpKind::Ne => ast::BinOpKind::Ne,
560        hir::BinOpKind::Or => ast::BinOpKind::Or,
561        hir::BinOpKind::Add => ast::BinOpKind::Add,
562        hir::BinOpKind::And => ast::BinOpKind::And,
563        hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
564        hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
565        hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
566        hir::BinOpKind::Div => ast::BinOpKind::Div,
567        hir::BinOpKind::Mul => ast::BinOpKind::Mul,
568        hir::BinOpKind::Rem => ast::BinOpKind::Rem,
569        hir::BinOpKind::Shl => ast::BinOpKind::Shl,
570        hir::BinOpKind::Shr => ast::BinOpKind::Shr,
571        hir::BinOpKind::Sub => ast::BinOpKind::Sub,
572    }
573}
574
575/// A parsed `Vec` initialization expression
576#[derive(Clone, Copy)]
577pub enum VecInitKind {
578    /// `Vec::new()`
579    New,
580    /// `Vec::default()` or `Default::default()`
581    Default,
582    /// `Vec::with_capacity(123)`
583    WithConstCapacity(u128),
584    /// `Vec::with_capacity(slice.len())`
585    WithExprCapacity(HirId),
586}
587
588/// Checks if the given expression is an initialization of `Vec` and returns its kind.
589pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
590    if let ExprKind::Call(func, args) = expr.kind {
591        match func.kind {
592            ExprKind::Path(QPath::TypeRelative(ty, name))
593                if cx.typeck_results().node_type(ty.hir_id).is_diag_item(cx, sym::Vec) =>
594            {
595                if name.ident.name == sym::new {
596                    return Some(VecInitKind::New);
597                } else if name.ident.name == symbol::kw::Default {
598                    return Some(VecInitKind::Default);
599                } else if name.ident.name == sym::with_capacity {
600                    let arg = args.first()?;
601                    return match ConstEvalCtxt::new(cx).eval_local(arg, expr.span.ctxt()) {
602                        Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
603                        _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
604                    };
605                }
606            },
607            ExprKind::Path(QPath::Resolved(_, path))
608                if cx.tcx.is_diagnostic_item(sym::default_fn, path.res.opt_def_id()?)
609                    && cx.typeck_results().expr_ty(expr).is_diag_item(cx, sym::Vec) =>
610            {
611                return Some(VecInitKind::Default);
612            },
613            _ => (),
614        }
615    }
616    None
617}
618
619/// Checks that a condition doesn't have a `let` expression, to keep `If` and `While` from accepting
620/// `if let` and `while let`.
621pub const fn has_let_expr<'tcx>(cond: &'tcx Expr<'tcx>) -> bool {
622    match &cond.kind {
623        ExprKind::Let(_) => true,
624        ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
625        _ => false,
626    }
627}