]> git.lizzy.rs Git - rust.git/blob - clippy_utils/src/higher.rs
Fix manual_match with let-expressions
[rust.git] / clippy_utils / src / higher.rs
1 //! This module contains functions that retrieves specifiec elements.
2
3 #![deny(clippy::missing_docs_in_private_items)]
4
5 use crate::{is_expn_of, match_def_path, paths};
6 use if_chain::if_chain;
7 use rustc_ast::ast::{self, LitKind};
8 use rustc_hir as hir;
9 use rustc_hir::{Arm, Block, BorrowKind, Expr, ExprKind, LoopSource, MatchSource, Node, Pat, StmtKind, UnOp};
10 use rustc_lint::LateContext;
11 use rustc_span::{sym, ExpnKind, Span, Symbol};
12
13 /// The essential nodes of a desugared for loop as well as the entire span:
14 /// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
15 pub struct ForLoop<'tcx> {
16     pub pat: &'tcx hir::Pat<'tcx>,
17     pub arg: &'tcx hir::Expr<'tcx>,
18     pub body: &'tcx hir::Expr<'tcx>,
19     pub span: Span,
20 }
21
22 impl<'tcx> ForLoop<'tcx> {
23     #[inline]
24     pub fn hir(expr: &Expr<'tcx>) -> Option<Self> {
25         if_chain! {
26             if let hir::ExprKind::Match(ref iterexpr, ref arms, hir::MatchSource::ForLoopDesugar) = expr.kind;
27             if let Some(first_arm) = arms.get(0);
28             if let hir::ExprKind::Call(_, ref iterargs) = iterexpr.kind;
29             if let Some(first_arg) = iterargs.get(0);
30             if iterargs.len() == 1 && arms.len() == 1 && first_arm.guard.is_none();
31             if let hir::ExprKind::Loop(ref block, ..) = first_arm.body.kind;
32             if block.expr.is_none();
33             if let [ _, _, ref let_stmt, ref body ] = *block.stmts;
34             if let hir::StmtKind::Local(ref local) = let_stmt.kind;
35             if let hir::StmtKind::Expr(ref body_expr) = body.kind;
36             then {
37                 return Some(Self {
38                     pat: &*local.pat,
39                     arg: first_arg,
40                     body: body_expr,
41                     span: first_arm.span
42                 });
43             }
44         }
45         None
46     }
47 }
48
49 pub struct If<'hir> {
50     pub cond: &'hir Expr<'hir>,
51     pub r#else: Option<&'hir Expr<'hir>>,
52     pub then: &'hir Expr<'hir>,
53 }
54
55 impl<'hir> If<'hir> {
56     #[inline]
57     pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
58         if let ExprKind::If(
59             Expr {
60                 kind: ExprKind::DropTemps(cond),
61                 ..
62             },
63             then,
64             r#else,
65         ) = expr.kind
66         {
67             Some(Self { cond, r#else, then })
68         } else {
69             None
70         }
71     }
72 }
73
74 pub struct IfLet<'hir> {
75     pub let_pat: &'hir Pat<'hir>,
76     pub let_expr: &'hir Expr<'hir>,
77     pub if_then: &'hir Expr<'hir>,
78     pub if_else: Option<&'hir Expr<'hir>>,
79 }
80
81 impl<'hir> IfLet<'hir> {
82     pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
83         if let ExprKind::If(
84             Expr {
85                 kind: ExprKind::Let(let_pat, let_expr, _),
86                 ..
87             },
88             if_then,
89             if_else,
90         ) = expr.kind
91         {
92             let hir = cx.tcx.hir();
93             let mut iter = hir.parent_iter(expr.hir_id);
94             if let Some((_, Node::Block(Block { stmts: [], .. }))) = iter.next() {
95                 if let Some((
96                     _,
97                     Node::Expr(Expr {
98                         kind: ExprKind::Loop(_, _, LoopSource::While, _),
99                         ..
100                     }),
101                 )) = iter.next()
102                 {
103                     // while loop desugar
104                     return None;
105                 }
106             }
107             return Some(Self {
108                 let_pat,
109                 let_expr,
110                 if_then,
111                 if_else,
112             });
113         }
114         None
115     }
116 }
117
118 pub enum IfLetOrMatch<'hir> {
119     Match(&'hir Expr<'hir>, &'hir [Arm<'hir>], MatchSource),
120     /// scrutinee, pattern, then block, else block
121     IfLet(
122         &'hir Expr<'hir>,
123         &'hir Pat<'hir>,
124         &'hir Expr<'hir>,
125         Option<&'hir Expr<'hir>>,
126     ),
127 }
128
129 impl<'hir> IfLetOrMatch<'hir> {
130     pub fn parse(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option<Self> {
131         match expr.kind {
132             ExprKind::Match(expr, arms, source) => Some(Self::Match(expr, arms, source)),
133             _ => IfLet::hir(cx, expr).map(
134                 |IfLet {
135                      let_expr,
136                      let_pat,
137                      if_then,
138                      if_else,
139                  }| { Self::IfLet(let_expr, let_pat, if_then, if_else) },
140             ),
141         }
142     }
143 }
144
145 pub struct IfOrIfLet<'hir> {
146     pub cond: &'hir Expr<'hir>,
147     pub r#else: Option<&'hir Expr<'hir>>,
148     pub then: &'hir Expr<'hir>,
149 }
150
151 impl<'hir> IfOrIfLet<'hir> {
152     #[inline]
153     pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
154         if let ExprKind::If(cond, then, r#else) = expr.kind {
155             if let ExprKind::DropTemps(new_cond) = cond.kind {
156                 return Some(Self {
157                     cond: new_cond,
158                     r#else,
159                     then,
160                 });
161             }
162             if let ExprKind::Let(..) = cond.kind {
163                 return Some(Self { cond, r#else, then });
164             }
165         }
166         None
167     }
168 }
169
170 /// Represent a range akin to `ast::ExprKind::Range`.
171 #[derive(Debug, Copy, Clone)]
172 pub struct Range<'a> {
173     /// The lower bound of the range, or `None` for ranges such as `..X`.
174     pub start: Option<&'a hir::Expr<'a>>,
175     /// The upper bound of the range, or `None` for ranges such as `X..`.
176     pub end: Option<&'a hir::Expr<'a>>,
177     /// Whether the interval is open or closed.
178     pub limits: ast::RangeLimits,
179 }
180
181 impl<'a> Range<'a> {
182     /// Higher a `hir` range to something similar to `ast::ExprKind::Range`.
183     pub fn hir(expr: &'a hir::Expr<'_>) -> Option<Range<'a>> {
184         /// Finds the field named `name` in the field. Always return `Some` for
185         /// convenience.
186         fn get_field<'c>(name: &str, fields: &'c [hir::ExprField<'_>]) -> Option<&'c hir::Expr<'c>> {
187             let expr = &fields.iter().find(|field| field.ident.name.as_str() == name)?.expr;
188             Some(expr)
189         }
190
191         match expr.kind {
192             hir::ExprKind::Call(ref path, ref args)
193                 if matches!(
194                     path.kind,
195                     hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, _))
196                 ) =>
197             {
198                 Some(Range {
199                     start: Some(&args[0]),
200                     end: Some(&args[1]),
201                     limits: ast::RangeLimits::Closed,
202                 })
203             },
204             hir::ExprKind::Struct(ref path, ref fields, None) => match path {
205                 hir::QPath::LangItem(hir::LangItem::RangeFull, _) => Some(Range {
206                     start: None,
207                     end: None,
208                     limits: ast::RangeLimits::HalfOpen,
209                 }),
210                 hir::QPath::LangItem(hir::LangItem::RangeFrom, _) => Some(Range {
211                     start: Some(get_field("start", fields)?),
212                     end: None,
213                     limits: ast::RangeLimits::HalfOpen,
214                 }),
215                 hir::QPath::LangItem(hir::LangItem::Range, _) => Some(Range {
216                     start: Some(get_field("start", fields)?),
217                     end: Some(get_field("end", fields)?),
218                     limits: ast::RangeLimits::HalfOpen,
219                 }),
220                 hir::QPath::LangItem(hir::LangItem::RangeToInclusive, _) => Some(Range {
221                     start: None,
222                     end: Some(get_field("end", fields)?),
223                     limits: ast::RangeLimits::Closed,
224                 }),
225                 hir::QPath::LangItem(hir::LangItem::RangeTo, _) => Some(Range {
226                     start: None,
227                     end: Some(get_field("end", fields)?),
228                     limits: ast::RangeLimits::HalfOpen,
229                 }),
230                 _ => None,
231             },
232             _ => None,
233         }
234     }
235 }
236
237 /// Represent the pre-expansion arguments of a `vec!` invocation.
238 pub enum VecArgs<'a> {
239     /// `vec![elem; len]`
240     Repeat(&'a hir::Expr<'a>, &'a hir::Expr<'a>),
241     /// `vec![a, b, c]`
242     Vec(&'a [hir::Expr<'a>]),
243 }
244
245 impl<'a> VecArgs<'a> {
246     /// Returns the arguments of the `vec!` macro if this expression was expanded
247     /// from `vec!`.
248     pub fn hir(cx: &LateContext<'_>, expr: &'a hir::Expr<'_>) -> Option<VecArgs<'a>> {
249         if_chain! {
250             if let hir::ExprKind::Call(ref fun, ref args) = expr.kind;
251             if let hir::ExprKind::Path(ref qpath) = fun.kind;
252             if is_expn_of(fun.span, "vec").is_some();
253             if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
254             then {
255                 return if match_def_path(cx, fun_def_id, &paths::VEC_FROM_ELEM) && args.len() == 2 {
256                     // `vec![elem; size]` case
257                     Some(VecArgs::Repeat(&args[0], &args[1]))
258                 }
259                 else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
260                     // `vec![a, b, c]` case
261                     if_chain! {
262                         if let hir::ExprKind::Box(ref boxed) = args[0].kind;
263                         if let hir::ExprKind::Array(ref args) = boxed.kind;
264                         then {
265                             return Some(VecArgs::Vec(&*args));
266                         }
267                     }
268
269                     None
270                 }
271                 else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
272                     Some(VecArgs::Vec(&[]))
273                 }
274                 else {
275                     None
276                 };
277             }
278         }
279
280         None
281     }
282 }
283
284 pub struct While<'hir> {
285     pub if_cond: &'hir Expr<'hir>,
286     pub if_then: &'hir Expr<'hir>,
287     pub if_else: Option<&'hir Expr<'hir>>,
288 }
289
290 impl<'hir> While<'hir> {
291     #[inline]
292     pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
293         if let ExprKind::Loop(
294             Block {
295                 expr:
296                     Some(Expr {
297                         kind:
298                             ExprKind::If(
299                                 Expr {
300                                     kind: ExprKind::DropTemps(if_cond),
301                                     ..
302                                 },
303                                 if_then,
304                                 if_else_ref,
305                             ),
306                         ..
307                     }),
308                 ..
309             },
310             _,
311             LoopSource::While,
312             _,
313         ) = expr.kind
314         {
315             let if_else = *if_else_ref;
316             return Some(Self {
317                 if_cond,
318                 if_then,
319                 if_else,
320             });
321         }
322         None
323     }
324 }
325
326 pub struct WhileLet<'hir> {
327     pub if_expr: &'hir Expr<'hir>,
328     pub let_pat: &'hir Pat<'hir>,
329     pub let_expr: &'hir Expr<'hir>,
330     pub if_then: &'hir Expr<'hir>,
331     pub if_else: Option<&'hir Expr<'hir>>,
332 }
333
334 impl<'hir> WhileLet<'hir> {
335     #[inline]
336     pub const fn hir(expr: &Expr<'hir>) -> Option<Self> {
337         if let ExprKind::Loop(
338             Block {
339                 expr: Some(if_expr), ..
340             },
341             _,
342             LoopSource::While,
343             _,
344         ) = expr.kind
345         {
346             if let Expr {
347                 kind:
348                     ExprKind::If(
349                         Expr {
350                             kind: ExprKind::Let(let_pat, let_expr, _),
351                             ..
352                         },
353                         if_then,
354                         if_else_ref,
355                     ),
356                 ..
357             } = if_expr
358             {
359                 let if_else = *if_else_ref;
360                 return Some(Self {
361                     if_expr,
362                     let_pat,
363                     let_expr,
364                     if_then,
365                     if_else,
366                 });
367             }
368         }
369         None
370     }
371 }
372
373 /// Converts a hir binary operator to the corresponding `ast` type.
374 #[must_use]
375 pub fn binop(op: hir::BinOpKind) -> ast::BinOpKind {
376     match op {
377         hir::BinOpKind::Eq => ast::BinOpKind::Eq,
378         hir::BinOpKind::Ge => ast::BinOpKind::Ge,
379         hir::BinOpKind::Gt => ast::BinOpKind::Gt,
380         hir::BinOpKind::Le => ast::BinOpKind::Le,
381         hir::BinOpKind::Lt => ast::BinOpKind::Lt,
382         hir::BinOpKind::Ne => ast::BinOpKind::Ne,
383         hir::BinOpKind::Or => ast::BinOpKind::Or,
384         hir::BinOpKind::Add => ast::BinOpKind::Add,
385         hir::BinOpKind::And => ast::BinOpKind::And,
386         hir::BinOpKind::BitAnd => ast::BinOpKind::BitAnd,
387         hir::BinOpKind::BitOr => ast::BinOpKind::BitOr,
388         hir::BinOpKind::BitXor => ast::BinOpKind::BitXor,
389         hir::BinOpKind::Div => ast::BinOpKind::Div,
390         hir::BinOpKind::Mul => ast::BinOpKind::Mul,
391         hir::BinOpKind::Rem => ast::BinOpKind::Rem,
392         hir::BinOpKind::Shl => ast::BinOpKind::Shl,
393         hir::BinOpKind::Shr => ast::BinOpKind::Shr,
394         hir::BinOpKind::Sub => ast::BinOpKind::Sub,
395     }
396 }
397
398 /// Extract args from an assert-like macro.
399 /// Currently working with:
400 /// - `assert!`, `assert_eq!` and `assert_ne!`
401 /// - `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!`
402 /// For example:
403 /// `assert!(expr)` will return `Some([expr])`
404 /// `debug_assert_eq!(a, b)` will return `Some([a, b])`
405 pub fn extract_assert_macro_args<'tcx>(e: &'tcx Expr<'tcx>) -> Option<Vec<&'tcx Expr<'tcx>>> {
406     /// Try to match the AST for a pattern that contains a match, for example when two args are
407     /// compared
408     fn ast_matchblock(matchblock_expr: &'tcx Expr<'tcx>) -> Option<Vec<&Expr<'_>>> {
409         if_chain! {
410             if let ExprKind::Match(headerexpr, _, _) = &matchblock_expr.kind;
411             if let ExprKind::Tup([lhs, rhs]) = &headerexpr.kind;
412             if let ExprKind::AddrOf(BorrowKind::Ref, _, lhs) = lhs.kind;
413             if let ExprKind::AddrOf(BorrowKind::Ref, _, rhs) = rhs.kind;
414             then {
415                 return Some(vec![lhs, rhs]);
416             }
417         }
418         None
419     }
420
421     if let ExprKind::Block(block, _) = e.kind {
422         if block.stmts.len() == 1 {
423             if let StmtKind::Semi(matchexpr) = block.stmts.get(0)?.kind {
424                 // macros with unique arg: `{debug_}assert!` (e.g., `debug_assert!(some_condition)`)
425                 if_chain! {
426                     if let Some(If { cond, .. }) = If::hir(matchexpr);
427                     if let ExprKind::Unary(UnOp::Not, condition) = cond.kind;
428                     then {
429                         return Some(vec![condition]);
430                     }
431                 }
432
433                 // debug macros with two args: `debug_assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
434                 if_chain! {
435                     if let ExprKind::Block(matchblock,_) = matchexpr.kind;
436                     if let Some(matchblock_expr) = matchblock.expr;
437                     then {
438                         return ast_matchblock(matchblock_expr);
439                     }
440                 }
441             }
442         } else if let Some(matchblock_expr) = block.expr {
443             // macros with two args: `assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
444             return ast_matchblock(matchblock_expr);
445         }
446     }
447     None
448 }
449
450 /// A parsed `format!` expansion
451 pub struct FormatExpn<'tcx> {
452     /// Span of `format!(..)`
453     pub call_site: Span,
454     /// Inner `format_args!` expansion
455     pub format_args: FormatArgsExpn<'tcx>,
456 }
457
458 impl FormatExpn<'tcx> {
459     /// Parses an expanded `format!` invocation
460     pub fn parse(expr: &'tcx Expr<'tcx>) -> Option<Self> {
461         if_chain! {
462             if let ExprKind::Block(block, _) = expr.kind;
463             if let [stmt] = block.stmts;
464             if let StmtKind::Local(local) = stmt.kind;
465             if let Some(init) = local.init;
466             if let ExprKind::Call(_, [format_args]) = init.kind;
467             let expn_data = expr.span.ctxt().outer_expn_data();
468             if let ExpnKind::Macro(_, sym::format) = expn_data.kind;
469             if let Some(format_args) = FormatArgsExpn::parse(format_args);
470             then {
471                 Some(FormatExpn {
472                     call_site: expn_data.call_site,
473                     format_args,
474                 })
475             } else {
476                 None
477             }
478         }
479     }
480 }
481
482 /// A parsed `format_args!` expansion
483 pub struct FormatArgsExpn<'tcx> {
484     /// Span of the first argument, the format string
485     pub format_string_span: Span,
486     /// Values passed after the format string
487     pub value_args: Vec<&'tcx Expr<'tcx>>,
488
489     /// String literal expressions which represent the format string split by "{}"
490     pub format_string_parts: &'tcx [Expr<'tcx>],
491     /// Symbols corresponding to [`Self::format_string_parts`]
492     pub format_string_symbols: Vec<Symbol>,
493     /// Expressions like `ArgumentV1::new(arg0, Debug::fmt)`
494     pub args: &'tcx [Expr<'tcx>],
495     /// The final argument passed to `Arguments::new_v1_formatted`, if applicable
496     pub fmt_expr: Option<&'tcx Expr<'tcx>>,
497 }
498
499 impl FormatArgsExpn<'tcx> {
500     /// Parses an expanded `format_args!` or `format_args_nl!` invocation
501     pub fn parse(expr: &'tcx Expr<'tcx>) -> Option<Self> {
502         if_chain! {
503             if let ExpnKind::Macro(_, name) = expr.span.ctxt().outer_expn_data().kind;
504             let name = name.as_str();
505             if name.ends_with("format_args") || name.ends_with("format_args_nl");
506
507             if let ExprKind::Match(inner_match, [arm], _) = expr.kind;
508
509             // `match match`, if you will
510             if let ExprKind::Match(args, [inner_arm], _) = inner_match.kind;
511             if let ExprKind::Tup(value_args) = args.kind;
512             if let Some(value_args) = value_args
513                 .iter()
514                 .map(|e| match e.kind {
515                     ExprKind::AddrOf(_, _, e) => Some(e),
516                     _ => None,
517                 })
518                 .collect();
519             if let ExprKind::Array(args) = inner_arm.body.kind;
520
521             if let ExprKind::Block(Block { stmts: [], expr: Some(expr), .. }, _) = arm.body.kind;
522             if let ExprKind::Call(_, call_args) = expr.kind;
523             if let Some((strs_ref, fmt_expr)) = match call_args {
524                 // Arguments::new_v1
525                 [strs_ref, _] => Some((strs_ref, None)),
526                 // Arguments::new_v1_formatted
527                 [strs_ref, _, fmt_expr] => Some((strs_ref, Some(fmt_expr))),
528                 _ => None,
529             };
530             if let ExprKind::AddrOf(BorrowKind::Ref, _, strs_arr) = strs_ref.kind;
531             if let ExprKind::Array(format_string_parts) = strs_arr.kind;
532             if let Some(format_string_symbols) = format_string_parts
533                 .iter()
534                 .map(|e| {
535                     if let ExprKind::Lit(lit) = &e.kind {
536                         if let LitKind::Str(symbol, _style) = lit.node {
537                             return Some(symbol);
538                         }
539                     }
540                     None
541                 })
542                 .collect();
543             then {
544                 Some(FormatArgsExpn {
545                     format_string_span: strs_ref.span,
546                     value_args,
547                     format_string_parts,
548                     format_string_symbols,
549                     args,
550                     fmt_expr,
551                 })
552             } else {
553                 None
554             }
555         }
556     }
557 }
558
559 /// Checks if a `let` statement is from a `for` loop desugaring.
560 pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool {
561     // This will detect plain for-loops without an actual variable binding:
562     //
563     // ```
564     // for x in some_vec {
565     //     // do stuff
566     // }
567     // ```
568     if_chain! {
569         if let Some(ref expr) = local.init;
570         if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind;
571         then {
572             return true;
573         }
574     }
575
576     // This detects a variable binding in for loop to avoid `let_unit_value`
577     // lint (see issue #1964).
578     //
579     // ```
580     // for _ in vec![()] {
581     //     // anything
582     // }
583     // ```
584     if let hir::LocalSource::ForLoopDesugar = local.source {
585         return true;
586     }
587
588     false
589 }