1 #![allow(clippy::similar_names)] // `expr` and `expn`
3 use crate::visitors
::{for_each_expr, Descend}
;
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}
;
17 const FORMAT_MACRO_DIAG_ITEMS
: &[Symbol
] = &[
21 sym
::debug_assert_eq_macro
,
22 sym
::debug_assert_macro
,
23 sym
::debug_assert_ne_macro
,
26 sym
::format_args_macro
,
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
)
44 /// A macro call, like `vec![1, 2, 3]`.
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.
49 /// This structure is similar to `ExpnData` but it precludes desugaring expansions.
51 pub struct MacroCall
{
56 /// The expansion produced by the macro call
58 /// Span of the macro call site
63 pub fn is_local(&self) -> bool
{
64 span_is_local(self.span
)
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() {
75 let expn
= ctxt
.outer_expn();
76 let data
= expn
.expn_data();
77 span
= data
.call_site
;
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())
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() {
92 let data
= expn
.expn_data();
93 let backtrace
= expn_backtrace(data
.call_site
);
94 std
::iter
::once((expn
, data
))
96 .find_map(|(_
, data
)| data
.macro_def_id
)
97 .map_or(true, DefId
::is_local
)
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
{
105 kind
: ExpnKind
::Macro(kind
, _
),
106 macro_def_id
: Some(def_id
),
109 } => Some(MacroCall
{
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()
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()) {
131 root_macro_call(node
.span())
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
)
140 .flat_map(move |expn
| macro_backtrace(span
).take_while(move |macro_call
| macro_call
.expn
!= expn
))
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.
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()`).
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.
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
;
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()),
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());
183 if parent_macro_call
.expn
.is_descendant_of(expn
) {
184 // `node` is input to a macro call
188 Some(parent_macro_call
.expn
)
191 /* Specific Macro Utils */
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 }
;
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
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
)
213 pub enum PanicExpn
<'a
> {
214 /// No arguments - `panic!()`
216 /// A string literal or any `&str` - `panic!("message")` or `panic!(message)`
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
>),
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
),
232 let ExprKind
::AddrOf(_
, _
, e
) = &arg
.kind
else { return None }
;
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!(..)));`
239 // It should have 4 arguments in total (we already matched with the first argument,
240 // so we're just checking for 3)
244 // `msg_arg` is either `None` (no custom message) or `Some(format_args!(..))` (custom message)
245 let msg_arg
= &rest
[2];
247 ExprKind
::Call(_
, [fmt_arg
]) => Self::Format(fmt_arg
),
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
<'_
>,
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
;
276 /// Finds the arguments of an `assert_eq!` or `debug_assert_eq!` macro call within the macro
278 pub fn find_assert_eq_args
<'a
>(
279 cx
: &LateContext
<'_
>,
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
))
286 fn find_assert_args_inner
<'a
, const N
: usize>(
287 cx
: &LateContext
<'_
>,
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
))?
,
296 let mut args
= ArrayVec
::new();
297 let panic_expn
= for_each_expr(expr
, |e
| {
299 match PanicExpn
::parse(e
) {
300 Some(expn
) => ControlFlow
::Break(expn
),
301 None
=> ControlFlow
::Continue(Descend
::Yes
),
303 } else if is_assert_arg(cx
, e
, expn
) {
305 ControlFlow
::Continue(Descend
::No
)
307 ControlFlow
::Continue(Descend
::Yes
)
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
))
317 fn find_assert_within_debug_assert
<'a
>(
318 cx
: &LateContext
<'_
>,
322 ) -> Option
<(&'a Expr
<'a
>, ExpnId
)> {
323 for_each_expr(expr
, |e
| {
324 if !e
.span
.from_expansion() {
325 return ControlFlow
::Continue(Descend
::No
);
327 let e_expn
= e
.span
.ctxt().outer_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
))
333 ControlFlow
::Continue(Descend
::No
)
338 fn is_assert_arg(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>, assert_expn
: ExpnId
) -> bool
{
339 if !expr
.span
.from_expansion() {
342 let result
= macro_backtrace(expr
.span
).try_for_each(|macro_call
| {
343 if macro_call
.expn
== assert_expn
{
344 ControlFlow
::Break(false)
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),
355 ControlFlow
::Break(is_assert_arg
) => is_assert_arg
,
356 ControlFlow
::Continue(()) => true,
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`].
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);
370 !CALLED
.swap(true, Ordering
::SeqCst
),
371 "incorrect assumption: `AST_FORMAT_ARGS` should only be accessed by a single thread",
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());
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
))
396 ControlFlow
::Break(expr
)
398 ControlFlow
::Continue(Descend
::Yes
)
401 ControlFlow
::Continue(Descend
::No
)
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
);
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
)
422 ControlFlow
::Continue(())
428 /// Span of the `:` and format specifiers
431 /// format!("{:.}"), format!("{foo:.}")
434 pub fn format_placeholder_format_span(placeholder
: &FormatPlaceholder
) -> Option
<Span
> {
435 let base
= placeholder
.span?
.data();
437 // `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing
440 placeholder
.argument
.span?
.hi(),
441 base
.hi
- BytePos(1),
447 /// Span covering the format string and values
450 /// format("{}.{}", 10, 11)
451 /// // ^^^^^^^^^^^^^^^
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
458 .to(hygiene
::walk_chain(last
.expr
.span
, format_args
.span
.ctxt())),
462 /// Returns the [`Span`] of the value at `index` extended to the previous comma, e.g. for the value
466 /// format("{}.{}", 10, 11)
469 pub fn format_arg_removal_span(format_args
: &FormatArgs
, index
: usize) -> Option
<Span
> {
470 let ctxt
= format_args
.span
.ctxt();
472 let current
= hygiene
::walk_chain(format_args
.arguments
.by_index(index
)?
.expr
.span
, ctxt
);
474 let prev
= if index
== 0 {
477 hygiene
::walk_chain(format_args
.arguments
.by_index(index
- 1)?
.expr
.span
, ctxt
)
480 Some(current
.with_lo(prev
.hi()))
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)`
488 /// Appears as a width, e.g. `format!("{:width$}", foo, width = 1)`
490 /// Appears as a precision, e.g. `format!("{:.precision$}", foo, precision = 1)`
494 /// A node with a `HirId` and a `Span`
496 fn hir_id(&self) -> HirId
;
497 fn span(&self) -> Span
;
500 macro_rules
! impl_hir_node
{
502 $
(impl HirNode
for hir
::$t
<'_
> {
503 fn hir_id(&self) -> HirId
{
506 fn span(&self) -> Span
{
513 impl_hir_node
!(Expr
, Pat
);
515 impl HirNode
for hir
::Item
<'_
> {
516 fn hir_id(&self) -> HirId
{
520 fn span(&self) -> Span
{