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