1use std::ops::Range;
2
3use parse::Position::ArgumentNamed;
4use rustc_ast::tokenstream::TokenStream;
5use rustc_ast::{
6 Expr, ExprKind, FormatAlignment, FormatArgPosition, FormatArgPositionKind, FormatArgs,
7 FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArguments, FormatCount,
8 FormatDebugHex, FormatOptions, FormatPlaceholder, FormatSign, FormatTrait, Recovered, StmtKind,
9 token,
10};
11use rustc_data_structures::fx::FxHashSet;
12use rustc_errors::{
13 Applicability, BufferedEarlyLint, DecorateDiagCompat, Diag, Diagnostic, MultiSpan, PResult,
14 SingleLabelManySpans, listify, pluralize,
15};
16use rustc_expand::base::*;
17use rustc_lint_defs::LintId;
18use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
19use rustc_parse::exp;
20use rustc_parse_format as parse;
21use rustc_span::{BytePos, ErrorGuaranteed, Ident, InnerSpan, Span, Symbol};
22
23use crate::diagnostics;
24use crate::util::{ExprToSpannedString, expr_to_spanned_string};
25
26#[derive(#[automatically_derived]#[doc(hidden)]unsafe impl ::core::clone::TrivialClone
for PositionUsedAs {
}
#[automatically_derived]impl ::core::clone::Clone for PositionUsedAs {
#[inline]
fn clone(&self) -> Self {
let _: ::core::clone::AssertParamIsClone<Option<Span>>;
*self
}
}Clone, #[automatically_derived]impl ::core::marker::Copy for PositionUsedAs { }Copy, #[automatically_derived]impl ::core::fmt::Debug for PositionUsedAs {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
Self::Placeholder(__self_0) =>
::core::fmt::Formatter::debug_struct_field1_finish(f,
"Placeholder", &__self_0),
Self::Precision =>
::core::fmt::Formatter::write_str(f, "Precision"),
Self::Width => ::core::fmt::Formatter::write_str(f, "Width"),
}
}
}Debug, #[automatically_derived]impl ::core::marker::StructuralPartialEq for
PositionUsedAs {
}
#[automatically_derived]impl ::core::cmp::PartialEq for PositionUsedAs {
#[inline]
fn eq(&self, other: &Self) -> bool {
::core::intrinsics::discriminant_value(self) ==
::core::intrinsics::discriminant_value(other) &&
match (self, other) {
(Self::Placeholder(__self_0), Self::Placeholder(__arg1_0)) =>
__self_0 == __arg1_0,
_ => true,
}
}
}PartialEq, #[automatically_derived]impl ::core::cmp::Eq for PositionUsedAs {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<Option<Span>>;
}
}Eq)]
39enum PositionUsedAs {
40 Placeholder(Option<Span>),
41 Precision,
42 Width,
43}
44use PositionUsedAs::*;
45
46#[derive(#[automatically_derived]impl ::core::fmt::Debug for MacroInput {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(f,
"MacroInput fmtstr args is_direct_literal", &self.fmtstr,
&self.args, &&self.is_direct_literal)
}
}Debug)]
47struct MacroInput {
48 fmtstr: Box<Expr>,
49 args: FormatArguments,
50 is_direct_literal: bool,
60}
61
62fn parse_args<'a>(ecx: &ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a, MacroInput> {
72 let mut p = ecx.new_parser_from_tts(tts);
73
74 let fmtstr = match p.token.kind {
76 token::Eof => {
77 return Err(ecx.dcx().create_err(diagnostics::FormatRequiresString { span: sp }));
78 }
79 token::Literal(token::Lit { kind: token::Str | token::StrRaw(_), .. }) => {
83 p.parse_literal_maybe_minus()?
84 }
85 _ => p.parse_expr()?,
87 };
88
89 let mut args = FormatArguments::new();
91 let mut first = true;
92 while p.token != token::Eof {
93 if !p.eat(::rustc_parse::parser::token_type::ExpTokenPair {
tok: rustc_ast::token::Comma,
token_type: ::rustc_parse::parser::token_type::TokenType::Comma,
}exp!(Comma)) {
95 if first {
96 p.clear_expected_token_types();
97 }
98
99 match p.expect(::rustc_parse::parser::token_type::ExpTokenPair {
tok: rustc_ast::token::Comma,
token_type: ::rustc_parse::parser::token_type::TokenType::Comma,
}exp!(Comma)) {
100 Err(err) => {
101 if token::TokenKind::Comma.similar_tokens().contains(&p.token.kind) {
102 err.emit();
105 p.bump();
106 } else {
107 return Err(err);
109 }
110 }
111 Ok(Recovered::Yes(_)) => (),
112 Ok(Recovered::No) => ::core::panicking::unreachable_default_msg()unreachable!(),
113 }
114 }
115 first = false;
116 if p.token == token::Eof {
118 break;
119 }
120 match p.token.ident() {
122 Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => {
123 p.bump();
124 p.expect(::rustc_parse::parser::token_type::ExpTokenPair {
tok: rustc_ast::token::Eq,
token_type: ::rustc_parse::parser::token_type::TokenType::Eq,
}exp!(Eq))?;
125 let expr = p.parse_expr()?;
126 if let Some((_, prev)) = args.by_name(ident.name) {
127 ecx.dcx().emit_err(diagnostics::FormatDuplicateArg {
128 span: ident.span,
129 prev: prev.kind.ident().unwrap().span,
130 duplicate: ident.span,
131 ident,
132 });
133 continue;
134 }
135 args.add(FormatArgument {
136 original_span: ident.span.to(expr.span),
137 kind: FormatArgumentKind::Named(ident),
138 expr,
139 });
140 }
141 _ => {
142 let expr = p.parse_expr()?;
143 if !args.named_args().is_empty() {
144 return Err(ecx.dcx().create_err(diagnostics::PositionalAfterNamed {
145 span: expr.span,
146 args: args
147 .named_args()
148 .iter()
149 .filter_map(|a| a.kind.ident().map(|ident| (a, ident)))
150 .map(|(arg, n)| n.span.to(arg.expr.span))
151 .collect(),
152 }));
153 }
154 args.add(FormatArgument {
155 original_span: expr.span,
156 kind: FormatArgumentKind::Normal,
157 expr,
158 });
159 }
160 }
161 }
162
163 let is_direct_literal = #[allow(non_exhaustive_omitted_patterns)] match fmtstr.kind {
ExprKind::Lit(_) => true,
_ => false,
}matches!(fmtstr.kind, ExprKind::Lit(_));
165
166 Ok(MacroInput { fmtstr, args, is_direct_literal })
167}
168
169fn make_format_args(
170 ecx: &mut ExtCtxt<'_>,
171 input: MacroInput,
172 append_newline: bool,
173 macro_span: Span,
174) -> ExpandResult<Result<FormatArgs, ErrorGuaranteed>, ()> {
175 let unexpanded_fmt_span = input.fmtstr.span;
176
177 let MacroInput { fmtstr: efmt, mut args, is_direct_literal } = input;
178
179 let ExprToSpannedString {
180 symbol: fmt_str,
181 span: fmt_span,
182 style: fmt_style,
183 uncooked_symbol: uncooked_fmt_str,
184 } = {
185 let snippet = if let ExprKind::Block(b, None) = &efmt.kind
188 && b.stmts.len() <= 1
189 {
190 Some(ecx.sess.source_map().span_to_snippet(unexpanded_fmt_span))
191 } else {
192 None
193 };
194
195 let ExpandResult::Ready(mac) =
196 expr_to_spanned_string(ecx, efmt.clone(), "format argument must be a string literal")
197 else {
198 return ExpandResult::Retry(());
199 };
200 match mac {
201 Ok(mut fmt) if append_newline => {
202 fmt.symbol = Symbol::intern(&::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}\n", fmt.symbol))
})format!("{}\n", fmt.symbol));
203 fmt
204 }
205 Ok(fmt) => fmt,
206 Err(err) => {
207 let guar = match err {
208 Ok((mut err, suggested)) => {
209 if !suggested {
210 if let ExprKind::Block(block, None) = &efmt.kind
211 && let [stmt] = block.stmts.as_slice()
212 && let StmtKind::Expr(expr) = &stmt.kind
213 && let ExprKind::Path(None, path) = &expr.kind
214 && path.segments.len() == 1
215 && path.segments[0].args.is_none()
216 {
217 err.multipart_suggestion(
218 "quote your inlined format argument to use as string literal",
219 ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[(unexpanded_fmt_span.shrink_to_hi(), "\"".to_string()),
(unexpanded_fmt_span.shrink_to_lo(), "\"".to_string())]))vec![
220 (unexpanded_fmt_span.shrink_to_hi(), "\"".to_string()),
221 (unexpanded_fmt_span.shrink_to_lo(), "\"".to_string()),
222 ],
223 Applicability::MaybeIncorrect,
224 );
225 } else {
226 let should_suggest = |kind: &ExprKind| -> bool {
228 match kind {
229 ExprKind::Block(b, None) if b.stmts.is_empty() => true,
230 ExprKind::Tup(v) if v.is_empty() => true,
231 _ => false,
232 }
233 };
234
235 let mut sugg_fmt = String::new();
236 for kind in std::iter::once(&efmt.kind)
237 .chain(args.explicit_args().iter().map(|a| &a.expr.kind))
238 {
239 sugg_fmt.push_str(if should_suggest(kind) {
240 "{:?} "
241 } else {
242 "{} "
243 });
244 }
245 sugg_fmt = sugg_fmt.trim_end().to_string();
246 err.span_suggestion_verbose(
247 unexpanded_fmt_span.shrink_to_lo(),
248 "you might be missing a string literal to format with",
249 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("\"{0}\", ", sugg_fmt))
})format!("\"{sugg_fmt}\", "),
250 Applicability::MaybeIncorrect,
251 );
252
253 if let Some(Ok(snippet)) = snippet.as_ref() {
254 match snippet.as_str() {
255 "{}" | "{:?}" | "{:#?}" => {
256 err.span_suggestion_verbose(
257 unexpanded_fmt_span,
258 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("you might want to enclose `{0}` with `\"\"`",
snippet))
})format!("you might want to enclose `{snippet}` with `\"\"`"),
259 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("\"{0}\"", snippet))
})format!("\"{snippet}\""),
260 Applicability::MaybeIncorrect,
261 );
262 }
263 _ => {}
264 };
265 }
266 }
267 }
268 err.emit_err()
269 }
270 Err(guar) => guar,
271 };
272 return ExpandResult::Ready(Err(guar));
273 }
274 }
275 };
276
277 let str_style = match fmt_style {
278 rustc_ast::StrStyle::Cooked => None,
279 rustc_ast::StrStyle::Raw(raw) => Some(raw as usize),
280 };
281
282 let fmt_str = fmt_str.as_str(); let fmt_snippet = ecx.source_map().span_to_snippet(unexpanded_fmt_span).ok();
284 let mut parser = parse::Parser::new(
285 fmt_str,
286 str_style,
287 fmt_snippet,
288 append_newline,
289 parse::ParseMode::Format,
290 );
291
292 let mut pieces = Vec::new();
293 while let Some(piece) = parser.next() {
294 if !parser.errors.is_empty() {
295 break;
296 } else {
297 pieces.push(piece);
298 }
299 }
300
301 let is_source_literal = parser.is_source_literal;
302
303 if !parser.errors.is_empty() {
304 let err = parser.errors.remove(0);
305 let sp = if is_source_literal {
306 fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end))
307 } else {
308 fmt_span
317 };
318 let mut e = diagnostics::InvalidFormatString {
319 span: sp,
320 note_: None,
321 label_: None,
322 sugg_: None,
323 desc: err.description,
324 label1: err.label,
325 };
326 if let Some(note) = err.note {
327 e.note_ = Some(diagnostics::InvalidFormatStringNote { note });
328 }
329 if let Some((label, span)) = err.secondary_label
330 && is_source_literal
331 {
332 e.label_ = Some(diagnostics::InvalidFormatStringLabel {
333 span: fmt_span.from_inner(InnerSpan::new(span.start, span.end)),
334 label,
335 });
336 }
337 match err.suggestion {
338 parse::Suggestion::None => {}
339 parse::Suggestion::UsePositional => {
340 let captured_arg_span =
341 fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end));
342 if is_source_literal
343 && let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span)
344 {
345 let span = match args.unnamed_args().last() {
346 Some(arg) => arg.expr.span,
347 None => fmt_span,
348 };
349 e.sugg_ = Some(diagnostics::InvalidFormatStringSuggestion::UsePositional {
350 captured: captured_arg_span,
351 len: args.unnamed_args().len().to_string(),
352 span: span.shrink_to_hi(),
353 arg,
354 });
355 }
356 }
357 parse::Suggestion::RemoveRawIdent(span) => {
358 if is_source_literal {
359 let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end));
360 e.sugg_ =
361 Some(diagnostics::InvalidFormatStringSuggestion::RemoveRawIdent { span })
362 }
363 }
364 parse::Suggestion::ReorderFormatParameter(span, replacement) => {
365 if is_source_literal {
366 let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end));
367 e.sugg_ =
368 Some(diagnostics::InvalidFormatStringSuggestion::ReorderFormatParameter {
369 span,
370 replacement,
371 });
372 }
373 }
374 parse::Suggestion::AddMissingColon(span) => {
375 if is_source_literal {
376 let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end));
377 e.sugg_ =
378 Some(diagnostics::InvalidFormatStringSuggestion::AddMissingColon { span });
379 }
380 }
381 parse::Suggestion::UseRustDebugPrintingMacro => {
382 if let [arg] = args.all_args() {
384 let expr_span = arg.expr.span;
385 if let Ok(expr_snippet) = ecx.source_map().span_to_snippet(expr_span) {
386 let replacement = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}!({1})", "dbg", expr_snippet))
})format!("{}!({})", "dbg", expr_snippet);
387
388 let call_span = macro_span.source_callsite();
389 e.sugg_ = Some(
390 diagnostics::InvalidFormatStringSuggestion::UseRustDebugPrintingMacro {
391 macro_span: call_span,
392 replacement,
393 },
394 );
395 }
396 }
397 }
398 }
399 let guar = ecx.dcx().emit_err(e);
400 return ExpandResult::Ready(Err(guar));
401 }
402
403 let to_span = |inner_span: Range<usize>| {
404 is_source_literal.then(|| {
405 fmt_span.from_inner(InnerSpan { start: inner_span.start, end: inner_span.end })
406 })
407 };
408
409 let mut used = ::alloc::vec::from_elem(false, args.explicit_args().len())vec![false; args.explicit_args().len()];
410 let mut invalid_refs = Vec::new();
411 let mut numeric_references_to_named_arg = Vec::new();
412
413 enum ArgRef<'a> {
414 Index(usize),
415 Name(&'a str, Option<Span>),
416 }
417 use ArgRef::*;
418
419 let mut unnamed_arg_after_named_arg = false;
420
421 let mut lookup_arg = |arg: ArgRef<'_>,
422 span: Option<Span>,
423 used_as: PositionUsedAs,
424 kind: FormatArgPositionKind|
425 -> FormatArgPosition {
426 let index = match arg {
427 Index(index) => {
428 if let Some(arg) = args.by_index(index) {
429 used[index] = true;
430 if arg.kind.ident().is_some() {
431 numeric_references_to_named_arg.push((index, span, used_as));
433 }
434 Ok(index)
435 } else {
436 invalid_refs.push((index, span, used_as, kind));
438 Err(index)
439 }
440 }
441 Name(name, span) => {
442 let name = Symbol::intern(name);
443 if let Some((index, _)) = args.by_name(name) {
444 if index < args.explicit_args().len() {
446 used[index] = true;
448 }
449 Ok(index)
450 } else {
451 let span = span.unwrap_or(fmt_span);
453 let ident = Ident::new(name, span);
454 let expr = if is_direct_literal {
455 ecx.expr_ident(span, ident)
456 } else {
457 let guar = ecx.dcx().emit_err(diagnostics::FormatNoArgNamed { span, name });
460 unnamed_arg_after_named_arg = true;
461 DummyResult::raw_expr(span, Some(guar))
462 };
463 Ok(args.add(FormatArgument {
464 original_span: span,
465 kind: FormatArgumentKind::Captured(ident),
466 expr,
467 }))
468 }
469 }
470 };
471 FormatArgPosition { index, kind, span }
472 };
473
474 let mut template = Vec::new();
475 let mut unfinished_literal = String::new();
476 let mut placeholder_index = 0;
477
478 for piece in &pieces {
479 match piece.clone() {
480 parse::Piece::Lit(s) => {
481 unfinished_literal.push_str(s);
482 }
483 parse::Piece::NextArgument(parse::Argument { position, position_span, format }) => {
484 if !unfinished_literal.is_empty() {
485 template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
486 unfinished_literal.clear();
487 }
488
489 let span =
490 parser.arg_places.get(placeholder_index).and_then(|s| to_span(s.clone()));
491 placeholder_index += 1;
492
493 let position_span = to_span(position_span);
494 let argument = match position {
495 parse::ArgumentImplicitlyIs(i) => lookup_arg(
496 Index(i),
497 position_span,
498 Placeholder(span),
499 FormatArgPositionKind::Implicit,
500 ),
501 parse::ArgumentIs(i) => lookup_arg(
502 Index(i),
503 position_span,
504 Placeholder(span),
505 FormatArgPositionKind::Number,
506 ),
507 parse::ArgumentNamed(name) => lookup_arg(
508 Name(name, position_span),
509 position_span,
510 Placeholder(span),
511 FormatArgPositionKind::Named,
512 ),
513 };
514
515 let alignment = match format.align {
516 parse::AlignUnknown => None,
517 parse::AlignLeft => Some(FormatAlignment::Left),
518 parse::AlignRight => Some(FormatAlignment::Right),
519 parse::AlignCenter => Some(FormatAlignment::Center),
520 };
521
522 let format_trait = match format.ty {
523 "" => FormatTrait::Display,
524 "?" => FormatTrait::Debug,
525 "e" => FormatTrait::LowerExp,
526 "E" => FormatTrait::UpperExp,
527 "o" => FormatTrait::Octal,
528 "p" => FormatTrait::Pointer,
529 "b" => FormatTrait::Binary,
530 "x" => FormatTrait::LowerHex,
531 "X" => FormatTrait::UpperHex,
532 _ => {
533 invalid_placeholder_type_error(ecx, format.ty, format.ty_span, fmt_span);
534 FormatTrait::Display
535 }
536 };
537
538 let precision_span = format.precision_span.and_then(to_span);
539 let precision = match format.precision {
540 parse::CountIs(n) => Some(FormatCount::Literal(n)),
541 parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
542 Name(name, to_span(name_span)),
543 precision_span,
544 Precision,
545 FormatArgPositionKind::Named,
546 ))),
547 parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
548 Index(i),
549 precision_span,
550 Precision,
551 FormatArgPositionKind::Number,
552 ))),
553 parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg(
554 Index(i),
555 precision_span,
556 Precision,
557 FormatArgPositionKind::Implicit,
558 ))),
559 parse::CountImplied => None,
560 };
561
562 let width_span = format.width_span.and_then(to_span);
563 let width = match format.width {
564 parse::CountIs(n) => Some(FormatCount::Literal(n)),
565 parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
566 Name(name, to_span(name_span)),
567 width_span,
568 Width,
569 FormatArgPositionKind::Named,
570 ))),
571 parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
572 Index(i),
573 width_span,
574 Width,
575 FormatArgPositionKind::Number,
576 ))),
577 parse::CountIsStar(_) => ::core::panicking::unreachable_default_msg()unreachable!(),
578 parse::CountImplied => None,
579 };
580
581 template.push(FormatArgsPiece::Placeholder(FormatPlaceholder {
582 argument,
583 span,
584 format_trait,
585 format_options: FormatOptions {
586 fill: format.fill,
587 alignment,
588 sign: format.sign.map(|s| match s {
589 parse::Sign::Plus => FormatSign::Plus,
590 parse::Sign::Minus => FormatSign::Minus,
591 }),
592 alternate: format.alternate,
593 zero_pad: format.zero_pad,
594 debug_hex: format.debug_hex.map(|s| match s {
595 parse::DebugHex::Lower => FormatDebugHex::Lower,
596 parse::DebugHex::Upper => FormatDebugHex::Upper,
597 }),
598 precision,
599 width,
600 },
601 }));
602 }
603 }
604 }
605
606 if !unfinished_literal.is_empty() {
607 template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
608 }
609
610 if !invalid_refs.is_empty() {
611 report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser);
612 }
613
614 let unused = used
615 .iter()
616 .enumerate()
617 .filter(|&(_, used)| !used)
618 .map(|(i, _)| {
619 let named = #[allow(non_exhaustive_omitted_patterns)] match args.explicit_args()[i].kind {
FormatArgumentKind::Named(_) => true,
_ => false,
}matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_));
620 (args.explicit_args()[i].expr.span, named)
621 })
622 .collect::<Vec<_>>();
623
624 let has_unused = !unused.is_empty();
625 if has_unused {
626 let foreign_fmt_str =
627 if append_newline { fmt_str.strip_suffix('\n').unwrap_or(fmt_str) } else { fmt_str };
628 report_missing_placeholders(
629 ecx,
630 unused,
631 &used,
632 &args,
633 &pieces,
634 &invalid_refs,
635 str_style,
636 foreign_fmt_str,
637 uncooked_fmt_str.1.as_str(),
638 fmt_span,
639 is_source_literal,
640 );
641 }
642
643 if invalid_refs.is_empty() && !has_unused && !unnamed_arg_after_named_arg {
646 for &(index, span, used_as) in &numeric_references_to_named_arg {
647 let (position_sp_to_replace, position_sp_for_msg) = match used_as {
648 Placeholder(pspan) => (span, pspan),
649 Precision => {
650 let span = span.map(|span| span.with_lo(span.lo() + BytePos(1)));
652 (span, span)
653 }
654 Width => (span, span),
655 };
656 let arg_name = args.explicit_args()[index].kind.ident().unwrap();
657 ecx.buffered_early_lint.push(BufferedEarlyLint {
658 span: Some(arg_name.span.into()),
659 node_id: rustc_ast::CRATE_NODE_ID,
660 lint_id: LintId::of(NAMED_ARGUMENTS_USED_POSITIONALLY),
661 diagnostic: DecorateDiagCompat(Box::new(move |dcx, level, sess| {
662 let (suggestion, name) =
663 if let Some(positional_arg_to_replace) = position_sp_to_replace {
664 let mut name = arg_name.name.to_string();
665 let is_formatting_arg = #[allow(non_exhaustive_omitted_patterns)] match used_as {
Width | Precision => true,
_ => false,
}matches!(used_as, Width | Precision);
666 if is_formatting_arg {
667 name.push('$')
668 };
669 let span_to_replace = if let Ok(positional_arg_content) = sess
670 .downcast_ref::<rustc_session::Session>()
671 .expect("expected a `Session`")
672 .source_map()
673 .span_to_snippet(positional_arg_to_replace)
674 && positional_arg_content.starts_with(':')
675 {
676 positional_arg_to_replace.shrink_to_lo()
677 } else {
678 positional_arg_to_replace
679 };
680 (Some(span_to_replace), name)
681 } else {
682 (None, String::new())
683 };
684
685 diagnostics::NamedArgumentUsedPositionally {
686 named_arg_sp: arg_name.span,
687 position_label_sp: position_sp_for_msg,
688 suggestion,
689 name,
690 named_arg_name: arg_name.name.to_string(),
691 }
692 .into_diag(dcx, level)
693 })),
694 });
695 }
696 }
697
698 ExpandResult::Ready(Ok(FormatArgs {
699 span: fmt_span,
700 template,
701 arguments: args,
702 uncooked_fmt_str,
703 is_source_literal,
704 }))
705}
706
707fn invalid_placeholder_type_error(
708 ecx: &ExtCtxt<'_>,
709 ty: &str,
710 ty_span: Option<Range<usize>>,
711 fmt_span: Span,
712) {
713 let sp = ty_span.map(|sp| fmt_span.from_inner(InnerSpan::new(sp.start, sp.end)));
714 let suggs = if let Some(sp) = sp {
715 [
716 ("", "Display"),
717 ("?", "Debug"),
718 ("e", "LowerExp"),
719 ("E", "UpperExp"),
720 ("o", "Octal"),
721 ("p", "Pointer"),
722 ("b", "Binary"),
723 ("x", "LowerHex"),
724 ("X", "UpperHex"),
725 ]
726 .into_iter()
727 .map(|(fmt, trait_name)| diagnostics::FormatUnknownTraitSugg { span: sp, fmt, trait_name })
728 .collect()
729 } else {
730 ::alloc::vec::Vec::new()vec![]
731 };
732 ecx.dcx().emit_err(diagnostics::FormatUnknownTrait { span: sp.unwrap_or(fmt_span), ty, suggs });
733}
734
735fn report_missing_placeholders(
736 ecx: &ExtCtxt<'_>,
737 unused: Vec<(Span, bool)>,
738 used: &[bool],
739 args: &FormatArguments,
740 pieces: &[parse::Piece<'_>],
741 invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)],
742 str_style: Option<usize>,
743 fmt_str: &str,
744 uncooked_fmt_str: &str,
745 fmt_span: Span,
746 is_source_literal: bool,
747) {
748 let mut diag = if let &[(span, named)] = &unused[..] {
749 ecx.dcx().create_err(diagnostics::FormatUnusedArg { span, named })
750 } else {
751 let unused_labels = unused
752 .iter()
753 .map(|&(span, named)| diagnostics::FormatUnusedArg { span, named })
754 .collect();
755 let unused_spans = unused.iter().map(|&(span, _)| span).collect();
756 ecx.dcx().create_err(diagnostics::FormatUnusedArgs {
757 fmt: fmt_span,
758 unused: unused_spans,
759 unused_labels,
760 })
761 };
762
763 let placeholders = pieces
764 .iter()
765 .filter_map(|piece| {
766 if let parse::Piece::NextArgument(argument) = piece
767 && let ArgumentNamed(binding) = argument.position
768 {
769 let span = fmt_span.from_inner(InnerSpan::new(
770 argument.position_span.start,
771 argument.position_span.end,
772 ));
773 Some((span, binding))
774 } else {
775 None
776 }
777 })
778 .collect::<Vec<_>>();
779
780 if !placeholders.is_empty() {
781 if let Some(new_diag) = report_redundant_format_arguments(ecx, args, used, placeholders) {
782 diag.cancel();
783 new_diag.emit();
784 return;
785 }
786 }
787
788 let mut found_foreign = false;
790
791 if unused.len() > args.explicit_args().len() / 2 {
794 use super::format_foreign as foreign;
795
796 let mut explained = FxHashSet::default();
799
800 macro_rules! check_foreign {
801 ($kind:ident) => {{
802 let mut show_doc_note = false;
803
804 let mut suggestions = vec![];
805 let padding = str_style.map(|i| i + 2).unwrap_or(1);
807 for sub in foreign::$kind::iter_subs(fmt_str, padding) {
808 let (trn, success) = match sub.translate() {
809 Ok(trn) => (trn, true),
810 Err(Some(msg)) => (msg, false),
811
812 _ => continue,
814 };
815
816 let sub_str = sub.to_string();
817 if !explained.insert(sub_str.clone()) {
818 continue;
819 }
820
821 if !found_foreign {
822 found_foreign = true;
823 show_doc_note = true;
824 }
825
826 if is_source_literal {
827 let sp = fmt_span.from_inner(sub.position());
828
829 if success {
830 suggestions.push((sp, trn));
831 } else {
832 diag.span_note(
833 sp,
834 format!("format specifiers use curly braces, and {}", trn),
835 );
836 }
837 } else if success {
838 diag.note(format!(
842 "format specifiers use curly braces: `{sub_str}` should be written as \
843 `{trn}`"
844 ));
845 } else {
846 diag.span_note(
847 fmt_span,
848 format!("format specifiers use curly braces, and {trn}"),
849 );
850 }
851 }
852
853 if show_doc_note {
854 diag.note(concat!(
855 stringify!($kind),
856 " formatting is not supported; see the documentation for `std::fmt`",
857 ));
858 }
859 if suggestions.len() > 0 {
860 diag.multipart_suggestion(
861 "format specifiers use curly braces",
862 suggestions,
863 Applicability::MachineApplicable,
864 );
865 }
866 }};
867 }
868
869 {
let mut show_doc_note = false;
let mut suggestions = ::alloc::vec::Vec::new();
let padding = str_style.map(|i| i + 2).unwrap_or(1);
for sub in foreign::printf::iter_subs(fmt_str, padding) {
let (trn, success) =
match sub.translate() {
Ok(trn) => (trn, true),
Err(Some(msg)) => (msg, false),
_ => continue,
};
let sub_str = sub.to_string();
if !explained.insert(sub_str.clone()) { continue; }
if !found_foreign { found_foreign = true; show_doc_note = true; }
if is_source_literal {
let sp = fmt_span.from_inner(sub.position());
if success {
suggestions.push((sp, trn));
} else {
diag.span_note(sp,
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("format specifiers use curly braces, and {0}",
trn))
}));
}
} else if success {
diag.note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("format specifiers use curly braces: `{0}` should be written as `{1}`",
sub_str, trn))
}));
} else {
diag.span_note(fmt_span,
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("format specifiers use curly braces, and {0}",
trn))
}));
}
}
if show_doc_note {
diag.note("printf formatting is not supported; see the documentation for `std::fmt`");
}
if suggestions.len() > 0 {
diag.multipart_suggestion("format specifiers use curly braces",
suggestions, Applicability::MachineApplicable);
}
};check_foreign!(printf);
870 if !found_foreign {
871 {
let mut show_doc_note = false;
let mut suggestions = ::alloc::vec::Vec::new();
let padding = str_style.map(|i| i + 2).unwrap_or(1);
for sub in foreign::shell::iter_subs(fmt_str, padding) {
let (trn, success) =
match sub.translate() {
Ok(trn) => (trn, true),
Err(Some(msg)) => (msg, false),
_ => continue,
};
let sub_str = sub.to_string();
if !explained.insert(sub_str.clone()) { continue; }
if !found_foreign { found_foreign = true; show_doc_note = true; }
if is_source_literal {
let sp = fmt_span.from_inner(sub.position());
if success {
suggestions.push((sp, trn));
} else {
diag.span_note(sp,
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("format specifiers use curly braces, and {0}",
trn))
}));
}
} else if success {
diag.note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("format specifiers use curly braces: `{0}` should be written as `{1}`",
sub_str, trn))
}));
} else {
diag.span_note(fmt_span,
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("format specifiers use curly braces, and {0}",
trn))
}));
}
}
if show_doc_note {
diag.note("shell formatting is not supported; see the documentation for `std::fmt`");
}
if suggestions.len() > 0 {
diag.multipart_suggestion("format specifiers use curly braces",
suggestions, Applicability::MachineApplicable);
}
};check_foreign!(shell);
872 }
873 }
874 if !found_foreign && unused.len() == 1 {
875 diag.span_label(fmt_span, "formatting specifier missing");
876 }
877
878 if !found_foreign && invalid_refs.is_empty() {
879 let show_example = !used.contains(&true);
881
882 if !show_example {
883 if unused.len() > 1 {
884 diag.note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("consider adding {0} format specifiers",
unused.len()))
})format!("consider adding {} format specifiers", unused.len()));
885 }
886 } else {
887 let msg = if unused.len() == 1 {
888 "a format specifier".to_string()
889 } else {
890 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0} format specifiers",
unused.len()))
})format!("{} format specifiers", unused.len())
891 };
892
893 let sugg = match str_style {
894 None => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("\"{0}{1}\"", uncooked_fmt_str,
"{}".repeat(unused.len())))
})format!("\"{}{}\"", uncooked_fmt_str, "{}".repeat(unused.len())),
895 Some(n_hashes) => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("r{0}\"{2}{1}\"{0}",
"#".repeat(n_hashes), "{}".repeat(unused.len()),
uncooked_fmt_str))
})format!(
896 "r{hashes}\"{uncooked_fmt_str}{fmt_specifiers}\"{hashes}",
897 hashes = "#".repeat(n_hashes),
898 fmt_specifiers = "{}".repeat(unused.len())
899 ),
900 };
901 let msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("format specifiers use curly braces, consider adding {0}",
msg))
})format!("format specifiers use curly braces, consider adding {msg}");
902
903 diag.span_suggestion_verbose(fmt_span, msg, sugg, Applicability::MaybeIncorrect);
904 }
905 }
906
907 diag.emit();
908}
909
910fn report_redundant_format_arguments<'a>(
913 ecx: &ExtCtxt<'a>,
914 args: &FormatArguments,
915 used: &[bool],
916 placeholders: Vec<(Span, &str)>,
917) -> Option<Diag<'a>> {
918 let mut fmt_arg_indices = ::alloc::vec::Vec::new()vec![];
919 let mut args_spans = ::alloc::vec::Vec::new()vec![];
920 let mut fmt_spans = ::alloc::vec::Vec::new()vec![];
921
922 for (i, unnamed_arg) in args.unnamed_args().iter().enumerate().rev() {
923 let Some(ty) = unnamed_arg.expr.to_ty() else { continue };
924 let Some(argument_binding) = ty.kind.is_simple_path() else { continue };
925 let argument_binding = argument_binding.as_str();
926
927 if used[i] {
928 continue;
929 }
930
931 let matching_placeholders = placeholders
932 .iter()
933 .filter(|(_, inline_binding)| argument_binding == *inline_binding)
934 .map(|(span, _)| span)
935 .collect::<Vec<_>>();
936
937 if !matching_placeholders.is_empty() {
938 fmt_arg_indices.push(i);
939 args_spans.push(unnamed_arg.expr.span);
940 for span in &matching_placeholders {
941 if fmt_spans.contains(*span) {
942 continue;
943 }
944 fmt_spans.push(**span);
945 }
946 }
947 }
948
949 if !args_spans.is_empty() {
950 let multispan = MultiSpan::from(fmt_spans);
951 let mut suggestion_spans = ::alloc::vec::Vec::new()vec![];
952
953 for (arg_span, fmt_arg_idx) in args_spans.iter().zip(fmt_arg_indices.iter()) {
954 let span = if fmt_arg_idx + 1 == args.explicit_args().len() {
955 *arg_span
956 } else {
957 arg_span.until(args.explicit_args()[*fmt_arg_idx + 1].expr.span)
958 };
959
960 suggestion_spans.push(span);
961 }
962
963 let sugg = if args.named_args().is_empty() {
964 Some(diagnostics::FormatRedundantArgsSugg { spans: suggestion_spans })
965 } else {
966 None
967 };
968
969 return Some(ecx.dcx().create_err(diagnostics::FormatRedundantArgs {
970 n: args_spans.len(),
971 span: MultiSpan::from(args_spans),
972 note: multispan,
973 sugg,
974 }));
975 }
976
977 None
978}
979
980fn report_invalid_references(
985 ecx: &ExtCtxt<'_>,
986 invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)],
987 template: &[FormatArgsPiece],
988 fmt_span: Span,
989 args: &FormatArguments,
990 parser: parse::Parser<'_>,
991) {
992 let num_args_desc = match args.explicit_args().len() {
993 0 => "no arguments were given".to_string(),
994 1 => "there is 1 argument".to_string(),
995 n => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("there are {0} arguments", n))
})format!("there are {n} arguments"),
996 };
997
998 let mut e;
999
1000 if template.iter().all(|piece| match piece {
1001 FormatArgsPiece::Placeholder(FormatPlaceholder {
1002 argument: FormatArgPosition { kind: FormatArgPositionKind::Number, .. },
1003 ..
1004 }) => false,
1005 FormatArgsPiece::Placeholder(FormatPlaceholder {
1006 format_options:
1007 FormatOptions {
1008 precision:
1009 Some(FormatCount::Argument(FormatArgPosition {
1010 kind: FormatArgPositionKind::Number,
1011 ..
1012 })),
1013 ..
1014 }
1015 | FormatOptions {
1016 width:
1017 Some(FormatCount::Argument(FormatArgPosition {
1018 kind: FormatArgPositionKind::Number,
1019 ..
1020 })),
1021 ..
1022 },
1023 ..
1024 }) => false,
1025 _ => true,
1026 }) {
1027 let mut spans = Vec::new();
1030 let mut num_placeholders = 0;
1031 let mut has_white_space_only_missing_arg = false;
1032 for piece in template {
1033 let mut placeholder = None;
1034 if let FormatArgsPiece::Placeholder(FormatPlaceholder {
1036 format_options:
1037 FormatOptions {
1038 precision:
1039 Some(FormatCount::Argument(FormatArgPosition {
1040 span,
1041 kind: FormatArgPositionKind::Implicit,
1042 ..
1043 })),
1044 ..
1045 },
1046 ..
1047 }) = piece
1048 {
1049 placeholder = *span;
1050 num_placeholders += 1;
1051 }
1052 if let FormatArgsPiece::Placeholder(FormatPlaceholder {
1054 argument: FormatArgPosition { kind: FormatArgPositionKind::Implicit, index, .. },
1055 span,
1056 ..
1057 }) = piece
1058 {
1059 placeholder = *span;
1060 num_placeholders += 1;
1061 if index.is_err()
1063 && let Some(span) = span
1064 && let Ok(snippet) = ecx.source_map().span_to_snippet(*span)
1065 && snippet.chars().any(|c| c.is_whitespace() && c != ' ')
1066 {
1067 has_white_space_only_missing_arg = true;
1068 }
1069 }
1070 spans.extend(placeholder);
1072 }
1073 let span = if spans.is_empty() {
1074 MultiSpan::from_span(fmt_span)
1075 } else {
1076 MultiSpan::from_spans(spans)
1077 };
1078 e = ecx.dcx().create_err(diagnostics::FormatPositionalMismatch {
1079 span,
1080 n: num_placeholders,
1081 desc: num_args_desc,
1082 highlight: SingleLabelManySpans {
1083 spans: args.explicit_args().iter().map(|arg| arg.expr.span).collect(),
1084 label: "",
1085 },
1086 });
1087 let mut has_precision_star = false;
1089 for piece in template {
1090 if let FormatArgsPiece::Placeholder(FormatPlaceholder {
1091 format_options:
1092 FormatOptions {
1093 precision:
1094 Some(FormatCount::Argument(FormatArgPosition {
1095 index,
1096 span: Some(span),
1097 kind: FormatArgPositionKind::Implicit,
1098 ..
1099 })),
1100 ..
1101 },
1102 ..
1103 }) = piece
1104 {
1105 let (Ok(index) | Err(index)) = index;
1106 has_precision_star = true;
1107 e.span_label(
1108 *span,
1109 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("this precision flag adds an extra required argument at position {0}, which is why there {1} expected",
index,
if num_placeholders == 1 {
"is 1 argument".to_string()
} else {
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("are {0} arguments",
num_placeholders))
})
}))
})format!(
1110 "this precision flag adds an extra required argument at position {}, which is why there {} expected",
1111 index,
1112 if num_placeholders == 1 {
1113 "is 1 argument".to_string()
1114 } else {
1115 format!("are {num_placeholders} arguments")
1116 },
1117 ),
1118 );
1119 }
1120 }
1121 if has_precision_star {
1122 e.note("positional arguments are zero-based");
1123 }
1124 if has_white_space_only_missing_arg {
1125 e.note("if you intended to print `{`, you can escape it with `{{`");
1126 }
1127 } else {
1128 let mut indexes: Vec<_> = invalid_refs.iter().map(|&(index, _, _, _)| index).collect();
1129 indexes.sort();
1132 indexes.dedup();
1133 let span = if !parser.is_source_literal || parser.arg_places.is_empty() {
1134 MultiSpan::from_span(fmt_span)
1135 } else {
1136 MultiSpan::from_spans(invalid_refs.iter().filter_map(|&(_, span, _, _)| span).collect())
1137 };
1138 let arg_list = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("argument{0} {1}",
if indexes.len() == 1 { "" } else { "s" },
listify(&indexes,
|i: &usize| i.to_string()).unwrap_or_default()))
})format!(
1139 "argument{} {}",
1140 pluralize!(indexes.len()),
1141 listify(&indexes, |i: &usize| i.to_string()).unwrap_or_default()
1142 );
1143 e = ecx.dcx().struct_span_err(
1144 span,
1145 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid reference to positional {0} ({1})",
arg_list, num_args_desc))
})format!("invalid reference to positional {arg_list} ({num_args_desc})"),
1146 );
1147 e.note("positional arguments are zero-based");
1148 }
1149
1150 if template.iter().any(|piece| match piece {
1151 FormatArgsPiece::Placeholder(FormatPlaceholder { format_options: f, .. }) => {
1152 *f != FormatOptions::default()
1153 }
1154 _ => false,
1155 }) {
1156 e.note("for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html");
1157 }
1158
1159 e.emit();
1160}
1161
1162fn expand_format_args_impl<'cx>(
1163 ecx: &'cx mut ExtCtxt<'_>,
1164 mut sp: Span,
1165 tts: TokenStream,
1166 nl: bool,
1167) -> MacroExpanderResult<'cx> {
1168 sp = ecx.with_def_site_ctxt(sp);
1169 ExpandResult::Ready(match parse_args(ecx, sp, tts) {
1170 Ok(input) => {
1171 let ExpandResult::Ready(mac) = make_format_args(ecx, input, nl, sp) else {
1172 return ExpandResult::Retry(());
1173 };
1174 match mac {
1175 Ok(format_args) => {
1176 MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(Box::new(format_args))))
1177 }
1178 Err(guar) => MacEager::expr(DummyResult::raw_expr(sp, Some(guar))),
1179 }
1180 }
1181 Err(err) => {
1182 let guar = err.emit_err();
1183 DummyResult::any(sp, guar)
1184 }
1185 })
1186}
1187
1188pub(crate) fn expand_format_args<'cx>(
1189 ecx: &'cx mut ExtCtxt<'_>,
1190 sp: Span,
1191 tts: TokenStream,
1192) -> MacroExpanderResult<'cx> {
1193 expand_format_args_impl(ecx, sp, tts, false)
1194}
1195
1196pub(crate) fn expand_format_args_nl<'cx>(
1197 ecx: &'cx mut ExtCtxt<'_>,
1198 sp: Span,
1199 tts: TokenStream,
1200) -> MacroExpanderResult<'cx> {
1201 expand_format_args_impl(ecx, sp, tts, true)
1202}