]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_utils/src/macros.rs
New upstream version 1.70.0+dfsg1
[rustc.git] / src / tools / clippy / clippy_utils / src / macros.rs
1 #![allow(clippy::similar_names)] // `expr` and `expn`
2
3 use crate::visitors::{for_each_expr, Descend};
4
5 use arrayvec::ArrayVec;
6 use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
7 use rustc_data_structures::fx::FxHashMap;
8 use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
9 use rustc_lint::LateContext;
10 use rustc_span::def_id::DefId;
11 use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
12 use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, Symbol};
13 use std::cell::RefCell;
14 use std::ops::ControlFlow;
15 use std::sync::atomic::{AtomicBool, Ordering};
16
17 const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
18 sym::assert_eq_macro,
19 sym::assert_macro,
20 sym::assert_ne_macro,
21 sym::debug_assert_eq_macro,
22 sym::debug_assert_macro,
23 sym::debug_assert_ne_macro,
24 sym::eprint_macro,
25 sym::eprintln_macro,
26 sym::format_args_macro,
27 sym::format_macro,
28 sym::print_macro,
29 sym::println_macro,
30 sym::std_panic_macro,
31 sym::write_macro,
32 sym::writeln_macro,
33 ];
34
35 /// Returns true if a given Macro `DefId` is a format macro (e.g. `println!`)
36 pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool {
37 if let Some(name) = cx.tcx.get_diagnostic_name(macro_def_id) {
38 FORMAT_MACRO_DIAG_ITEMS.contains(&name)
39 } else {
40 false
41 }
42 }
43
44 /// A macro call, like `vec![1, 2, 3]`.
45 ///
46 /// Use `tcx.item_name(macro_call.def_id)` to get the macro name.
47 /// Even better is to check if it is a diagnostic item.
48 ///
49 /// This structure is similar to `ExpnData` but it precludes desugaring expansions.
50 #[derive(Debug)]
51 pub struct MacroCall {
52 /// Macro `DefId`
53 pub def_id: DefId,
54 /// Kind of macro
55 pub kind: MacroKind,
56 /// The expansion produced by the macro call
57 pub expn: ExpnId,
58 /// Span of the macro call site
59 pub span: Span,
60 }
61
62 impl MacroCall {
63 pub fn is_local(&self) -> bool {
64 span_is_local(self.span)
65 }
66 }
67
68 /// Returns an iterator of expansions that created the given span
69 pub fn expn_backtrace(mut span: Span) -> impl Iterator<Item = (ExpnId, ExpnData)> {
70 std::iter::from_fn(move || {
71 let ctxt = span.ctxt();
72 if ctxt == SyntaxContext::root() {
73 return None;
74 }
75 let expn = ctxt.outer_expn();
76 let data = expn.expn_data();
77 span = data.call_site;
78 Some((expn, data))
79 })
80 }
81
82 /// Checks whether the span is from the root expansion or a locally defined macro
83 pub fn span_is_local(span: Span) -> bool {
84 !span.from_expansion() || expn_is_local(span.ctxt().outer_expn())
85 }
86
87 /// Checks whether the expansion is the root expansion or a locally defined macro
88 pub fn expn_is_local(expn: ExpnId) -> bool {
89 if expn == ExpnId::root() {
90 return true;
91 }
92 let data = expn.expn_data();
93 let backtrace = expn_backtrace(data.call_site);
94 std::iter::once((expn, data))
95 .chain(backtrace)
96 .find_map(|(_, data)| data.macro_def_id)
97 .map_or(true, DefId::is_local)
98 }
99
100 /// Returns an iterator of macro expansions that created the given span.
101 /// Note that desugaring expansions are skipped.
102 pub fn macro_backtrace(span: Span) -> impl Iterator<Item = MacroCall> {
103 expn_backtrace(span).filter_map(|(expn, data)| match data {
104 ExpnData {
105 kind: ExpnKind::Macro(kind, _),
106 macro_def_id: Some(def_id),
107 call_site: span,
108 ..
109 } => Some(MacroCall {
110 def_id,
111 kind,
112 expn,
113 span,
114 }),
115 _ => None,
116 })
117 }
118
119 /// If the macro backtrace of `span` has a macro call at the root expansion
120 /// (i.e. not a nested macro call), returns `Some` with the `MacroCall`
121 pub fn root_macro_call(span: Span) -> Option<MacroCall> {
122 macro_backtrace(span).last()
123 }
124
125 /// Like [`root_macro_call`], but only returns `Some` if `node` is the "first node"
126 /// produced by the macro call, as in [`first_node_in_macro`].
127 pub fn root_macro_call_first_node(cx: &LateContext<'_>, node: &impl HirNode) -> Option<MacroCall> {
128 if first_node_in_macro(cx, node) != Some(ExpnId::root()) {
129 return None;
130 }
131 root_macro_call(node.span())
132 }
133
134 /// Like [`macro_backtrace`], but only returns macro calls where `node` is the "first node" of the
135 /// macro call, as in [`first_node_in_macro`].
136 pub fn first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> impl Iterator<Item = MacroCall> {
137 let span = node.span();
138 first_node_in_macro(cx, node)
139 .into_iter()
140 .flat_map(move |expn| macro_backtrace(span).take_while(move |macro_call| macro_call.expn != expn))
141 }
142
143 /// If `node` is the "first node" in a macro expansion, returns `Some` with the `ExpnId` of the
144 /// macro call site (i.e. the parent of the macro expansion). This generally means that `node`
145 /// is the outermost node of an entire macro expansion, but there are some caveats noted below.
146 /// This is useful for finding macro calls while visiting the HIR without processing the macro call
147 /// at every node within its expansion.
148 ///
149 /// If you already have immediate access to the parent node, it is simpler to
150 /// just check the context of that span directly (e.g. `parent.span.from_expansion()`).
151 ///
152 /// If a macro call is in statement position, it expands to one or more statements.
153 /// In that case, each statement *and* their immediate descendants will all yield `Some`
154 /// with the `ExpnId` of the containing block.
155 ///
156 /// A node may be the "first node" of multiple macro calls in a macro backtrace.
157 /// The expansion of the outermost macro call site is returned in such cases.
158 pub fn first_node_in_macro(cx: &LateContext<'_>, node: &impl HirNode) -> Option<ExpnId> {
159 // get the macro expansion or return `None` if not found
160 // `macro_backtrace` importantly ignores desugaring expansions
161 let expn = macro_backtrace(node.span()).next()?.expn;
162
163 // get the parent node, possibly skipping over a statement
164 // if the parent is not found, it is sensible to return `Some(root)`
165 let hir = cx.tcx.hir();
166 let mut parent_iter = hir.parent_iter(node.hir_id());
167 let (parent_id, _) = match parent_iter.next() {
168 None => return Some(ExpnId::root()),
169 Some((_, Node::Stmt(_))) => match parent_iter.next() {
170 None => return Some(ExpnId::root()),
171 Some(next) => next,
172 },
173 Some(next) => next,
174 };
175
176 // get the macro expansion of the parent node
177 let parent_span = hir.span(parent_id);
178 let Some(parent_macro_call) = macro_backtrace(parent_span).next() else {
179 // the parent node is not in a macro
180 return Some(ExpnId::root());
181 };
182
183 if parent_macro_call.expn.is_descendant_of(expn) {
184 // `node` is input to a macro call
185 return None;
186 }
187
188 Some(parent_macro_call.expn)
189 }
190
191 /* Specific Macro Utils */
192
193 /// Is `def_id` of `std::panic`, `core::panic` or any inner implementation macros
194 pub fn is_panic(cx: &LateContext<'_>, def_id: DefId) -> bool {
195 let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return false };
196 matches!(
197 name,
198 sym::core_panic_macro
199 | sym::std_panic_macro
200 | sym::core_panic_2015_macro
201 | sym::std_panic_2015_macro
202 | sym::core_panic_2021_macro
203 )
204 }
205
206 /// Is `def_id` of `assert!` or `debug_assert!`
207 pub fn is_assert_macro(cx: &LateContext<'_>, def_id: DefId) -> bool {
208 let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return false };
209 matches!(name, sym::assert_macro | sym::debug_assert_macro)
210 }
211
212 #[derive(Debug)]
213 pub enum PanicExpn<'a> {
214 /// No arguments - `panic!()`
215 Empty,
216 /// A string literal or any `&str` - `panic!("message")` or `panic!(message)`
217 Str(&'a Expr<'a>),
218 /// A single argument that implements `Display` - `panic!("{}", object)`
219 Display(&'a Expr<'a>),
220 /// Anything else - `panic!("error {}: {}", a, b)`
221 Format(&'a Expr<'a>),
222 }
223
224 impl<'a> PanicExpn<'a> {
225 pub fn parse(expr: &'a Expr<'a>) -> Option<Self> {
226 let ExprKind::Call(callee, [arg, rest @ ..]) = &expr.kind else { return None };
227 let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else { return None };
228 let result = match path.segments.last().unwrap().ident.as_str() {
229 "panic" if arg.span.ctxt() == expr.span.ctxt() => Self::Empty,
230 "panic" | "panic_str" => Self::Str(arg),
231 "panic_display" => {
232 let ExprKind::AddrOf(_, _, e) = &arg.kind else { return None };
233 Self::Display(e)
234 },
235 "panic_fmt" => Self::Format(arg),
236 // Since Rust 1.52, `assert_{eq,ne}` macros expand to use:
237 // `core::panicking::assert_failed(.., left_val, right_val, None | Some(format_args!(..)));`
238 "assert_failed" => {
239 // It should have 4 arguments in total (we already matched with the first argument,
240 // so we're just checking for 3)
241 if rest.len() != 3 {
242 return None;
243 }
244 // `msg_arg` is either `None` (no custom message) or `Some(format_args!(..))` (custom message)
245 let msg_arg = &rest[2];
246 match msg_arg.kind {
247 ExprKind::Call(_, [fmt_arg]) => Self::Format(fmt_arg),
248 _ => Self::Empty,
249 }
250 },
251 _ => return None,
252 };
253 Some(result)
254 }
255 }
256
257 /// Finds the arguments of an `assert!` or `debug_assert!` macro call within the macro expansion
258 pub fn find_assert_args<'a>(
259 cx: &LateContext<'_>,
260 expr: &'a Expr<'a>,
261 expn: ExpnId,
262 ) -> Option<(&'a Expr<'a>, PanicExpn<'a>)> {
263 find_assert_args_inner(cx, expr, expn).map(|([e], mut p)| {
264 // `assert!(..)` expands to `core::panicking::panic("assertion failed: ...")` (which we map to
265 // `PanicExpn::Str(..)`) and `assert!(.., "..")` expands to
266 // `core::panicking::panic_fmt(format_args!(".."))` (which we map to `PanicExpn::Format(..)`).
267 // So even we got `PanicExpn::Str(..)` that means there is no custom message provided
268 if let PanicExpn::Str(_) = p {
269 p = PanicExpn::Empty;
270 }
271
272 (e, p)
273 })
274 }
275
276 /// Finds the arguments of an `assert_eq!` or `debug_assert_eq!` macro call within the macro
277 /// expansion
278 pub fn find_assert_eq_args<'a>(
279 cx: &LateContext<'_>,
280 expr: &'a Expr<'a>,
281 expn: ExpnId,
282 ) -> Option<(&'a Expr<'a>, &'a Expr<'a>, PanicExpn<'a>)> {
283 find_assert_args_inner(cx, expr, expn).map(|([a, b], p)| (a, b, p))
284 }
285
286 fn find_assert_args_inner<'a, const N: usize>(
287 cx: &LateContext<'_>,
288 expr: &'a Expr<'a>,
289 expn: ExpnId,
290 ) -> Option<([&'a Expr<'a>; N], PanicExpn<'a>)> {
291 let macro_id = expn.expn_data().macro_def_id?;
292 let (expr, expn) = match cx.tcx.item_name(macro_id).as_str().strip_prefix("debug_") {
293 None => (expr, expn),
294 Some(inner_name) => find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?,
295 };
296 let mut args = ArrayVec::new();
297 let panic_expn = for_each_expr(expr, |e| {
298 if args.is_full() {
299 match PanicExpn::parse(e) {
300 Some(expn) => ControlFlow::Break(expn),
301 None => ControlFlow::Continue(Descend::Yes),
302 }
303 } else if is_assert_arg(cx, e, expn) {
304 args.push(e);
305 ControlFlow::Continue(Descend::No)
306 } else {
307 ControlFlow::Continue(Descend::Yes)
308 }
309 });
310 let args = args.into_inner().ok()?;
311 // if no `panic!(..)` is found, use `PanicExpn::Empty`
312 // to indicate that the default assertion message is used
313 let panic_expn = panic_expn.unwrap_or(PanicExpn::Empty);
314 Some((args, panic_expn))
315 }
316
317 fn find_assert_within_debug_assert<'a>(
318 cx: &LateContext<'_>,
319 expr: &'a Expr<'a>,
320 expn: ExpnId,
321 assert_name: Symbol,
322 ) -> Option<(&'a Expr<'a>, ExpnId)> {
323 for_each_expr(expr, |e| {
324 if !e.span.from_expansion() {
325 return ControlFlow::Continue(Descend::No);
326 }
327 let e_expn = e.span.ctxt().outer_expn();
328 if e_expn == expn {
329 ControlFlow::Continue(Descend::Yes)
330 } else if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
331 ControlFlow::Break((e, e_expn))
332 } else {
333 ControlFlow::Continue(Descend::No)
334 }
335 })
336 }
337
338 fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool {
339 if !expr.span.from_expansion() {
340 return true;
341 }
342 let result = macro_backtrace(expr.span).try_for_each(|macro_call| {
343 if macro_call.expn == assert_expn {
344 ControlFlow::Break(false)
345 } else {
346 match cx.tcx.item_name(macro_call.def_id) {
347 // `cfg!(debug_assertions)` in `debug_assert!`
348 sym::cfg => ControlFlow::Continue(()),
349 // assert!(other_macro!(..))
350 _ => ControlFlow::Break(true),
351 }
352 }
353 });
354 match result {
355 ControlFlow::Break(is_assert_arg) => is_assert_arg,
356 ControlFlow::Continue(()) => true,
357 }
358 }
359
360 thread_local! {
361 /// We preserve the [`FormatArgs`] structs from the early pass for use in the late pass to be
362 /// able to access the many features of a [`LateContext`].
363 ///
364 /// A thread local is used because [`FormatArgs`] is `!Send` and `!Sync`, we are making an
365 /// assumption that the early pass the populates the map and the later late passes will all be
366 /// running on the same thread.
367 static AST_FORMAT_ARGS: RefCell<FxHashMap<Span, FormatArgs>> = {
368 static CALLED: AtomicBool = AtomicBool::new(false);
369 debug_assert!(
370 !CALLED.swap(true, Ordering::SeqCst),
371 "incorrect assumption: `AST_FORMAT_ARGS` should only be accessed by a single thread",
372 );
373
374 RefCell::default()
375 };
376 }
377
378 /// Record [`rustc_ast::FormatArgs`] for use in late lint passes, this should only be called by
379 /// `FormatArgsCollector`
380 pub fn collect_ast_format_args(span: Span, format_args: &FormatArgs) {
381 AST_FORMAT_ARGS.with(|ast_format_args| {
382 ast_format_args.borrow_mut().insert(span, format_args.clone());
383 });
384 }
385
386 /// Calls `callback` with an AST [`FormatArgs`] node if a `format_args` expansion is found as a
387 /// descendant of `expn_id`
388 pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId, callback: impl FnOnce(&FormatArgs)) {
389 let format_args_expr = for_each_expr(start, |expr| {
390 let ctxt = expr.span.ctxt();
391 if ctxt.outer_expn().is_descendant_of(expn_id) {
392 if macro_backtrace(expr.span)
393 .map(|macro_call| cx.tcx.item_name(macro_call.def_id))
394 .any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))
395 {
396 ControlFlow::Break(expr)
397 } else {
398 ControlFlow::Continue(Descend::Yes)
399 }
400 } else {
401 ControlFlow::Continue(Descend::No)
402 }
403 });
404
405 if let Some(expr) = format_args_expr {
406 AST_FORMAT_ARGS.with(|ast_format_args| {
407 ast_format_args.borrow().get(&expr.span).map(callback);
408 });
409 }
410 }
411
412 /// Attempt to find the [`rustc_hir::Expr`] that corresponds to the [`FormatArgument`]'s value, if
413 /// it cannot be found it will return the [`rustc_ast::Expr`].
414 pub fn find_format_arg_expr<'hir, 'ast>(
415 start: &'hir Expr<'hir>,
416 target: &'ast FormatArgument,
417 ) -> Result<&'hir rustc_hir::Expr<'hir>, &'ast rustc_ast::Expr> {
418 for_each_expr(start, |expr| {
419 if expr.span == target.expr.span {
420 ControlFlow::Break(expr)
421 } else {
422 ControlFlow::Continue(())
423 }
424 })
425 .ok_or(&target.expr)
426 }
427
428 /// Span of the `:` and format specifiers
429 ///
430 /// ```ignore
431 /// format!("{:.}"), format!("{foo:.}")
432 /// ^^ ^^
433 /// ```
434 pub fn format_placeholder_format_span(placeholder: &FormatPlaceholder) -> Option<Span> {
435 let base = placeholder.span?.data();
436
437 // `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing
438 // brace `{...|}`
439 Some(Span::new(
440 placeholder.argument.span?.hi(),
441 base.hi - BytePos(1),
442 base.ctxt,
443 base.parent,
444 ))
445 }
446
447 /// Span covering the format string and values
448 ///
449 /// ```ignore
450 /// format("{}.{}", 10, 11)
451 /// // ^^^^^^^^^^^^^^^
452 /// ```
453 pub fn format_args_inputs_span(format_args: &FormatArgs) -> Span {
454 match format_args.arguments.explicit_args() {
455 [] => format_args.span,
456 [.., last] => format_args
457 .span
458 .to(hygiene::walk_chain(last.expr.span, format_args.span.ctxt())),
459 }
460 }
461
462 /// Returns the [`Span`] of the value at `index` extended to the previous comma, e.g. for the value
463 /// `10`
464 ///
465 /// ```ignore
466 /// format("{}.{}", 10, 11)
467 /// // ^^^^
468 /// ```
469 pub fn format_arg_removal_span(format_args: &FormatArgs, index: usize) -> Option<Span> {
470 let ctxt = format_args.span.ctxt();
471
472 let current = hygiene::walk_chain(format_args.arguments.by_index(index)?.expr.span, ctxt);
473
474 let prev = if index == 0 {
475 format_args.span
476 } else {
477 hygiene::walk_chain(format_args.arguments.by_index(index - 1)?.expr.span, ctxt)
478 };
479
480 Some(current.with_lo(prev.hi()))
481 }
482
483 /// Where a format parameter is being used in the format string
484 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
485 pub enum FormatParamUsage {
486 /// Appears as an argument, e.g. `format!("{}", foo)`
487 Argument,
488 /// Appears as a width, e.g. `format!("{:width$}", foo, width = 1)`
489 Width,
490 /// Appears as a precision, e.g. `format!("{:.precision$}", foo, precision = 1)`
491 Precision,
492 }
493
494 /// A node with a `HirId` and a `Span`
495 pub trait HirNode {
496 fn hir_id(&self) -> HirId;
497 fn span(&self) -> Span;
498 }
499
500 macro_rules! impl_hir_node {
501 ($($t:ident),*) => {
502 $(impl HirNode for hir::$t<'_> {
503 fn hir_id(&self) -> HirId {
504 self.hir_id
505 }
506 fn span(&self) -> Span {
507 self.span
508 }
509 })*
510 };
511 }
512
513 impl_hir_node!(Expr, Pat);
514
515 impl HirNode for hir::Item<'_> {
516 fn hir_id(&self) -> HirId {
517 self.hir_id()
518 }
519
520 fn span(&self) -> Span {
521 self.span
522 }
523 }