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