]> git.lizzy.rs Git - rust.git/blob - clippy_utils/src/higher.rs
Merge remote-tracking branch 'upstream/master' into rustup
[rust.git] / clippy_utils / src / higher.rs
1 //! This module contains functions for retrieve the original AST from lowered
2 //! `hir`.
3
4 #![deny(clippy::missing_docs_in_private_items)]
5
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::{BorrowKind, Expr, ExprKind, StmtKind, UnOp};
11 use rustc_lint::LateContext;
12 use rustc_span::{sym, ExpnKind, Span, Symbol};
13
14 /// Represent a range akin to `ast::ExprKind::Range`.
15 #[derive(Debug, Copy, Clone)]
16 pub struct Range<'a> {
17     /// The lower bound of the range, or `None` for ranges such as `..X`.
18     pub start: Option<&'a hir::Expr<'a>>,
19     /// The upper bound of the range, or `None` for ranges such as `X..`.
20     pub end: Option<&'a hir::Expr<'a>>,
21     /// Whether the interval is open or closed.
22     pub limits: ast::RangeLimits,
23 }
24
25 /// Higher a `hir` range to something similar to `ast::ExprKind::Range`.
26 pub fn range<'a>(expr: &'a hir::Expr<'_>) -> Option<Range<'a>> {
27     /// Finds the field named `name` in the field. Always return `Some` for
28     /// convenience.
29     fn get_field<'c>(name: &str, fields: &'c [hir::ExprField<'_>]) -> Option<&'c hir::Expr<'c>> {
30         let expr = &fields.iter().find(|field| field.ident.name.as_str() == name)?.expr;
31
32         Some(expr)
33     }
34
35     match expr.kind {
36         hir::ExprKind::Call(path, args)
37             if matches!(
38                 path.kind,
39                 hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::RangeInclusiveNew, _))
40             ) =>
41         {
42             Some(Range {
43                 start: Some(&args[0]),
44                 end: Some(&args[1]),
45                 limits: ast::RangeLimits::Closed,
46             })
47         },
48         hir::ExprKind::Struct(path, fields, None) => match path {
49             hir::QPath::LangItem(hir::LangItem::RangeFull, _) => Some(Range {
50                 start: None,
51                 end: None,
52                 limits: ast::RangeLimits::HalfOpen,
53             }),
54             hir::QPath::LangItem(hir::LangItem::RangeFrom, _) => Some(Range {
55                 start: Some(get_field("start", fields)?),
56                 end: None,
57                 limits: ast::RangeLimits::HalfOpen,
58             }),
59             hir::QPath::LangItem(hir::LangItem::Range, _) => Some(Range {
60                 start: Some(get_field("start", fields)?),
61                 end: Some(get_field("end", fields)?),
62                 limits: ast::RangeLimits::HalfOpen,
63             }),
64             hir::QPath::LangItem(hir::LangItem::RangeToInclusive, _) => Some(Range {
65                 start: None,
66                 end: Some(get_field("end", fields)?),
67                 limits: ast::RangeLimits::Closed,
68             }),
69             hir::QPath::LangItem(hir::LangItem::RangeTo, _) => Some(Range {
70                 start: None,
71                 end: Some(get_field("end", fields)?),
72                 limits: ast::RangeLimits::HalfOpen,
73             }),
74             _ => None,
75         },
76         _ => None,
77     }
78 }
79
80 /// Checks if a `let` statement is from a `for` loop desugaring.
81 pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool {
82     // This will detect plain for-loops without an actual variable binding:
83     //
84     // ```
85     // for x in some_vec {
86     //     // do stuff
87     // }
88     // ```
89     if_chain! {
90         if let Some(expr) = local.init;
91         if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind;
92         then {
93             return true;
94         }
95     }
96
97     // This detects a variable binding in for loop to avoid `let_unit_value`
98     // lint (see issue #1964).
99     //
100     // ```
101     // for _ in vec![()] {
102     //     // anything
103     // }
104     // ```
105     if let hir::LocalSource::ForLoopDesugar = local.source {
106         return true;
107     }
108
109     false
110 }
111
112 /// Recover the essential nodes of a desugared for loop as well as the entire span:
113 /// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
114 pub fn for_loop<'tcx>(
115     expr: &'tcx hir::Expr<'tcx>,
116 ) -> Option<(&hir::Pat<'_>, &'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>, Span)> {
117     if_chain! {
118         if let hir::ExprKind::Match(iterexpr, arms, hir::MatchSource::ForLoopDesugar) = expr.kind;
119         if let hir::ExprKind::Call(_, iterargs) = iterexpr.kind;
120         if iterargs.len() == 1 && arms.len() == 1 && arms[0].guard.is_none();
121         if let hir::ExprKind::Loop(block, ..) = arms[0].body.kind;
122         if block.expr.is_none();
123         if let [ _, _, ref let_stmt, ref body ] = *block.stmts;
124         if let hir::StmtKind::Local(local) = let_stmt.kind;
125         if let hir::StmtKind::Expr(expr) = body.kind;
126         then {
127             return Some((&*local.pat, &iterargs[0], expr, arms[0].span));
128         }
129     }
130     None
131 }
132
133 /// Recover the essential nodes of a desugared while loop:
134 /// `while cond { body }` becomes `(cond, body)`.
135 pub fn while_loop<'tcx>(expr: &'tcx hir::Expr<'tcx>) -> Option<(&'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>)> {
136     if_chain! {
137         if let hir::ExprKind::Loop(hir::Block { expr: Some(expr), .. }, _, hir::LoopSource::While, _) = &expr.kind;
138         if let hir::ExprKind::Match(cond, arms, hir::MatchSource::WhileDesugar) = &expr.kind;
139         if let hir::ExprKind::DropTemps(cond) = &cond.kind;
140         if let [hir::Arm { body, .. }, ..] = &arms[..];
141         then {
142             return Some((cond, body));
143         }
144     }
145     None
146 }
147
148 /// Represent the pre-expansion arguments of a `vec!` invocation.
149 pub enum VecArgs<'a> {
150     /// `vec![elem; len]`
151     Repeat(&'a hir::Expr<'a>, &'a hir::Expr<'a>),
152     /// `vec![a, b, c]`
153     Vec(&'a [hir::Expr<'a>]),
154 }
155
156 /// Returns the arguments of the `vec!` macro if this expression was expanded
157 /// from `vec!`.
158 pub fn vec_macro<'e>(cx: &LateContext<'_>, expr: &'e hir::Expr<'_>) -> Option<VecArgs<'e>> {
159     if_chain! {
160         if let hir::ExprKind::Call(fun, args) = expr.kind;
161         if let hir::ExprKind::Path(ref qpath) = fun.kind;
162         if is_expn_of(fun.span, "vec").is_some();
163         if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
164         then {
165             return if match_def_path(cx, fun_def_id, &paths::VEC_FROM_ELEM) && args.len() == 2 {
166                 // `vec![elem; size]` case
167                 Some(VecArgs::Repeat(&args[0], &args[1]))
168             }
169             else if match_def_path(cx, fun_def_id, &paths::SLICE_INTO_VEC) && args.len() == 1 {
170                 // `vec![a, b, c]` case
171                 if_chain! {
172                     if let hir::ExprKind::Box(boxed) = args[0].kind;
173                     if let hir::ExprKind::Array(args) = boxed.kind;
174                     then {
175                         return Some(VecArgs::Vec(&*args));
176                     }
177                 }
178
179                 None
180             }
181             else if match_def_path(cx, fun_def_id, &paths::VEC_NEW) && args.is_empty() {
182                 Some(VecArgs::Vec(&[]))
183             }
184             else {
185                 None
186             };
187         }
188     }
189
190     None
191 }
192
193 /// Extract args from an assert-like macro.
194 /// Currently working with:
195 /// - `assert!`, `assert_eq!` and `assert_ne!`
196 /// - `debug_assert!`, `debug_assert_eq!` and `debug_assert_ne!`
197 /// For example:
198 /// `assert!(expr)` will return `Some([expr])`
199 /// `debug_assert_eq!(a, b)` will return `Some([a, b])`
200 pub fn extract_assert_macro_args<'tcx>(e: &'tcx Expr<'tcx>) -> Option<Vec<&'tcx Expr<'tcx>>> {
201     /// Try to match the AST for a pattern that contains a match, for example when two args are
202     /// compared
203     fn ast_matchblock(matchblock_expr: &'tcx Expr<'tcx>) -> Option<Vec<&Expr<'_>>> {
204         if_chain! {
205             if let ExprKind::Match(headerexpr, _, _) = &matchblock_expr.kind;
206             if let ExprKind::Tup([lhs, rhs]) = &headerexpr.kind;
207             if let ExprKind::AddrOf(BorrowKind::Ref, _, lhs) = lhs.kind;
208             if let ExprKind::AddrOf(BorrowKind::Ref, _, rhs) = rhs.kind;
209             then {
210                 return Some(vec![lhs, rhs]);
211             }
212         }
213         None
214     }
215
216     if let ExprKind::Block(block, _) = e.kind {
217         if block.stmts.len() == 1 {
218             if let StmtKind::Semi(matchexpr) = block.stmts.get(0)?.kind {
219                 // macros with unique arg: `{debug_}assert!` (e.g., `debug_assert!(some_condition)`)
220                 if_chain! {
221                     if let ExprKind::If(clause, _, _)  = matchexpr.kind;
222                     if let ExprKind::Unary(UnOp::Not, condition) = clause.kind;
223                     then {
224                         return Some(vec![condition]);
225                     }
226                 }
227
228                 // debug macros with two args: `debug_assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
229                 if_chain! {
230                     if let ExprKind::Block(matchblock,_) = matchexpr.kind;
231                     if let Some(matchblock_expr) = matchblock.expr;
232                     then {
233                         return ast_matchblock(matchblock_expr);
234                     }
235                 }
236             }
237         } else if let Some(matchblock_expr) = block.expr {
238             // macros with two args: `assert_{ne, eq}` (e.g., `assert_ne!(a, b)`)
239             return ast_matchblock(matchblock_expr);
240         }
241     }
242     None
243 }
244
245 /// A parsed `format!` expansion
246 pub struct FormatExpn<'tcx> {
247     /// Span of `format!(..)`
248     pub call_site: Span,
249     /// Inner `format_args!` expansion
250     pub format_args: FormatArgsExpn<'tcx>,
251 }
252
253 impl FormatExpn<'tcx> {
254     /// Parses an expanded `format!` invocation
255     pub fn parse(expr: &'tcx Expr<'tcx>) -> Option<Self> {
256         if_chain! {
257             if let ExprKind::Block(block, _) = expr.kind;
258             if let [stmt] = block.stmts;
259             if let StmtKind::Local(local) = stmt.kind;
260             if let Some(init) = local.init;
261             if let ExprKind::Call(_, [format_args]) = init.kind;
262             let expn_data = expr.span.ctxt().outer_expn_data();
263             if let ExpnKind::Macro(_, sym::format) = expn_data.kind;
264             if let Some(format_args) = FormatArgsExpn::parse(format_args);
265             then {
266                 Some(FormatExpn {
267                     call_site: expn_data.call_site,
268                     format_args,
269                 })
270             } else {
271                 None
272             }
273         }
274     }
275 }
276
277 /// A parsed `format_args!` expansion
278 pub struct FormatArgsExpn<'tcx> {
279     /// Span of the first argument, the format string
280     pub format_string_span: Span,
281     /// Values passed after the format string
282     pub value_args: Vec<&'tcx Expr<'tcx>>,
283
284     /// String literal expressions which represent the format string split by "{}"
285     pub format_string_parts: &'tcx [Expr<'tcx>],
286     /// Symbols corresponding to [`Self::format_string_parts`]
287     pub format_string_symbols: Vec<Symbol>,
288     /// Expressions like `ArgumentV1::new(arg0, Debug::fmt)`
289     pub args: &'tcx [Expr<'tcx>],
290     /// The final argument passed to `Arguments::new_v1_formatted`, if applicable
291     pub fmt_expr: Option<&'tcx Expr<'tcx>>,
292 }
293
294 impl FormatArgsExpn<'tcx> {
295     /// Parses an expanded `format_args!` or `format_args_nl!` invocation
296     pub fn parse(expr: &'tcx Expr<'tcx>) -> Option<Self> {
297         if_chain! {
298             if let ExpnKind::Macro(_, name) = expr.span.ctxt().outer_expn_data().kind;
299             let name = name.as_str();
300             if name.ends_with("format_args") || name.ends_with("format_args_nl");
301             if let ExprKind::Call(_, args) = expr.kind;
302             if let Some((strs_ref, args, fmt_expr)) = match args {
303                 // Arguments::new_v1
304                 [strs_ref, args] => Some((strs_ref, args, None)),
305                 // Arguments::new_v1_formatted
306                 [strs_ref, args, fmt_expr] => Some((strs_ref, args, Some(fmt_expr))),
307                 _ => None,
308             };
309             if let ExprKind::AddrOf(BorrowKind::Ref, _, strs_arr) = strs_ref.kind;
310             if let ExprKind::Array(format_string_parts) = strs_arr.kind;
311             if let Some(format_string_symbols) = format_string_parts
312                 .iter()
313                 .map(|e| {
314                     if let ExprKind::Lit(lit) = &e.kind {
315                         if let LitKind::Str(symbol, _style) = lit.node {
316                             return Some(symbol);
317                         }
318                     }
319                     None
320                 })
321                 .collect();
322             if let ExprKind::AddrOf(BorrowKind::Ref, _, args) = args.kind;
323             if let ExprKind::Match(args, [arm], _) = args.kind;
324             if let ExprKind::Tup(value_args) = args.kind;
325             if let Some(value_args) = value_args
326                 .iter()
327                 .map(|e| match e.kind {
328                     ExprKind::AddrOf(_, _, e) => Some(e),
329                     _ => None,
330                 })
331                 .collect();
332             if let ExprKind::Array(args) = arm.body.kind;
333             then {
334                 Some(FormatArgsExpn {
335                     format_string_span: strs_ref.span,
336                     value_args,
337                     format_string_parts,
338                     format_string_symbols,
339                     args,
340                     fmt_expr,
341                 })
342             } else {
343                 None
344             }
345         }
346     }
347 }