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