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