• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
hir(expr: &Expr<'tcx>) -> Option<Self>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
hir(expr: &Expr<'hir>) -> Option<Self>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
hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self>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
parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self>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
hir(expr: &Expr<'hir>) -> Option<Self>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`.
hir(expr: &'a hir::Expr<'_>) -> Option<Range<'a>>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!`.
hir(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> Option<VecArgs<'a>>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 let hir::ExprKind::Call(_, [arg]) = &args[0].kind
291                         && let hir::ExprKind::Array(args) = arg.kind {
292                         Some(VecArgs::Vec(args))
293                     } else {
294                         None
295                     }
296                 } else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
297                     Some(VecArgs::Vec(&[]))
298                 } else {
299                     None
300                 };
301             }
302         }
303 
304         None
305     }
306 }
307 
308 /// A desugared `while` loop
309 pub struct While<'hir> {
310     /// `while` loop condition
311     pub condition: &'hir Expr<'hir>,
312     /// `while` loop body
313     pub body: &'hir Expr<'hir>,
314     /// Span of the loop header
315     pub span: Span,
316 }
317 
318 impl<'hir> While<'hir> {
319     #[inline]
320     /// Parses a desugared `while` loop
hir(expr: &Expr<'hir>) -> Option<Self>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             span,
342         ) = expr.kind
343         {
344             return Some(Self { condition, body, span });
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
hir(expr: &Expr<'hir>) -> Option<Self>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]
binop(op: hir::BinOpKind) -> ast::BinOpKind403 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     WithConstCapacity(u128),
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.
get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind>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                     return match constant_simple(cx, cx.typeck_results(), arg) {
453                         Some(Constant::Int(num)) => Some(VecInitKind::WithConstCapacity(num)),
454                         _ => Some(VecInitKind::WithExprCapacity(arg.hir_id)),
455                     };
456                 };
457             },
458             ExprKind::Path(QPath::Resolved(_, path))
459                 if match_def_path(cx, path.res.opt_def_id()?, &paths::DEFAULT_TRAIT_METHOD)
460                     && is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) =>
461             {
462                 return Some(VecInitKind::Default);
463             },
464             _ => (),
465         }
466     }
467     None
468 }
469