1 // Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 use self::ArgumentType::*;
12 use self::Position::*;
14 use fmt_macros as parse;
17 use syntax::ext::base::*;
18 use syntax::ext::base;
19 use syntax::ext::build::AstBuilder;
20 use syntax::parse::token;
22 use syntax::symbol::Symbol;
23 use syntax_pos::{Span, DUMMY_SP};
24 use syntax::tokenstream;
26 use std::collections::{HashMap, HashSet};
27 use std::collections::hash_map::Entry;
40 struct Context<'a, 'b: 'a> {
41 ecx: &'a mut ExtCtxt<'b>,
42 /// The macro's call site. References to unstable formatting internals must
43 /// use this span to pass the stability checker.
45 /// The span of the format string literal.
48 /// List of parsed argument expressions.
49 /// Named expressions are resolved early, and are appended to the end of
50 /// argument expressions.
52 /// Example showing the various data structures in motion:
54 /// * Original: `"{foo:o} {:o} {foo:x} {0:x} {1:o} {:x} {1:x} {0:o}"`
55 /// * Implicit argument resolution: `"{foo:o} {0:o} {foo:x} {0:x} {1:o} {1:x} {1:x} {0:o}"`
56 /// * Name resolution: `"{2:o} {0:o} {2:x} {0:x} {1:o} {1:x} {1:x} {0:o}"`
57 /// * `arg_types` (in JSON): `[[0, 1, 0], [0, 1, 1], [0, 1]]`
58 /// * `arg_unique_types` (in simplified JSON): `[["o", "x"], ["o", "x"], ["o", "x"]]`
59 /// * `names` (in JSON): `{"foo": 2}`
60 args: Vec<P<ast::Expr>>,
61 /// Placeholder slot numbers indexed by argument.
62 arg_types: Vec<Vec<usize>>,
63 /// Unique format specs seen for each argument.
64 arg_unique_types: Vec<Vec<ArgumentType>>,
65 /// Map from named arguments to their resolved indices.
66 names: HashMap<String, usize>,
68 /// The latest consecutive literal strings, or empty if there weren't any.
71 /// Collection of the compiled `rt::Argument` structures
72 pieces: Vec<P<ast::Expr>>,
73 /// Collection of string literals
74 str_pieces: Vec<P<ast::Expr>>,
75 /// Stays `true` if all formatting parameters are default (as in "{}{}").
76 all_pieces_simple: bool,
78 /// Mapping between positional argument references and indices into the
79 /// final generated static argument array. We record the starting indices
80 /// corresponding to each positional argument, and number of references
81 /// consumed so far for each argument, to facilitate correct `Position`
82 /// mapping in `trans_piece`. In effect this can be seen as a "flattened"
83 /// version of `arg_unique_types`.
85 /// Again with the example described above in docstring for `args`:
87 /// * `arg_index_map` (in JSON): `[[0, 1, 0], [2, 3, 3], [4, 5]]`
88 arg_index_map: Vec<Vec<usize>>,
90 /// Starting offset of count argument slots.
91 count_args_index_offset: usize,
93 /// Count argument slots and tracking data structures.
94 /// Count arguments are separately tracked for de-duplication in case
95 /// multiple references are made to one argument. For example, in this
98 /// * Original: `"{:.*} {:.foo$} {1:.*} {:.0$}"`
99 /// * Implicit argument resolution: `"{1:.0$} {2:.foo$} {1:.3$} {4:.0$}"`
100 /// * Name resolution: `"{1:.0$} {2:.5$} {1:.3$} {4:.0$}"`
101 /// * `count_positions` (in JSON): `{0: 0, 5: 1, 3: 2}`
102 /// * `count_args`: `vec![Exact(0), Exact(5), Exact(3)]`
103 count_args: Vec<Position>,
104 /// Relative slot numbers for count arguments.
105 count_positions: HashMap<usize, usize>,
106 /// Number of count slots assigned.
107 count_positions_count: usize,
109 /// Current position of the implicit positional arg pointer, as if it
110 /// still existed in this phase of processing.
111 /// Used only for `all_pieces_simple` tracking in `trans_piece`.
113 /// Keep track of invalid references to positional arguments
114 invalid_refs: Vec<usize>,
117 /// Parses the arguments from the given list of tokens, returning None
118 /// if there's a parse error so we can continue parsing other format!
121 /// If parsing succeeds, the return value is:
124 /// Some((fmtstr, parsed arguments, index map for named arguments))
126 fn parse_args(ecx: &mut ExtCtxt,
128 tts: &[tokenstream::TokenTree])
129 -> Option<(P<ast::Expr>, Vec<P<ast::Expr>>, HashMap<String, usize>)> {
130 let mut args = Vec::<P<ast::Expr>>::new();
131 let mut names = HashMap::<String, usize>::new();
133 let mut p = ecx.new_parser_from_tts(tts);
135 if p.token == token::Eof {
136 ecx.span_err(sp, "requires at least a format string argument");
139 let fmtstr = panictry!(p.parse_expr());
140 let mut named = false;
141 while p.token != token::Eof {
142 if !p.eat(&token::Comma) {
143 ecx.span_err(sp, "expected token: `,`");
146 if p.token == token::Eof {
148 } // accept trailing commas
149 if named || (p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq)) {
151 let ident = match p.token {
158 "expected ident, positional arguments \
159 cannot follow named arguments");
164 &format!("expected ident for named argument, found `{}`",
165 p.this_token_to_string()));
169 let name: &str = &ident.name.as_str();
171 panictry!(p.expect(&token::Eq));
172 let e = panictry!(p.parse_expr());
173 if let Some(prev) = names.get(name) {
174 ecx.struct_span_err(e.span, &format!("duplicate argument named `{}`", name))
175 .span_note(args[*prev].span, "previously here")
180 // Resolve names into slots early.
181 // Since all the positional args are already seen at this point
182 // if the input is valid, we can simply append to the positional
183 // args. And remember the names.
184 let slot = args.len();
185 names.insert(name.to_string(), slot);
188 args.push(panictry!(p.parse_expr()));
191 Some((fmtstr, args, names))
194 impl<'a, 'b> Context<'a, 'b> {
195 fn resolve_name_inplace(&self, p: &mut parse::Piece) {
196 // NOTE: the `unwrap_or` branch is needed in case of invalid format
197 // arguments, e.g. `format_args!("{foo}")`.
198 let lookup = |s| *self.names.get(s).unwrap_or(&0);
201 parse::String(_) => {}
202 parse::NextArgument(ref mut arg) => {
203 if let parse::ArgumentNamed(s) = arg.position {
204 arg.position = parse::ArgumentIs(lookup(s));
206 if let parse::CountIsName(s) = arg.format.width {
207 arg.format.width = parse::CountIsParam(lookup(s));
209 if let parse::CountIsName(s) = arg.format.precision {
210 arg.format.precision = parse::CountIsParam(lookup(s));
216 /// Verifies one piece of a parse string, and remembers it if valid.
217 /// All errors are not emitted as fatal so we can continue giving errors
218 /// about this and possibly other format strings.
219 fn verify_piece(&mut self, p: &parse::Piece) {
221 parse::String(..) => {}
222 parse::NextArgument(ref arg) => {
223 // width/precision first, if they have implicit positional
224 // parameters it makes more sense to consume them first.
225 self.verify_count(arg.format.width);
226 self.verify_count(arg.format.precision);
228 // argument second, if it's an implicit positional parameter
229 // it's written second, so it should come after width/precision.
230 let pos = match arg.position {
231 parse::ArgumentIs(i) | parse::ArgumentImplicitlyIs(i) => Exact(i),
232 parse::ArgumentNamed(s) => Named(s.to_string()),
235 let ty = Placeholder(arg.format.ty.to_string());
236 self.verify_arg_type(pos, ty);
241 fn verify_count(&mut self, c: parse::Count) {
243 parse::CountImplied |
244 parse::CountIs(..) => {}
245 parse::CountIsParam(i) => {
246 self.verify_arg_type(Exact(i), Count);
248 parse::CountIsName(s) => {
249 self.verify_arg_type(Named(s.to_string()), Count);
254 fn describe_num_args(&self) -> String {
255 match self.args.len() {
256 0 => "no arguments were given".to_string(),
257 1 => "there is 1 argument".to_string(),
258 x => format!("there are {} arguments", x),
262 /// Handle invalid references to positional arguments. Output different
263 /// errors for the case where all arguments are positional and for when
264 /// there are named arguments or numbered positional arguments in the
266 fn report_invalid_references(&self, numbered_position_args: bool) {
268 let mut refs: Vec<String> = self.invalid_refs
270 .map(|r| r.to_string())
273 if self.names.is_empty() && !numbered_position_args {
274 e = self.ecx.mut_span_err(self.fmtsp,
275 &format!("{} positional argument{} in format string, but {}",
277 if self.pieces.len() > 1 { "s" } else { "" },
278 self.describe_num_args()));
280 let arg_list = match refs.len() {
281 1 => format!("argument {}", refs.pop().unwrap()),
282 _ => format!("arguments {head} and {tail}",
283 tail=refs.pop().unwrap(),
284 head=refs.join(", "))
287 e = self.ecx.mut_span_err(self.fmtsp,
288 &format!("invalid reference to positional {} ({})",
290 self.describe_num_args()));
291 e.note("positional arguments are zero-based");
297 /// Actually verifies and tracks a given format placeholder
298 /// (a.k.a. argument).
299 fn verify_arg_type(&mut self, arg: Position, ty: ArgumentType) {
302 if self.args.len() <= arg {
303 self.invalid_refs.push(arg);
308 // record every (position, type) combination only once
309 let ref mut seen_ty = self.arg_unique_types[arg];
310 let i = match seen_ty.iter().position(|x| *x == ty) {
313 let i = seen_ty.len();
318 self.arg_types[arg].push(i);
321 match self.count_positions.entry(arg) {
322 Entry::Vacant(e) => {
323 let i = self.count_positions_count;
325 self.count_args.push(Exact(arg));
326 self.count_positions_count += 1;
328 Entry::Occupied(_) => {}
335 let idx = match self.names.get(&name) {
338 let msg = format!("there is no argument named `{}`", name);
339 self.ecx.span_err(self.fmtsp, &msg[..]);
343 // Treat as positional arg.
344 self.verify_arg_type(Exact(idx), ty)
349 /// Builds the mapping between format placeholders and argument objects.
350 fn build_index_map(&mut self) {
351 // NOTE: Keep the ordering the same as `into_expr`'s expansion would do!
352 let args_len = self.args.len();
353 self.arg_index_map.reserve(args_len);
355 let mut sofar = 0usize;
358 for i in 0..args_len {
359 let ref arg_types = self.arg_types[i];
360 let mut arg_offsets = Vec::with_capacity(arg_types.len());
361 for offset in arg_types {
362 arg_offsets.push(sofar + *offset);
364 self.arg_index_map.push(arg_offsets);
365 sofar += self.arg_unique_types[i].len();
368 // Record starting index for counts, which appear just after arguments
369 self.count_args_index_offset = sofar;
372 fn rtpath(ecx: &ExtCtxt, s: &str) -> Vec<ast::Ident> {
373 ecx.std_path(&["fmt", "rt", "v1", s])
376 fn trans_count(&self, c: parse::Count) -> P<ast::Expr> {
378 let count = |c, arg| {
379 let mut path = Context::rtpath(self.ecx, "Count");
380 path.push(self.ecx.ident_of(c));
382 Some(arg) => self.ecx.expr_call_global(sp, path, vec![arg]),
383 None => self.ecx.expr_path(self.ecx.path_global(sp, path)),
387 parse::CountIs(i) => count("Is", Some(self.ecx.expr_usize(sp, i))),
388 parse::CountIsParam(i) => {
389 // This needs mapping too, as `i` is referring to a macro
391 let i = match self.count_positions.get(&i) {
393 None => 0, // error already emitted elsewhere
395 let i = i + self.count_args_index_offset;
396 count("Param", Some(self.ecx.expr_usize(sp, i)))
398 parse::CountImplied => count("Implied", None),
399 // should never be the case, names are already resolved
400 parse::CountIsName(_) => panic!("should never happen"),
404 /// Translate the accumulated string literals to a literal expression
405 fn trans_literal_string(&mut self) -> P<ast::Expr> {
407 let s = Symbol::intern(&self.literal);
408 self.literal.clear();
409 self.ecx.expr_str(sp, s)
412 /// Translate a `parse::Piece` to a static `rt::Argument` or append
413 /// to the `literal` string.
414 fn trans_piece(&mut self,
415 piece: &parse::Piece,
416 arg_index_consumed: &mut Vec<usize>)
417 -> Option<P<ast::Expr>> {
420 parse::String(s) => {
421 self.literal.push_str(s);
424 parse::NextArgument(ref arg) => {
425 // Translate the position
428 let mut path = Context::rtpath(self.ecx, "Position");
429 path.push(self.ecx.ident_of(c));
432 let arg = self.ecx.expr_usize(sp, i);
433 self.ecx.expr_call_global(sp, path, vec![arg])
435 None => self.ecx.expr_path(self.ecx.path_global(sp, path)),
440 | parse::ArgumentImplicitlyIs(i) => {
441 // Map to index in final generated argument array
442 // in case of multiple types specified
443 let arg_idx = match arg_index_consumed.get_mut(i) {
444 None => 0, // error already emitted elsewhere
446 let ref idx_map = self.arg_index_map[i];
447 // unwrap_or branch: error already emitted elsewhere
448 let arg_idx = *idx_map.get(*offset).unwrap_or(&0);
453 pos("At", Some(arg_idx))
456 // should never be the case, because names are already
458 parse::ArgumentNamed(_) => panic!("should never happen"),
462 let simple_arg = parse::Argument {
464 // We don't have ArgumentNext any more, so we have to
465 // track the current argument ourselves.
470 format: parse::FormatSpec {
471 fill: arg.format.fill,
472 align: parse::AlignUnknown,
474 precision: parse::CountImplied,
475 width: parse::CountImplied,
480 let fill = match arg.format.fill {
485 if *arg != simple_arg || fill != ' ' {
486 self.all_pieces_simple = false;
489 // Translate the format
490 let fill = self.ecx.expr_lit(sp, ast::LitKind::Char(fill));
492 let mut p = Context::rtpath(self.ecx, "Alignment");
493 p.push(self.ecx.ident_of(name));
494 self.ecx.path_global(sp, p)
496 let align = match arg.format.align {
497 parse::AlignLeft => align("Left"),
498 parse::AlignRight => align("Right"),
499 parse::AlignCenter => align("Center"),
500 parse::AlignUnknown => align("Unknown"),
502 let align = self.ecx.expr_path(align);
503 let flags = self.ecx.expr_u32(sp, arg.format.flags);
504 let prec = self.trans_count(arg.format.precision);
505 let width = self.trans_count(arg.format.width);
506 let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, "FormatSpec"));
508 self.ecx.expr_struct(sp,
511 .field_imm(sp, self.ecx.ident_of("fill"), fill),
512 self.ecx.field_imm(sp,
513 self.ecx.ident_of("align"),
515 self.ecx.field_imm(sp,
516 self.ecx.ident_of("flags"),
518 self.ecx.field_imm(sp,
519 self.ecx.ident_of("precision"),
521 self.ecx.field_imm(sp,
522 self.ecx.ident_of("width"),
525 let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, "Argument"));
526 Some(self.ecx.expr_struct(sp,
528 vec![self.ecx.field_imm(sp,
529 self.ecx.ident_of("position"),
531 self.ecx.field_imm(sp,
532 self.ecx.ident_of("format"),
538 /// Actually builds the expression which the format_args! block will be
540 fn into_expr(self) -> P<ast::Expr> {
541 let mut locals = Vec::new();
542 let mut counts = Vec::new();
543 let mut pats = Vec::new();
544 let mut heads = Vec::new();
546 // First, build up the static array which will become our precompiled
548 let pieces = self.ecx.expr_vec_slice(self.fmtsp, self.str_pieces);
550 // Before consuming the expressions, we have to remember spans for
551 // count arguments as they are now generated separate from other
552 // arguments, hence have no access to the `P<ast::Expr>`'s.
553 let spans_pos: Vec<_> = self.args.iter().map(|e| e.span.clone()).collect();
555 // Right now there is a bug such that for the expression:
557 // the lifetime of `1` doesn't outlast the call to `bar`, so it's not
558 // valid for the call to `foo`. To work around this all arguments to the
559 // format! string are shoved into locals. Furthermore, we shove the address
560 // of each variable because we don't want to move out of the arguments
561 // passed to this function.
562 for (i, e) in self.args.into_iter().enumerate() {
563 let name = self.ecx.ident_of(&format!("__arg{}", i));
565 DUMMY_SP.with_ctxt(e.span.ctxt().apply_mark(self.ecx.current_expansion.mark));
566 pats.push(self.ecx.pat_ident(span, name));
567 for ref arg_ty in self.arg_unique_types[i].iter() {
568 locals.push(Context::format_arg(self.ecx, self.macsp, e.span, arg_ty, name));
570 heads.push(self.ecx.expr_addr_of(e.span, e));
572 for pos in self.count_args {
573 let name = self.ecx.ident_of(&match pos {
574 Exact(i) => format!("__arg{}", i),
575 _ => panic!("should never happen"),
577 let span = match pos {
578 Exact(i) => spans_pos[i],
579 _ => panic!("should never happen"),
581 counts.push(Context::format_arg(self.ecx, self.macsp, span, &Count, name));
584 // Now create a vector containing all the arguments
585 let args = locals.into_iter().chain(counts.into_iter());
587 let args_array = self.ecx.expr_vec(self.fmtsp, args.collect());
589 // Constructs an AST equivalent to:
591 // match (&arg0, &arg1) {
592 // (tmp0, tmp1) => args_array
601 // Because of #11585 the new temporary lifetime rule, the enclosing
602 // statements for these temporaries become the let's themselves.
603 // If one or more of them are RefCell's, RefCell borrow() will also
604 // end there; they don't last long enough for args_array to use them.
605 // The match expression solves the scope problem.
607 // Note, it may also very well be transformed to:
612 // ref tmp1 => args_array } } }
614 // But the nested match expression is proved to perform not as well
615 // as series of let's; the first approach does.
616 let pat = self.ecx.pat_tuple(self.fmtsp, pats);
617 let arm = self.ecx.arm(self.fmtsp, vec![pat], args_array);
618 let head = self.ecx.expr(self.fmtsp, ast::ExprKind::Tup(heads));
619 let result = self.ecx.expr_match(self.fmtsp, head, vec![arm]);
621 let args_slice = self.ecx.expr_addr_of(self.fmtsp, result);
623 // Now create the fmt::Arguments struct with all our locals we created.
624 let (fn_name, fn_args) = if self.all_pieces_simple {
625 ("new_v1", vec![pieces, args_slice])
627 // Build up the static array which will store our precompiled
628 // nonstandard placeholders, if there are any.
629 let fmt = self.ecx.expr_vec_slice(self.macsp, self.pieces);
631 ("new_v1_formatted", vec![pieces, args_slice, fmt])
634 let path = self.ecx.std_path(&["fmt", "Arguments", fn_name]);
635 self.ecx.expr_call_global(self.macsp, path, fn_args)
638 fn format_arg(ecx: &ExtCtxt,
644 sp = sp.with_ctxt(sp.ctxt().apply_mark(ecx.current_expansion.mark));
645 let arg = ecx.expr_ident(sp, arg);
646 let trait_ = match *ty {
647 Placeholder(ref tyname) => {
659 ecx.span_err(sp, &format!("unknown format trait `{}`", *tyname));
665 let path = ecx.std_path(&["fmt", "ArgumentV1", "from_usize"]);
666 return ecx.expr_call_global(macsp, path, vec![arg]);
670 let path = ecx.std_path(&["fmt", trait_, "fmt"]);
671 let format_fn = ecx.path_global(sp, path);
672 let path = ecx.std_path(&["fmt", "ArgumentV1", "new"]);
673 ecx.expr_call_global(macsp, path, vec![arg, ecx.expr_path(format_fn)])
677 pub fn expand_format_args<'cx>(ecx: &'cx mut ExtCtxt,
679 tts: &[tokenstream::TokenTree])
680 -> Box<base::MacResult + 'cx> {
681 sp = sp.with_ctxt(sp.ctxt().apply_mark(ecx.current_expansion.mark));
682 match parse_args(ecx, sp, tts) {
683 Some((efmt, args, names)) => {
684 MacEager::expr(expand_preparsed_format_args(ecx, sp, efmt, args, names))
686 None => DummyResult::expr(sp),
690 /// Take the various parts of `format_args!(efmt, args..., name=names...)`
691 /// and construct the appropriate formatting expression.
692 pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
695 args: Vec<P<ast::Expr>>,
696 names: HashMap<String, usize>)
698 // NOTE: this verbose way of initializing `Vec<Vec<ArgumentType>>` is because
699 // `ArgumentType` does not derive `Clone`.
700 let arg_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect();
701 let arg_unique_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect();
702 let mut macsp = ecx.call_site();
703 macsp = macsp.with_ctxt(macsp.ctxt().apply_mark(ecx.current_expansion.mark));
704 let msg = "format argument must be a string literal.";
705 let fmt = match expr_to_spanned_string(ecx, efmt, msg) {
707 None => return DummyResult::raw_expr(sp),
710 let mut cx = Context {
717 arg_index_map: Vec::new(),
718 count_args: Vec::new(),
719 count_positions: HashMap::new(),
720 count_positions_count: 0,
721 count_args_index_offset: 0,
722 literal: String::new(),
724 str_pieces: Vec::new(),
725 all_pieces_simple: true,
728 invalid_refs: Vec::new(),
731 let fmt_str = &*fmt.node.0.as_str();
732 let mut parser = parse::Parser::new(fmt_str);
733 let mut pieces = vec![];
736 match parser.next() {
738 if !parser.errors.is_empty() {
741 cx.verify_piece(&piece);
742 cx.resolve_name_inplace(&mut piece);
749 let numbered_position_args = pieces.iter().any(|arg: &parse::Piece| {
751 parse::String(_) => false,
752 parse::NextArgument(arg) => {
754 parse::Position::ArgumentIs(_) => true,
761 cx.build_index_map();
763 let mut arg_index_consumed = vec![0usize; cx.arg_index_map.len()];
764 for piece in pieces {
765 if let Some(piece) = cx.trans_piece(&piece, &mut arg_index_consumed) {
766 let s = cx.trans_literal_string();
767 cx.str_pieces.push(s);
768 cx.pieces.push(piece);
772 if !parser.errors.is_empty() {
773 let (err, note) = parser.errors.remove(0);
774 let mut e = cx.ecx.struct_span_err(cx.fmtsp, &format!("invalid format string: {}", err));
775 if let Some(note) = note {
779 return DummyResult::raw_expr(sp);
781 if !cx.literal.is_empty() {
782 let s = cx.trans_literal_string();
783 cx.str_pieces.push(s);
786 if cx.invalid_refs.len() >= 1 {
787 cx.report_invalid_references(numbered_position_args);
790 // Make sure that all arguments were used and all arguments have types.
791 let num_pos_args = cx.args.len() - cx.names.len();
792 let mut errs = vec![];
793 for (i, ty) in cx.arg_types.iter().enumerate() {
795 if cx.count_positions.contains_key(&i) {
798 let msg = if i >= num_pos_args {
800 "named argument never used"
802 // positional argument
803 "argument never used"
805 errs.push((cx.args[i].span, msg));
809 let args_used = cx.arg_types.len() - errs.len();
810 let args_unused = errs.len();
814 let (sp, msg) = errs.into_iter().next().unwrap();
815 cx.ecx.struct_span_err(sp, msg)
817 let mut diag = cx.ecx.struct_span_err(
818 errs.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(),
819 "multiple unused formatting arguments"
821 diag.span_label(cx.fmtsp, "multiple unused arguments in this statement");
826 // Decide if we want to look for foreign formatting directives.
827 if args_used < args_unused {
828 use super::format_foreign as foreign;
830 // The set of foreign substitutions we've explained. This prevents spamming the user
831 // with `%d should be written as {}` over and over again.
832 let mut explained = HashSet::new();
834 // Used to ensure we only report translations for *one* kind of foreign format.
835 let mut found_foreign = false;
837 macro_rules! check_foreign {
839 let mut show_doc_note = false;
841 for sub in foreign::$kind::iter_subs(fmt_str) {
842 let trn = match sub.translate() {
845 // If it has no translation, don't call it out specifically.
849 let sub = String::from(sub.as_str());
850 if explained.contains(&sub) {
853 explained.insert(sub.clone());
856 found_foreign = true;
857 show_doc_note = true;
860 diag.help(&format!("`{}` should be written as `{}`", sub, trn));
864 diag.note(concat!(stringify!($kind), " formatting not supported; see \
865 the documentation for `std::fmt`"));
870 check_foreign!(printf);
872 check_foreign!(shell);