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