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