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 };
203 sym::core_panic_macro
204 | sym::std_panic_macro
205 | sym::core_panic_2015_macro
206 | sym::std_panic_2015_macro
207 | sym::core_panic_2021_macro
211 /// Is `def_id` of `assert!` or `debug_assert!`
212 pub fn is_assert_macro(cx: &LateContext<'_>, def_id: DefId) -> bool {
213 let Some(name) = cx.tcx.get_diagnostic_name(def_id) else { return false };
214 matches!(name, sym::assert_macro | sym::debug_assert_macro)
217 pub enum PanicExpn<'a> {
218 /// No arguments - `panic!()`
220 /// A string literal or any `&str` - `panic!("message")` or `panic!(message)`
222 /// A single argument that implements `Display` - `panic!("{}", object)`
223 Display(&'a Expr<'a>),
224 /// Anything else - `panic!("error {}: {}", a, b)`
225 Format(FormatArgsExpn<'a>),
228 impl<'a> PanicExpn<'a> {
229 pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Self> {
230 if !macro_backtrace(expr.span).any(|macro_call| is_panic(cx, macro_call.def_id)) {
233 let ExprKind::Call(callee, [arg]) = &expr.kind else { return None };
234 let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else { return None };
235 let result = match path.segments.last().unwrap().ident.as_str() {
236 "panic" if arg.span.ctxt() == expr.span.ctxt() => Self::Empty,
237 "panic" | "panic_str" => Self::Str(arg),
239 let ExprKind::AddrOf(_, _, e) = &arg.kind else { return None };
242 "panic_fmt" => Self::Format(FormatArgsExpn::parse(cx, arg)?),
249 /// Finds the arguments of an `assert!` or `debug_assert!` macro call within the macro expansion
250 pub fn find_assert_args<'a>(
251 cx: &LateContext<'_>,
254 ) -> Option<(&'a Expr<'a>, PanicExpn<'a>)> {
255 find_assert_args_inner(cx, expr, expn).map(|([e], p)| (e, p))
258 /// Finds the arguments of an `assert_eq!` or `debug_assert_eq!` macro call within the macro
260 pub fn find_assert_eq_args<'a>(
261 cx: &LateContext<'_>,
264 ) -> Option<(&'a Expr<'a>, &'a Expr<'a>, PanicExpn<'a>)> {
265 find_assert_args_inner(cx, expr, expn).map(|([a, b], p)| (a, b, p))
268 fn find_assert_args_inner<'a, const N: usize>(
269 cx: &LateContext<'_>,
272 ) -> Option<([&'a Expr<'a>; N], PanicExpn<'a>)> {
273 let macro_id = expn.expn_data().macro_def_id?;
274 let (expr, expn) = match cx.tcx.item_name(macro_id).as_str().strip_prefix("debug_") {
275 None => (expr, expn),
276 Some(inner_name) => find_assert_within_debug_assert(cx, expr, expn, Symbol::intern(inner_name))?,
278 let mut args = ArrayVec::new();
279 let mut panic_expn = None;
280 let _: Option<!> = for_each_expr(expr, |e| {
282 if panic_expn.is_none() && e.span.ctxt() != expr.span.ctxt() {
283 panic_expn = PanicExpn::parse(cx, e);
285 ControlFlow::Continue(Descend::from(panic_expn.is_none()))
286 } else if is_assert_arg(cx, e, expn) {
288 ControlFlow::Continue(Descend::No)
290 ControlFlow::Continue(Descend::Yes)
293 let args = args.into_inner().ok()?;
294 // if no `panic!(..)` is found, use `PanicExpn::Empty`
295 // to indicate that the default assertion message is used
296 let panic_expn = panic_expn.unwrap_or(PanicExpn::Empty);
297 Some((args, panic_expn))
300 fn find_assert_within_debug_assert<'a>(
301 cx: &LateContext<'_>,
305 ) -> Option<(&'a Expr<'a>, ExpnId)> {
306 for_each_expr(expr, |e| {
307 if !e.span.from_expansion() {
308 return ControlFlow::Continue(Descend::No);
310 let e_expn = e.span.ctxt().outer_expn();
312 ControlFlow::Continue(Descend::Yes)
313 } else if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
314 ControlFlow::Break((e, e_expn))
316 ControlFlow::Continue(Descend::No)
321 fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool {
322 if !expr.span.from_expansion() {
325 let result = macro_backtrace(expr.span).try_for_each(|macro_call| {
326 if macro_call.expn == assert_expn {
327 ControlFlow::Break(false)
329 match cx.tcx.item_name(macro_call.def_id) {
330 // `cfg!(debug_assertions)` in `debug_assert!`
331 sym::cfg => ControlFlow::CONTINUE,
332 // assert!(other_macro!(..))
333 _ => ControlFlow::Break(true),
338 ControlFlow::Break(is_assert_arg) => is_assert_arg,
339 ControlFlow::Continue(()) => true,
343 /// The format string doesn't exist in the HIR, so we reassemble it from source code
345 pub struct FormatString {
346 /// Span of the whole format string literal, including `[r#]"`.
348 /// Snippet of the whole format string literal, including `[r#]"`.
350 /// If the string is raw `r"..."`/`r#""#`, how many `#`s does it have on each side.
351 pub style: Option<usize>,
352 /// The unescaped value of the format string, e.g. `"val – {}"` for the literal
353 /// `"val \u{2013} {}"`.
354 pub unescaped: String,
355 /// The format string split by format args like `{..}`.
356 pub parts: Vec<Symbol>,
360 fn new(cx: &LateContext<'_>, pieces: &Expr<'_>) -> Option<Self> {
361 // format_args!(r"a {} b \", 1);
365 // ::core::fmt::Arguments::new_v1(&["a ", " b \\"],
366 // &[::core::fmt::ArgumentV1::new_display(&1)]);
368 // where `pieces` is the expression `&["a ", " b \\"]`. It has the span of `r"a {} b \"`
369 let span = pieces.span;
370 let snippet = snippet_opt(cx, span)?;
372 let (inner, style) = match tokenize(&snippet).next()?.kind {
373 TokenKind::Literal { kind, .. } => {
374 let style = match kind {
375 LiteralKind::Str { .. } => None,
376 LiteralKind::RawStr { n_hashes: Some(n), .. } => Some(n.into()),
380 let start = style.map_or(1, |n| 2 + n);
381 let end = snippet.len() - style.map_or(1, |n| 1 + n);
383 (&snippet[start..end], style)
388 let mode = if style.is_some() {
389 unescape::Mode::RawStr
394 let mut unescaped = String::with_capacity(inner.len());
395 unescape_literal(inner, mode, &mut |_, ch| match ch {
396 Ok(ch) => unescaped.push(ch),
397 Err(e) if !e.is_fatal() => (),
398 Err(e) => panic!("{e:?}"),
401 let mut parts = Vec::new();
402 let _: Option<!> = for_each_expr(pieces, |expr| {
403 if let ExprKind::Lit(lit) = &expr.kind
404 && let LitKind::Str(symbol, _) = lit.node
408 ControlFlow::Continue(())
421 struct FormatArgsValues<'tcx> {
422 /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
423 /// `format!("{x} {} {}", 1, z + 2)`.
424 value_args: Vec<&'tcx Expr<'tcx>>,
425 /// Maps an `rt::v1::Argument::position` or an `rt::v1::Count::Param` to its index in
427 pos_to_value_index: Vec<usize>,
428 /// Used to check if a value is declared inline & to resolve `InnerSpan`s.
429 format_string_span: SpanData,
432 impl<'tcx> FormatArgsValues<'tcx> {
433 fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self {
434 let mut pos_to_value_index = Vec::new();
435 let mut value_args = Vec::new();
436 let _: Option<!> = for_each_expr(args, |expr| {
437 if expr.span.ctxt() == args.span.ctxt() {
438 // ArgumentV1::new_<format_trait>(<val>)
439 // ArgumentV1::from_usize(<val>)
440 if let ExprKind::Call(callee, [val]) = expr.kind
441 && let ExprKind::Path(QPath::TypeRelative(ty, _)) = callee.kind
442 && let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind
443 && path.segments.last().unwrap().ident.name == sym::ArgumentV1
445 let val_idx = if val.span.ctxt() == expr.span.ctxt()
446 && let ExprKind::Field(_, field) = val.kind
447 && let Ok(idx) = field.name.as_str().parse()
452 // assume the value expression is passed directly
453 pos_to_value_index.len()
456 pos_to_value_index.push(val_idx);
458 ControlFlow::Continue(Descend::Yes)
460 // assume that any expr with a differing span is a value
461 value_args.push(expr);
462 ControlFlow::Continue(Descend::No)
474 /// The positions of a format argument's value, precision and width
476 /// A position is an index into the second argument of `Arguments::new_v1[_formatted]`
477 #[derive(Debug, Default, Copy, Clone)]
478 struct ParamPosition {
479 /// The position stored in `rt::v1::Argument::position`.
481 /// The position stored in `rt::v1::FormatSpec::width` if it is a `Count::Param`.
482 width: Option<usize>,
483 /// The position stored in `rt::v1::FormatSpec::precision` if it is a `Count::Param`.
484 precision: Option<usize>,
487 impl<'tcx> Visitor<'tcx> for ParamPosition {
488 fn visit_expr_field(&mut self, field: &'tcx ExprField<'tcx>) {
489 fn parse_count(expr: &Expr<'_>) -> Option<usize> {
490 // ::core::fmt::rt::v1::Count::Param(1usize),
491 if let ExprKind::Call(ctor, [val]) = expr.kind
492 && let ExprKind::Path(QPath::Resolved(_, path)) = ctor.kind
493 && path.segments.last()?.ident.name == sym::Param
494 && let ExprKind::Lit(lit) = &val.kind
495 && let LitKind::Int(pos, _) = lit.node
503 match field.ident.name {
505 if let ExprKind::Lit(lit) = &field.expr.kind
506 && let LitKind::Int(pos, _) = lit.node
508 self.value = pos as usize;
512 self.precision = parse_count(field.expr);
515 self.width = parse_count(field.expr);
517 _ => walk_expr(self, field.expr),
522 /// Parses the `fmt` arg of `Arguments::new_v1_formatted(pieces, args, fmt, _)`
523 fn parse_rt_fmt<'tcx>(fmt_arg: &'tcx Expr<'tcx>) -> Option<impl Iterator<Item = ParamPosition> + 'tcx> {
524 if let ExprKind::AddrOf(.., array) = fmt_arg.kind
525 && let ExprKind::Array(specs) = array.kind
527 Some(specs.iter().map(|spec| {
528 let mut position = ParamPosition::default();
529 position.visit_expr(spec);
537 /// `Span::from_inner`, but for `rustc_parse_format`'s `InnerSpan`
538 fn span_from_inner(base: SpanData, inner: rpf::InnerSpan) -> Span {
540 base.lo + BytePos::from_usize(inner.start),
541 base.lo + BytePos::from_usize(inner.end),
547 /// How a format parameter is used in the format string
548 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
549 pub enum FormatParamKind {
550 /// An implicit parameter , such as `{}` or `{:?}`.
552 /// A parameter with an explicit number, e.g. `{1}`, `{0:?}`, or `{:.0$}`
554 /// A parameter with an asterisk precision. e.g. `{:.*}`.
556 /// A named parameter with a named `value_arg`, such as the `x` in `format!("{x}", x = 1)`.
558 /// An implicit named parameter, such as the `y` in `format!("{y}")`.
562 /// Where a format parameter is being used in the format string
563 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
564 pub enum FormatParamUsage {
565 /// Appears as an argument, e.g. `format!("{}", foo)`
567 /// Appears as a width, e.g. `format!("{:width$}", foo, width = 1)`
569 /// Appears as a precision, e.g. `format!("{:.precision$}", foo, precision = 1)`
573 /// A `FormatParam` is any place in a `FormatArgument` that refers to a supplied value, e.g.
576 /// let precision = 2;
577 /// format!("{:.precision$}", 0.1234);
580 /// has two `FormatParam`s, a [`FormatParamKind::Implicit`] `.kind` with a `.value` of `0.1234`
581 /// and a [`FormatParamKind::NamedInline("precision")`] `.kind` with a `.value` of `2`
582 #[derive(Debug, Copy, Clone)]
583 pub struct FormatParam<'tcx> {
584 /// The expression this parameter refers to.
585 pub value: &'tcx Expr<'tcx>,
586 /// How this parameter refers to its `value`.
587 pub kind: FormatParamKind,
588 /// Where this format param is being used - argument/width/precision
589 pub usage: FormatParamUsage,
590 /// Span of the parameter, may be zero width. Includes the whitespace of implicit parameters.
593 /// format!("{}, { }, {0}, {name}", ...);
599 impl<'tcx> FormatParam<'tcx> {
601 mut kind: FormatParamKind,
602 usage: FormatParamUsage,
604 inner: rpf::InnerSpan,
605 values: &FormatArgsValues<'tcx>,
607 let value_index = *values.pos_to_value_index.get(position)?;
608 let value = *values.value_args.get(value_index)?;
609 let span = span_from_inner(values.format_string_span, inner);
611 // if a param is declared inline, e.g. `format!("{x}")`, the generated expr's span points
612 // into the format string
613 if let FormatParamKind::Named(name) = kind && values.format_string_span.contains(value.span.data()) {
614 kind = FormatParamKind::NamedInline(name);
626 /// Used by [width](https://doc.rust-lang.org/std/fmt/#width) and
627 /// [precision](https://doc.rust-lang.org/std/fmt/#precision) specifiers.
628 #[derive(Debug, Copy, Clone)]
629 pub enum Count<'tcx> {
630 /// Specified with a literal number, stores the value.
632 /// Specified using `$` and `*` syntaxes. The `*` format is still considered to be
633 /// `FormatParamKind::Numbered`.
634 Param(FormatParam<'tcx>),
636 Implied(Option<Span>),
639 impl<'tcx> Count<'tcx> {
641 usage: FormatParamUsage,
642 count: rpf::Count<'_>,
643 position: Option<usize>,
644 inner: Option<rpf::InnerSpan>,
645 values: &FormatArgsValues<'tcx>,
647 let span = inner.map(|inner| span_from_inner(values.format_string_span, inner));
650 rpf::Count::CountIs(val) => Self::Is(val, span?),
651 rpf::Count::CountIsName(name, _) => Self::Param(FormatParam::new(
652 FormatParamKind::Named(Symbol::intern(name)),
658 rpf::Count::CountIsParam(_) => Self::Param(FormatParam::new(
659 FormatParamKind::Numbered,
665 rpf::Count::CountIsStar(_) => Self::Param(FormatParam::new(
666 FormatParamKind::Starred,
672 rpf::Count::CountImplied => Self::Implied(span),
676 pub fn is_implied(self) -> bool {
677 matches!(self, Count::Implied(_))
680 pub fn param(self) -> Option<FormatParam<'tcx>> {
682 Count::Param(param) => Some(param),
687 pub fn span(self) -> Option<Span> {
689 Count::Is(_, span) => Some(span),
690 Count::Param(param) => Some(param.span),
691 Count::Implied(span) => span,
696 /// Specification for the formatting of an argument in the format string. See
697 /// <https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters> for the precise meanings.
699 pub struct FormatSpec<'tcx> {
700 /// Optionally specified character to fill alignment with.
701 pub fill: Option<char>,
702 /// Optionally specified alignment.
703 pub align: Alignment,
704 /// Packed version of various flags provided, see [`rustc_parse_format::Flag`].
706 /// Represents either the maximum width or the integer precision.
707 pub precision: Count<'tcx>,
708 /// The minimum width, will be padded according to `width`/`align`
709 pub width: Count<'tcx>,
710 /// The formatting trait used by the argument, e.g. `sym::Display` for `{}`, `sym::Debug` for
713 pub trait_span: Option<Span>,
716 impl<'tcx> FormatSpec<'tcx> {
717 fn new(spec: rpf::FormatSpec<'_>, positions: ParamPosition, values: &FormatArgsValues<'tcx>) -> Option<Self> {
722 precision: Count::new(
723 FormatParamUsage::Precision,
730 FormatParamUsage::Width,
736 r#trait: match spec.ty {
740 "x" => sym!(LowerHex),
741 "X" => sym!(UpperHex),
744 "e" => sym!(LowerExp),
745 "E" => sym!(UpperExp),
750 .map(|span| span_from_inner(values.format_string_span, span)),
754 /// Returns true if this format spec is unchanged from the default. e.g. returns true for `{}`,
755 /// `{foo}` and `{2}`, but false for `{:?}`, `{foo:5}` and `{3:.5}`
756 pub fn is_default(&self) -> bool {
757 self.r#trait == sym::Display && self.is_default_for_trait()
760 /// Has no other formatting specifiers than setting the format trait. returns true for `{}`,
761 /// `{foo}`, `{:?}`, but false for `{foo:5}`, `{3:.5?}`
762 pub fn is_default_for_trait(&self) -> bool {
763 self.width.is_implied()
764 && self.precision.is_implied()
765 && self.align == Alignment::AlignUnknown
770 /// A format argument, such as `{}`, `{foo:?}`.
772 pub struct FormatArg<'tcx> {
773 /// The parameter the argument refers to.
774 pub param: FormatParam<'tcx>,
775 /// How to format `param`.
776 pub format: FormatSpec<'tcx>,
777 /// span of the whole argument, `{..}`.
781 impl<'tcx> FormatArg<'tcx> {
782 /// Span of the `:` and format specifiers
785 /// format!("{:.}"), format!("{foo:.}")
788 pub fn format_span(&self) -> Span {
789 let base = self.span.data();
791 // `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing
793 Span::new(self.param.span.hi(), base.hi - BytePos(1), base.ctxt, base.parent)
797 /// A parsed `format_args!` expansion.
799 pub struct FormatArgsExpn<'tcx> {
800 /// The format string literal.
801 pub format_string: FormatString,
802 /// The format arguments, such as `{:?}`.
803 pub args: Vec<FormatArg<'tcx>>,
804 /// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will
805 /// include this added newline.
807 /// Spans of the commas between the format string and explicit values, excluding any trailing
811 /// format!("..", 1, 2, 3,)
814 comma_spans: Vec<Span>,
815 /// Explicit values passed after the format string, ignoring implicit captures. `[1, z + 2]` for
816 /// `format!("{x} {} {y}", 1, z + 2)`.
817 explicit_values: Vec<&'tcx Expr<'tcx>>,
820 impl<'tcx> FormatArgsExpn<'tcx> {
821 /// Gets the spans of the commas inbetween the format string and explicit args, not including
822 /// any trailing comma
825 /// format!("{} {}", a, b)
829 /// Ensures that the format string and values aren't coming from a proc macro that sets the
830 /// output span to that of its input
831 fn comma_spans(cx: &LateContext<'_>, explicit_values: &[&Expr<'_>], fmt_span: Span) -> Option<Vec<Span>> {
832 // `format!("{} {} {c}", "one", "two", c = "three")`
833 // ^^^^^ ^^^^^ ^^^^^^^
834 let value_spans = explicit_values
836 .map(|val| hygiene::walk_chain(val.span, fmt_span.ctxt()));
838 // `format!("{} {} {c}", "one", "two", c = "three")`
840 let between_spans = once(fmt_span)
843 .map(|(start, end)| start.between(end));
845 let mut comma_spans = Vec::new();
846 for between_span in between_spans {
848 let mut seen_comma = false;
850 for token in tokenize(&snippet_opt(cx, between_span)?) {
852 TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {},
853 TokenKind::Comma if !seen_comma => {
856 let base = between_span.data();
857 comma_spans.push(Span::new(
858 base.lo + BytePos(offset),
859 base.lo + BytePos(offset + 1),
864 // named arguments, `start_val, name = end_val`
865 // ^^^^^^^^^ between_span
866 TokenKind::Ident | TokenKind::Eq if seen_comma => {},
867 // An unexpected token usually indicates the format string or a value came from a proc macro output
868 // that sets the span of its output to an input, e.g. `println!(some_proc_macro!("input"), ..)` that
869 // emits a string literal with the span set to that of `"input"`
883 pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
884 let macro_name = macro_backtrace(expr.span)
885 .map(|macro_call| cx.tcx.item_name(macro_call.def_id))
886 .find(|&name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))?;
887 let newline = macro_name == sym::format_args_nl;
889 // ::core::fmt::Arguments::new_v1(pieces, args)
890 // ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg)
891 if let ExprKind::Call(callee, [pieces, args, rest @ ..]) = expr.kind
892 && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind
893 && is_path_diagnostic_item(cx, ty, sym::Arguments)
894 && matches!(seg.ident.as_str(), "new_v1" | "new_v1_formatted")
896 let format_string = FormatString::new(cx, pieces)?;
898 let mut parser = rpf::Parser::new(
899 &format_string.unescaped,
901 Some(format_string.snippet.clone()),
902 // `format_string.unescaped` does not contain the appended newline
904 rpf::ParseMode::Format,
907 let parsed_args = parser
909 .filter_map(|piece| match piece {
910 rpf::Piece::NextArgument(a) => Some(a),
911 rpf::Piece::String(_) => None,
914 if !parser.errors.is_empty() {
918 let positions = if let Some(fmt_arg) = rest.first() {
919 // If the argument contains format specs, `new_v1_formatted(_, _, fmt, _)`, parse
922 Either::Left(parse_rt_fmt(fmt_arg)?)
924 // If no format specs are given, the positions are in the given order and there are
925 // no `precision`/`width`s to consider.
927 Either::Right((0..).map(|n| ParamPosition {
934 let values = FormatArgsValues::new(args, format_string.span.data());
936 let args = izip!(positions, parsed_args, parser.arg_places)
937 .map(|(position, parsed_arg, arg_span)| {
939 param: FormatParam::new(
940 match parsed_arg.position {
941 rpf::Position::ArgumentImplicitlyIs(_) => FormatParamKind::Implicit,
942 rpf::Position::ArgumentIs(_) => FormatParamKind::Numbered,
943 // NamedInline is handled by `FormatParam::new()`
944 rpf::Position::ArgumentNamed(name) => FormatParamKind::Named(Symbol::intern(name)),
946 FormatParamUsage::Argument,
948 parsed_arg.position_span,
951 format: FormatSpec::new(parsed_arg.format, position, &values)?,
952 span: span_from_inner(values.format_string_span, arg_span),
955 .collect::<Option<Vec<_>>>()?;
957 let mut explicit_values = values.value_args;
958 // remove values generated for implicitly captured vars
959 let len = explicit_values
961 .take_while(|val| !format_string.span.contains(val.span))
963 explicit_values.truncate(len);
965 let comma_spans = Self::comma_spans(cx, &explicit_values, format_string.span)?;
979 pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
980 for_each_expr(expr, |e| {
981 let e_ctxt = e.span.ctxt();
982 if e_ctxt == expr.span.ctxt() {
983 ControlFlow::Continue(Descend::Yes)
984 } else if e_ctxt.outer_expn().is_descendant_of(expn_id) {
985 if let Some(args) = FormatArgsExpn::parse(cx, e) {
986 ControlFlow::Break(args)
988 ControlFlow::Continue(Descend::No)
991 ControlFlow::Continue(Descend::No)
996 /// Source callsite span of all inputs
997 pub fn inputs_span(&self) -> Span {
998 match *self.explicit_values {
999 [] => self.format_string.span,
1003 .to(hygiene::walk_chain(last.span, self.format_string.span.ctxt())),
1007 /// Get the span of a value expanded to the previous comma, e.g. for the value `10`
1010 /// format("{}.{}", 10, 11)
1013 pub fn value_with_prev_comma_span(&self, value_id: HirId) -> Option<Span> {
1014 for (comma_span, value) in zip(&self.comma_spans, &self.explicit_values) {
1015 if value.hir_id == value_id {
1016 return Some(comma_span.to(hygiene::walk_chain(value.span, comma_span.ctxt())));
1023 /// Iterator of all format params, both values and those referenced by `width`/`precision`s.
1024 pub fn params(&'tcx self) -> impl Iterator<Item = FormatParam<'tcx>> {
1027 .flat_map(|arg| [Some(arg.param), arg.format.precision.param(), arg.format.width.param()])
1032 /// A node with a `HirId` and a `Span`
1034 fn hir_id(&self) -> HirId;
1035 fn span(&self) -> Span;
1038 macro_rules! impl_hir_node {
1039 ($($t:ident),*) => {
1040 $(impl HirNode for hir::$t<'_> {
1041 fn hir_id(&self) -> HirId {
1044 fn span(&self) -> Span {
1051 impl_hir_node!(Expr, Pat);
1053 impl HirNode for hir::Item<'_> {
1054 fn hir_id(&self) -> HirId {
1058 fn span(&self) -> Span {