]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
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; | |
136023e0 | 8 | use rustc_ast::ast::{self, LitKind}; |
f20569fa XL |
9 | use rustc_hir as hir; |
10 | use rustc_hir::{BorrowKind, Expr, ExprKind, StmtKind, UnOp}; | |
11 | use rustc_lint::LateContext; | |
136023e0 | 12 | use rustc_span::{sym, ExpnKind, Span, Symbol}; |
f20569fa XL |
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 { | |
17df50a5 | 36 | hir::ExprKind::Call(path, args) |
f20569fa XL |
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 | }, | |
17df50a5 | 48 | hir::ExprKind::Struct(path, fields, None) => match path { |
f20569fa XL |
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! { | |
17df50a5 | 90 | if let Some(expr) = local.init; |
f20569fa XL |
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! { | |
17df50a5 XL |
118 | if let hir::ExprKind::Match(iterexpr, arms, hir::MatchSource::ForLoopDesugar) = expr.kind; |
119 | if let hir::ExprKind::Call(_, iterargs) = iterexpr.kind; | |
f20569fa | 120 | if iterargs.len() == 1 && arms.len() == 1 && arms[0].guard.is_none(); |
17df50a5 | 121 | if let hir::ExprKind::Loop(block, ..) = arms[0].body.kind; |
f20569fa XL |
122 | if block.expr.is_none(); |
123 | if let [ _, _, ref let_stmt, ref body ] = *block.stmts; | |
17df50a5 XL |
124 | if let hir::StmtKind::Local(local) = let_stmt.kind; |
125 | if let hir::StmtKind::Expr(expr) = body.kind; | |
f20569fa XL |
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! { | |
17df50a5 | 160 | if let hir::ExprKind::Call(fun, args) = expr.kind; |
f20569fa XL |
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! { | |
17df50a5 XL |
172 | if let hir::ExprKind::Box(boxed) = args[0].kind; |
173 | if let hir::ExprKind::Array(args) = boxed.kind; | |
f20569fa XL |
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! { | |
17df50a5 | 205 | if let ExprKind::Match(headerexpr, _, _) = &matchblock_expr.kind; |
f20569fa XL |
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 | ||
17df50a5 | 216 | if let ExprKind::Block(block, _) = e.kind { |
f20569fa | 217 | if block.stmts.len() == 1 { |
17df50a5 | 218 | if let StmtKind::Semi(matchexpr) = block.stmts.get(0)?.kind { |
f20569fa XL |
219 | // macros with unique arg: `{debug_}assert!` (e.g., `debug_assert!(some_condition)`) |
220 | if_chain! { | |
17df50a5 | 221 | if let ExprKind::If(clause, _, _) = matchexpr.kind; |
f20569fa XL |
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! { | |
17df50a5 XL |
230 | if let ExprKind::Block(matchblock,_) = matchexpr.kind; |
231 | if let Some(matchblock_expr) = matchblock.expr; | |
f20569fa XL |
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)`) | |
17df50a5 | 239 | return ast_matchblock(matchblock_expr); |
f20569fa XL |
240 | } |
241 | } | |
242 | None | |
243 | } | |
136023e0 XL |
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 [`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 | } |