1 #![allow(clippy::similar_names)] // `expr` and `expn`
3 use crate::is_path_diagnostic_item;
4 use crate::source::snippet_opt;
5 use crate::visitors::{for_each_expr, Descend};
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::iter::{once, zip};
20 use std::ops::ControlFlow;
22 const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
26 sym::debug_assert_eq_macro,
27 sym::debug_assert_macro,
28 sym::debug_assert_ne_macro,
31 sym::format_args_macro,
40 /// Returns true if a given Macro `DefId` is a format macro (e.g. `println!`)
41 pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool {
42 if let Some(name) = cx.tcx.get_diagnostic_name(macro_def_id) {
43 FORMAT_MACRO_DIAG_ITEMS.contains(&name)
49 /// A macro call, like `vec![1, 2, 3]`.
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.
54 /// This structure is similar to `ExpnData` but it precludes desugaring expansions.
56 pub struct MacroCall {
61 /// The expansion produced by the macro call
63 /// Span of the macro call site
68 pub fn is_local(&self) -> bool {
69 span_is_local(self.span)
73 /// Returns an iterator of expansions that created the given span
74 pub 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() {
80 let expn = ctxt.outer_expn();
81 let data = expn.expn_data();
82 span = data.call_site;
87 /// Checks whether the span is from the root expansion or a locally defined macro
88 pub fn span_is_local(span: Span) -> bool {
89 !span.from_expansion() || expn_is_local(span.ctxt().outer_expn())
92 /// Checks whether the expansion is the root expansion or a locally defined macro
93 pub fn expn_is_local(expn: ExpnId) -> bool {
94 if expn == ExpnId::root() {
97 let data = expn.expn_data();
98 let backtrace = expn_backtrace(data.call_site);
99 std::iter::once((expn, data))
101 .find_map(|(_, data)| data.macro_def_id)
102 .map_or(true, DefId::is_local)
105 /// Returns an iterator of macro expansions that created the given span.
106 /// Note that desugaring expansions are skipped.
107 pub fn macro_backtrace(span: Span) -> impl Iterator<Item = MacroCall> {
108 expn_backtrace(span).filter_map(|(expn, data)| match data {
110 kind: ExpnKind::Macro(kind, _),
111 macro_def_id: Some(def_id),
114 } => Some(MacroCall {
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 pub fn root_macro_call(span: Span) -> Option<MacroCall> {
127 macro_backtrace(span).last()
130 /// Like [`root_macro_call`], but only returns `Some` if `node` is the "first node"
131 /// produced by the macro call, as in [`first_node_in_macro`].
132 pub fn root_macro_call_first_node(cx: &LateContext<'_>, node: &impl HirNode) -> Option<MacroCall> {
133 if first_node_in_macro(cx, node) != Some(ExpnId::root()) {
136 root_macro_call(node.span())
139 /// Like [`macro_backtrace`], but only returns macro calls where `node` is the "first node" of the
140 /// macro call, as in [`first_node_in_macro`].
141 pub fn first_node_macro_backtrace(cx: &LateContext<'_>, node: &impl HirNode) -> impl Iterator<Item = MacroCall> {
142 let span = node.span();
143 first_node_in_macro(cx, node)
145 .flat_map(move |expn| macro_backtrace(span).take_while(move |macro_call| macro_call.expn != expn))
148 /// If `node` is the "first node" in a macro expansion, returns `Some` with the `ExpnId` of the
149 /// macro call site (i.e. the parent of the macro expansion). This generally means that `node`
150 /// is the outermost node of an entire macro expansion, but there are some caveats noted below.
151 /// This is useful for finding macro calls while visiting the HIR without processing the macro call
152 /// at every node within its expansion.
154 /// If you already have immediate access to the parent node, it is simpler to
155 /// just check the context of that span directly (e.g. `parent.span.from_expansion()`).
157 /// If a macro call is in statement position, it expands to one or more statements.
158 /// In that case, each statement *and* their immediate descendants will all yield `Some`
159 /// with the `ExpnId` of the containing block.
161 /// A node may be the "first node" of multiple macro calls in a macro backtrace.
162 /// The expansion of the outermost macro call site is returned in such cases.
163 pub fn first_node_in_macro(cx: &LateContext<'_>, node: &impl HirNode) -> Option<ExpnId> {
164 // get the macro expansion or return `None` if not found
165 // `macro_backtrace` importantly ignores desugaring expansions
166 let expn = macro_backtrace(node.span()).next()?.expn;
168 // get the parent node, possibly skipping over a statement
169 // if the parent is not found, it is sensible to return `Some(root)`
170 let hir = cx.tcx.hir();
171 let mut parent_iter = hir.parent_iter(node.hir_id());
172 let (parent_id, _) = match parent_iter.next() {
173 None => return Some(ExpnId::root()),
174 Some((_, Node::Stmt(_))) => match parent_iter.next() {
175 None => return Some(ExpnId::root()),
181 // get the macro expansion of the parent node
182 let parent_span = hir.span(parent_id);
183 let Some(parent_macro_call) = macro_backtrace(parent_span).next() else {
184 // the parent node is not in a macro
185 return Some(ExpnId::root());
188 if parent_macro_call.expn.is_descendant_of(expn) {
189 // `node` is input to a macro call
193 Some(parent_macro_call.expn)
196 /* Specific Macro Utils */
198 /// Is `def_id` of `std::panic`, `core::panic` or any inner implementation macros
199 pub fn is_panic(cx: &LateContext<'_>, def_id: DefId) -> bool {
200 let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return false };
205 | "core_panic_2015_macro"
206 | "std_panic_2015_macro"
207 | "core_panic_2021_macro"
211 pub enum PanicExpn<'a> {
212 /// No arguments - `panic!()`
214 /// A string literal or any `&str` - `panic!("message")` or `panic!(message)`
216 /// A single argument that implements `Display` - `panic!("{}", object)`
217 Display(&'a Expr<'a>),
218 /// Anything else - `panic!("error {}: {}", a, b)`
219 Format(FormatArgsExpn<'a>),
222 impl<'a> PanicExpn<'a> {
223 pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Self> {
224 if !macro_backtrace(expr.span).any(|macro_call| is_panic(cx, macro_call.def_id)) {
227 let ExprKind::Call(callee, [arg]) = &expr.kind else { return None };
228 let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else { return None };
229 let result = match path.segments.last().unwrap().ident.as_str() {
230 "panic" if arg.span.ctxt() == expr.span.ctxt() => Self::Empty,
231 "panic" | "panic_str" => Self::Str(arg),
233 let ExprKind::AddrOf(_, _, e) = &arg.kind else { return None };
236 "panic_fmt" => Self::Format(FormatArgsExpn::parse(cx, arg)?),
243 /// Finds the arguments of an `assert!` or `debug_assert!` macro call within the macro expansion
244 pub fn find_assert_args<'a>(
245 cx: &LateContext<'_>,
248 ) -> Option<(&'a Expr<'a>, PanicExpn<'a>)> {
249 find_assert_args_inner(cx, expr, expn).map(|([e], p)| (e, p))
252 /// Finds the arguments of an `assert_eq!` or `debug_assert_eq!` macro call within the macro
254 pub fn find_assert_eq_args<'a>(
255 cx: &LateContext<'_>,
258 ) -> Option<(&'a Expr<'a>, &'a Expr<'a>, PanicExpn<'a>)> {
259 find_assert_args_inner(cx, expr, expn).map(|([a, b], p)| (a, b, p))
262 fn find_assert_args_inner<'a, const N: usize>(
263 cx: &LateContext<'_>,
266 ) -> Option<([&'a Expr<'a>; N], PanicExpn<'a>)> {
267 let macro_id = expn.expn_data().macro_def_id?;
268 let (expr, expn) = match cx.tcx.item_name(macro_id).as_str().strip_prefix("debug_") {
269 None => (expr, expn),
270 Some(inner_name) => find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?,
272 let mut args = ArrayVec::new();
273 let mut panic_expn = None;
274 let _: Option<!> = for_each_expr(expr, |e| {
276 if panic_expn.is_none() && e.span.ctxt() != expr.span.ctxt() {
277 panic_expn = PanicExpn::parse(cx, e);
279 ControlFlow::Continue(Descend::from(panic_expn.is_none()))
280 } else if is_assert_arg(cx, e, expn) {
282 ControlFlow::Continue(Descend::No)
284 ControlFlow::Continue(Descend::Yes)
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))
294 fn find_assert_within_debug_assert<'a>(
295 cx: &LateContext<'_>,
299 ) -> Option<(&'a Expr<'a>, ExpnId)> {
300 for_each_expr(expr, |e| {
301 if !e.span.from_expansion() {
302 return ControlFlow::Continue(Descend::No);
304 let e_expn = e.span.ctxt().outer_expn();
306 ControlFlow::Continue(Descend::Yes)
307 } else if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
308 ControlFlow::Break((e, e_expn))
310 ControlFlow::Continue(Descend::No)
315 fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool {
316 if !expr.span.from_expansion() {
319 let result = macro_backtrace(expr.span).try_for_each(|macro_call| {
320 if macro_call.expn == assert_expn {
321 ControlFlow::Break(false)
323 match cx.tcx.item_name(macro_call.def_id) {
324 // `cfg!(debug_assertions)` in `debug_assert!`
325 sym::cfg => ControlFlow::CONTINUE,
326 // assert!(other_macro!(..))
327 _ => ControlFlow::Break(true),
332 ControlFlow::Break(is_assert_arg) => is_assert_arg,
333 ControlFlow::Continue(()) => true,
337 /// The format string doesn't exist in the HIR, so we reassemble it from source code
339 pub struct FormatString {
340 /// Span of the whole format string literal, including `[r#]"`.
342 /// Snippet of the whole format string literal, including `[r#]"`.
344 /// If the string is raw `r"..."`/`r#""#`, how many `#`s does it have on each side.
345 pub style: Option<usize>,
346 /// The unescaped value of the format string, e.g. `"val – {}"` for the literal
347 /// `"val \u{2013} {}"`.
348 pub unescaped: String,
349 /// The format string split by format args like `{..}`.
350 pub parts: Vec<Symbol>,
354 fn new(cx: &LateContext<'_>, pieces: &Expr<'_>) -> Option<Self> {
355 // format_args!(r"a {} b \", 1);
359 // ::core::fmt::Arguments::new_v1(&["a ", " b \\"],
360 // &[::core::fmt::ArgumentV1::new_display(&1)]);
362 // where `pieces` is the expression `&["a ", " b \\"]`. It has the span of `r"a {} b \"`
363 let span = pieces.span;
364 let snippet = snippet_opt(cx, span)?;
366 let (inner, style) = match tokenize(&snippet).next()?.kind {
367 TokenKind::Literal { kind, .. } => {
368 let style = match kind {
369 LiteralKind::Str { .. } => None,
370 LiteralKind::RawStr { n_hashes: Some(n), .. } => Some(n.into()),
374 let start = style.map_or(1, |n| 2 + n);
375 let end = snippet.len() - style.map_or(1, |n| 1 + n);
377 (&snippet[start..end], style)
382 let mode = if style.is_some() {
383 unescape::Mode::RawStr
388 let mut unescaped = String::with_capacity(inner.len());
389 unescape_literal(inner, mode, &mut |_, ch| match ch {
390 Ok(ch) => unescaped.push(ch),
391 Err(e) if !e.is_fatal() => (),
392 Err(e) => panic!("{e:?}"),
395 let mut parts = Vec::new();
396 let _: Option<!> = for_each_expr(pieces, |expr| {
397 if let ExprKind::Lit(lit) = &expr.kind
398 && let LitKind::Str(symbol, _) = lit.node
402 ControlFlow::Continue(())
415 struct FormatArgsValues<'tcx> {
416 /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
417 /// `format!("{x} {} {}", 1, z + 2)`.
418 value_args: Vec<&'tcx Expr<'tcx>>,
419 /// Maps an `rt::v1::Argument::position` or an `rt::v1::Count::Param` to its index in
421 pos_to_value_index: Vec<usize>,
422 /// Used to check if a value is declared inline & to resolve `InnerSpan`s.
423 format_string_span: SpanData,
426 impl<'tcx> FormatArgsValues<'tcx> {
427 fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self {
428 let mut pos_to_value_index = Vec::new();
429 let mut value_args = Vec::new();
430 let _: Option<!> = for_each_expr(args, |expr| {
431 if expr.span.ctxt() == args.span.ctxt() {
432 // ArgumentV1::new_<format_trait>(<val>)
433 // ArgumentV1::from_usize(<val>)
434 if let ExprKind::Call(callee, [val]) = expr.kind
435 && let ExprKind::Path(QPath::TypeRelative(ty, _)) = callee.kind
436 && let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind
437 && path.segments.last().unwrap().ident.name == sym::ArgumentV1
439 let val_idx = if val.span.ctxt() == expr.span.ctxt()
440 && let ExprKind::Field(_, field) = val.kind
441 && let Ok(idx) = field.name.as_str().parse()
446 // assume the value expression is passed directly
447 pos_to_value_index.len()
450 pos_to_value_index.push(val_idx);
452 ControlFlow::Continue(Descend::Yes)
454 // assume that any expr with a differing span is a value
455 value_args.push(expr);
456 ControlFlow::Continue(Descend::No)
468 /// The positions of a format argument's value, precision and width
470 /// A position is an index into the second argument of `Arguments::new_v1[_formatted]`
471 #[derive(Debug, Default, Copy, Clone)]
472 struct ParamPosition {
473 /// The position stored in `rt::v1::Argument::position`.
475 /// The position stored in `rt::v1::FormatSpec::width` if it is a `Count::Param`.
476 width: Option<usize>,
477 /// The position stored in `rt::v1::FormatSpec::precision` if it is a `Count::Param`.
478 precision: Option<usize>,
481 impl<'tcx> Visitor<'tcx> for ParamPosition {
482 fn visit_expr_field(&mut self, field: &'tcx ExprField<'tcx>) {
483 fn parse_count(expr: &Expr<'_>) -> Option<usize> {
484 // ::core::fmt::rt::v1::Count::Param(1usize),
485 if let ExprKind::Call(ctor, [val]) = expr.kind
486 && let ExprKind::Path(QPath::Resolved(_, path)) = ctor.kind
487 && path.segments.last()?.ident.name == sym::Param
488 && let ExprKind::Lit(lit) = &val.kind
489 && let LitKind::Int(pos, _) = lit.node
497 match field.ident.name {
499 if let ExprKind::Lit(lit) = &field.expr.kind
500 && let LitKind::Int(pos, _) = lit.node
502 self.value = pos as usize;
506 self.precision = parse_count(field.expr);
509 self.width = parse_count(field.expr);
511 _ => walk_expr(self, field.expr),
516 /// Parses the `fmt` arg of `Arguments::new_v1_formatted(pieces, args, fmt, _)`
517 fn parse_rt_fmt<'tcx>(fmt_arg: &'tcx Expr<'tcx>) -> Option<impl Iterator<Item = ParamPosition> + 'tcx> {
518 if let ExprKind::AddrOf(.., array) = fmt_arg.kind
519 && let ExprKind::Array(specs) = array.kind
521 Some(specs.iter().map(|spec| {
522 let mut position = ParamPosition::default();
523 position.visit_expr(spec);
531 /// `Span::from_inner`, but for `rustc_parse_format`'s `InnerSpan`
532 fn span_from_inner(base: SpanData, inner: rpf::InnerSpan) -> Span {
534 base.lo + BytePos::from_usize(inner.start),
535 base.lo + BytePos::from_usize(inner.end),
541 /// How a format parameter is used in the format string
542 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
543 pub enum FormatParamKind {
544 /// An implicit parameter , such as `{}` or `{:?}`.
546 /// A parameter with an explicit number, e.g. `{1}`, `{0:?}`, or `{:.0$}`
548 /// A parameter with an asterisk precision. e.g. `{:.*}`.
550 /// A named parameter with a named `value_arg`, such as the `x` in `format!("{x}", x = 1)`.
552 /// An implicit named parameter, such as the `y` in `format!("{y}")`.
556 /// Where a format parameter is being used in the format string
557 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
558 pub enum FormatParamUsage {
559 /// Appears as an argument, e.g. `format!("{}", foo)`
561 /// Appears as a width, e.g. `format!("{:width$}", foo, width = 1)`
563 /// Appears as a precision, e.g. `format!("{:.precision$}", foo, precision = 1)`
567 /// A `FormatParam` is any place in a `FormatArgument` that refers to a supplied value, e.g.
570 /// let precision = 2;
571 /// format!("{:.precision$}", 0.1234);
574 /// has two `FormatParam`s, a [`FormatParamKind::Implicit`] `.kind` with a `.value` of `0.1234`
575 /// and a [`FormatParamKind::NamedInline("precision")`] `.kind` with a `.value` of `2`
576 #[derive(Debug, Copy, Clone)]
577 pub struct FormatParam<'tcx> {
578 /// The expression this parameter refers to.
579 pub value: &'tcx Expr<'tcx>,
580 /// How this parameter refers to its `value`.
581 pub kind: FormatParamKind,
582 /// Where this format param is being used - argument/width/precision
583 pub usage: FormatParamUsage,
584 /// Span of the parameter, may be zero width. Includes the whitespace of implicit parameters.
587 /// format!("{}, { }, {0}, {name}", ...);
593 impl<'tcx> FormatParam<'tcx> {
595 mut kind: FormatParamKind,
596 usage: FormatParamUsage,
598 inner: rpf::InnerSpan,
599 values: &FormatArgsValues<'tcx>,
601 let value_index = *values.pos_to_value_index.get(position)?;
602 let value = *values.value_args.get(value_index)?;
603 let span = span_from_inner(values.format_string_span, inner);
605 // if a param is declared inline, e.g. `format!("{x}")`, the generated expr's span points
606 // into the format string
607 if let FormatParamKind::Named(name) = kind && values.format_string_span.contains(value.span.data()) {
608 kind = FormatParamKind::NamedInline(name);
620 /// Used by [width](https://doc.rust-lang.org/std/fmt/#width) and
621 /// [precision](https://doc.rust-lang.org/std/fmt/#precision) specifiers.
622 #[derive(Debug, Copy, Clone)]
623 pub enum Count<'tcx> {
624 /// Specified with a literal number, stores the value.
626 /// Specified using `$` and `*` syntaxes. The `*` format is still considered to be
627 /// `FormatParamKind::Numbered`.
628 Param(FormatParam<'tcx>),
633 impl<'tcx> Count<'tcx> {
635 usage: FormatParamUsage,
636 count: rpf::Count<'_>,
637 position: Option<usize>,
638 inner: Option<rpf::InnerSpan>,
639 values: &FormatArgsValues<'tcx>,
642 rpf::Count::CountIs(val) => Self::Is(val, span_from_inner(values.format_string_span, inner?)),
643 rpf::Count::CountIsName(name, _) => Self::Param(FormatParam::new(
644 FormatParamKind::Named(Symbol::intern(name)),
650 rpf::Count::CountIsParam(_) => Self::Param(FormatParam::new(
651 FormatParamKind::Numbered,
657 rpf::Count::CountIsStar(_) => Self::Param(FormatParam::new(
658 FormatParamKind::Starred,
664 rpf::Count::CountImplied => Self::Implied,
668 pub fn is_implied(self) -> bool {
669 matches!(self, Count::Implied)
672 pub fn param(self) -> Option<FormatParam<'tcx>> {
674 Count::Param(param) => Some(param),
680 /// Specification for the formatting of an argument in the format string. See
681 /// <https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters> for the precise meanings.
683 pub struct FormatSpec<'tcx> {
684 /// Optionally specified character to fill alignment with.
685 pub fill: Option<char>,
686 /// Optionally specified alignment.
687 pub align: Alignment,
688 /// Packed version of various flags provided, see [`rustc_parse_format::Flag`].
690 /// Represents either the maximum width or the integer precision.
691 pub precision: Count<'tcx>,
692 /// The minimum width, will be padded according to `width`/`align`
693 pub width: Count<'tcx>,
694 /// The formatting trait used by the argument, e.g. `sym::Display` for `{}`, `sym::Debug` for
697 pub trait_span: Option<Span>,
700 impl<'tcx> FormatSpec<'tcx> {
701 fn new(spec: rpf::FormatSpec<'_>, positions: ParamPosition, values: &FormatArgsValues<'tcx>) -> Option<Self> {
706 precision: Count::new(
707 FormatParamUsage::Precision,
714 FormatParamUsage::Width,
720 r#trait: match spec.ty {
724 "x" => sym!(LowerHex),
725 "X" => sym!(UpperHex),
728 "e" => sym!(LowerExp),
729 "E" => sym!(UpperExp),
734 .map(|span| span_from_inner(values.format_string_span, span)),
738 /// Returns true if this format spec is unchanged from the default. e.g. returns true for `{}`,
739 /// `{foo}` and `{2}`, but false for `{:?}`, `{foo:5}` and `{3:.5}`
740 pub fn is_default(&self) -> bool {
741 self.r#trait == sym::Display
742 && self.width.is_implied()
743 && self.precision.is_implied()
744 && self.align == Alignment::AlignUnknown
749 /// A format argument, such as `{}`, `{foo:?}`.
751 pub struct FormatArg<'tcx> {
752 /// The parameter the argument refers to.
753 pub param: FormatParam<'tcx>,
754 /// How to format `param`.
755 pub format: FormatSpec<'tcx>,
756 /// span of the whole argument, `{..}`.
760 /// A parsed `format_args!` expansion.
762 pub struct FormatArgsExpn<'tcx> {
763 /// The format string literal.
764 pub format_string: FormatString,
765 /// The format arguments, such as `{:?}`.
766 pub args: Vec<FormatArg<'tcx>>,
767 /// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will
768 /// include this added newline.
770 /// Spans of the commas between the format string and explicit values, excluding any trailing
774 /// format!("..", 1, 2, 3,)
777 comma_spans: Vec<Span>,
778 /// Explicit values passed after the format string, ignoring implicit captures. `[1, z + 2]` for
779 /// `format!("{x} {} {y}", 1, z + 2)`.
780 explicit_values: Vec<&'tcx Expr<'tcx>>,
783 impl<'tcx> FormatArgsExpn<'tcx> {
784 /// Gets the spans of the commas inbetween the format string and explicit args, not including
785 /// any trailing comma
788 /// format!("{} {}", a, b)
792 /// Ensures that the format string and values aren't coming from a proc macro that sets the
793 /// output span to that of its input
794 fn comma_spans(cx: &LateContext<'_>, explicit_values: &[&Expr<'_>], fmt_span: Span) -> Option<Vec<Span>> {
795 // `format!("{} {} {c}", "one", "two", c = "three")`
796 // ^^^^^ ^^^^^ ^^^^^^^
797 let value_spans = explicit_values
799 .map(|val| hygiene::walk_chain(val.span, fmt_span.ctxt()));
801 // `format!("{} {} {c}", "one", "two", c = "three")`
803 let between_spans = once(fmt_span)
806 .map(|(start, end)| start.between(end));
808 let mut comma_spans = Vec::new();
809 for between_span in between_spans {
811 let mut seen_comma = false;
813 for token in tokenize(&snippet_opt(cx, between_span)?) {
815 TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {},
816 TokenKind::Comma if !seen_comma => {
819 let base = between_span.data();
820 comma_spans.push(Span::new(
821 base.lo + BytePos(offset),
822 base.lo + BytePos(offset + 1),
827 // named arguments, `start_val, name = end_val`
828 // ^^^^^^^^^ between_span
829 TokenKind::Ident | TokenKind::Eq if seen_comma => {},
830 // An unexpected token usually indicates the format string or a value came from a proc macro output
831 // that sets the span of its output to an input, e.g. `println!(some_proc_macro!("input"), ..)` that
832 // emits a string literal with the span set to that of `"input"`
846 pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
847 let macro_name = macro_backtrace(expr.span)
848 .map(|macro_call| cx.tcx.item_name(macro_call.def_id))
849 .find(|&name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))?;
850 let newline = macro_name == sym::format_args_nl;
852 // ::core::fmt::Arguments::new_v1(pieces, args)
853 // ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg)
854 if let ExprKind::Call(callee, [pieces, args, rest @ ..]) = expr.kind
855 && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind
856 && is_path_diagnostic_item(cx, ty, sym::Arguments)
857 && matches!(seg.ident.as_str(), "new_v1" | "new_v1_formatted")
859 let format_string = FormatString::new(cx, pieces)?;
861 let mut parser = rpf::Parser::new(
862 &format_string.unescaped,
864 Some(format_string.snippet.clone()),
865 // `format_string.unescaped` does not contain the appended newline
867 rpf::ParseMode::Format,
870 let parsed_args = parser
872 .filter_map(|piece| match piece {
873 rpf::Piece::NextArgument(a) => Some(a),
874 rpf::Piece::String(_) => None,
877 if !parser.errors.is_empty() {
881 let positions = if let Some(fmt_arg) = rest.first() {
882 // If the argument contains format specs, `new_v1_formatted(_, _, fmt, _)`, parse
885 Either::Left(parse_rt_fmt(fmt_arg)?)
887 // If no format specs are given, the positions are in the given order and there are
888 // no `precision`/`width`s to consider.
890 Either::Right((0..).map(|n| ParamPosition {
897 let values = FormatArgsValues::new(args, format_string.span.data());
899 let args = izip!(positions, parsed_args, parser.arg_places)
900 .map(|(position, parsed_arg, arg_span)| {
902 param: FormatParam::new(
903 match parsed_arg.position {
904 rpf::Position::ArgumentImplicitlyIs(_) => FormatParamKind::Implicit,
905 rpf::Position::ArgumentIs(_) => FormatParamKind::Numbered,
906 // NamedInline is handled by `FormatParam::new()`
907 rpf::Position::ArgumentNamed(name) => FormatParamKind::Named(Symbol::intern(name)),
909 FormatParamUsage::Argument,
911 parsed_arg.position_span,
914 format: FormatSpec::new(parsed_arg.format, position, &values)?,
915 span: span_from_inner(values.format_string_span, arg_span),
918 .collect::<Option<Vec<_>>>()?;
920 let mut explicit_values = values.value_args;
921 // remove values generated for implicitly captured vars
922 let len = explicit_values
924 .take_while(|val| !format_string.span.contains(val.span))
926 explicit_values.truncate(len);
928 let comma_spans = Self::comma_spans(cx, &explicit_values, format_string.span)?;
942 pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
943 for_each_expr(expr, |e| {
944 let e_ctxt = e.span.ctxt();
945 if e_ctxt == expr.span.ctxt() {
946 ControlFlow::Continue(Descend::Yes)
947 } else if e_ctxt.outer_expn().is_descendant_of(expn_id) {
948 if let Some(args) = FormatArgsExpn::parse(cx, e) {
949 ControlFlow::Break(args)
951 ControlFlow::Continue(Descend::No)
954 ControlFlow::Continue(Descend::No)
959 /// Source callsite span of all inputs
960 pub fn inputs_span(&self) -> Span {
961 match *self.explicit_values {
962 [] => self.format_string.span,
966 .to(hygiene::walk_chain(last.span, self.format_string.span.ctxt())),
970 /// Get the span of a value expanded to the previous comma, e.g. for the value `10`
973 /// format("{}.{}", 10, 11)
976 pub fn value_with_prev_comma_span(&self, value_id: HirId) -> Option<Span> {
977 for (comma_span, value) in zip(&self.comma_spans, &self.explicit_values) {
978 if value.hir_id == value_id {
979 return Some(comma_span.to(hygiene::walk_chain(value.span, comma_span.ctxt())));
986 /// Iterator of all format params, both values and those referenced by `width`/`precision`s.
987 pub fn params(&'tcx self) -> impl Iterator<Item = FormatParam<'tcx>> {
990 .flat_map(|arg| [Some(arg.param), arg.format.precision.param(), arg.format.width.param()])
995 /// A node with a `HirId` and a `Span`
997 fn hir_id(&self) -> HirId;
998 fn span(&self) -> Span;
1001 macro_rules! impl_hir_node {
1002 ($($t:ident),*) => {
1003 $(impl HirNode for hir::$t<'_> {
1004 fn hir_id(&self) -> HirId {
1007 fn span(&self) -> Span {
1014 impl_hir_node!(Expr, Pat);
1016 impl HirNode for hir::Item<'_> {
1017 fn hir_id(&self) -> HirId {
1021 fn span(&self) -> Span {