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