Thanks to visit codestin.com
Credit goes to doc.rust-lang.org

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            && let Some(name) = cx.tcx.get_diagnostic_name(fun_def_id)
291        {
292            return match (name, args) {
293                (sym::vec_from_elem, [elem, size]) => {
294                    // `vec![elem; size]` case
295                    Some(VecArgs::Repeat(elem, size))
296                },
297                (sym::slice_into_vec, [slice])
298                    if let ExprKind::Call(_, [arg]) = slice.kind
299                        && let ExprKind::Array(args) = arg.kind =>
300                {
301                    // `vec![a, b, c]` case
302                    Some(VecArgs::Vec(args))
303                },
304                (sym::vec_new, []) => Some(VecArgs::Vec(&[])),
305                _ => None,
306            };
307        }
308
309        None
310    }
311}
312
313/// A desugared `while` loop
314pub struct While<'hir> {
315    /// `while` loop condition
316    pub condition: &'hir Expr<'hir>,
317    /// `while` loop body
318    pub body: &'hir Expr<'hir>,
319    /// Span of the loop header
320    pub span: Span,
321}
322
323impl<'hir> While<'hir> {
324    #[inline]
325    /// Parses a desugared `while` loop
326    pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
327        if let ExprKind::Loop(
328            Block {
329                expr:
330                    Some(Expr {
331                        kind: ExprKind::If(condition, body, _),
332                        ..
333                    }),
334                ..
335            },
336            _,
337            LoopSource::While,
338            span,
339        ) = expr.kind
340            && !has_let_expr(condition)
341        {
342            return Some(Self { condition, body, span });
343        }
344        None
345    }
346}
347
348/// A desugared `while let` loop
349pub struct WhileLet<'hir> {
350    /// `while let` loop item pattern
351    pub let_pat: &'hir Pat<'hir>,
352    /// `while let` loop scrutinee
353    pub let_expr: &'hir Expr<'hir>,
354    /// `while let` loop body
355    pub if_then: &'hir Expr<'hir>,
356    pub label: Option<ast::Label>,
357    /// `while let PAT = EXPR`
358    ///        ^^^^^^^^^^^^^^
359    pub let_span: Span,
360}
361
362impl<'hir> WhileLet<'hir> {
363    #[inline]
364    /// Parses a desugared `while let` loop
365    pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
366        if let ExprKind::Loop(
367            &Block {
368                expr:
369                    Some(&Expr {
370                        kind:
371                            ExprKind::If(
372                                &Expr {
373                                    kind:
374                                        ExprKind::Let(&hir::LetExpr {
375                                            pat: let_pat,
376                                            init: let_expr,
377                                            span: let_span,
378                                            ..
379                                        }),
380                                    ..
381                                },
382                                if_then,
383                                _,
384                            ),
385                        ..
386                    }),
387                ..
388            },
389            label,
390            LoopSource::While,
391            _,
392        ) = expr.kind
393        {
394            return Some(Self {
395                let_pat,
396                let_expr,
397                if_then,
398                label,
399                let_span,
400            });
401        }
402        None
403    }
404}
405
406/// Converts a `hir` binary operator to the corresponding `ast` type.
407#[must_use]
408pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
409    match op {
410        hir::BinOpKind::Eq => ast::BinOpKind::Eq,
411        hir::BinOpKind::Ge => ast::BinOpKind::Ge,
412        hir::BinOpKind::Gt => ast::BinOpKind::Gt,
413        hir::BinOpKind::Le => ast::BinOpKind::Le,
414        hir::BinOpKind::Lt => ast::BinOpKind::Lt,
415        hir::BinOpKind::Ne => ast::BinOpKind::Ne,
416        hir::BinOpKind::Or => ast::BinOpKind::Or,
417        hir::BinOpKind::Add => ast::BinOpKind::Add,
418        hir::BinOpKind::And => ast::BinOpKind::And,
419        hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
420        hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
421        hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
422        hir::BinOpKind::Div => ast::BinOpKind::Div,
423        hir::BinOpKind::Mul => ast::BinOpKind::Mul,
424        hir::BinOpKind::Rem => ast::BinOpKind::Rem,
425        hir::BinOpKind::Shl => ast::BinOpKind::Shl,
426        hir::BinOpKind::Shr => ast::BinOpKind::Shr,
427        hir::BinOpKind::Sub => ast::BinOpKind::Sub,
428    }
429}
430
431/// A parsed `Vec` initialization expression
432#[derive(Clone, Copy)]
433pub enum VecInitKind {
434    /// `Vec::new()`
435    New,
436    /// `Vec::default()` or `Default::default()`
437    Default,
438    /// `Vec::with_capacity(123)`
439    WithConstCapacity(u128),
440    /// `Vec::with_capacity(slice.len())`
441    WithExprCapacity(HirId),
442}
443
444/// Checks if the given expression is an initialization of `Vec` and returns its kind.
445pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
446    if let ExprKind::Call(func, args) = expr.kind {
447        match func.kind {
448            ExprKind::Path(QPath::TypeRelative(ty, name))
449                if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) =>
450            {
451                if name.ident.name == sym::new {
452                    return Some(VecInitKind::New);
453                } else if name.ident.name == symbol::kw::Default {
454                    return Some(VecInitKind::Default);
455                } else if name.ident.name == sym::with_capacity {
456                    let arg = args.first()?;
457                    return match ConstEvalCtxt::new(cx).eval_simple(arg) {
458                        Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
459                        _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
460                    };
461                }
462            },
463            ExprKind::Path(QPath::Resolved(_, path))
464                if cx.tcx.is_diagnostic_item(sym::default_fn, path.res.opt_def_id()?)
465                    && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) =>
466            {
467                return Some(VecInitKind::Default);
468            },
469            _ => (),
470        }
471    }
472    None
473}
474
475/// Checks that a condition doesn't have a `let` expression, to keep `If` and `While` from accepting
476/// `if let` and `while let`.
477pub const fn has_let_expr<'tcx>(cond: &'tcx Expr<'tcx>) -> bool {
478    match &cond.kind {
479        ExprKind::Let(_) => true,
480        ExprKind::Binary(_, lhs, rhs) => has_let_expr(lhs) || has_let_expr(rhs),
481        _ => false,
482    }
483}