]> git.lizzy.rs Git - rust.git/blob - clippy_utils/src/higher.rs
Overhaul `TyS` and `Ty`.
[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                 } else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
288                     // `vec![a, b, c]` case
289                     if_chain! {
290                         if let hir::ExprKind::Box(boxed) = args[0].kind;
291                         if let hir::ExprKind::Array(args) = boxed.kind;
292                         then {
293                             return Some(VecArgs::Vec(args));
294                         }
295                     }
296
297                     None
298                 } else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
299                     Some(VecArgs::Vec(&[]))
300                 } else {
301                     None
302                 };
303             }
304         }
305
306         None
307     }
308 }
309
310 /// A desugared `while` loop
311 pub struct While<'hir> {
312     /// `while` loop condition
313     pub condition: &'hir Expr<'hir>,
314     /// `while` loop body
315     pub body: &'hir Expr<'hir>,
316 }
317
318 impl<'hir> While<'hir> {
319     #[inline]
320     /// Parses a desugared `while` loop
321     pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
322         if let ExprKind::Loop(
323             Block {
324                 expr:
325                     Some(Expr {
326                         kind:
327                             ExprKind::If(
328                                 Expr {
329                                     kind: ExprKind::DropTemps(condition),
330                                     ..
331                                 },
332                                 body,
333                                 _,
334                             ),
335                         ..
336                     }),
337                 ..
338             },
339             _,
340             LoopSource::While,
341             _,
342         ) = expr.kind
343         {
344             return Some(Self { condition, body });
345         }
346         None
347     }
348 }
349
350 /// A desugared `while let` loop
351 pub struct WhileLet<'hir> {
352     /// `while let` loop item pattern
353     pub let_pat: &'hir Pat<'hir>,
354     /// `while let` loop scrutinee
355     pub let_expr: &'hir Expr<'hir>,
356     /// `while let` loop body
357     pub if_then: &'hir Expr<'hir>,
358 }
359
360 impl<'hir> WhileLet<'hir> {
361     #[inline]
362     /// Parses a desugared `while let` loop
363     pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
364         if let ExprKind::Loop(
365             Block {
366                 expr:
367                     Some(Expr {
368                         kind:
369                             ExprKind::If(
370                                 Expr {
371                                     kind:
372                                         ExprKind::Let(hir::Let {
373                                             pat: let_pat,
374                                             init: let_expr,
375                                             ..
376                                         }),
377                                     ..
378                                 },
379                                 if_then,
380                                 _,
381                             ),
382                         ..
383                     }),
384                 ..
385             },
386             _,
387             LoopSource::While,
388             _,
389         ) = expr.kind
390         {
391             return Some(Self {
392                 let_pat,
393                 let_expr,
394                 if_then,
395             });
396         }
397         None
398     }
399 }
400
401 /// Converts a hir binary operator to the corresponding `ast` type.
402 #[must_use]
403 pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
404     match op {
405         hir::BinOpKind::Eq => ast::BinOpKind::Eq,
406         hir::BinOpKind::Ge => ast::BinOpKind::Ge,
407         hir::BinOpKind::Gt => ast::BinOpKind::Gt,
408         hir::BinOpKind::Le => ast::BinOpKind::Le,
409         hir::BinOpKind::Lt => ast::BinOpKind::Lt,
410         hir::BinOpKind::Ne => ast::BinOpKind::Ne,
411         hir::BinOpKind::Or => ast::BinOpKind::Or,
412         hir::BinOpKind::Add => ast::BinOpKind::Add,
413         hir::BinOpKind::And => ast::BinOpKind::And,
414         hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
415         hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
416         hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
417         hir::BinOpKind::Div => ast::BinOpKind::Div,
418         hir::BinOpKind::Mul => ast::BinOpKind::Mul,
419         hir::BinOpKind::Rem => ast::BinOpKind::Rem,
420         hir::BinOpKind::Shl => ast::BinOpKind::Shl,
421         hir::BinOpKind::Shr => ast::BinOpKind::Shr,
422         hir::BinOpKind::Sub => ast::BinOpKind::Sub,
423     }
424 }
425
426 /// A parsed `Vec` initialization expression
427 #[derive(Clone, Copy)]
428 pub enum VecInitKind {
429     /// `Vec::new()`
430     New,
431     /// `Vec::default()` or `Default::default()`
432     Default,
433     /// `Vec::with_capacity(123)`
434     WithLiteralCapacity(u64),
435     /// `Vec::with_capacity(slice.len())`
436     WithExprCapacity(HirId),
437 }
438
439 /// Checks if given expression is an initialization of `Vec` and returns its kind.
440 pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
441     if let ExprKind::Call(func, args) = expr.kind {
442         match func.kind {
443             ExprKind::Path(QPath::TypeRelative(ty, name))
444                 if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) =>
445             {
446                 if name.ident.name == sym::new {
447                     return Some(VecInitKind::New);
448                 } else if name.ident.name == symbol::kw::Default {
449                     return Some(VecInitKind::Default);
450                 } else if name.ident.name.as_str() == "with_capacity" {
451                     let arg = args.get(0)?;
452                     if_chain! {
453                         if let ExprKind::Lit(lit) = &arg.kind;
454                         if let LitKind::Int(num, _) = lit.node;
455                         then {
456                             return Some(VecInitKind::WithLiteralCapacity(num.try_into().ok()?));
457                         }
458                     }
459                     return Some(VecInitKind::WithExprCapacity(arg.hir_id));
460                 }
461             },
462             ExprKind::Path(QPath::Resolved(_, path))
463                 if match_def_path(cx, path.res.opt_def_id()?, &paths::DEFAULT_TRAIT_METHOD)
464                     && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) =>
465             {
466                 return Some(VecInitKind::Default);
467             },
468             _ => (),
469         }
470     }
471     None
472 }