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