]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_utils/src/macros.rs
Auto merge of #93348 - spastorino:fix-perf-overlap-mode2, r=nikomatsakis
[rust.git] / src / tools / clippy / clippy_utils / src / macros.rs
1 #![allow(clippy::similar_names)] // `expr` and `expn`
2
3 use crate::visitors::expr_visitor_no_bodies;
4
5 use arrayvec::ArrayVec;
6 use if_chain::if_chain;
7 use rustc_ast::ast::LitKind;
8 use rustc_hir::intravisit::Visitor;
9 use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
10 use rustc_lint::LateContext;
11 use rustc_span::def_id::DefId;
12 use rustc_span::hygiene::{MacroKind, SyntaxContext};
13 use rustc_span::{sym, ExpnData, ExpnId, ExpnKind, Span, Symbol};
14 use std::ops::ControlFlow;
15
16 /// A macro call, like `vec![1, 2, 3]`.
17 ///
18 /// Use `tcx.item_name(macro_call.def_id)` to get the macro name.
19 /// Even better is to check if it is a diagnostic item.
20 ///
21 /// This structure is similar to `ExpnData` but it precludes desugaring expansions.
22 #[derive(Debug)]
23 pub struct MacroCall {
24     /// Macro `DefId`
25     pub def_id: DefId,
26     /// Kind of macro
27     pub kind: MacroKind,
28     /// The expansion produced by the macro call
29     pub expn: ExpnId,
30     /// Span of the macro call site
31     pub span: Span,
32 }
33
34 impl MacroCall {
35     pub fn is_local(&self) -> bool {
36         span_is_local(self.span)
37     }
38 }
39
40 /// Returns an iterator of expansions that created the given span
41 pub fn expn_backtrace(mut span: Span) -> impl Iterator<Item = (ExpnId, ExpnData)> {
42     std::iter::from_fn(move || {
43         let ctxt = span.ctxt();
44         if ctxt == SyntaxContext::root() {
45             return None;
46         }
47         let expn = ctxt.outer_expn();
48         let data = expn.expn_data();
49         span = data.call_site;
50         Some((expn, data))
51     })
52 }
53
54 /// Checks whether the span is from the root expansion or a locally defined macro
55 pub fn span_is_local(span: Span) -> bool {
56     !span.from_expansion() || expn_is_local(span.ctxt().outer_expn())
57 }
58
59 /// Checks whether the expansion is the root expansion or a locally defined macro
60 pub fn expn_is_local(expn: ExpnId) -> bool {
61     if expn == ExpnId::root() {
62         return true;
63     }
64     let data = expn.expn_data();
65     let backtrace = expn_backtrace(data.call_site);
66     std::iter::once((expn, data))
67         .chain(backtrace)
68         .find_map(|(_, data)| data.macro_def_id)
69         .map_or(true, DefId::is_local)
70 }
71
72 /// Returns an iterator of macro expansions that created the given span.
73 /// Note that desugaring expansions are skipped.
74 pub fn macro_backtrace(span: Span) -> impl Iterator<Item = MacroCall> {
75     expn_backtrace(span).filter_map(|(expn, data)| match data {
76         ExpnData {
77             kind: ExpnKind::Macro(kind, _),
78             macro_def_id: Some(def_id),
79             call_site: span,
80             ..
81         } => Some(MacroCall {
82             def_id,
83             kind,
84             expn,
85             span,
86         }),
87         _ => None,
88     })
89 }
90
91 /// If the macro backtrace of `span` has a macro call at the root expansion
92 /// (i.e. not a nested macro call), returns `Some` with the `MacroCall`
93 pub fn root_macro_call(span: Span) -> Option<MacroCall> {
94     macro_backtrace(span).last()
95 }
96
97 /// Like [`root_macro_call`], but only returns `Some` if `node` is the "first node"
98 /// produced by the macro call, as in [`first_node_in_macro`].
99 pub fn root_macro_call_first_node(cx: &LateContext<'_>, node: &impl HirNode) -> Option<MacroCall> {
100     if first_node_in_macro(cx, node) != Some(ExpnId::root()) {
101         return None;
102     }
103     root_macro_call(node.span())
104 }
105
106 /// Like [`macro_backtrace`], but only returns macro calls where `node` is the "first node" of the
107 /// macro call, as in [`first_node_in_macro`].
108 pub fn first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> impl Iterator<Item = MacroCall> {
109     let span = node.span();
110     first_node_in_macro(cx, node)
111         .into_iter()
112         .flat_map(move |expn| macro_backtrace(span).take_while(move |macro_call| macro_call.expn != expn))
113 }
114
115 /// If `node` is the "first node" in a macro expansion, returns `Some` with the `ExpnId` of the
116 /// macro call site (i.e. the parent of the macro expansion). This generally means that `node`
117 /// is the outermost node of an entire macro expansion, but there are some caveats noted below.
118 /// This is useful for finding macro calls while visiting the HIR without processing the macro call
119 /// at every node within its expansion.
120 ///
121 /// If you already have immediate access to the parent node, it is simpler to
122 /// just check the context of that span directly (e.g. `parent.span.from_expansion()`).
123 ///
124 /// If a macro call is in statement position, it expands to one or more statements.
125 /// In that case, each statement *and* their immediate descendants will all yield `Some`
126 /// with the `ExpnId` of the containing block.
127 ///
128 /// A node may be the "first node" of multiple macro calls in a macro backtrace.
129 /// The expansion of the outermost macro call site is returned in such cases.
130 pub fn first_node_in_macro(cx: &LateContext<'_>, node: &impl HirNode) -> Option<ExpnId> {
131     // get the macro expansion or return `None` if not found
132     // `macro_backtrace` importantly ignores desugaring expansions
133     let expn = macro_backtrace(node.span()).next()?.expn;
134
135     // get the parent node, possibly skipping over a statement
136     // if the parent is not found, it is sensible to return `Some(root)`
137     let hir = cx.tcx.hir();
138     let mut parent_iter = hir.parent_iter(node.hir_id());
139     let (parent_id, _) = match parent_iter.next() {
140         None => return Some(ExpnId::root()),
141         Some((_, Node::Stmt(_))) => match parent_iter.next() {
142             None => return Some(ExpnId::root()),
143             Some(next) => next,
144         },
145         Some(next) => next,
146     };
147
148     // get the macro expansion of the parent node
149     let parent_span = hir.span(parent_id);
150     let Some(parent_macro_call) = macro_backtrace(parent_span).next() else {
151         // the parent node is not in a macro
152         return Some(ExpnId::root());
153     };
154
155     if parent_macro_call.expn.is_descendant_of(expn) {
156         // `node` is input to a macro call
157         return None;
158     }
159
160     Some(parent_macro_call.expn)
161 }
162
163 /* Specific Macro Utils */
164
165 /// Is `def_id` of `std::panic`, `core::panic` or any inner implementation macros
166 pub fn is_panic(cx: &LateContext<'_>, def_id: DefId) -> bool {
167     let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return false };
168     matches!(
169         name.as_str(),
170         "core_panic_macro"
171             | "std_panic_macro"
172             | "core_panic_2015_macro"
173             | "std_panic_2015_macro"
174             | "core_panic_2021_macro"
175     )
176 }
177
178 pub enum PanicExpn<'a> {
179     /// No arguments - `panic!()`
180     Empty,
181     /// A string literal or any `&str` - `panic!("message")` or `panic!(message)`
182     Str(&'a Expr<'a>),
183     /// A single argument that implements `Display` - `panic!("{}", object)`
184     Display(&'a Expr<'a>),
185     /// Anything else - `panic!("error {}: {}", a, b)`
186     Format(FormatArgsExpn<'a>),
187 }
188
189 impl<'a> PanicExpn<'a> {
190     pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Self> {
191         if !macro_backtrace(expr.span).any(|macro_call| is_panic(cx, macro_call.def_id)) {
192             return None;
193         }
194         let ExprKind::Call(callee, [arg]) = &expr.kind else { return None };
195         let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else { return None };
196         let result = match path.segments.last().unwrap().ident.as_str() {
197             "panic" if arg.span.ctxt() == expr.span.ctxt() => Self::Empty,
198             "panic" | "panic_str" => Self::Str(arg),
199             "panic_display" => {
200                 let ExprKind::AddrOf(_, _, e) = &arg.kind else { return None };
201                 Self::Display(e)
202             },
203             "panic_fmt" => Self::Format(FormatArgsExpn::parse(cx, arg)?),
204             _ => return None,
205         };
206         Some(result)
207     }
208 }
209
210 /// Finds the arguments of an `assert!` or `debug_assert!` macro call within the macro expansion
211 pub fn find_assert_args<'a>(
212     cx: &LateContext<'_>,
213     expr: &'a Expr<'a>,
214     expn: ExpnId,
215 ) -> Option<(&'a Expr<'a>, PanicExpn<'a>)> {
216     find_assert_args_inner(cx, expr, expn).map(|([e], p)| (e, p))
217 }
218
219 /// Finds the arguments of an `assert_eq!` or `debug_assert_eq!` macro call within the macro
220 /// expansion
221 pub fn find_assert_eq_args<'a>(
222     cx: &LateContext<'_>,
223     expr: &'a Expr<'a>,
224     expn: ExpnId,
225 ) -> Option<(&'a Expr<'a>, &'a Expr<'a>, PanicExpn<'a>)> {
226     find_assert_args_inner(cx, expr, expn).map(|([a, b], p)| (a, b, p))
227 }
228
229 fn find_assert_args_inner<'a, const N: usize>(
230     cx: &LateContext<'_>,
231     expr: &'a Expr<'a>,
232     expn: ExpnId,
233 ) -> Option<([&'a Expr<'a>; N], PanicExpn<'a>)> {
234     let macro_id = expn.expn_data().macro_def_id?;
235     let (expr, expn) = match cx.tcx.item_name(macro_id).as_str().strip_prefix("debug_") {
236         None => (expr, expn),
237         Some(inner_name) => find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?,
238     };
239     let mut args = ArrayVec::new();
240     let mut panic_expn = None;
241     expr_visitor_no_bodies(|e| {
242         if args.is_full() {
243             if panic_expn.is_none() && e.span.ctxt() != expr.span.ctxt() {
244                 panic_expn = PanicExpn::parse(cx, e);
245             }
246             panic_expn.is_none()
247         } else if is_assert_arg(cx, e, expn) {
248             args.push(e);
249             false
250         } else {
251             true
252         }
253     })
254     .visit_expr(expr);
255     let args = args.into_inner().ok()?;
256     // if no `panic!(..)` is found, use `PanicExpn::Empty`
257     // to indicate that the default assertion message is used
258     let panic_expn = panic_expn.unwrap_or(PanicExpn::Empty);
259     Some((args, panic_expn))
260 }
261
262 fn find_assert_within_debug_assert<'a>(
263     cx: &LateContext<'_>,
264     expr: &'a Expr<'a>,
265     expn: ExpnId,
266     assert_name: Symbol,
267 ) -> Option<(&'a Expr<'a>, ExpnId)> {
268     let mut found = None;
269     expr_visitor_no_bodies(|e| {
270         if found.is_some() || !e.span.from_expansion() {
271             return false;
272         }
273         let e_expn = e.span.ctxt().outer_expn();
274         if e_expn == expn {
275             return true;
276         }
277         if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
278             found = Some((e, e_expn));
279         }
280         false
281     })
282     .visit_expr(expr);
283     found
284 }
285
286 fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool {
287     if !expr.span.from_expansion() {
288         return true;
289     }
290     let result = macro_backtrace(expr.span).try_for_each(|macro_call| {
291         if macro_call.expn == assert_expn {
292             ControlFlow::Break(false)
293         } else {
294             match cx.tcx.item_name(macro_call.def_id) {
295                 // `cfg!(debug_assertions)` in `debug_assert!`
296                 sym::cfg => ControlFlow::CONTINUE,
297                 // assert!(other_macro!(..))
298                 _ => ControlFlow::Break(true),
299             }
300         }
301     });
302     match result {
303         ControlFlow::Break(is_assert_arg) => is_assert_arg,
304         ControlFlow::Continue(()) => true,
305     }
306 }
307
308 /// A parsed `format_args!` expansion
309 pub struct FormatArgsExpn<'tcx> {
310     /// Span of the first argument, the format string
311     pub format_string_span: Span,
312     /// The format string split by formatted args like `{..}`
313     pub format_string_parts: Vec<Symbol>,
314     /// Values passed after the format string
315     pub value_args: Vec<&'tcx Expr<'tcx>>,
316     /// Each element is a `value_args` index and a formatting trait (e.g. `sym::Debug`)
317     pub formatters: Vec<(usize, Symbol)>,
318     /// List of `fmt::v1::Argument { .. }` expressions. If this is empty,
319     /// then `formatters` represents the format args (`{..}`).
320     /// If this is non-empty, it represents the format args, and the `position`
321     /// parameters within the struct expressions are indexes of `formatters`.
322     pub specs: Vec<&'tcx Expr<'tcx>>,
323 }
324
325 impl<'tcx> FormatArgsExpn<'tcx> {
326     /// Parses an expanded `format_args!` or `format_args_nl!` invocation
327     pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
328         macro_backtrace(expr.span).find(|macro_call| {
329             matches!(
330                 cx.tcx.item_name(macro_call.def_id),
331                 sym::const_format_args | sym::format_args | sym::format_args_nl
332             )
333         })?;
334         let mut format_string_span: Option<Span> = None;
335         let mut format_string_parts: Vec<Symbol> = Vec::new();
336         let mut value_args: Vec<&Expr<'_>> = Vec::new();
337         let mut formatters: Vec<(usize, Symbol)> = Vec::new();
338         let mut specs: Vec<&Expr<'_>> = Vec::new();
339         expr_visitor_no_bodies(|e| {
340             // if we're still inside of the macro definition...
341             if e.span.ctxt() == expr.span.ctxt() {
342                 // ArgumnetV1::new_<format_trait>(<value>)
343                 if_chain! {
344                     if let ExprKind::Call(callee, [val]) = e.kind;
345                     if let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind;
346                     if let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind;
347                     if path.segments.last().unwrap().ident.name == sym::ArgumentV1;
348                     if seg.ident.name.as_str().starts_with("new_");
349                     then {
350                         let val_idx = if_chain! {
351                             if val.span.ctxt() == expr.span.ctxt();
352                             if let ExprKind::Field(_, field) = val.kind;
353                             if let Ok(idx) = field.name.as_str().parse();
354                             then {
355                                 // tuple index
356                                 idx
357                             } else {
358                                 // assume the value expression is passed directly
359                                 formatters.len()
360                             }
361                         };
362                         let fmt_trait = match seg.ident.name.as_str() {
363                             "new_display" => "Display",
364                             "new_debug" => "Debug",
365                             "new_lower_exp" => "LowerExp",
366                             "new_upper_exp" => "UpperExp",
367                             "new_octal" => "Octal",
368                             "new_pointer" => "Pointer",
369                             "new_binary" => "Binary",
370                             "new_lower_hex" => "LowerHex",
371                             "new_upper_hex" => "UpperHex",
372                             _ => unreachable!(),
373                         };
374                         formatters.push((val_idx, Symbol::intern(fmt_trait)));
375                     }
376                 }
377                 if let ExprKind::Struct(QPath::Resolved(_, path), ..) = e.kind {
378                     if path.segments.last().unwrap().ident.name == sym::Argument {
379                         specs.push(e);
380                     }
381                 }
382                 // walk through the macro expansion
383                 return true;
384             }
385             // assume that the first expr with a differing context represents
386             // (and has the span of) the format string
387             if format_string_span.is_none() {
388                 format_string_span = Some(e.span);
389                 let span = e.span;
390                 // walk the expr and collect string literals which are format string parts
391                 expr_visitor_no_bodies(|e| {
392                     if e.span.ctxt() != span.ctxt() {
393                         // defensive check, probably doesn't happen
394                         return false;
395                     }
396                     if let ExprKind::Lit(lit) = &e.kind {
397                         if let LitKind::Str(symbol, _s) = lit.node {
398                             format_string_parts.push(symbol);
399                         }
400                     }
401                     true
402                 })
403                 .visit_expr(e);
404             } else {
405                 // assume that any further exprs with a differing context are value args
406                 value_args.push(e);
407             }
408             // don't walk anything not from the macro expansion (e.a. inputs)
409             false
410         })
411         .visit_expr(expr);
412         Some(FormatArgsExpn {
413             format_string_span: format_string_span?,
414             format_string_parts,
415             value_args,
416             formatters,
417             specs,
418         })
419     }
420
421     /// Finds a nested call to `format_args!` within a `format!`-like macro call
422     pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
423         let mut format_args = None;
424         expr_visitor_no_bodies(|e| {
425             if format_args.is_some() {
426                 return false;
427             }
428             let e_ctxt = e.span.ctxt();
429             if e_ctxt == expr.span.ctxt() {
430                 return true;
431             }
432             if e_ctxt.outer_expn().is_descendant_of(expn_id) {
433                 format_args = FormatArgsExpn::parse(cx, e);
434             }
435             false
436         })
437         .visit_expr(expr);
438         format_args
439     }
440
441     /// Returns a vector of `FormatArgsArg`.
442     pub fn args(&self) -> Option<Vec<FormatArgsArg<'tcx>>> {
443         if self.specs.is_empty() {
444             let args = std::iter::zip(&self.value_args, &self.formatters)
445                 .map(|(value, &(_, format_trait))| FormatArgsArg {
446                     value,
447                     format_trait,
448                     spec: None,
449                 })
450                 .collect();
451             return Some(args);
452         }
453         self.specs
454             .iter()
455             .map(|spec| {
456                 if_chain! {
457                     // struct `core::fmt::rt::v1::Argument`
458                     if let ExprKind::Struct(_, fields, _) = spec.kind;
459                     if let Some(position_field) = fields.iter().find(|f| f.ident.name == sym::position);
460                     if let ExprKind::Lit(lit) = &position_field.expr.kind;
461                     if let LitKind::Int(position, _) = lit.node;
462                     if let Ok(i) = usize::try_from(position);
463                     if let Some(&(j, format_trait)) = self.formatters.get(i);
464                     then {
465                         Some(FormatArgsArg { value: self.value_args[j], format_trait, spec: Some(spec) })
466                     } else {
467                         None
468                     }
469                 }
470             })
471             .collect()
472     }
473
474     /// Span of all inputs
475     pub fn inputs_span(&self) -> Span {
476         match *self.value_args {
477             [] => self.format_string_span,
478             [.., last] => self.format_string_span.to(last.span),
479         }
480     }
481 }
482
483 /// Type representing a `FormatArgsExpn`'s format arguments
484 pub struct FormatArgsArg<'tcx> {
485     /// An element of `value_args` according to `position`
486     pub value: &'tcx Expr<'tcx>,
487     /// An element of `args` according to `position`
488     pub format_trait: Symbol,
489     /// An element of `specs`
490     pub spec: Option<&'tcx Expr<'tcx>>,
491 }
492
493 impl<'tcx> FormatArgsArg<'tcx> {
494     /// Returns true if any formatting parameters are used that would have an effect on strings,
495     /// like `{:+2}` instead of just `{}`.
496     pub fn has_string_formatting(&self) -> bool {
497         self.spec.map_or(false, |spec| {
498             // `!` because these conditions check that `self` is unformatted.
499             !if_chain! {
500                 // struct `core::fmt::rt::v1::Argument`
501                 if let ExprKind::Struct(_, fields, _) = spec.kind;
502                 if let Some(format_field) = fields.iter().find(|f| f.ident.name == sym::format);
503                 // struct `core::fmt::rt::v1::FormatSpec`
504                 if let ExprKind::Struct(_, subfields, _) = format_field.expr.kind;
505                 if subfields.iter().all(|field| match field.ident.name {
506                     sym::precision | sym::width => match field.expr.kind {
507                         ExprKind::Path(QPath::Resolved(_, path)) => {
508                             path.segments.last().unwrap().ident.name == sym::Implied
509                         }
510                         _ => false,
511                     }
512                     _ => true,
513                 });
514                 then { true } else { false }
515             }
516         })
517     }
518 }
519
520 /// A node with a `HirId` and a `Span`
521 pub trait HirNode {
522     fn hir_id(&self) -> HirId;
523     fn span(&self) -> Span;
524 }
525
526 macro_rules! impl_hir_node {
527     ($($t:ident),*) => {
528         $(impl HirNode for hir::$t<'_> {
529             fn hir_id(&self) -> HirId {
530                 self.hir_id
531             }
532             fn span(&self) -> Span {
533                 self.span
534             }
535         })*
536     };
537 }
538
539 impl_hir_node!(Expr, Pat);
540
541 impl HirNode for hir::Item<'_> {
542     fn hir_id(&self) -> HirId {
543         self.hir_id()
544     }
545
546     fn span(&self) -> Span {
547         self.span
548     }
549 }