diff --git a/compiler/rustc_builtin_macros/messages.ftl b/compiler/rustc_builtin_macros/messages.ftl index 1501bd6c73e29..176e62fcefcf7 100644 --- a/compiler/rustc_builtin_macros/messages.ftl +++ b/compiler/rustc_builtin_macros/messages.ftl @@ -316,3 +316,5 @@ builtin_macros_unexpected_lit = expected path to a trait, found literal .other = for example, write `#[derive(Debug)]` for `Debug` builtin_macros_unnameable_test_items = cannot test inner items + +builtin_macros_use_rust_debug_printing_macro = use rust debug printing macro diff --git a/compiler/rustc_builtin_macros/src/errors.rs b/compiler/rustc_builtin_macros/src/errors.rs index 5d4c4e340fa1f..4cd38d7bc8c0a 100644 --- a/compiler/rustc_builtin_macros/src/errors.rs +++ b/compiler/rustc_builtin_macros/src/errors.rs @@ -652,6 +652,18 @@ pub(crate) enum InvalidFormatStringSuggestion { #[primary_span] span: Span, }, + + #[suggestion( + builtin_macros_use_rust_debug_printing_macro, + code = "{replacement}", + style = "verbose", + applicability = "machine-applicable" + )] + UseRustDebugPrintingMacro { + #[primary_span] + macro_span: Span, + replacement: String, + }, } #[derive(Diagnostic)] diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 12cb2cd00694d..4417b61d4f6a3 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -160,6 +160,7 @@ fn make_format_args( ecx: &mut ExtCtxt<'_>, input: MacroInput, append_newline: bool, + macro_span: Span, ) -> ExpandResult, ()> { let msg = "format argument must be a string literal"; let unexpanded_fmt_span = input.fmtstr.span; @@ -333,6 +334,44 @@ fn make_format_args( let span = fmt_span.from_inner(InnerSpan::new(span.start, span.end)); e.sugg_ = Some(errors::InvalidFormatStringSuggestion::AddMissingColon { span }); } + parse::Suggestion::UseRustDebugPrintingMacro() => { + // there should only be suggestion when we have only one argument + if args.all_args().len() == 1 { + let arg = &args.all_args()[0]; + match &arg.kind { + // println!("{x=}") + FormatArgumentKind::Captured(ident) => { + let ident_name = ident.name.to_ident_string(); + + let replacement = format!("dbg!({})", ident_name); + + e.sugg_ = Some( + errors::InvalidFormatStringSuggestion::UseRustDebugPrintingMacro { + macro_span, + replacement, + }, + ); + } + + // println!("{=}", x) or println!("{0=}", x) + FormatArgumentKind::Normal => { + let expr_span = arg.expr.span; + + if let Ok(expr_snippet) = ecx.source_map().span_to_snippet(expr_span) { + let replacement = format!("dbg!({})", expr_snippet); + + e.sugg_ = Some( + errors::InvalidFormatStringSuggestion::UseRustDebugPrintingMacro { + macro_span, + replacement, + }, + ); + } + } + _ => {} + } + } + } } let guar = ecx.dcx().emit_err(e); return ExpandResult::Ready(Err(guar)); @@ -1048,7 +1087,7 @@ fn expand_format_args_impl<'cx>( sp = ecx.with_def_site_ctxt(sp); ExpandResult::Ready(match parse_args(ecx, sp, tts) { Ok(input) => { - let ExpandResult::Ready(mac) = make_format_args(ecx, input, nl) else { + let ExpandResult::Ready(mac) = make_format_args(ecx, input, nl, sp) else { return ExpandResult::Retry(()); }; match mac { diff --git a/compiler/rustc_parse_format/src/lib.rs b/compiler/rustc_parse_format/src/lib.rs index 490af1257760d..077f5714f1360 100644 --- a/compiler/rustc_parse_format/src/lib.rs +++ b/compiler/rustc_parse_format/src/lib.rs @@ -187,6 +187,9 @@ pub enum Suggestion { /// Add missing colon: /// `format!("{foo?}")` -> `format!("{foo:?}")` AddMissingColon(Range), + /// Use Rust format string: + /// `format!("{x=}")` -> `dbg!(x)` + UseRustDebugPrintingMacro(), } /// The parser structure for interpreting the input format string. This is @@ -462,6 +465,7 @@ impl<'input> Parser<'input> { ('?', _) => self.suggest_format_debug(), ('<' | '^' | '>', _) => self.suggest_format_align(c), (',', _) => self.suggest_unsupported_python_numeric_grouping(), + ('=', '}') => self.suggest_rust_debug_printing_macro(), _ => self.suggest_positional_arg_instead_of_captured_arg(arg), } } @@ -871,6 +875,27 @@ impl<'input> Parser<'input> { } } + fn suggest_rust_debug_printing_macro(&mut self) { + if let Some((range, _)) = self.consume_pos('=') { + self.errors.insert( + 0, + ParseError { + description: + "python's f-string debug `=` is not supported in rust, use `dbg(x)` instead" + .to_owned(), + note: Some(format!("to print `{{`, you can escape it using `{{{{`",)), + label: "expected `}`".to_owned(), + span: range, + secondary_label: self + .last_open_brace + .clone() + .map(|sp| ("because of this opening brace".to_owned(), sp)), + suggestion: Suggestion::UseRustDebugPrintingMacro(), + }, + ); + } + } + fn suggest_format_align(&mut self, alignment: char) { if let Some((range, _)) = self.consume_pos(alignment) { self.errors.insert( diff --git a/tests/ui/fmt/format-string-error-2.rs b/tests/ui/fmt/format-string-error-2.rs index 63d65023eb78b..c1d228bfbc9c4 100644 --- a/tests/ui/fmt/format-string-error-2.rs +++ b/tests/ui/fmt/format-string-error-2.rs @@ -88,4 +88,7 @@ raw { \n //~^ ERROR invalid format string: expected `}`, found `?` println!("{x,}, world!",); //~^ ERROR invalid format string: python's numeric grouping `,` is not supported in rust format strings + + println!("{x=}"); + //~^ ERROR invalid format string: python's f-string debug `=` is not supported in rust, use `dbg(x)` instead } diff --git a/tests/ui/fmt/format-string-error-2.stderr b/tests/ui/fmt/format-string-error-2.stderr index e7fbc2e81fc6b..b12e827853f80 100644 --- a/tests/ui/fmt/format-string-error-2.stderr +++ b/tests/ui/fmt/format-string-error-2.stderr @@ -198,5 +198,15 @@ LL | println!("{x,}, world!",); | = note: to print `{`, you can escape it using `{{` -error: aborting due to 20 previous errors +error: invalid format string: python's f-string debug `=` is not supported in rust, use `dbg(x)` instead + --> $DIR/format-string-error-2.rs:92:17 + | +LL | println!("{x=}"); + | - ^ expected `}` in format string + | | + | because of this opening brace + | + = note: to print `{`, you can escape it using `{{` + +error: aborting due to 21 previous errors