diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index f536d0b5900..1fdc312603d 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -250,6 +250,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. @@ -263,8 +268,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); @@ -332,7 +337,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; @@ -344,7 +349,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()?; @@ -355,11 +360,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 }); } } } @@ -1180,7 +1185,7 @@ pub fn expand_preparsed_format_args( ecx: &mut ExtCtxt<'_>, sp: Span, efmt: P, - args: Vec>, + args: Vec, names: FxHashMap, append_newline: bool, ) -> P { @@ -1270,6 +1275,24 @@ 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::>(); + let mut suggestions = vec![(captured_arg_span, positional_args.len().to_string())]; + 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, + }; + suggestions.push((span.shrink_to_hi(), format!(", {}", arg))) + } + e.multipart_suggestion_verbose( + "consider using a positional formatting argument instead", + suggestions, + Applicability::MachineApplicable, + ); + } e.emit(); return DummyResult::raw_expr(sp, true); } @@ -1284,7 +1307,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 a7ff9711691..4d8bd20aa36 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 @@ -228,13 +229,19 @@ impl<'a> Iterator for Parser<'a> { Some(String(self.string(pos + 1))) } else { let arg = self.argument(lbrace_end); - if let Some(rbrace_byte_idx) = self.must_consume('}') { - let lbrace_inner_offset = self.to_span_index(pos); - let rbrace_inner_offset = self.to_span_index(rbrace_byte_idx); - if self.is_literal { - self.arg_places.push( - lbrace_inner_offset.to(InnerOffset(rbrace_inner_offset.0 + 1)), - ); + match self.must_consume('}') { + Some(rbrace_byte_idx) => { + let lbrace_inner_offset = self.to_span_index(pos); + let rbrace_inner_offset = self.to_span_index(rbrace_byte_idx); + if self.is_literal { + self.arg_places.push( + lbrace_inner_offset + .to(InnerOffset(rbrace_inner_offset.0 + 1)), + ); + } + } + None => { + self.suggest_positional_arg_instead_of_captured_arg(arg); } } Some(NextArgument(arg)) @@ -313,6 +320,7 @@ impl<'a> Parser<'a> { label: label.into(), span, secondary_label: None, + should_be_replaced_with_positional_argument: false, }); } @@ -336,6 +344,7 @@ impl<'a> Parser<'a> { label: label.into(), span, secondary_label: None, + should_be_replaced_with_positional_argument: false, }); } @@ -407,6 +416,7 @@ impl<'a> Parser<'a> { label, span: pos.to(pos), secondary_label, + should_be_replaced_with_positional_argument: false, }); None } @@ -434,6 +444,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 +761,29 @@ 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); + 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 00000000000..a8d7b44fb3d --- /dev/null +++ b/src/test/ui/fmt/struct-field-as-captured-argument.fixed @@ -0,0 +1,15 @@ +// 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 +} 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 00000000000..e23c14190b0 --- /dev/null +++ b/src/test/ui/fmt/struct-field-as-captured-argument.rs @@ -0,0 +1,15 @@ +// 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 +} 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 00000000000..28d3c8f838b --- /dev/null +++ b/src/test/ui/fmt/struct-field-as-captured-argument.stderr @@ -0,0 +1,46 @@ +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: aborting due to 4 previous errors +