]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_builtin_macros/src/format.rs
New upstream version 1.69.0+dfsg1
[rustc.git] / compiler / rustc_builtin_macros / src / format.rs
CommitLineData
74b04a01 1use rustc_ast::ptr::P;
2b03887a 2use rustc_ast::token;
74b04a01 3use rustc_ast::tokenstream::TokenStream;
9ffffee4
FG
4use rustc_ast::{
5 Expr, ExprKind, FormatAlignment, FormatArgPosition, FormatArgPositionKind, FormatArgs,
6 FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArguments, FormatCount,
7 FormatDebugHex, FormatOptions, FormatPlaceholder, FormatSign, FormatTrait,
8};
2b03887a 9use rustc_data_structures::fx::FxHashSet;
04454e1e 10use rustc_errors::{pluralize, Applicability, MultiSpan, PResult};
dfeec247 11use rustc_expand::base::{self, *};
f035d41b 12use rustc_parse_format as parse;
2b03887a 13use rustc_span::symbol::{Ident, Symbol};
064997fb 14use rustc_span::{BytePos, InnerSpan, Span};
1a4d82fc 15
064997fb
FG
16use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
17use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, LintId};
064997fb 18
2b03887a
FG
19// The format_args!() macro is expanded in three steps:
20// 1. First, `parse_args` will parse the `(literal, arg, arg, name=arg, name=arg)` syntax,
21// but doesn't parse the template (the literal) itself.
22// 2. Second, `make_format_args` will parse the template, the format options, resolve argument references,
9ffffee4
FG
23// produce diagnostics, and turn the whole thing into a `FormatArgs` AST node.
24// 3. Much later, in AST lowering (rustc_ast_lowering), that `FormatArgs` structure will be turned
25// into the expression of type `core::fmt::Arguments`.
5bcae85e 26
9ffffee4 27// See rustc_ast/src/format.rs for the FormatArgs structure and glossary.
1a4d82fc 28
2b03887a
FG
29// Only used in parse_args and report_invalid_references,
30// to indicate how a referred argument was used.
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32enum PositionUsedAs {
33 Placeholder(Option<Span>),
34 Precision,
35 Width,
1a4d82fc 36}
2b03887a 37use PositionUsedAs::*;
1a4d82fc 38
0731742a 39/// Parses the arguments from the given list of tokens, returning the diagnostic
1a4d82fc
JJ
40/// if there's a parse error so we can continue parsing other format!
41/// expressions.
42///
43/// If parsing succeeds, the return value is:
041b39d2
XL
44///
45/// ```text
2b03887a 46/// Ok((fmtstr, parsed arguments))
92a42be0 47/// ```
0731742a
XL
48fn parse_args<'a>(
49 ecx: &mut ExtCtxt<'a>,
50 sp: Span,
e1599b0c 51 tts: TokenStream,
2b03887a
FG
52) -> PResult<'a, (P<Expr>, FormatArguments)> {
53 let mut args = FormatArguments::new();
1a4d82fc
JJ
54
55 let mut p = ecx.new_parser_from_tts(tts);
56
57 if p.token == token::Eof {
0731742a 58 return Err(ecx.struct_span_err(sp, "requires at least a format string argument"));
1a4d82fc 59 }
b7449926 60
1b1a35ee
XL
61 let first_token = &p.token;
62 let fmtstr = match first_token.kind {
63 token::TokenKind::Literal(token::Lit {
64 kind: token::LitKind::Str | token::LitKind::StrRaw(_),
65 ..
66 }) => {
67 // If the first token is a string literal, then a format expression
68 // is constructed from it.
69 //
70 // This allows us to properly handle cases when the first comma
71 // after the format string is mistakenly replaced with any operator,
72 // which cause the expression parser to eat too much tokens.
73 p.parse_literal_maybe_minus()?
74 }
75 _ => {
76 // Otherwise, we fall back to the expression parser.
77 p.parse_expr()?
78 }
79 };
80
e1599b0c 81 let mut first = true;
b7449926 82
1a4d82fc 83 while p.token != token::Eof {
9cc50fc6 84 if !p.eat(&token::Comma) {
e1599b0c 85 if first {
1b1a35ee
XL
86 p.clear_expected_tokens();
87 }
88
c295e0f8
XL
89 match p.expect(&token::Comma) {
90 Err(mut err) => {
91 match token::TokenKind::Comma.similar_tokens() {
92 Some(tks) if tks.contains(&p.token.kind) => {
93 // If a similar token is found, then it may be a typo. We
94 // consider it as a comma, and continue parsing.
95 err.emit();
96 p.bump();
97 }
98 // Otherwise stop the parsing and return the error.
99 _ => return Err(err),
100 }
101 }
102 Ok(recovered) => {
103 assert!(recovered);
1b1a35ee 104 }
e1599b0c 105 }
1a4d82fc 106 }
e1599b0c 107 first = false;
9e0c209e
SL
108 if p.token == token::Eof {
109 break;
110 } // accept trailing commas
74b04a01
XL
111 match p.token.ident() {
112 Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => {
0731742a 113 p.bump();
74b04a01 114 p.expect(&token::Eq)?;
2b03887a
FG
115 let expr = p.parse_expr()?;
116 if let Some((_, prev)) = args.by_name(ident.name) {
117 ecx.struct_span_err(
118 ident.span,
119 &format!("duplicate argument named `{}`", ident),
120 )
121 .span_label(prev.kind.ident().unwrap().span, "previously here")
122 .span_label(ident.span, "duplicate argument")
123 .emit();
74b04a01
XL
124 continue;
125 }
2b03887a 126 args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr });
1a4d82fc 127 }
74b04a01 128 _ => {
2b03887a
FG
129 let expr = p.parse_expr()?;
130 if !args.named_args().is_empty() {
74b04a01 131 let mut err = ecx.struct_span_err(
2b03887a 132 expr.span,
74b04a01
XL
133 "positional arguments cannot follow named arguments",
134 );
2b03887a
FG
135 err.span_label(
136 expr.span,
137 "positional arguments must be before named arguments",
138 );
139 for arg in args.named_args() {
140 if let Some(name) = arg.kind.ident() {
141 err.span_label(name.span.to(arg.expr.span), "named argument");
142 }
74b04a01
XL
143 }
144 err.emit();
416331ca 145 }
2b03887a 146 args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr });
416331ca 147 }
1a4d82fc
JJ
148 }
149 }
2b03887a 150 Ok((fmtstr, args))
1a4d82fc
JJ
151}
152
2b03887a 153pub fn make_format_args(
416331ca 154 ecx: &mut ExtCtxt<'_>,
2b03887a
FG
155 efmt: P<Expr>,
156 mut args: FormatArguments,
416331ca 157 append_newline: bool,
2b03887a 158) -> Result<FormatArgs, ()> {
8faf50e0 159 let msg = "format argument must be a string literal";
2b03887a 160 let unexpanded_fmt_span = efmt.span;
e1599b0c 161 let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) {
8faf50e0 162 Ok(mut fmt) if append_newline => {
e1599b0c 163 fmt.0 = Symbol::intern(&format!("{}\n", fmt.0));
8faf50e0
XL
164 fmt
165 }
166 Ok(fmt) => fmt,
0731742a 167 Err(err) => {
c295e0f8 168 if let Some((mut err, suggested)) = err {
2b03887a 169 let sugg_fmt = match args.explicit_args().len() {
0731742a 170 0 => "{}".to_string(),
2b03887a 171 _ => format!("{}{{}}", "{} ".repeat(args.explicit_args().len())),
0731742a 172 };
c295e0f8
XL
173 if !suggested {
174 err.span_suggestion(
2b03887a 175 unexpanded_fmt_span.shrink_to_lo(),
c295e0f8
XL
176 "you might be missing a string literal to format with",
177 format!("\"{}\", ", sugg_fmt),
178 Applicability::MaybeIncorrect,
179 );
180 }
0731742a
XL
181 err.emit();
182 }
2b03887a 183 return Err(());
8faf50e0
XL
184 }
185 };
b7449926 186
e1599b0c 187 let str_style = match fmt_style {
2b03887a
FG
188 rustc_ast::StrStyle::Cooked => None,
189 rustc_ast::StrStyle::Raw(raw) => Some(raw as usize),
0731742a
XL
190 };
191
a2a8927a 192 let fmt_str = fmt_str.as_str(); // for the suggestions below
2b03887a 193 let fmt_snippet = ecx.source_map().span_to_snippet(unexpanded_fmt_span).ok();
f9f354fc
XL
194 let mut parser = parse::Parser::new(
195 fmt_str,
196 str_style,
197 fmt_snippet,
198 append_newline,
199 parse::ParseMode::Format,
200 );
b7449926 201
2b03887a 202 let mut pieces = Vec::new();
b7449926
XL
203 while let Some(piece) = parser.next() {
204 if !parser.errors.is_empty() {
205 break;
206 } else {
2b03887a 207 pieces.push(piece);
b7449926
XL
208 }
209 }
210
2b03887a
FG
211 let is_literal = parser.is_literal;
212
b7449926
XL
213 if !parser.errors.is_empty() {
214 let err = parser.errors.remove(0);
2b03887a 215 let sp = if is_literal {
04454e1e 216 fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end))
136023e0
XL
217 } else {
218 // The format string could be another macro invocation, e.g.:
219 // format!(concat!("abc", "{}"), 4);
220 // However, `err.span` is an inner span relative to the *result* of
221 // the macro invocation, which is why we would get a nonsensical
222 // result calling `fmt_span.from_inner(err.span)` as above, and
223 // might even end up inside a multibyte character (issue #86085).
224 // Therefore, we conservatively report the error for the entire
225 // argument span here.
226 fmt_span
227 };
dfeec247 228 let mut e = ecx.struct_span_err(sp, &format!("invalid format string: {}", err.description));
b7449926
XL
229 e.span_label(sp, err.label + " in format string");
230 if let Some(note) = err.note {
231 e.note(&note);
232 }
2b03887a
FG
233 if let Some((label, span)) = err.secondary_label && is_literal {
234 e.span_label(fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label);
0731742a 235 }
064997fb
FG
236 if err.should_be_replaced_with_positional_argument {
237 let captured_arg_span =
238 fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end));
064997fb 239 if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) {
2b03887a 240 let span = match args.unnamed_args().last() {
064997fb 241 Some(arg) => arg.expr.span,
2b03887a 242 None => fmt_span,
064997fb
FG
243 };
244 e.multipart_suggestion_verbose(
245 "consider using a positional formatting argument instead",
246 vec![
2b03887a 247 (captured_arg_span, args.unnamed_args().len().to_string()),
064997fb
FG
248 (span.shrink_to_hi(), format!(", {}", arg)),
249 ],
250 Applicability::MachineApplicable,
251 );
252 }
253 }
b7449926 254 e.emit();
2b03887a 255 return Err(());
b7449926
XL
256 }
257
2b03887a
FG
258 let to_span = |inner_span: rustc_parse_format::InnerSpan| {
259 is_literal.then(|| {
260 fmt_span.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end })
261 })
262 };
263
264 let mut used = vec![false; args.explicit_args().len()];
265 let mut invalid_refs = Vec::new();
266 let mut numeric_refences_to_named_arg = Vec::new();
b7449926 267
2b03887a
FG
268 enum ArgRef<'a> {
269 Index(usize),
270 Name(&'a str, Option<Span>),
271 }
272 use ArgRef::*;
273
274 let mut lookup_arg = |arg: ArgRef<'_>,
275 span: Option<Span>,
276 used_as: PositionUsedAs,
277 kind: FormatArgPositionKind|
278 -> FormatArgPosition {
279 let index = match arg {
280 Index(index) => {
281 if let Some(arg) = args.by_index(index) {
282 used[index] = true;
283 if arg.kind.ident().is_some() {
284 // This was a named argument, but it was used as a positional argument.
285 numeric_refences_to_named_arg.push((index, span, used_as));
286 }
287 Ok(index)
288 } else {
289 // Doesn't exist as an explicit argument.
290 invalid_refs.push((index, span, used_as, kind));
291 Err(index)
292 }
293 }
294 Name(name, span) => {
295 let name = Symbol::intern(name);
296 if let Some((index, _)) = args.by_name(name) {
297 // Name found in `args`, so we resolve it to its index.
298 if index < args.explicit_args().len() {
299 // Mark it as used, if it was an explicit argument.
300 used[index] = true;
301 }
302 Ok(index)
303 } else {
304 // Name not found in `args`, so we add it as an implicitly captured argument.
305 let span = span.unwrap_or(fmt_span);
306 let ident = Ident::new(name, span);
307 let expr = if is_literal {
308 ecx.expr_ident(span, ident)
309 } else {
310 // For the moment capturing variables from format strings expanded from macros is
311 // disabled (see RFC #2795)
312 ecx.struct_span_err(span, &format!("there is no argument named `{name}`"))
313 .note(format!("did you intend to capture a variable `{name}` from the surrounding scope?"))
314 .note("to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro")
315 .emit();
316 DummyResult::raw_expr(span, true)
317 };
318 Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr }))
319 }
320 }
321 };
322 FormatArgPosition { index, kind, span }
1a4d82fc
JJ
323 };
324
2b03887a
FG
325 let mut template = Vec::new();
326 let mut unfinished_literal = String::new();
327 let mut placeholder_index = 0;
dfeec247 328
2b03887a
FG
329 for piece in pieces {
330 match piece {
331 parse::Piece::String(s) => {
332 unfinished_literal.push_str(s);
333 }
9c376795 334 parse::Piece::NextArgument(box parse::Argument { position, position_span, format }) => {
2b03887a
FG
335 if !unfinished_literal.is_empty() {
336 template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
337 unfinished_literal.clear();
338 }
abe05a73 339
2b03887a
FG
340 let span = parser.arg_places.get(placeholder_index).and_then(|&s| to_span(s));
341 placeholder_index += 1;
342
343 let position_span = to_span(position_span);
344 let argument = match position {
345 parse::ArgumentImplicitlyIs(i) => lookup_arg(
346 Index(i),
347 position_span,
348 Placeholder(span),
349 FormatArgPositionKind::Implicit,
350 ),
351 parse::ArgumentIs(i) => lookup_arg(
352 Index(i),
353 position_span,
354 Placeholder(span),
355 FormatArgPositionKind::Number,
356 ),
357 parse::ArgumentNamed(name) => lookup_arg(
358 Name(name, position_span),
359 position_span,
360 Placeholder(span),
361 FormatArgPositionKind::Named,
362 ),
363 };
5bcae85e 364
2b03887a
FG
365 let alignment = match format.align {
366 parse::AlignUnknown => None,
367 parse::AlignLeft => Some(FormatAlignment::Left),
368 parse::AlignRight => Some(FormatAlignment::Right),
369 parse::AlignCenter => Some(FormatAlignment::Center),
370 };
b7449926 371
2b03887a
FG
372 let format_trait = match format.ty {
373 "" => FormatTrait::Display,
374 "?" => FormatTrait::Debug,
375 "e" => FormatTrait::LowerExp,
376 "E" => FormatTrait::UpperExp,
377 "o" => FormatTrait::Octal,
378 "p" => FormatTrait::Pointer,
379 "b" => FormatTrait::Binary,
380 "x" => FormatTrait::LowerHex,
381 "X" => FormatTrait::UpperHex,
382 _ => {
383 invalid_placeholder_type_error(ecx, format.ty, format.ty_span, fmt_span);
384 FormatTrait::Display
385 }
386 };
387
388 let precision_span = format.precision_span.and_then(to_span);
389 let precision = match format.precision {
390 parse::CountIs(n) => Some(FormatCount::Literal(n)),
391 parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
392 Name(name, to_span(name_span)),
393 precision_span,
394 Precision,
395 FormatArgPositionKind::Named,
396 ))),
397 parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
398 Index(i),
399 precision_span,
400 Precision,
401 FormatArgPositionKind::Number,
402 ))),
403 parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg(
404 Index(i),
405 precision_span,
406 Precision,
407 FormatArgPositionKind::Implicit,
408 ))),
409 parse::CountImplied => None,
410 };
411
412 let width_span = format.width_span.and_then(to_span);
413 let width = match format.width {
414 parse::CountIs(n) => Some(FormatCount::Literal(n)),
415 parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
416 Name(name, to_span(name_span)),
417 width_span,
418 Width,
419 FormatArgPositionKind::Named,
420 ))),
421 parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
422 Index(i),
423 width_span,
424 Width,
425 FormatArgPositionKind::Number,
426 ))),
427 parse::CountIsStar(_) => unreachable!(),
428 parse::CountImplied => None,
429 };
430
431 template.push(FormatArgsPiece::Placeholder(FormatPlaceholder {
432 argument,
433 span,
434 format_trait,
435 format_options: FormatOptions {
436 fill: format.fill,
437 alignment,
9ffffee4
FG
438 sign: format.sign.map(|s| match s {
439 parse::Sign::Plus => FormatSign::Plus,
440 parse::Sign::Minus => FormatSign::Minus,
441 }),
442 alternate: format.alternate,
443 zero_pad: format.zero_pad,
444 debug_hex: format.debug_hex.map(|s| match s {
445 parse::DebugHex::Lower => FormatDebugHex::Lower,
446 parse::DebugHex::Upper => FormatDebugHex::Upper,
447 }),
2b03887a
FG
448 precision,
449 width,
450 },
451 }));
452 }
5bcae85e
SL
453 }
454 }
455
2b03887a
FG
456 if !unfinished_literal.is_empty() {
457 template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
1a4d82fc
JJ
458 }
459
2b03887a
FG
460 if !invalid_refs.is_empty() {
461 report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser);
abe05a73
XL
462 }
463
2b03887a 464 let unused = used
dfeec247
XL
465 .iter()
466 .enumerate()
2b03887a 467 .filter(|&(_, used)| !used)
dfeec247 468 .map(|(i, _)| {
2b03887a 469 let msg = if let FormatArgumentKind::Named(_) = args.explicit_args()[i].kind {
dfeec247
XL
470 "named argument never used"
471 } else {
dfeec247
XL
472 "argument never used"
473 };
2b03887a 474 (args.explicit_args()[i].expr.span, msg)
dfeec247
XL
475 })
476 .collect::<Vec<_>>();
b7449926 477
2b03887a
FG
478 if !unused.is_empty() {
479 // If there's a lot of unused arguments,
480 // let's check if this format arguments looks like another syntax (printf / shell).
481 let detect_foreign_fmt = unused.len() > args.explicit_args().len() / 2;
482 report_missing_placeholders(ecx, unused, detect_foreign_fmt, str_style, fmt_str, fmt_span);
483 }
476ff2be 484
2b03887a
FG
485 // Only check for unused named argument names if there are no other errors to avoid causing
486 // too much noise in output errors, such as when a named argument is entirely unused.
487 if invalid_refs.is_empty() && ecx.sess.err_count() == 0 {
488 for &(index, span, used_as) in &numeric_refences_to_named_arg {
489 let (position_sp_to_replace, position_sp_for_msg) = match used_as {
490 Placeholder(pspan) => (span, pspan),
491 Precision => {
492 // Strip the leading `.` for precision.
493 let span = span.map(|span| span.with_lo(span.lo() + BytePos(1)));
494 (span, span)
0731742a 495 }
2b03887a
FG
496 Width => (span, span),
497 };
498 let arg_name = args.explicit_args()[index].kind.ident().unwrap();
499 ecx.buffered_early_lint.push(BufferedEarlyLint {
500 span: arg_name.span.into(),
501 msg: format!("named argument `{}` is not used by name", arg_name.name).into(),
502 node_id: rustc_ast::CRATE_NODE_ID,
503 lint_id: LintId::of(&NAMED_ARGUMENTS_USED_POSITIONALLY),
504 diagnostic: BuiltinLintDiagnostics::NamedArgumentUsedPositionally {
505 position_sp_to_replace,
506 position_sp_for_msg,
507 named_arg_sp: arg_name.span,
508 named_arg_name: arg_name.name.to_string(),
509 is_formatting_arg: matches!(used_as, Width | Precision),
510 },
511 });
512 }
513 }
476ff2be 514
2b03887a
FG
515 Ok(FormatArgs { span: fmt_span, template, arguments: args })
516}
476ff2be 517
2b03887a
FG
518fn invalid_placeholder_type_error(
519 ecx: &ExtCtxt<'_>,
520 ty: &str,
521 ty_span: Option<rustc_parse_format::InnerSpan>,
522 fmt_span: Span,
523) {
524 let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end)));
525 let mut err =
526 ecx.struct_span_err(sp.unwrap_or(fmt_span), &format!("unknown format trait `{}`", ty));
527 err.note(
528 "the only appropriate formatting traits are:\n\
529 - ``, which uses the `Display` trait\n\
530 - `?`, which uses the `Debug` trait\n\
531 - `e`, which uses the `LowerExp` trait\n\
532 - `E`, which uses the `UpperExp` trait\n\
533 - `o`, which uses the `Octal` trait\n\
534 - `p`, which uses the `Pointer` trait\n\
535 - `b`, which uses the `Binary` trait\n\
536 - `x`, which uses the `LowerHex` trait\n\
537 - `X`, which uses the `UpperHex` trait",
538 );
539 if let Some(sp) = sp {
540 for (fmt, name) in &[
541 ("", "Display"),
542 ("?", "Debug"),
543 ("e", "LowerExp"),
544 ("E", "UpperExp"),
545 ("o", "Octal"),
546 ("p", "Pointer"),
547 ("b", "Binary"),
548 ("x", "LowerHex"),
549 ("X", "UpperHex"),
550 ] {
551 err.tool_only_span_suggestion(
552 sp,
553 &format!("use the `{}` trait", name),
554 *fmt,
555 Applicability::MaybeIncorrect,
556 );
557 }
558 }
559 err.emit();
560}
476ff2be 561
2b03887a
FG
562fn report_missing_placeholders(
563 ecx: &mut ExtCtxt<'_>,
564 unused: Vec<(Span, &str)>,
565 detect_foreign_fmt: bool,
566 str_style: Option<usize>,
567 fmt_str: &str,
568 fmt_span: Span,
569) {
570 let mut diag = if let &[(span, msg)] = &unused[..] {
571 let mut diag = ecx.struct_span_err(span, msg);
572 diag.span_label(span, msg);
573 diag
574 } else {
575 let mut diag = ecx.struct_span_err(
576 unused.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(),
577 "multiple unused formatting arguments",
578 );
579 diag.span_label(fmt_span, "multiple missing formatting specifiers");
580 for &(span, msg) in &unused {
581 diag.span_label(span, msg);
582 }
583 diag
584 };
476ff2be 585
2b03887a
FG
586 // Used to ensure we only report translations for *one* kind of foreign format.
587 let mut found_foreign = false;
588
589 // Decide if we want to look for foreign formatting directives.
590 if detect_foreign_fmt {
591 use super::format_foreign as foreign;
592
9c376795 593 // The set of foreign substitutions we've explained. This prevents spamming the user
2b03887a
FG
594 // with `%d should be written as {}` over and over again.
595 let mut explained = FxHashSet::default();
596
597 macro_rules! check_foreign {
598 ($kind:ident) => {{
599 let mut show_doc_note = false;
600
601 let mut suggestions = vec![];
602 // account for `"` and account for raw strings `r#`
603 let padding = str_style.map(|i| i + 2).unwrap_or(1);
604 for sub in foreign::$kind::iter_subs(fmt_str, padding) {
605 let (trn, success) = match sub.translate() {
606 Ok(trn) => (trn, true),
607 Err(Some(msg)) => (msg, false),
608
609 // If it has no translation, don't call it out specifically.
610 _ => continue,
611 };
612
613 let pos = sub.position();
614 let sub = String::from(sub.as_str());
615 if explained.contains(&sub) {
616 continue;
617 }
618 explained.insert(sub.clone());
476ff2be 619
2b03887a
FG
620 if !found_foreign {
621 found_foreign = true;
622 show_doc_note = true;
623 }
476ff2be 624
2b03887a
FG
625 if let Some(inner_sp) = pos {
626 let sp = fmt_span.from_inner(inner_sp);
476ff2be 627
2b03887a
FG
628 if success {
629 suggestions.push((sp, trn));
630 } else {
631 diag.span_note(
632 sp,
633 &format!("format specifiers use curly braces, and {}", trn),
634 );
476ff2be 635 }
2b03887a
FG
636 } else {
637 if success {
638 diag.help(&format!("`{}` should be written as `{}`", sub, trn));
8faf50e0 639 } else {
2b03887a 640 diag.note(&format!("`{}` should use curly braces, and {}", sub, trn));
8faf50e0 641 }
476ff2be 642 }
2b03887a 643 }
476ff2be 644
2b03887a
FG
645 if show_doc_note {
646 diag.note(concat!(
647 stringify!($kind),
9c376795 648 " formatting is not supported; see the documentation for `std::fmt`",
2b03887a
FG
649 ));
650 }
651 if suggestions.len() > 0 {
652 diag.multipart_suggestion(
653 "format specifiers use curly braces",
654 suggestions,
655 Applicability::MachineApplicable,
656 );
657 }
658 }};
8faf50e0 659 }
476ff2be 660
2b03887a
FG
661 check_foreign!(printf);
662 if !found_foreign {
663 check_foreign!(shell);
664 }
665 }
666 if !found_foreign && unused.len() == 1 {
667 diag.span_label(fmt_span, "formatting specifier missing");
1a4d82fc
JJ
668 }
669
2b03887a 670 diag.emit();
1a4d82fc 671}
5099ac24 672
2b03887a
FG
673/// Handle invalid references to positional arguments. Output different
674/// errors for the case where all arguments are positional and for when
675/// there are named arguments or numbered positional arguments in the
676/// format string.
677fn report_invalid_references(
678 ecx: &mut ExtCtxt<'_>,
679 invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)],
680 template: &[FormatArgsPiece],
681 fmt_span: Span,
682 args: &FormatArguments,
683 parser: parse::Parser<'_>,
684) {
685 let num_args_desc = match args.explicit_args().len() {
686 0 => "no arguments were given".to_string(),
687 1 => "there is 1 argument".to_string(),
688 n => format!("there are {} arguments", n),
689 };
690
691 let mut e;
5099ac24 692
2b03887a
FG
693 if template.iter().all(|piece| match piece {
694 FormatArgsPiece::Placeholder(FormatPlaceholder {
695 argument: FormatArgPosition { kind: FormatArgPositionKind::Number, .. },
696 ..
697 }) => false,
698 FormatArgsPiece::Placeholder(FormatPlaceholder {
699 format_options:
700 FormatOptions {
701 precision:
702 Some(FormatCount::Argument(FormatArgPosition {
703 kind: FormatArgPositionKind::Number,
704 ..
705 })),
706 ..
707 }
708 | FormatOptions {
709 width:
710 Some(FormatCount::Argument(FormatArgPosition {
711 kind: FormatArgPositionKind::Number,
712 ..
713 })),
714 ..
715 },
716 ..
717 }) => false,
718 _ => true,
719 }) {
720 // There are no numeric positions.
721 // Collect all the implicit positions:
722 let mut spans = Vec::new();
723 let mut num_placeholders = 0;
724 for piece in template {
725 let mut placeholder = None;
726 // `{arg:.*}`
727 if let FormatArgsPiece::Placeholder(FormatPlaceholder {
728 format_options:
729 FormatOptions {
730 precision:
731 Some(FormatCount::Argument(FormatArgPosition {
732 span,
733 kind: FormatArgPositionKind::Implicit,
734 ..
735 })),
736 ..
737 },
738 ..
739 }) = piece
740 {
741 placeholder = *span;
742 num_placeholders += 1;
5099ac24 743 }
2b03887a
FG
744 // `{}`
745 if let FormatArgsPiece::Placeholder(FormatPlaceholder {
746 argument: FormatArgPosition { kind: FormatArgPositionKind::Implicit, .. },
747 span,
748 ..
749 }) = piece
750 {
751 placeholder = *span;
752 num_placeholders += 1;
753 }
754 // For `{:.*}`, we only push one span.
755 spans.extend(placeholder);
5099ac24 756 }
2b03887a
FG
757 let span = if spans.is_empty() {
758 MultiSpan::from_span(fmt_span)
759 } else {
760 MultiSpan::from_spans(spans)
761 };
762 e = ecx.struct_span_err(
763 span,
764 &format!(
765 "{} positional argument{} in format string, but {}",
766 num_placeholders,
767 pluralize!(num_placeholders),
768 num_args_desc,
769 ),
770 );
771 for arg in args.explicit_args() {
772 e.span_label(arg.expr.span, "");
773 }
774 // Point out `{:.*}` placeholders: those take an extra argument.
775 let mut has_precision_star = false;
776 for piece in template {
777 if let FormatArgsPiece::Placeholder(FormatPlaceholder {
778 format_options:
779 FormatOptions {
780 precision:
781 Some(FormatCount::Argument(FormatArgPosition {
782 index,
783 span: Some(span),
784 kind: FormatArgPositionKind::Implicit,
785 ..
786 })),
787 ..
788 },
789 ..
790 }) = piece
791 {
792 let (Ok(index) | Err(index)) = index;
793 has_precision_star = true;
794 e.span_label(
795 *span,
796 &format!(
797 "this precision flag adds an extra required argument at position {}, which is why there {} expected",
798 index,
799 if num_placeholders == 1 {
800 "is 1 argument".to_string()
801 } else {
802 format!("are {} arguments", num_placeholders)
803 },
804 ),
805 );
806 }
807 }
808 if has_precision_star {
809 e.note("positional arguments are zero-based");
5099ac24 810 }
2b03887a
FG
811 } else {
812 let mut indexes: Vec<_> = invalid_refs.iter().map(|&(index, _, _, _)| index).collect();
813 // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)`
814 // for `println!("{7:7$}", 1);`
815 indexes.sort();
816 indexes.dedup();
817 let span: MultiSpan = if !parser.is_literal || parser.arg_places.is_empty() {
818 MultiSpan::from_span(fmt_span)
819 } else {
820 MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect())
821 };
822 let arg_list = if let &[index] = &indexes[..] {
823 format!("argument {index}")
824 } else {
825 let tail = indexes.pop().unwrap();
826 format!(
827 "arguments {head} and {tail}",
828 head = indexes.into_iter().map(|i| i.to_string()).collect::<Vec<_>>().join(", ")
829 )
830 };
831 e = ecx.struct_span_err(
832 span,
833 &format!("invalid reference to positional {} ({})", arg_list, num_args_desc),
834 );
835 e.note("positional arguments are zero-based");
836 }
5099ac24 837
2b03887a
FG
838 if template.iter().any(|piece| match piece {
839 FormatArgsPiece::Placeholder(FormatPlaceholder { format_options: f, .. }) => {
840 *f != FormatOptions::default()
5099ac24 841 }
2b03887a
FG
842 _ => false,
843 }) {
844 e.note("for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html");
845 }
846
847 e.emit();
848}
5099ac24 849
2b03887a
FG
850fn expand_format_args_impl<'cx>(
851 ecx: &'cx mut ExtCtxt<'_>,
852 mut sp: Span,
853 tts: TokenStream,
854 nl: bool,
855) -> Box<dyn base::MacResult + 'cx> {
856 sp = ecx.with_def_site_ctxt(sp);
857 match parse_args(ecx, sp, tts) {
858 Ok((efmt, args)) => {
859 if let Ok(format_args) = make_format_args(ecx, efmt, args, nl) {
9ffffee4 860 MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(P(format_args))))
2b03887a
FG
861 } else {
862 MacEager::expr(DummyResult::raw_expr(sp, true))
863 }
864 }
865 Err(mut err) => {
866 err.emit();
867 DummyResult::any(sp)
5099ac24
FG
868 }
869 }
2b03887a
FG
870}
871
872pub fn expand_format_args<'cx>(
873 ecx: &'cx mut ExtCtxt<'_>,
874 sp: Span,
875 tts: TokenStream,
876) -> Box<dyn base::MacResult + 'cx> {
877 expand_format_args_impl(ecx, sp, tts, false)
878}
5099ac24 879
2b03887a
FG
880pub fn expand_format_args_nl<'cx>(
881 ecx: &'cx mut ExtCtxt<'_>,
882 sp: Span,
883 tts: TokenStream,
884) -> Box<dyn base::MacResult + 'cx> {
885 expand_format_args_impl(ecx, sp, tts, true)
5099ac24 886}