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