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