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