7 use rustc_ast
::tokenstream
::TokenStream
;
8 use rustc_data_structures
::fx
::{FxHashMap, FxHashSet}
;
9 use rustc_errors
::{pluralize, Applicability, DiagnosticBuilder}
;
10 use rustc_expand
::base
::{self, *}
;
11 use rustc_parse_format
as parse
;
12 use rustc_span
::symbol
::{sym, Ident, Symbol}
;
13 use rustc_span
::{MultiSpan, Span}
;
16 use std
::collections
::hash_map
::Entry
;
20 Placeholder(&'
static str),
29 struct Context
<'a
, 'b
> {
30 ecx
: &'a
mut ExtCtxt
<'b
>,
31 /// The macro's call site. References to unstable formatting internals must
32 /// use this span to pass the stability checker.
34 /// The span of the format string literal.
37 /// List of parsed argument expressions.
38 /// Named expressions are resolved early, and are appended to the end of
39 /// argument expressions.
41 /// Example showing the various data structures in motion:
43 /// * Original: `"{foo:o} {:o} {foo:x} {0:x} {1:o} {:x} {1:x} {0:o}"`
44 /// * Implicit argument resolution: `"{foo:o} {0:o} {foo:x} {0:x} {1:o} {1:x} {1:x} {0:o}"`
45 /// * Name resolution: `"{2:o} {0:o} {2:x} {0:x} {1:o} {1:x} {1:x} {0:o}"`
46 /// * `arg_types` (in JSON): `[[0, 1, 0], [0, 1, 1], [0, 1]]`
47 /// * `arg_unique_types` (in simplified JSON): `[["o", "x"], ["o", "x"], ["o", "x"]]`
48 /// * `names` (in JSON): `{"foo": 2}`
49 args
: Vec
<P
<ast
::Expr
>>,
50 /// Placeholder slot numbers indexed by argument.
51 arg_types
: Vec
<Vec
<usize>>,
52 /// Unique format specs seen for each argument.
53 arg_unique_types
: Vec
<Vec
<ArgumentType
>>,
54 /// Map from named arguments to their resolved indices.
55 names
: FxHashMap
<Symbol
, usize>,
57 /// The latest consecutive literal strings, or empty if there weren't any.
60 /// Collection of the compiled `rt::Argument` structures
61 pieces
: Vec
<P
<ast
::Expr
>>,
62 /// Collection of string literals
63 str_pieces
: Vec
<P
<ast
::Expr
>>,
64 /// Stays `true` if all formatting parameters are default (as in "{}{}").
65 all_pieces_simple
: bool
,
67 /// Mapping between positional argument references and indices into the
68 /// final generated static argument array. We record the starting indices
69 /// corresponding to each positional argument, and number of references
70 /// consumed so far for each argument, to facilitate correct `Position`
71 /// mapping in `build_piece`. In effect this can be seen as a "flattened"
72 /// version of `arg_unique_types`.
74 /// Again with the example described above in docstring for `args`:
76 /// * `arg_index_map` (in JSON): `[[0, 1, 0], [2, 3, 3], [4, 5]]`
77 arg_index_map
: Vec
<Vec
<usize>>,
79 /// Starting offset of count argument slots.
80 count_args_index_offset
: usize,
82 /// Count argument slots and tracking data structures.
83 /// Count arguments are separately tracked for de-duplication in case
84 /// multiple references are made to one argument. For example, in this
87 /// * Original: `"{:.*} {:.foo$} {1:.*} {:.0$}"`
88 /// * Implicit argument resolution: `"{1:.0$} {2:.foo$} {1:.3$} {4:.0$}"`
89 /// * Name resolution: `"{1:.0$} {2:.5$} {1:.3$} {4:.0$}"`
90 /// * `count_positions` (in JSON): `{0: 0, 5: 1, 3: 2}`
91 /// * `count_args`: `vec![Exact(0), Exact(5), Exact(3)]`
92 count_args
: Vec
<Position
>,
93 /// Relative slot numbers for count arguments.
94 count_positions
: FxHashMap
<usize, usize>,
95 /// Number of count slots assigned.
96 count_positions_count
: usize,
98 /// Current position of the implicit positional arg pointer, as if it
99 /// still existed in this phase of processing.
100 /// Used only for `all_pieces_simple` tracking in `build_piece`.
102 /// Current piece being evaluated, used for error reporting.
104 /// Keep track of invalid references to positional arguments.
105 invalid_refs
: Vec
<(usize, usize)>,
106 /// Spans of all the formatting arguments, in order.
107 arg_spans
: Vec
<Span
>,
108 /// All the formatting arguments that have formatting flags set, in order for diagnostics.
109 arg_with_formatting
: Vec
<parse
::FormatSpec
<'a
>>,
111 /// Whether this format string came from a string literal, as opposed to a macro.
115 /// Parses the arguments from the given list of tokens, returning the diagnostic
116 /// if there's a parse error so we can continue parsing other format!
119 /// If parsing succeeds, the return value is:
122 /// Some((fmtstr, parsed arguments, index map for named arguments))
125 ecx
: &mut ExtCtxt
<'a
>,
128 ) -> Result
<(P
<ast
::Expr
>, Vec
<P
<ast
::Expr
>>, FxHashMap
<Symbol
, usize>), DiagnosticBuilder
<'a
>> {
129 let mut args
= Vec
::<P
<ast
::Expr
>>::new();
130 let mut names
= FxHashMap
::<Symbol
, usize>::default();
132 let mut p
= ecx
.new_parser_from_tts(tts
);
134 if p
.token
== token
::Eof
{
135 return Err(ecx
.struct_span_err(sp
, "requires at least a format string argument"));
138 let first_token
= &p
.token
;
139 let fmtstr
= match first_token
.kind
{
140 token
::TokenKind
::Literal(token
::Lit
{
141 kind
: token
::LitKind
::Str
| token
::LitKind
::StrRaw(_
),
144 // If the first token is a string literal, then a format expression
145 // is constructed from it.
147 // This allows us to properly handle cases when the first comma
148 // after the format string is mistakenly replaced with any operator,
149 // which cause the expression parser to eat too much tokens.
150 p
.parse_literal_maybe_minus()?
153 // Otherwise, we fall back to the expression parser.
158 let mut first
= true;
159 let mut named
= false;
161 while p
.token
!= token
::Eof
{
162 if !p
.eat(&token
::Comma
) {
164 p
.clear_expected_tokens();
167 // `Parser::expect` tries to recover using the
168 // `Parser::unexpected_try_recover` function. This function is able
169 // to recover if the expected token is a closing delimiter.
171 // As `,` is not a closing delimiter, it will always return an `Err`
173 let mut err
= p
.expect(&token
::Comma
).unwrap_err();
175 match token
::TokenKind
::Comma
.similar_tokens() {
176 Some(tks
) if tks
.contains(&p
.token
.kind
) => {
177 // If a similar token is found, then it may be a typo. We
178 // consider it as a comma, and continue parsing.
182 // Otherwise stop the parsing and return the error.
183 _
=> return Err(err
),
187 if p
.token
== token
::Eof
{
189 } // accept trailing commas
190 match p
.token
.ident() {
191 Some((ident
, _
)) if p
.look_ahead(1, |t
| *t
== token
::Eq
) => {
194 p
.expect(&token
::Eq
)?
;
195 let e
= p
.parse_expr()?
;
196 if let Some(prev
) = names
.get(&ident
.name
) {
197 ecx
.struct_span_err(e
.span
, &format
!("duplicate argument named `{}`", ident
))
198 .span_label(args
[*prev
].span
, "previously here")
199 .span_label(e
.span
, "duplicate argument")
204 // Resolve names into slots early.
205 // Since all the positional args are already seen at this point
206 // if the input is valid, we can simply append to the positional
207 // args. And remember the names.
208 let slot
= args
.len();
209 names
.insert(ident
.name
, slot
);
213 let e
= p
.parse_expr()?
;
215 let mut err
= ecx
.struct_span_err(
217 "positional arguments cannot follow named arguments",
219 err
.span_label(e
.span
, "positional arguments must be before named arguments");
220 for pos
in names
.values() {
221 err
.span_label(args
[*pos
].span
, "named argument");
229 Ok((fmtstr
, args
, names
))
232 impl<'a
, 'b
> Context
<'a
, 'b
> {
233 fn resolve_name_inplace(&self, p
: &mut parse
::Piece
<'_
>) {
234 // NOTE: the `unwrap_or` branch is needed in case of invalid format
235 // arguments, e.g., `format_args!("{foo}")`.
236 let lookup
= |s
: Symbol
| *self.names
.get(&s
).unwrap_or(&0);
239 parse
::String(_
) => {}
240 parse
::NextArgument(ref mut arg
) => {
241 if let parse
::ArgumentNamed(s
) = arg
.position
{
242 arg
.position
= parse
::ArgumentIs(lookup(s
));
244 if let parse
::CountIsName(s
) = arg
.format
.width
{
245 arg
.format
.width
= parse
::CountIsParam(lookup(s
));
247 if let parse
::CountIsName(s
) = arg
.format
.precision
{
248 arg
.format
.precision
= parse
::CountIsParam(lookup(s
));
254 /// Verifies one piece of a parse string, and remembers it if valid.
255 /// All errors are not emitted as fatal so we can continue giving errors
256 /// about this and possibly other format strings.
257 fn verify_piece(&mut self, p
: &parse
::Piece
<'_
>) {
259 parse
::String(..) => {}
260 parse
::NextArgument(ref arg
) => {
261 // width/precision first, if they have implicit positional
262 // parameters it makes more sense to consume them first.
263 self.verify_count(arg
.format
.width
);
264 self.verify_count(arg
.format
.precision
);
266 // argument second, if it's an implicit positional parameter
267 // it's written second, so it should come after width/precision.
268 let pos
= match arg
.position
{
269 parse
::ArgumentIs(i
) | parse
::ArgumentImplicitlyIs(i
) => Exact(i
),
270 parse
::ArgumentNamed(s
) => Named(s
),
273 let ty
= Placeholder(match arg
.format
.ty
{
284 let fmtsp
= self.fmtsp
;
285 let sp
= arg
.format
.ty_span
.map(|sp
| fmtsp
.from_inner(sp
));
286 let mut err
= self.ecx
.struct_span_err(
288 &format
!("unknown format trait `{}`", arg
.format
.ty
),
291 "the only appropriate formatting traits are:\n\
292 - ``, which uses the `Display` trait\n\
293 - `?`, which uses the `Debug` trait\n\
294 - `e`, which uses the `LowerExp` trait\n\
295 - `E`, which uses the `UpperExp` trait\n\
296 - `o`, which uses the `Octal` trait\n\
297 - `p`, which uses the `Pointer` trait\n\
298 - `b`, which uses the `Binary` trait\n\
299 - `x`, which uses the `LowerHex` trait\n\
300 - `X`, which uses the `UpperHex` trait",
302 if let Some(sp
) = sp
{
303 for (fmt
, name
) in &[
314 // FIXME: rustfix (`run-rustfix`) fails to apply suggestions.
315 // > "Cannot replace slice of data that was already replaced"
316 err
.tool_only_span_suggestion(
318 &format
!("use the `{}` trait", name
),
320 Applicability
::MaybeIncorrect
,
328 self.verify_arg_type(pos
, ty
);
334 fn verify_count(&mut self, c
: parse
::Count
) {
336 parse
::CountImplied
| parse
::CountIs(..) => {}
337 parse
::CountIsParam(i
) => {
338 self.verify_arg_type(Exact(i
), Count
);
340 parse
::CountIsName(s
) => {
341 self.verify_arg_type(Named(s
), Count
);
346 fn describe_num_args(&self) -> Cow
<'_
, str> {
347 match self.args
.len() {
348 0 => "no arguments were given".into(),
349 1 => "there is 1 argument".into(),
350 x
=> format
!("there are {} arguments", x
).into(),
354 /// Handle invalid references to positional arguments. Output different
355 /// errors for the case where all arguments are positional and for when
356 /// there are named arguments or numbered positional arguments in the
358 fn report_invalid_references(&self, numbered_position_args
: bool
) {
360 let sp
= if !self.arg_spans
.is_empty() {
361 // Point at the formatting arguments.
362 MultiSpan
::from_spans(self.arg_spans
.clone())
364 MultiSpan
::from_span(self.fmtsp
)
367 self.invalid_refs
.iter().map(|(r
, pos
)| (r
.to_string(), self.arg_spans
.get(*pos
)));
369 let mut zero_based_note
= false;
371 let count
= self.pieces
.len()
372 + self.arg_with_formatting
.iter().filter(|fmt
| fmt
.precision_span
.is_some()).count();
373 if self.names
.is_empty() && !numbered_position_args
&& count
!= self.args
.len() {
374 e
= self.ecx
.struct_span_err(
377 "{} positional argument{} in format string, but {}",
380 self.describe_num_args(),
383 for arg
in &self.args
{
384 // Point at the arguments that will be formatted.
385 e
.span_label(arg
.span
, "");
388 let (mut refs
, spans
): (Vec
<_
>, Vec
<_
>) = refs
.unzip();
389 // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)`
390 // for `println!("{7:7$}", 1);`
393 let spans
: Vec
<_
> = spans
.into_iter().filter_map(|sp
| sp
.copied()).collect();
394 let sp
= if self.arg_spans
.is_empty() || spans
.is_empty() {
395 MultiSpan
::from_span(self.fmtsp
)
397 MultiSpan
::from_spans(spans
)
399 let arg_list
= if refs
.len() == 1 {
400 format
!("argument {}", refs
[0])
402 let reg
= refs
.pop().unwrap();
403 format
!("arguments {head} and {tail}", head
= refs
.join(", "), tail
= reg
)
406 e
= self.ecx
.struct_span_err(
409 "invalid reference to positional {} ({})",
411 self.describe_num_args()
414 zero_based_note
= true;
417 for fmt
in &self.arg_with_formatting
{
418 if let Some(span
) = fmt
.precision_span
{
419 let span
= self.fmtsp
.from_inner(span
);
420 match fmt
.precision
{
421 parse
::CountIsParam(pos
) if pos
> self.args
.len() => {
425 "this precision flag expects an `usize` argument at position {}, \
428 self.describe_num_args(),
431 zero_based_note
= true;
433 parse
::CountIsParam(pos
) => {
434 let count
= self.pieces
.len()
438 .filter(|fmt
| fmt
.precision_span
.is_some())
440 e
.span_label(span
, &format
!(
441 "this precision flag adds an extra required argument at position {}, \
442 which is why there {} expected",
445 "is 1 argument".to_string()
447 format
!("are {} arguments", count
)
450 if let Some(arg
) = self.args
.get(pos
) {
453 "this parameter corresponds to the precision flag",
456 zero_based_note
= true;
461 if let Some(span
) = fmt
.width_span
{
462 let span
= self.fmtsp
.from_inner(span
);
464 parse
::CountIsParam(pos
) if pos
> self.args
.len() => {
468 "this width flag expects an `usize` argument at position {}, \
471 self.describe_num_args(),
474 zero_based_note
= true;
481 e
.note("positional arguments are zero-based");
483 if !self.arg_with_formatting
.is_empty() {
485 "for information about formatting flags, visit \
486 https://doc.rust-lang.org/std/fmt/index.html",
493 /// Actually verifies and tracks a given format placeholder
494 /// (a.k.a. argument).
495 fn verify_arg_type(&mut self, arg
: Position
, ty
: ArgumentType
) {
498 if self.args
.len() <= arg
{
499 self.invalid_refs
.push((arg
, self.curpiece
));
504 // record every (position, type) combination only once
505 let seen_ty
= &mut self.arg_unique_types
[arg
];
506 let i
= seen_ty
.iter().position(|x
| *x
== ty
).unwrap_or_else(|| {
507 let i
= seen_ty
.len();
511 self.arg_types
[arg
].push(i
);
514 if let Entry
::Vacant(e
) = self.count_positions
.entry(arg
) {
515 let i
= self.count_positions_count
;
517 self.count_args
.push(Exact(arg
));
518 self.count_positions_count
+= 1;
525 match self.names
.get(&name
) {
527 // Treat as positional arg.
528 self.verify_arg_type(Exact(idx
), ty
)
531 let capture_feature_enabled
= self
535 .map_or(false, |features
| features
.format_args_capture
);
537 // For the moment capturing variables from format strings expanded from macros is
538 // disabled (see RFC #2795)
539 let can_capture
= capture_feature_enabled
&& self.is_literal
;
542 // Treat this name as a variable to capture from the surrounding scope
543 let idx
= self.args
.len();
544 self.arg_types
.push(Vec
::new());
545 self.arg_unique_types
.push(Vec
::new());
546 let span
= if self.is_literal
{
547 *self.arg_spans
.get(self.curpiece
).unwrap_or(&self.fmtsp
)
551 self.args
.push(self.ecx
.expr_ident(span
, Ident
::new(name
, span
)));
552 self.names
.insert(name
, idx
);
553 self.verify_arg_type(Exact(idx
), ty
)
555 let msg
= format
!("there is no argument named `{}`", name
);
556 let sp
= if self.is_literal
{
557 *self.arg_spans
.get(self.curpiece
).unwrap_or(&self.fmtsp
)
561 let mut err
= self.ecx
.struct_span_err(sp
, &msg
[..]);
563 if capture_feature_enabled
&& !self.is_literal
{
565 "did you intend to capture a variable `{}` from \
566 the surrounding scope?",
570 "to avoid ambiguity, `format_args!` cannot capture variables \
571 when the format string is expanded from a macro",
573 } else if self.ecx
.parse_sess().unstable_features
.is_nightly_build() {
575 "if you intended to capture `{}` from the surrounding scope, add \
576 `#![feature(format_args_capture)]` to the crate attributes",
589 /// Builds the mapping between format placeholders and argument objects.
590 fn build_index_map(&mut self) {
591 // NOTE: Keep the ordering the same as `into_expr`'s expansion would do!
592 let args_len
= self.args
.len();
593 self.arg_index_map
.reserve(args_len
);
595 let mut sofar
= 0usize
;
598 for i
in 0..args_len
{
599 let arg_types
= &self.arg_types
[i
];
600 let arg_offsets
= arg_types
.iter().map(|offset
| sofar
+ *offset
).collect
::<Vec
<_
>>();
601 self.arg_index_map
.push(arg_offsets
);
602 sofar
+= self.arg_unique_types
[i
].len();
605 // Record starting index for counts, which appear just after arguments
606 self.count_args_index_offset
= sofar
;
609 fn rtpath(ecx
: &ExtCtxt
<'_
>, s
: Symbol
) -> Vec
<Ident
> {
610 ecx
.std_path(&[sym
::fmt
, sym
::rt
, sym
::v1
, s
])
613 fn build_count(&self, c
: parse
::Count
) -> P
<ast
::Expr
> {
615 let count
= |c
, arg
| {
616 let mut path
= Context
::rtpath(self.ecx
, sym
::Count
);
617 path
.push(Ident
::new(c
, sp
));
619 Some(arg
) => self.ecx
.expr_call_global(sp
, path
, vec
![arg
]),
620 None
=> self.ecx
.expr_path(self.ecx
.path_global(sp
, path
)),
624 parse
::CountIs(i
) => count(sym
::Is
, Some(self.ecx
.expr_usize(sp
, i
))),
625 parse
::CountIsParam(i
) => {
626 // This needs mapping too, as `i` is referring to a macro
627 // argument. If `i` is not found in `count_positions` then
628 // the error had already been emitted elsewhere.
629 let i
= self.count_positions
.get(&i
).cloned().unwrap_or(0)
630 + self.count_args_index_offset
;
631 count(sym
::Param
, Some(self.ecx
.expr_usize(sp
, i
)))
633 parse
::CountImplied
=> count(sym
::Implied
, None
),
634 // should never be the case, names are already resolved
635 parse
::CountIsName(_
) => panic
!("should never happen"),
639 /// Build a literal expression from the accumulated string literals
640 fn build_literal_string(&mut self) -> P
<ast
::Expr
> {
642 let s
= Symbol
::intern(&self.literal
);
643 self.literal
.clear();
644 self.ecx
.expr_str(sp
, s
)
647 /// Builds a static `rt::Argument` from a `parse::Piece` or append
648 /// to the `literal` string.
651 piece
: &parse
::Piece
<'a
>,
652 arg_index_consumed
: &mut Vec
<usize>,
653 ) -> Option
<P
<ast
::Expr
>> {
656 parse
::String(s
) => {
657 self.literal
.push_str(s
);
660 parse
::NextArgument(ref arg
) => {
661 // Build the position
664 parse
::ArgumentIs(i
) | parse
::ArgumentImplicitlyIs(i
) => {
665 // Map to index in final generated argument array
666 // in case of multiple types specified
667 let arg_idx
= match arg_index_consumed
.get_mut(i
) {
668 None
=> 0, // error already emitted elsewhere
670 let idx_map
= &self.arg_index_map
[i
];
671 // unwrap_or branch: error already emitted elsewhere
672 let arg_idx
= *idx_map
.get(*offset
).unwrap_or(&0);
677 self.ecx
.expr_usize(sp
, arg_idx
)
680 // should never be the case, because names are already
682 parse
::ArgumentNamed(_
) => panic
!("should never happen"),
686 let simple_arg
= parse
::Argument
{
688 // We don't have ArgumentNext any more, so we have to
689 // track the current argument ourselves.
694 format
: parse
::FormatSpec
{
695 fill
: arg
.format
.fill
,
696 align
: parse
::AlignUnknown
,
698 precision
: parse
::CountImplied
,
699 precision_span
: None
,
700 width
: parse
::CountImplied
,
703 ty_span
: arg
.format
.ty_span
,
707 let fill
= arg
.format
.fill
.unwrap_or(' '
);
709 let pos_simple
= arg
.position
.index() == simple_arg
.position
.index();
711 if arg
.format
.precision_span
.is_some() || arg
.format
.width_span
.is_some() {
712 self.arg_with_formatting
.push(arg
.format
);
714 if !pos_simple
|| arg
.format
!= simple_arg
.format
|| fill
!= ' '
{
715 self.all_pieces_simple
= false;
719 let fill
= self.ecx
.expr_lit(sp
, ast
::LitKind
::Char(fill
));
721 let mut p
= Context
::rtpath(self.ecx
, sym
::Alignment
);
722 p
.push(Ident
::new(name
, sp
));
723 self.ecx
.path_global(sp
, p
)
725 let align
= match arg
.format
.align
{
726 parse
::AlignLeft
=> align(sym
::Left
),
727 parse
::AlignRight
=> align(sym
::Right
),
728 parse
::AlignCenter
=> align(sym
::Center
),
729 parse
::AlignUnknown
=> align(sym
::Unknown
),
731 let align
= self.ecx
.expr_path(align
);
732 let flags
= self.ecx
.expr_u32(sp
, arg
.format
.flags
);
733 let prec
= self.build_count(arg
.format
.precision
);
734 let width
= self.build_count(arg
.format
.width
);
735 let path
= self.ecx
.path_global(sp
, Context
::rtpath(self.ecx
, sym
::FormatSpec
));
736 let fmt
= self.ecx
.expr_struct(
740 self.ecx
.field_imm(sp
, Ident
::new(sym
::fill
, sp
), fill
),
741 self.ecx
.field_imm(sp
, Ident
::new(sym
::align
, sp
), align
),
742 self.ecx
.field_imm(sp
, Ident
::new(sym
::flags
, sp
), flags
),
743 self.ecx
.field_imm(sp
, Ident
::new(sym
::precision
, sp
), prec
),
744 self.ecx
.field_imm(sp
, Ident
::new(sym
::width
, sp
), width
),
748 let path
= self.ecx
.path_global(sp
, Context
::rtpath(self.ecx
, sym
::Argument
));
749 Some(self.ecx
.expr_struct(
753 self.ecx
.field_imm(sp
, Ident
::new(sym
::position
, sp
), pos
),
754 self.ecx
.field_imm(sp
, Ident
::new(sym
::format
, sp
), fmt
),
761 /// Actually builds the expression which the format_args! block will be
763 fn into_expr(self) -> P
<ast
::Expr
> {
765 Vec
::with_capacity((0..self.args
.len()).map(|i
| self.arg_unique_types
[i
].len()).sum());
766 let mut counts
= Vec
::with_capacity(self.count_args
.len());
767 let mut pats
= Vec
::with_capacity(self.args
.len());
768 let mut heads
= Vec
::with_capacity(self.args
.len());
770 let names_pos
: Vec
<_
> = (0..self.args
.len())
771 .map(|i
| Ident
::from_str_and_span(&format
!("arg{}", i
), self.macsp
))
774 // First, build up the static array which will become our precompiled
776 let pieces
= self.ecx
.expr_vec_slice(self.fmtsp
, self.str_pieces
);
778 // Before consuming the expressions, we have to remember spans for
779 // count arguments as they are now generated separate from other
780 // arguments, hence have no access to the `P<ast::Expr>`'s.
781 let spans_pos
: Vec
<_
> = self.args
.iter().map(|e
| e
.span
).collect();
783 // Right now there is a bug such that for the expression:
785 // the lifetime of `1` doesn't outlast the call to `bar`, so it's not
786 // valid for the call to `foo`. To work around this all arguments to the
787 // format! string are shoved into locals. Furthermore, we shove the address
788 // of each variable because we don't want to move out of the arguments
789 // passed to this function.
790 for (i
, e
) in self.args
.into_iter().enumerate() {
791 let name
= names_pos
[i
];
792 let span
= self.ecx
.with_def_site_ctxt(e
.span
);
793 pats
.push(self.ecx
.pat_ident(span
, name
));
794 for arg_ty
in self.arg_unique_types
[i
].iter() {
795 locals
.push(Context
::format_arg(self.ecx
, self.macsp
, e
.span
, arg_ty
, name
));
797 heads
.push(self.ecx
.expr_addr_of(e
.span
, e
));
799 for pos
in self.count_args
{
800 let index
= match pos
{
802 _
=> panic
!("should never happen"),
804 let name
= names_pos
[index
];
805 let span
= spans_pos
[index
];
806 counts
.push(Context
::format_arg(self.ecx
, self.macsp
, span
, &Count
, name
));
809 // Now create a vector containing all the arguments
810 let args
= locals
.into_iter().chain(counts
.into_iter());
812 let args_array
= self.ecx
.expr_vec(self.macsp
, args
.collect());
814 // Constructs an AST equivalent to:
816 // match (&arg0, &arg1) {
817 // (tmp0, tmp1) => args_array
826 // Because of #11585 the new temporary lifetime rule, the enclosing
827 // statements for these temporaries become the let's themselves.
828 // If one or more of them are RefCell's, RefCell borrow() will also
829 // end there; they don't last long enough for args_array to use them.
830 // The match expression solves the scope problem.
832 // Note, it may also very well be transformed to:
837 // ref tmp1 => args_array } } }
839 // But the nested match expression is proved to perform not as well
840 // as series of let's; the first approach does.
841 let pat
= self.ecx
.pat_tuple(self.macsp
, pats
);
842 let arm
= self.ecx
.arm(self.macsp
, pat
, args_array
);
843 let head
= self.ecx
.expr(self.macsp
, ast
::ExprKind
::Tup(heads
));
844 let result
= self.ecx
.expr_match(self.macsp
, head
, vec
![arm
]);
846 let args_slice
= self.ecx
.expr_addr_of(self.macsp
, result
);
848 // Now create the fmt::Arguments struct with all our locals we created.
849 let (fn_name
, fn_args
) = if self.all_pieces_simple
{
850 ("new_v1", vec
![pieces
, args_slice
])
852 // Build up the static array which will store our precompiled
853 // nonstandard placeholders, if there are any.
854 let fmt
= self.ecx
.expr_vec_slice(self.macsp
, self.pieces
);
856 ("new_v1_formatted", vec
![pieces
, args_slice
, fmt
])
859 let path
= self.ecx
.std_path(&[sym
::fmt
, sym
::Arguments
, Symbol
::intern(fn_name
)]);
860 self.ecx
.expr_call_global(self.macsp
, path
, fn_args
)
870 sp
= ecx
.with_def_site_ctxt(sp
);
871 let arg
= ecx
.expr_ident(sp
, arg
);
872 let trait_
= match *ty
{
873 Placeholder(trait_
) if trait_
== "<invalid>" => return DummyResult
::raw_expr(sp
, true),
874 Placeholder(trait_
) => trait_
,
876 let path
= ecx
.std_path(&[sym
::fmt
, sym
::ArgumentV1
, sym
::from_usize
]);
877 return ecx
.expr_call_global(macsp
, path
, vec
![arg
]);
881 let path
= ecx
.std_path(&[sym
::fmt
, Symbol
::intern(trait_
), sym
::fmt
]);
882 let format_fn
= ecx
.path_global(sp
, path
);
883 let path
= ecx
.std_path(&[sym
::fmt
, sym
::ArgumentV1
, sym
::new
]);
884 ecx
.expr_call_global(macsp
, path
, vec
![arg
, ecx
.expr_path(format_fn
)])
888 fn expand_format_args_impl
<'cx
>(
889 ecx
: &'cx
mut ExtCtxt
<'_
>,
893 ) -> Box
<dyn base
::MacResult
+ 'cx
> {
894 sp
= ecx
.with_def_site_ctxt(sp
);
895 match parse_args(ecx
, sp
, tts
) {
896 Ok((efmt
, args
, names
)) => {
897 MacEager
::expr(expand_preparsed_format_args(ecx
, sp
, efmt
, args
, names
, nl
))
906 pub fn expand_format_args
<'cx
>(
907 ecx
: &'cx
mut ExtCtxt
<'_
>,
910 ) -> Box
<dyn base
::MacResult
+ 'cx
> {
911 expand_format_args_impl(ecx
, sp
, tts
, false)
914 pub fn expand_format_args_nl
<'cx
>(
915 ecx
: &'cx
mut ExtCtxt
<'_
>,
918 ) -> Box
<dyn base
::MacResult
+ 'cx
> {
919 expand_format_args_impl(ecx
, sp
, tts
, true)
922 /// Take the various parts of `format_args!(efmt, args..., name=names...)`
923 /// and construct the appropriate formatting expression.
924 pub fn expand_preparsed_format_args(
925 ecx
: &mut ExtCtxt
<'_
>,
928 args
: Vec
<P
<ast
::Expr
>>,
929 names
: FxHashMap
<Symbol
, usize>,
930 append_newline
: bool
,
932 // NOTE: this verbose way of initializing `Vec<Vec<ArgumentType>>` is because
933 // `ArgumentType` does not derive `Clone`.
934 let arg_types
: Vec
<_
> = (0..args
.len()).map(|_
| Vec
::new()).collect();
935 let arg_unique_types
: Vec
<_
> = (0..args
.len()).map(|_
| Vec
::new()).collect();
937 let mut macsp
= ecx
.call_site();
938 macsp
= ecx
.with_def_site_ctxt(macsp
);
940 let msg
= "format argument must be a string literal";
941 let fmt_sp
= efmt
.span
;
942 let (fmt_str
, fmt_style
, fmt_span
) = match expr_to_spanned_string(ecx
, efmt
, msg
) {
943 Ok(mut fmt
) if append_newline
=> {
944 fmt
.0 = Symbol
::intern(&format
!("{}\n", fmt
.0));
949 if let Some(mut err
) = err
{
950 let sugg_fmt
= match args
.len() {
951 0 => "{}".to_string(),
952 _
=> format
!("{}{{}}", "{} ".repeat(args
.len())),
955 fmt_sp
.shrink_to_lo(),
956 "you might be missing a string literal to format with",
957 format
!("\"{}\", ", sugg_fmt
),
958 Applicability
::MaybeIncorrect
,
962 return DummyResult
::raw_expr(sp
, true);
966 let str_style
= match fmt_style
{
967 ast
::StrStyle
::Cooked
=> None
,
968 ast
::StrStyle
::Raw(raw
) => Some(raw
as usize),
971 let fmt_str
= &fmt_str
.as_str(); // for the suggestions below
972 let fmt_snippet
= ecx
.source_map().span_to_snippet(fmt_sp
).ok();
973 let mut parser
= parse
::Parser
::new(
978 parse
::ParseMode
::Format
,
981 let mut unverified_pieces
= Vec
::new();
982 while let Some(piece
) = parser
.next() {
983 if !parser
.errors
.is_empty() {
986 unverified_pieces
.push(piece
);
990 if !parser
.errors
.is_empty() {
991 let err
= parser
.errors
.remove(0);
992 let sp
= fmt_span
.from_inner(err
.span
);
993 let mut e
= ecx
.struct_span_err(sp
, &format
!("invalid format string: {}", err
.description
));
994 e
.span_label(sp
, err
.label
+ " in format string");
995 if let Some(note
) = err
.note
{
998 if let Some((label
, span
)) = err
.secondary_label
{
999 let sp
= fmt_span
.from_inner(span
);
1000 e
.span_label(sp
, label
);
1003 return DummyResult
::raw_expr(sp
, true);
1006 let arg_spans
= parser
.arg_places
.iter().map(|span
| fmt_span
.from_inner(*span
)).collect();
1008 let named_pos
: FxHashSet
<usize> = names
.values().cloned().collect();
1010 let mut cx
= Context
{
1018 arg_index_map
: Vec
::new(),
1019 count_args
: Vec
::new(),
1020 count_positions
: FxHashMap
::default(),
1021 count_positions_count
: 0,
1022 count_args_index_offset
: 0,
1023 literal
: String
::new(),
1024 pieces
: Vec
::with_capacity(unverified_pieces
.len()),
1025 str_pieces
: Vec
::with_capacity(unverified_pieces
.len()),
1026 all_pieces_simple
: true,
1029 invalid_refs
: Vec
::new(),
1031 arg_with_formatting
: Vec
::new(),
1032 is_literal
: parser
.is_literal
,
1035 // This needs to happen *after* the Parser has consumed all pieces to create all the spans
1036 let pieces
= unverified_pieces
1039 cx
.verify_piece(&piece
);
1040 cx
.resolve_name_inplace(&mut piece
);
1043 .collect
::<Vec
<_
>>();
1045 let numbered_position_args
= pieces
.iter().any(|arg
: &parse
::Piece
<'_
>| match *arg
{
1046 parse
::String(_
) => false,
1047 parse
::NextArgument(arg
) => matches
!(arg
.position
, parse
::Position
::ArgumentIs(_
)),
1050 cx
.build_index_map();
1052 let mut arg_index_consumed
= vec
![0usize
; cx
.arg_index_map
.len()];
1054 for piece
in pieces
{
1055 if let Some(piece
) = cx
.build_piece(&piece
, &mut arg_index_consumed
) {
1056 let s
= cx
.build_literal_string();
1057 cx
.str_pieces
.push(s
);
1058 cx
.pieces
.push(piece
);
1062 if !cx
.literal
.is_empty() {
1063 let s
= cx
.build_literal_string();
1064 cx
.str_pieces
.push(s
);
1067 if !cx
.invalid_refs
.is_empty() {
1068 cx
.report_invalid_references(numbered_position_args
);
1071 // Make sure that all arguments were used and all arguments have types.
1076 .filter(|(i
, ty
)| ty
.is_empty() && !cx
.count_positions
.contains_key(&i
))
1078 let msg
= if named_pos
.contains(&i
) {
1080 "named argument never used"
1082 // positional argument
1083 "argument never used"
1085 (cx
.args
[i
].span
, msg
)
1087 .collect
::<Vec
<_
>>();
1089 let errs_len
= errs
.len();
1090 if !errs
.is_empty() {
1091 let args_used
= cx
.arg_types
.len() - errs_len
;
1092 let args_unused
= errs_len
;
1095 if let [(sp
, msg
)] = &errs
[..] {
1096 let mut diag
= cx
.ecx
.struct_span_err(*sp
, *msg
);
1097 diag
.span_label(*sp
, *msg
);
1100 let mut diag
= cx
.ecx
.struct_span_err(
1101 errs
.iter().map(|&(sp
, _
)| sp
).collect
::<Vec
<Span
>>(),
1102 "multiple unused formatting arguments",
1104 diag
.span_label(cx
.fmtsp
, "multiple missing formatting specifiers");
1105 for (sp
, msg
) in errs
{
1106 diag
.span_label(sp
, msg
);
1112 // Used to ensure we only report translations for *one* kind of foreign format.
1113 let mut found_foreign
= false;
1114 // Decide if we want to look for foreign formatting directives.
1115 if args_used
< args_unused
{
1116 use super::format_foreign
as foreign
;
1118 // The set of foreign substitutions we've explained. This prevents spamming the user
1119 // with `%d should be written as {}` over and over again.
1120 let mut explained
= FxHashSet
::default();
1122 macro_rules
! check_foreign
{
1124 let mut show_doc_note
= false;
1126 let mut suggestions
= vec
![];
1127 // account for `"` and account for raw strings `r#`
1128 let padding
= str_style
.map(|i
| i
+ 2).unwrap_or(1);
1129 for sub
in foreign
::$kind
::iter_subs(fmt_str
, padding
) {
1130 let trn
= match sub
.translate() {
1133 // If it has no translation, don't call it out specifically.
1137 let pos
= sub
.position();
1138 let sub
= String
::from(sub
.as_str());
1139 if explained
.contains(&sub
) {
1142 explained
.insert(sub
.clone());
1145 found_foreign
= true;
1146 show_doc_note
= true;
1149 if let Some(inner_sp
) = pos
{
1150 let sp
= fmt_sp
.from_inner(inner_sp
);
1151 suggestions
.push((sp
, trn
));
1153 diag
.help(&format
!("`{}` should be written as `{}`", sub
, trn
));
1160 " formatting not supported; see the documentation for `std::fmt`",
1163 if suggestions
.len() > 0 {
1164 diag
.multipart_suggestion(
1165 "format specifiers use curly braces",
1167 Applicability
::MachineApplicable
,
1173 check_foreign
!(printf
);
1175 check_foreign
!(shell
);
1178 if !found_foreign
&& errs_len
== 1 {
1179 diag
.span_label(cx
.fmtsp
, "formatting specifier missing");