diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index cec574e38c579..9eb96ec76800c 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -280,6 +280,11 @@ struct Context<'a, 'b> { unused_names_lint: PositionalNamedArgsLint, } +pub struct FormatArg { + expr: P, + named: bool, +} + /// Parses the arguments from the given list of tokens, returning the diagnostic /// if there's a parse error so we can continue parsing other format! /// expressions. @@ -293,8 +298,8 @@ fn parse_args<'a>( ecx: &mut ExtCtxt<'a>, sp: Span, tts: TokenStream, -) -> PResult<'a, (P, Vec>, FxHashMap)> { - let mut args = Vec::>::new(); +) -> PResult<'a, (P, Vec, FxHashMap)> { + let mut args = Vec::::new(); let mut names = FxHashMap::::default(); let mut p = ecx.new_parser_from_tts(tts); @@ -362,7 +367,7 @@ fn parse_args<'a>( let e = p.parse_expr()?; if let Some((prev, _)) = names.get(&ident.name) { ecx.struct_span_err(e.span, &format!("duplicate argument named `{}`", ident)) - .span_label(args[*prev].span, "previously here") + .span_label(args[*prev].expr.span, "previously here") .span_label(e.span, "duplicate argument") .emit(); continue; @@ -374,7 +379,7 @@ fn parse_args<'a>( // args. And remember the names. let slot = args.len(); names.insert(ident.name, (slot, ident.span)); - args.push(e); + args.push(FormatArg { expr: e, named: true }); } _ => { let e = p.parse_expr()?; @@ -385,11 +390,11 @@ fn parse_args<'a>( ); err.span_label(e.span, "positional arguments must be before named arguments"); for pos in names.values() { - err.span_label(args[pos.0].span, "named argument"); + err.span_label(args[pos.0].expr.span, "named argument"); } err.emit(); } - args.push(e); + args.push(FormatArg { expr: e, named: false }); } } } @@ -1214,7 +1219,7 @@ pub fn expand_preparsed_format_args( ecx: &mut ExtCtxt<'_>, sp: Span, efmt: P, - args: Vec>, + args: Vec, names: FxHashMap, append_newline: bool, ) -> P { @@ -1304,6 +1309,25 @@ pub fn expand_preparsed_format_args( e.span_label(fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label); } } + if err.should_be_replaced_with_positional_argument { + let captured_arg_span = + fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end)); + let positional_args = args.iter().filter(|arg| !arg.named).collect::>(); + if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) { + let span = match positional_args.last() { + Some(arg) => arg.expr.span, + None => fmt_sp, + }; + e.multipart_suggestion_verbose( + "consider using a positional formatting argument instead", + vec![ + (captured_arg_span, positional_args.len().to_string()), + (span.shrink_to_hi(), format!(", {}", arg)), + ], + Applicability::MachineApplicable, + ); + } + } e.emit(); return DummyResult::raw_expr(sp, true); } @@ -1318,7 +1342,7 @@ pub fn expand_preparsed_format_args( let mut cx = Context { ecx, - args, + args: args.into_iter().map(|arg| arg.expr).collect(), num_captured_args: 0, arg_types, arg_unique_types, diff --git a/compiler/rustc_parse_format/src/lib.rs b/compiler/rustc_parse_format/src/lib.rs index a7ff9711691fb..4890fade50faf 100644 --- a/compiler/rustc_parse_format/src/lib.rs +++ b/compiler/rustc_parse_format/src/lib.rs @@ -175,6 +175,7 @@ pub struct ParseError { pub label: string::String, pub span: InnerSpan, pub secondary_label: Option<(string::String, InnerSpan)>, + pub should_be_replaced_with_positional_argument: bool, } /// The parser structure for interpreting the input format string. This is @@ -236,6 +237,8 @@ impl<'a> Iterator for Parser<'a> { lbrace_inner_offset.to(InnerOffset(rbrace_inner_offset.0 + 1)), ); } + } else { + self.suggest_positional_arg_instead_of_captured_arg(arg); } Some(NextArgument(arg)) } @@ -313,6 +316,7 @@ impl<'a> Parser<'a> { label: label.into(), span, secondary_label: None, + should_be_replaced_with_positional_argument: false, }); } @@ -336,6 +340,7 @@ impl<'a> Parser<'a> { label: label.into(), span, secondary_label: None, + should_be_replaced_with_positional_argument: false, }); } @@ -407,6 +412,7 @@ impl<'a> Parser<'a> { label, span: pos.to(pos), secondary_label, + should_be_replaced_with_positional_argument: false, }); None } @@ -434,6 +440,7 @@ impl<'a> Parser<'a> { label, span: pos.to(pos), secondary_label, + should_be_replaced_with_positional_argument: false, }); } else { self.err(description, format!("expected `{:?}`", c), pos.to(pos)); @@ -750,6 +757,34 @@ impl<'a> Parser<'a> { } if found { Some(cur) } else { None } } + + fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: Argument<'a>) { + if let Some(end) = self.consume_pos('.') { + let byte_pos = self.to_span_index(end); + let start = InnerOffset(byte_pos.0 + 1); + let field = self.argument(start); + // We can only parse `foo.bar` field access, any deeper nesting, + // or another type of expression, like method calls, are not supported + if !self.consume('}') { + return; + } + if let ArgumentNamed(_) = arg.position { + if let ArgumentNamed(_) = field.position { + self.errors.insert( + 0, + ParseError { + description: "field access isn't supported".to_string(), + note: None, + label: "not supported".to_string(), + span: InnerSpan::new(arg.position_span.start, field.position_span.end), + secondary_label: None, + should_be_replaced_with_positional_argument: true, + }, + ); + } + } + } + } } /// Finds the indices of all characters that have been processed and differ between the actual diff --git a/src/test/ui/fmt/struct-field-as-captured-argument.fixed b/src/test/ui/fmt/struct-field-as-captured-argument.fixed new file mode 100644 index 0000000000000..f7244f6744f3a --- /dev/null +++ b/src/test/ui/fmt/struct-field-as-captured-argument.fixed @@ -0,0 +1,18 @@ +// run-rustfix + +#[derive(Debug)] +struct Foo { + field: usize, +} + +fn main() { + let foo = Foo { field: 0 }; + let bar = 3; + format!("{0}", foo.field); //~ ERROR invalid format string: field access isn't supported + format!("{1} {} {bar}", "aa", foo.field); //~ ERROR invalid format string: field access isn't supported + format!("{2} {} {1} {bar}", "aa", "bb", foo.field); //~ ERROR invalid format string: field access isn't supported + format!("{1} {} {baz}", "aa", foo.field, baz = 3); //~ ERROR invalid format string: field access isn't supported + format!("{1:?} {} {baz}", "aa", foo.field, baz = 3); //~ ERROR invalid format string: field access isn't supported + format!("{1:#?} {} {baz}", "aa", foo.field, baz = 3); //~ ERROR invalid format string: field access isn't supported + format!("{1:.3} {} {baz}", "aa", foo.field, baz = 3); //~ ERROR invalid format string: field access isn't supported +} diff --git a/src/test/ui/fmt/struct-field-as-captured-argument.rs b/src/test/ui/fmt/struct-field-as-captured-argument.rs new file mode 100644 index 0000000000000..ab5f2552bd323 --- /dev/null +++ b/src/test/ui/fmt/struct-field-as-captured-argument.rs @@ -0,0 +1,18 @@ +// run-rustfix + +#[derive(Debug)] +struct Foo { + field: usize, +} + +fn main() { + let foo = Foo { field: 0 }; + let bar = 3; + format!("{foo.field}"); //~ ERROR invalid format string: field access isn't supported + format!("{foo.field} {} {bar}", "aa"); //~ ERROR invalid format string: field access isn't supported + format!("{foo.field} {} {1} {bar}", "aa", "bb"); //~ ERROR invalid format string: field access isn't supported + format!("{foo.field} {} {baz}", "aa", baz = 3); //~ ERROR invalid format string: field access isn't supported + format!("{foo.field:?} {} {baz}", "aa", baz = 3); //~ ERROR invalid format string: field access isn't supported + format!("{foo.field:#?} {} {baz}", "aa", baz = 3); //~ ERROR invalid format string: field access isn't supported + format!("{foo.field:.3} {} {baz}", "aa", baz = 3); //~ ERROR invalid format string: field access isn't supported +} diff --git a/src/test/ui/fmt/struct-field-as-captured-argument.stderr b/src/test/ui/fmt/struct-field-as-captured-argument.stderr new file mode 100644 index 0000000000000..7ea8b4068f272 --- /dev/null +++ b/src/test/ui/fmt/struct-field-as-captured-argument.stderr @@ -0,0 +1,79 @@ +error: invalid format string: field access isn't supported + --> $DIR/struct-field-as-captured-argument.rs:11:15 + | +LL | format!("{foo.field}"); + | ^^^^^^^^^ not supported in format string + | +help: consider using a positional formatting argument instead + | +LL | format!("{0}", foo.field); + | ~ +++++++++++ + +error: invalid format string: field access isn't supported + --> $DIR/struct-field-as-captured-argument.rs:12:15 + | +LL | format!("{foo.field} {} {bar}", "aa"); + | ^^^^^^^^^ not supported in format string + | +help: consider using a positional formatting argument instead + | +LL | format!("{1} {} {bar}", "aa", foo.field); + | ~ +++++++++++ + +error: invalid format string: field access isn't supported + --> $DIR/struct-field-as-captured-argument.rs:13:15 + | +LL | format!("{foo.field} {} {1} {bar}", "aa", "bb"); + | ^^^^^^^^^ not supported in format string + | +help: consider using a positional formatting argument instead + | +LL | format!("{2} {} {1} {bar}", "aa", "bb", foo.field); + | ~ +++++++++++ + +error: invalid format string: field access isn't supported + --> $DIR/struct-field-as-captured-argument.rs:14:15 + | +LL | format!("{foo.field} {} {baz}", "aa", baz = 3); + | ^^^^^^^^^ not supported in format string + | +help: consider using a positional formatting argument instead + | +LL | format!("{1} {} {baz}", "aa", foo.field, baz = 3); + | ~ +++++++++++ + +error: invalid format string: field access isn't supported + --> $DIR/struct-field-as-captured-argument.rs:15:15 + | +LL | format!("{foo.field:?} {} {baz}", "aa", baz = 3); + | ^^^^^^^^^ not supported in format string + | +help: consider using a positional formatting argument instead + | +LL | format!("{1:?} {} {baz}", "aa", foo.field, baz = 3); + | ~ +++++++++++ + +error: invalid format string: field access isn't supported + --> $DIR/struct-field-as-captured-argument.rs:16:15 + | +LL | format!("{foo.field:#?} {} {baz}", "aa", baz = 3); + | ^^^^^^^^^ not supported in format string + | +help: consider using a positional formatting argument instead + | +LL | format!("{1:#?} {} {baz}", "aa", foo.field, baz = 3); + | ~ +++++++++++ + +error: invalid format string: field access isn't supported + --> $DIR/struct-field-as-captured-argument.rs:17:15 + | +LL | format!("{foo.field:.3} {} {baz}", "aa", baz = 3); + | ^^^^^^^^^ not supported in format string + | +help: consider using a positional formatting argument instead + | +LL | format!("{1:.3} {} {baz}", "aa", foo.field, baz = 3); + | ~ +++++++++++ + +error: aborting due to 7 previous errors +