clippy_utils/
macros.rs

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