]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_utils/src/macros.rs
Rollup merge of #102094 - GuillaumeGomez:bool-from-str-missing-docs, r=scottmcm
[rust.git] / src / tools / clippy / clippy_utils / src / macros.rs
1 #![allow(clippy::similar_names)] // `expr` and `expn`
2
3 use crate::is_path_diagnostic_item;
4 use crate::source::snippet_opt;
5 use crate::visitors::expr_visitor_no_bodies;
6
7 use arrayvec::ArrayVec;
8 use itertools::{izip, Either, Itertools};
9 use rustc_ast::ast::LitKind;
10 use rustc_hir::intravisit::{walk_expr, Visitor};
11 use rustc_hir::{self as hir, Expr, ExprField, ExprKind, HirId, Node, QPath};
12 use rustc_lexer::unescape::unescape_literal;
13 use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
14 use rustc_lint::LateContext;
15 use rustc_parse_format::{self as rpf, Alignment};
16 use rustc_span::def_id::DefId;
17 use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
18 use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Pos, Span, SpanData, Symbol};
19 use std::ops::ControlFlow;
20
21 const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
22     sym::assert_eq_macro,
23     sym::assert_macro,
24     sym::assert_ne_macro,
25     sym::debug_assert_eq_macro,
26     sym::debug_assert_macro,
27     sym::debug_assert_ne_macro,
28     sym::eprint_macro,
29     sym::eprintln_macro,
30     sym::format_args_macro,
31     sym::format_macro,
32     sym::print_macro,
33     sym::println_macro,
34     sym::std_panic_macro,
35     sym::write_macro,
36     sym::writeln_macro,
37 ];
38
39 /// Returns true if a given Macro `DefId` is a format macro (e.g. `println!`)
40 pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool {
41     if let Some(name) = cx.tcx.get_diagnostic_name(macro_def_id) {
42         FORMAT_MACRO_DIAG_ITEMS.contains(&name)
43     } else {
44         false
45     }
46 }
47
48 /// A macro call, like `vec![1, 2, 3]`.
49 ///
50 /// Use `tcx.item_name(macro_call.def_id)` to get the macro name.
51 /// Even better is to check if it is a diagnostic item.
52 ///
53 /// This structure is similar to `ExpnData` but it precludes desugaring expansions.
54 #[derive(Debug)]
55 pub struct MacroCall {
56     /// Macro `DefId`
57     pub def_id: DefId,
58     /// Kind of macro
59     pub kind: MacroKind,
60     /// The expansion produced by the macro call
61     pub expn: ExpnId,
62     /// Span of the macro call site
63     pub span: Span,
64 }
65
66 impl MacroCall {
67     pub fn is_local(&self) -> bool {
68         span_is_local(self.span)
69     }
70 }
71
72 /// Returns an iterator of expansions that created the given span
73 pub fn expn_backtrace(mut span: Span) -> impl Iterator<Item = (ExpnId, ExpnData)> {
74     std::iter::from_fn(move || {
75         let ctxt = span.ctxt();
76         if ctxt == SyntaxContext::root() {
77             return None;
78         }
79         let expn = ctxt.outer_expn();
80         let data = expn.expn_data();
81         span = data.call_site;
82         Some((expn, data))
83     })
84 }
85
86 /// Checks whether the span is from the root expansion or a locally defined macro
87 pub fn span_is_local(span: Span) -> bool {
88     !span.from_expansion() || expn_is_local(span.ctxt().outer_expn())
89 }
90
91 /// Checks whether the expansion is the root expansion or a locally defined macro
92 pub fn expn_is_local(expn: ExpnId) -> bool {
93     if expn == ExpnId::root() {
94         return true;
95     }
96     let data = expn.expn_data();
97     let backtrace = expn_backtrace(data.call_site);
98     std::iter::once((expn, data))
99         .chain(backtrace)
100         .find_map(|(_, data)| data.macro_def_id)
101         .map_or(true, DefId::is_local)
102 }
103
104 /// Returns an iterator of macro expansions that created the given span.
105 /// Note that desugaring expansions are skipped.
106 pub fn macro_backtrace(span: Span) -> impl Iterator<Item = MacroCall> {
107     expn_backtrace(span).filter_map(|(expn, data)| match data {
108         ExpnData {
109             kind: ExpnKind::Macro(kind, _),
110             macro_def_id: Some(def_id),
111             call_site: span,
112             ..
113         } => Some(MacroCall {
114             def_id,
115             kind,
116             expn,
117             span,
118         }),
119         _ => None,
120     })
121 }
122
123 /// If the macro backtrace of `span` has a macro call at the root expansion
124 /// (i.e. not a nested macro call), returns `Some` with the `MacroCall`
125 pub fn root_macro_call(span: Span) -> Option<MacroCall> {
126     macro_backtrace(span).last()
127 }
128
129 /// Like [`root_macro_call`], but only returns `Some` if `node` is the "first node"
130 /// produced by the macro call, as in [`first_node_in_macro`].
131 pub fn root_macro_call_first_node(cx: &LateContext<'_>, node: &impl HirNode) -> Option<MacroCall> {
132     if first_node_in_macro(cx, node) != Some(ExpnId::root()) {
133         return None;
134     }
135     root_macro_call(node.span())
136 }
137
138 /// Like [`macro_backtrace`], but only returns macro calls where `node` is the "first node" of the
139 /// macro call, as in [`first_node_in_macro`].
140 pub fn first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> impl Iterator<Item = MacroCall> {
141     let span = node.span();
142     first_node_in_macro(cx, node)
143         .into_iter()
144         .flat_map(move |expn| macro_backtrace(span).take_while(move |macro_call| macro_call.expn != expn))
145 }
146
147 /// If `node` is the "first node" in a macro expansion, returns `Some` with the `ExpnId` of the
148 /// macro call site (i.e. the parent of the macro expansion). This generally means that `node`
149 /// is the outermost node of an entire macro expansion, but there are some caveats noted below.
150 /// This is useful for finding macro calls while visiting the HIR without processing the macro call
151 /// at every node within its expansion.
152 ///
153 /// If you already have immediate access to the parent node, it is simpler to
154 /// just check the context of that span directly (e.g. `parent.span.from_expansion()`).
155 ///
156 /// If a macro call is in statement position, it expands to one or more statements.
157 /// In that case, each statement *and* their immediate descendants will all yield `Some`
158 /// with the `ExpnId` of the containing block.
159 ///
160 /// A node may be the "first node" of multiple macro calls in a macro backtrace.
161 /// The expansion of the outermost macro call site is returned in such cases.
162 pub fn first_node_in_macro(cx: &LateContext<'_>, node: &impl HirNode) -> Option<ExpnId> {
163     // get the macro expansion or return `None` if not found
164     // `macro_backtrace` importantly ignores desugaring expansions
165     let expn = macro_backtrace(node.span()).next()?.expn;
166
167     // get the parent node, possibly skipping over a statement
168     // if the parent is not found, it is sensible to return `Some(root)`
169     let hir = cx.tcx.hir();
170     let mut parent_iter = hir.parent_iter(node.hir_id());
171     let (parent_id, _) = match parent_iter.next() {
172         None => return Some(ExpnId::root()),
173         Some((_, Node::Stmt(_))) => match parent_iter.next() {
174             None => return Some(ExpnId::root()),
175             Some(next) => next,
176         },
177         Some(next) => next,
178     };
179
180     // get the macro expansion of the parent node
181     let parent_span = hir.span(parent_id);
182     let Some(parent_macro_call) = macro_backtrace(parent_span).next() else {
183         // the parent node is not in a macro
184         return Some(ExpnId::root());
185     };
186
187     if parent_macro_call.expn.is_descendant_of(expn) {
188         // `node` is input to a macro call
189         return None;
190     }
191
192     Some(parent_macro_call.expn)
193 }
194
195 /* Specific Macro Utils */
196
197 /// Is `def_id` of `std::panic`, `core::panic` or any inner implementation macros
198 pub fn is_panic(cx: &LateContext<'_>, def_id: DefId) -> bool {
199     let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return false };
200     matches!(
201         name.as_str(),
202         "core_panic_macro"
203             | "std_panic_macro"
204             | "core_panic_2015_macro"
205             | "std_panic_2015_macro"
206             | "core_panic_2021_macro"
207     )
208 }
209
210 pub enum PanicExpn<'a> {
211     /// No arguments - `panic!()`
212     Empty,
213     /// A string literal or any `&str` - `panic!("message")` or `panic!(message)`
214     Str(&'a Expr<'a>),
215     /// A single argument that implements `Display` - `panic!("{}", object)`
216     Display(&'a Expr<'a>),
217     /// Anything else - `panic!("error {}: {}", a, b)`
218     Format(FormatArgsExpn<'a>),
219 }
220
221 impl<'a> PanicExpn<'a> {
222     pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Self> {
223         if !macro_backtrace(expr.span).any(|macro_call| is_panic(cx, macro_call.def_id)) {
224             return None;
225         }
226         let ExprKind::Call(callee, [arg]) = &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(FormatArgsExpn::parse(cx, arg)?),
236             _ => return None,
237         };
238         Some(result)
239     }
240 }
241
242 /// Finds the arguments of an `assert!` or `debug_assert!` macro call within the macro expansion
243 pub fn find_assert_args<'a>(
244     cx: &LateContext<'_>,
245     expr: &'a Expr<'a>,
246     expn: ExpnId,
247 ) -> Option<(&'a Expr<'a>, PanicExpn<'a>)> {
248     find_assert_args_inner(cx, expr, expn).map(|([e], p)| (e, p))
249 }
250
251 /// Finds the arguments of an `assert_eq!` or `debug_assert_eq!` macro call within the macro
252 /// expansion
253 pub fn find_assert_eq_args<'a>(
254     cx: &LateContext<'_>,
255     expr: &'a Expr<'a>,
256     expn: ExpnId,
257 ) -> Option<(&'a Expr<'a>, &'a Expr<'a>, PanicExpn<'a>)> {
258     find_assert_args_inner(cx, expr, expn).map(|([a, b], p)| (a, b, p))
259 }
260
261 fn find_assert_args_inner<'a, const N: usize>(
262     cx: &LateContext<'_>,
263     expr: &'a Expr<'a>,
264     expn: ExpnId,
265 ) -> Option<([&'a Expr<'a>; N], PanicExpn<'a>)> {
266     let macro_id = expn.expn_data().macro_def_id?;
267     let (expr, expn) = match cx.tcx.item_name(macro_id).as_str().strip_prefix("debug_") {
268         None => (expr, expn),
269         Some(inner_name) => find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?,
270     };
271     let mut args = ArrayVec::new();
272     let mut panic_expn = None;
273     expr_visitor_no_bodies(|e| {
274         if args.is_full() {
275             if panic_expn.is_none() && e.span.ctxt() != expr.span.ctxt() {
276                 panic_expn = PanicExpn::parse(cx, e);
277             }
278             panic_expn.is_none()
279         } else if is_assert_arg(cx, e, expn) {
280             args.push(e);
281             false
282         } else {
283             true
284         }
285     })
286     .visit_expr(expr);
287     let args = args.into_inner().ok()?;
288     // if no `panic!(..)` is found, use `PanicExpn::Empty`
289     // to indicate that the default assertion message is used
290     let panic_expn = panic_expn.unwrap_or(PanicExpn::Empty);
291     Some((args, panic_expn))
292 }
293
294 fn find_assert_within_debug_assert<'a>(
295     cx: &LateContext<'_>,
296     expr: &'a Expr<'a>,
297     expn: ExpnId,
298     assert_name: Symbol,
299 ) -> Option<(&'a Expr<'a>, ExpnId)> {
300     let mut found = None;
301     expr_visitor_no_bodies(|e| {
302         if found.is_some() || !e.span.from_expansion() {
303             return false;
304         }
305         let e_expn = e.span.ctxt().outer_expn();
306         if e_expn == expn {
307             return true;
308         }
309         if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
310             found = Some((e, e_expn));
311         }
312         false
313     })
314     .visit_expr(expr);
315     found
316 }
317
318 fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool {
319     if !expr.span.from_expansion() {
320         return true;
321     }
322     let result = macro_backtrace(expr.span).try_for_each(|macro_call| {
323         if macro_call.expn == assert_expn {
324             ControlFlow::Break(false)
325         } else {
326             match cx.tcx.item_name(macro_call.def_id) {
327                 // `cfg!(debug_assertions)` in `debug_assert!`
328                 sym::cfg => ControlFlow::CONTINUE,
329                 // assert!(other_macro!(..))
330                 _ => ControlFlow::Break(true),
331             }
332         }
333     });
334     match result {
335         ControlFlow::Break(is_assert_arg) => is_assert_arg,
336         ControlFlow::Continue(()) => true,
337     }
338 }
339
340 /// The format string doesn't exist in the HIR, so we reassemble it from source code
341 #[derive(Debug)]
342 pub struct FormatString {
343     /// Span of the whole format string literal, including `[r#]"`.
344     pub span: Span,
345     /// Snippet of the whole format string literal, including `[r#]"`.
346     pub snippet: String,
347     /// If the string is raw `r"..."`/`r#""#`, how many `#`s does it have on each side.
348     pub style: Option<usize>,
349     /// The unescaped value of the format string, e.g. `"val â€“ {}"` for the literal
350     /// `"val \u{2013} {}"`.
351     pub unescaped: String,
352     /// The format string split by format args like `{..}`.
353     pub parts: Vec<Symbol>,
354 }
355
356 impl FormatString {
357     fn new(cx: &LateContext<'_>, pieces: &Expr<'_>) -> Option<Self> {
358         // format_args!(r"a {} b \", 1);
359         //
360         // expands to
361         //
362         // ::core::fmt::Arguments::new_v1(&["a ", " b \\"],
363         //      &[::core::fmt::ArgumentV1::new_display(&1)]);
364         //
365         // where `pieces` is the expression `&["a ", " b \\"]`. It has the span of `r"a {} b \"`
366         let span = pieces.span;
367         let snippet = snippet_opt(cx, span)?;
368
369         let (inner, style) = match tokenize(&snippet).next()?.kind {
370             TokenKind::Literal { kind, .. } => {
371                 let style = match kind {
372                     LiteralKind::Str { .. } => None,
373                     LiteralKind::RawStr { n_hashes: Some(n), .. } => Some(n.into()),
374                     _ => return None,
375                 };
376
377                 let start = style.map_or(1, |n| 2 + n);
378                 let end = snippet.len() - style.map_or(1, |n| 1 + n);
379
380                 (&snippet[start..end], style)
381             },
382             _ => return None,
383         };
384
385         let mode = if style.is_some() {
386             unescape::Mode::RawStr
387         } else {
388             unescape::Mode::Str
389         };
390
391         let mut unescaped = String::with_capacity(inner.len());
392         unescape_literal(inner, mode, &mut |_, ch| match ch {
393             Ok(ch) => unescaped.push(ch),
394             Err(e) if !e.is_fatal() => (),
395             Err(e) => panic!("{:?}", e),
396         });
397
398         let mut parts = Vec::new();
399         expr_visitor_no_bodies(|expr| {
400             if let ExprKind::Lit(lit) = &expr.kind {
401                 if let LitKind::Str(symbol, _) = lit.node {
402                     parts.push(symbol);
403                 }
404             }
405
406             true
407         })
408         .visit_expr(pieces);
409
410         Some(Self {
411             span,
412             snippet,
413             style,
414             unescaped,
415             parts,
416         })
417     }
418 }
419
420 struct FormatArgsValues<'tcx> {
421     /// See `FormatArgsExpn::value_args`
422     value_args: Vec<&'tcx Expr<'tcx>>,
423     /// Maps an `rt::v1::Argument::position` or an `rt::v1::Count::Param` to its index in
424     /// `value_args`
425     pos_to_value_index: Vec<usize>,
426     /// Used to check if a value is declared inline & to resolve `InnerSpan`s.
427     format_string_span: SpanData,
428 }
429
430 impl<'tcx> FormatArgsValues<'tcx> {
431     fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self {
432         let mut pos_to_value_index = Vec::new();
433         let mut value_args = Vec::new();
434         expr_visitor_no_bodies(|expr| {
435             if expr.span.ctxt() == args.span.ctxt() {
436                 // ArgumentV1::new_<format_trait>(<val>)
437                 // ArgumentV1::from_usize(<val>)
438                 if let ExprKind::Call(callee, [val]) = expr.kind
439                     && let ExprKind::Path(QPath::TypeRelative(ty, _)) = callee.kind
440                     && let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind
441                     && path.segments.last().unwrap().ident.name == sym::ArgumentV1
442                 {
443                     let val_idx = if val.span.ctxt() == expr.span.ctxt()
444                         && let ExprKind::Field(_, field) = val.kind
445                         && let Ok(idx) = field.name.as_str().parse()
446                     {
447                         // tuple index
448                         idx
449                     } else {
450                         // assume the value expression is passed directly
451                         pos_to_value_index.len()
452                     };
453
454                     pos_to_value_index.push(val_idx);
455                 }
456
457                 true
458             } else {
459                 // assume that any expr with a differing span is a value
460                 value_args.push(expr);
461
462                 false
463             }
464         })
465         .visit_expr(args);
466
467         Self {
468             value_args,
469             pos_to_value_index,
470             format_string_span,
471         }
472     }
473 }
474
475 /// The positions of a format argument's value, precision and width
476 ///
477 /// A position is an index into the second argument of `Arguments::new_v1[_formatted]`
478 #[derive(Debug, Default, Copy, Clone)]
479 struct ParamPosition {
480     /// The position stored in `rt::v1::Argument::position`.
481     value: usize,
482     /// The position stored in `rt::v1::FormatSpec::width` if it is a `Count::Param`.
483     width: Option<usize>,
484     /// The position stored in `rt::v1::FormatSpec::precision` if it is a `Count::Param`.
485     precision: Option<usize>,
486 }
487
488 impl<'tcx> Visitor<'tcx> for ParamPosition {
489     fn visit_expr_field(&mut self, field: &'tcx ExprField<'tcx>) {
490         fn parse_count(expr: &Expr<'_>) -> Option<usize> {
491             // ::core::fmt::rt::v1::Count::Param(1usize),
492             if let ExprKind::Call(ctor, [val]) = expr.kind
493                 && let ExprKind::Path(QPath::Resolved(_, path)) = ctor.kind
494                 && path.segments.last()?.ident.name == sym::Param
495                 && let ExprKind::Lit(lit) = &val.kind
496                 && let LitKind::Int(pos, _) = lit.node
497             {
498                 Some(pos as usize)
499             } else {
500                 None
501             }
502         }
503
504         match field.ident.name {
505             sym::position => {
506                 if let ExprKind::Lit(lit) = &field.expr.kind
507                     && let LitKind::Int(pos, _) = lit.node
508                 {
509                     self.value = pos as usize;
510                 }
511             },
512             sym::precision => {
513                 self.precision = parse_count(field.expr);
514             },
515             sym::width => {
516                 self.width = parse_count(field.expr);
517             },
518             _ => walk_expr(self, field.expr),
519         }
520     }
521 }
522
523 /// Parses the `fmt` arg of `Arguments::new_v1_formatted(pieces, args, fmt, _)`
524 fn parse_rt_fmt<'tcx>(fmt_arg: &'tcx Expr<'tcx>) -> Option<impl Iterator<Item = ParamPosition> + 'tcx> {
525     if let ExprKind::AddrOf(.., array) = fmt_arg.kind
526         && let ExprKind::Array(specs) = array.kind
527     {
528         Some(specs.iter().map(|spec| {
529             let mut position = ParamPosition::default();
530             position.visit_expr(spec);
531             position
532         }))
533     } else {
534         None
535     }
536 }
537
538 /// `Span::from_inner`, but for `rustc_parse_format`'s `InnerSpan`
539 fn span_from_inner(base: SpanData, inner: rpf::InnerSpan) -> Span {
540     Span::new(
541         base.lo + BytePos::from_usize(inner.start),
542         base.lo + BytePos::from_usize(inner.end),
543         base.ctxt,
544         base.parent,
545     )
546 }
547
548 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
549 pub enum FormatParamKind {
550     /// An implicit parameter , such as `{}` or `{:?}`.
551     Implicit,
552     /// A parameter with an explicit number, or an asterisk precision. e.g. `{1}`, `{0:?}`,
553     /// `{:.0$}` or `{:.*}`.
554     Numbered,
555     /// A named parameter with a named `value_arg`, such as the `x` in `format!("{x}", x = 1)`.
556     Named(Symbol),
557     /// An implicit named parameter, such as the `y` in `format!("{y}")`.
558     NamedInline(Symbol),
559 }
560
561 /// A `FormatParam` is any place in a `FormatArgument` that refers to a supplied value, e.g.
562 ///
563 /// ```
564 /// let precision = 2;
565 /// format!("{:.precision$}", 0.1234);
566 /// ```
567 ///
568 /// has two `FormatParam`s, a [`FormatParamKind::Implicit`] `.kind` with a `.value` of `0.1234`
569 /// and a [`FormatParamKind::NamedInline("precision")`] `.kind` with a `.value` of `2`
570 #[derive(Debug, Copy, Clone)]
571 pub struct FormatParam<'tcx> {
572     /// The expression this parameter refers to.
573     pub value: &'tcx Expr<'tcx>,
574     /// How this parameter refers to its `value`.
575     pub kind: FormatParamKind,
576     /// Span of the parameter, may be zero width. Includes the whitespace of implicit parameters.
577     ///
578     /// ```text
579     /// format!("{}, {  }, {0}, {name}", ...);
580     ///          ^    ~~    ~    ~~~~
581     /// ```
582     pub span: Span,
583 }
584
585 impl<'tcx> FormatParam<'tcx> {
586     fn new(
587         mut kind: FormatParamKind,
588         position: usize,
589         inner: rpf::InnerSpan,
590         values: &FormatArgsValues<'tcx>,
591     ) -> Option<Self> {
592         let value_index = *values.pos_to_value_index.get(position)?;
593         let value = *values.value_args.get(value_index)?;
594         let span = span_from_inner(values.format_string_span, inner);
595
596         // if a param is declared inline, e.g. `format!("{x}")`, the generated expr's span points
597         // into the format string
598         if let FormatParamKind::Named(name) = kind && values.format_string_span.contains(value.span.data()) {
599             kind = FormatParamKind::NamedInline(name);
600         }
601
602         Some(Self { value, kind, span })
603     }
604 }
605
606 /// Used by [width](https://doc.rust-lang.org/std/fmt/#width) and
607 /// [precision](https://doc.rust-lang.org/std/fmt/#precision) specifiers.
608 #[derive(Debug, Copy, Clone)]
609 pub enum Count<'tcx> {
610     /// Specified with a literal number, stores the value.
611     Is(usize, Span),
612     /// Specified using `$` and `*` syntaxes. The `*` format is still considered to be
613     /// `FormatParamKind::Numbered`.
614     Param(FormatParam<'tcx>),
615     /// Not specified.
616     Implied,
617 }
618
619 impl<'tcx> Count<'tcx> {
620     fn new(
621         count: rpf::Count<'_>,
622         position: Option<usize>,
623         inner: Option<rpf::InnerSpan>,
624         values: &FormatArgsValues<'tcx>,
625     ) -> Option<Self> {
626         Some(match count {
627             rpf::Count::CountIs(val) => Self::Is(val, span_from_inner(values.format_string_span, inner?)),
628             rpf::Count::CountIsName(name, span) => Self::Param(FormatParam::new(
629                 FormatParamKind::Named(Symbol::intern(name)),
630                 position?,
631                 span,
632                 values,
633             )?),
634             rpf::Count::CountIsParam(_) | rpf::Count::CountIsStar(_) => {
635                 Self::Param(FormatParam::new(FormatParamKind::Numbered, position?, inner?, values)?)
636             },
637             rpf::Count::CountImplied => Self::Implied,
638         })
639     }
640
641     pub fn is_implied(self) -> bool {
642         matches!(self, Count::Implied)
643     }
644
645     pub fn param(self) -> Option<FormatParam<'tcx>> {
646         match self {
647             Count::Param(param) => Some(param),
648             _ => None,
649         }
650     }
651 }
652
653 /// Specification for the formatting of an argument in the format string. See
654 /// <https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters> for the precise meanings.
655 #[derive(Debug)]
656 pub struct FormatSpec<'tcx> {
657     /// Optionally specified character to fill alignment with.
658     pub fill: Option<char>,
659     /// Optionally specified alignment.
660     pub align: Alignment,
661     /// Packed version of various flags provided, see [`rustc_parse_format::Flag`].
662     pub flags: u32,
663     /// Represents either the maximum width or the integer precision.
664     pub precision: Count<'tcx>,
665     /// The minimum width, will be padded according to `width`/`align`
666     pub width: Count<'tcx>,
667     /// The formatting trait used by the argument, e.g. `sym::Display` for `{}`, `sym::Debug` for
668     /// `{:?}`.
669     pub r#trait: Symbol,
670     pub trait_span: Option<Span>,
671 }
672
673 impl<'tcx> FormatSpec<'tcx> {
674     fn new(spec: rpf::FormatSpec<'_>, positions: ParamPosition, values: &FormatArgsValues<'tcx>) -> Option<Self> {
675         Some(Self {
676             fill: spec.fill,
677             align: spec.align,
678             flags: spec.flags,
679             precision: Count::new(spec.precision, positions.precision, spec.precision_span, values)?,
680             width: Count::new(spec.width, positions.width, spec.width_span, values)?,
681             r#trait: match spec.ty {
682                 "" => sym::Display,
683                 "?" => sym::Debug,
684                 "o" => sym!(Octal),
685                 "x" => sym!(LowerHex),
686                 "X" => sym!(UpperHex),
687                 "p" => sym::Pointer,
688                 "b" => sym!(Binary),
689                 "e" => sym!(LowerExp),
690                 "E" => sym!(UpperExp),
691                 _ => return None,
692             },
693             trait_span: spec
694                 .ty_span
695                 .map(|span| span_from_inner(values.format_string_span, span)),
696         })
697     }
698
699     /// Returns true if this format spec is unchanged from the default. e.g. returns true for `{}`,
700     /// `{foo}` and `{2}`, but false for `{:?}`, `{foo:5}` and `{3:.5}`
701     pub fn is_default(&self) -> bool {
702         self.r#trait == sym::Display
703             && self.width.is_implied()
704             && self.precision.is_implied()
705             && self.align == Alignment::AlignUnknown
706             && self.flags == 0
707     }
708 }
709
710 /// A format argument, such as `{}`, `{foo:?}`.
711 #[derive(Debug)]
712 pub struct FormatArg<'tcx> {
713     /// The parameter the argument refers to.
714     pub param: FormatParam<'tcx>,
715     /// How to format `param`.
716     pub format: FormatSpec<'tcx>,
717     /// span of the whole argument, `{..}`.
718     pub span: Span,
719 }
720
721 /// A parsed `format_args!` expansion.
722 #[derive(Debug)]
723 pub struct FormatArgsExpn<'tcx> {
724     /// The format string literal.
725     pub format_string: FormatString,
726     // The format arguments, such as `{:?}`.
727     pub args: Vec<FormatArg<'tcx>>,
728     /// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will
729     /// include this added newline.
730     pub newline: bool,
731     /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
732     /// `format!("{x} {} {y}", 1, z + 2)`.
733     value_args: Vec<&'tcx Expr<'tcx>>,
734 }
735
736 impl<'tcx> FormatArgsExpn<'tcx> {
737     pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
738         let macro_name = macro_backtrace(expr.span)
739             .map(|macro_call| cx.tcx.item_name(macro_call.def_id))
740             .find(|&name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))?;
741         let newline = macro_name == sym::format_args_nl;
742
743         // ::core::fmt::Arguments::new_v1(pieces, args)
744         // ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg)
745         if let ExprKind::Call(callee, [pieces, args, rest @ ..]) = expr.kind
746             && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind
747             && is_path_diagnostic_item(cx, ty, sym::Arguments)
748             && matches!(seg.ident.as_str(), "new_v1" | "new_v1_formatted")
749         {
750             let format_string = FormatString::new(cx, pieces)?;
751
752             let mut parser = rpf::Parser::new(
753                 &format_string.unescaped,
754                 format_string.style,
755                 Some(format_string.snippet.clone()),
756                 // `format_string.unescaped` does not contain the appended newline
757                 false,
758                 rpf::ParseMode::Format,
759             );
760
761             let parsed_args = parser
762                 .by_ref()
763                 .filter_map(|piece| match piece {
764                     rpf::Piece::NextArgument(a) => Some(a),
765                     rpf::Piece::String(_) => None,
766                 })
767                 .collect_vec();
768             if !parser.errors.is_empty() {
769                 return None;
770             }
771
772             let positions = if let Some(fmt_arg) = rest.first() {
773                 // If the argument contains format specs, `new_v1_formatted(_, _, fmt, _)`, parse
774                 // them.
775
776                 Either::Left(parse_rt_fmt(fmt_arg)?)
777             } else {
778                 // If no format specs are given, the positions are in the given order and there are
779                 // no `precision`/`width`s to consider.
780
781                 Either::Right((0..).map(|n| ParamPosition {
782                     value: n,
783                     width: None,
784                     precision: None,
785                 }))
786             };
787
788             let values = FormatArgsValues::new(args, format_string.span.data());
789
790             let args = izip!(positions, parsed_args, parser.arg_places)
791                 .map(|(position, parsed_arg, arg_span)| {
792                     Some(FormatArg {
793                         param: FormatParam::new(
794                             match parsed_arg.position {
795                                 rpf::Position::ArgumentImplicitlyIs(_) => FormatParamKind::Implicit,
796                                 rpf::Position::ArgumentIs(_) => FormatParamKind::Numbered,
797                                 // NamedInline is handled by `FormatParam::new()`
798                                 rpf::Position::ArgumentNamed(name) => FormatParamKind::Named(Symbol::intern(name)),
799                             },
800                             position.value,
801                             parsed_arg.position_span,
802                             &values,
803                         )?,
804                         format: FormatSpec::new(parsed_arg.format, position, &values)?,
805                         span: span_from_inner(values.format_string_span, arg_span),
806                     })
807                 })
808                 .collect::<Option<Vec<_>>>()?;
809
810             Some(Self {
811                 format_string,
812                 args,
813                 value_args: values.value_args,
814                 newline,
815             })
816         } else {
817             None
818         }
819     }
820
821     pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
822         let mut format_args = None;
823         expr_visitor_no_bodies(|e| {
824             if format_args.is_some() {
825                 return false;
826             }
827             let e_ctxt = e.span.ctxt();
828             if e_ctxt == expr.span.ctxt() {
829                 return true;
830             }
831             if e_ctxt.outer_expn().is_descendant_of(expn_id) {
832                 format_args = FormatArgsExpn::parse(cx, e);
833             }
834             false
835         })
836         .visit_expr(expr);
837         format_args
838     }
839
840     /// Source callsite span of all inputs
841     pub fn inputs_span(&self) -> Span {
842         match *self.value_args {
843             [] => self.format_string.span,
844             [.., last] => self
845                 .format_string
846                 .span
847                 .to(hygiene::walk_chain(last.span, self.format_string.span.ctxt())),
848         }
849     }
850
851     /// Iterator of all format params, both values and those referenced by `width`/`precision`s.
852     pub fn params(&'tcx self) -> impl Iterator<Item = FormatParam<'tcx>> {
853         self.args
854             .iter()
855             .flat_map(|arg| [Some(arg.param), arg.format.precision.param(), arg.format.width.param()])
856             .flatten()
857     }
858 }
859
860 /// A node with a `HirId` and a `Span`
861 pub trait HirNode {
862     fn hir_id(&self) -> HirId;
863     fn span(&self) -> Span;
864 }
865
866 macro_rules! impl_hir_node {
867     ($($t:ident),*) => {
868         $(impl HirNode for hir::$t<'_> {
869             fn hir_id(&self) -> HirId {
870                 self.hir_id
871             }
872             fn span(&self) -> Span {
873                 self.span
874             }
875         })*
876     };
877 }
878
879 impl_hir_node!(Expr, Pat);
880
881 impl HirNode for hir::Item<'_> {
882     fn hir_id(&self) -> HirId {
883         self.hir_id()
884     }
885
886     fn span(&self) -> Span {
887         self.span
888     }
889 }