Skip to content

Commit d3aa757

Browse files
authored
Rollup merge of #100058 - TaKO8Ki:suggest-positional-formatting-argument-instead-of-format-args-capture, r=estebank
Suggest a positional formatting argument instead of a captured argument This patch fixes a part of #96999. fixes #98241 fixes #97311 r? `@estebank`
2 parents 87dd56f + 8c85c99 commit d3aa757

File tree

5 files changed

+182
-8
lines changed

5 files changed

+182
-8
lines changed

compiler/rustc_builtin_macros/src/format.rs

+32-8
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,11 @@ struct Context<'a, 'b> {
280280
unused_names_lint: PositionalNamedArgsLint,
281281
}
282282

283+
pub struct FormatArg {
284+
expr: P<ast::Expr>,
285+
named: bool,
286+
}
287+
283288
/// Parses the arguments from the given list of tokens, returning the diagnostic
284289
/// if there's a parse error so we can continue parsing other format!
285290
/// expressions.
@@ -293,8 +298,8 @@ fn parse_args<'a>(
293298
ecx: &mut ExtCtxt<'a>,
294299
sp: Span,
295300
tts: TokenStream,
296-
) -> PResult<'a, (P<ast::Expr>, Vec<P<ast::Expr>>, FxHashMap<Symbol, (usize, Span)>)> {
297-
let mut args = Vec::<P<ast::Expr>>::new();
301+
) -> PResult<'a, (P<ast::Expr>, Vec<FormatArg>, FxHashMap<Symbol, (usize, Span)>)> {
302+
let mut args = Vec::<FormatArg>::new();
298303
let mut names = FxHashMap::<Symbol, (usize, Span)>::default();
299304

300305
let mut p = ecx.new_parser_from_tts(tts);
@@ -362,7 +367,7 @@ fn parse_args<'a>(
362367
let e = p.parse_expr()?;
363368
if let Some((prev, _)) = names.get(&ident.name) {
364369
ecx.struct_span_err(e.span, &format!("duplicate argument named `{}`", ident))
365-
.span_label(args[*prev].span, "previously here")
370+
.span_label(args[*prev].expr.span, "previously here")
366371
.span_label(e.span, "duplicate argument")
367372
.emit();
368373
continue;
@@ -374,7 +379,7 @@ fn parse_args<'a>(
374379
// args. And remember the names.
375380
let slot = args.len();
376381
names.insert(ident.name, (slot, ident.span));
377-
args.push(e);
382+
args.push(FormatArg { expr: e, named: true });
378383
}
379384
_ => {
380385
let e = p.parse_expr()?;
@@ -385,11 +390,11 @@ fn parse_args<'a>(
385390
);
386391
err.span_label(e.span, "positional arguments must be before named arguments");
387392
for pos in names.values() {
388-
err.span_label(args[pos.0].span, "named argument");
393+
err.span_label(args[pos.0].expr.span, "named argument");
389394
}
390395
err.emit();
391396
}
392-
args.push(e);
397+
args.push(FormatArg { expr: e, named: false });
393398
}
394399
}
395400
}
@@ -1214,7 +1219,7 @@ pub fn expand_preparsed_format_args(
12141219
ecx: &mut ExtCtxt<'_>,
12151220
sp: Span,
12161221
efmt: P<ast::Expr>,
1217-
args: Vec<P<ast::Expr>>,
1222+
args: Vec<FormatArg>,
12181223
names: FxHashMap<Symbol, (usize, Span)>,
12191224
append_newline: bool,
12201225
) -> P<ast::Expr> {
@@ -1304,6 +1309,25 @@ pub fn expand_preparsed_format_args(
13041309
e.span_label(fmt_span.from_inner(InnerSpan::new(span.start, span.end)), label);
13051310
}
13061311
}
1312+
if err.should_be_replaced_with_positional_argument {
1313+
let captured_arg_span =
1314+
fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end));
1315+
let positional_args = args.iter().filter(|arg| !arg.named).collect::<Vec<_>>();
1316+
if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) {
1317+
let span = match positional_args.last() {
1318+
Some(arg) => arg.expr.span,
1319+
None => fmt_sp,
1320+
};
1321+
e.multipart_suggestion_verbose(
1322+
"consider using a positional formatting argument instead",
1323+
vec![
1324+
(captured_arg_span, positional_args.len().to_string()),
1325+
(span.shrink_to_hi(), format!(", {}", arg)),
1326+
],
1327+
Applicability::MachineApplicable,
1328+
);
1329+
}
1330+
}
13071331
e.emit();
13081332
return DummyResult::raw_expr(sp, true);
13091333
}
@@ -1318,7 +1342,7 @@ pub fn expand_preparsed_format_args(
13181342

13191343
let mut cx = Context {
13201344
ecx,
1321-
args,
1345+
args: args.into_iter().map(|arg| arg.expr).collect(),
13221346
num_captured_args: 0,
13231347
arg_types,
13241348
arg_unique_types,

compiler/rustc_parse_format/src/lib.rs

+35
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ pub struct ParseError {
175175
pub label: string::String,
176176
pub span: InnerSpan,
177177
pub secondary_label: Option<(string::String, InnerSpan)>,
178+
pub should_be_replaced_with_positional_argument: bool,
178179
}
179180

180181
/// The parser structure for interpreting the input format string. This is
@@ -236,6 +237,8 @@ impl<'a> Iterator for Parser<'a> {
236237
lbrace_inner_offset.to(InnerOffset(rbrace_inner_offset.0 + 1)),
237238
);
238239
}
240+
} else {
241+
self.suggest_positional_arg_instead_of_captured_arg(arg);
239242
}
240243
Some(NextArgument(arg))
241244
}
@@ -313,6 +316,7 @@ impl<'a> Parser<'a> {
313316
label: label.into(),
314317
span,
315318
secondary_label: None,
319+
should_be_replaced_with_positional_argument: false,
316320
});
317321
}
318322

@@ -336,6 +340,7 @@ impl<'a> Parser<'a> {
336340
label: label.into(),
337341
span,
338342
secondary_label: None,
343+
should_be_replaced_with_positional_argument: false,
339344
});
340345
}
341346

@@ -407,6 +412,7 @@ impl<'a> Parser<'a> {
407412
label,
408413
span: pos.to(pos),
409414
secondary_label,
415+
should_be_replaced_with_positional_argument: false,
410416
});
411417
None
412418
}
@@ -434,6 +440,7 @@ impl<'a> Parser<'a> {
434440
label,
435441
span: pos.to(pos),
436442
secondary_label,
443+
should_be_replaced_with_positional_argument: false,
437444
});
438445
} else {
439446
self.err(description, format!("expected `{:?}`", c), pos.to(pos));
@@ -750,6 +757,34 @@ impl<'a> Parser<'a> {
750757
}
751758
if found { Some(cur) } else { None }
752759
}
760+
761+
fn suggest_positional_arg_instead_of_captured_arg(&mut self, arg: Argument<'a>) {
762+
if let Some(end) = self.consume_pos('.') {
763+
let byte_pos = self.to_span_index(end);
764+
let start = InnerOffset(byte_pos.0 + 1);
765+
let field = self.argument(start);
766+
// We can only parse `foo.bar` field access, any deeper nesting,
767+
// or another type of expression, like method calls, are not supported
768+
if !self.consume('}') {
769+
return;
770+
}
771+
if let ArgumentNamed(_) = arg.position {
772+
if let ArgumentNamed(_) = field.position {
773+
self.errors.insert(
774+
0,
775+
ParseError {
776+
description: "field access isn't supported".to_string(),
777+
note: None,
778+
label: "not supported".to_string(),
779+
span: InnerSpan::new(arg.position_span.start, field.position_span.end),
780+
secondary_label: None,
781+
should_be_replaced_with_positional_argument: true,
782+
},
783+
);
784+
}
785+
}
786+
}
787+
}
753788
}
754789

755790
/// Finds the indices of all characters that have been processed and differ between the actual
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// run-rustfix
2+
3+
#[derive(Debug)]
4+
struct Foo {
5+
field: usize,
6+
}
7+
8+
fn main() {
9+
let foo = Foo { field: 0 };
10+
let bar = 3;
11+
format!("{0}", foo.field); //~ ERROR invalid format string: field access isn't supported
12+
format!("{1} {} {bar}", "aa", foo.field); //~ ERROR invalid format string: field access isn't supported
13+
format!("{2} {} {1} {bar}", "aa", "bb", foo.field); //~ ERROR invalid format string: field access isn't supported
14+
format!("{1} {} {baz}", "aa", foo.field, baz = 3); //~ ERROR invalid format string: field access isn't supported
15+
format!("{1:?} {} {baz}", "aa", foo.field, baz = 3); //~ ERROR invalid format string: field access isn't supported
16+
format!("{1:#?} {} {baz}", "aa", foo.field, baz = 3); //~ ERROR invalid format string: field access isn't supported
17+
format!("{1:.3} {} {baz}", "aa", foo.field, baz = 3); //~ ERROR invalid format string: field access isn't supported
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// run-rustfix
2+
3+
#[derive(Debug)]
4+
struct Foo {
5+
field: usize,
6+
}
7+
8+
fn main() {
9+
let foo = Foo { field: 0 };
10+
let bar = 3;
11+
format!("{foo.field}"); //~ ERROR invalid format string: field access isn't supported
12+
format!("{foo.field} {} {bar}", "aa"); //~ ERROR invalid format string: field access isn't supported
13+
format!("{foo.field} {} {1} {bar}", "aa", "bb"); //~ ERROR invalid format string: field access isn't supported
14+
format!("{foo.field} {} {baz}", "aa", baz = 3); //~ ERROR invalid format string: field access isn't supported
15+
format!("{foo.field:?} {} {baz}", "aa", baz = 3); //~ ERROR invalid format string: field access isn't supported
16+
format!("{foo.field:#?} {} {baz}", "aa", baz = 3); //~ ERROR invalid format string: field access isn't supported
17+
format!("{foo.field:.3} {} {baz}", "aa", baz = 3); //~ ERROR invalid format string: field access isn't supported
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
error: invalid format string: field access isn't supported
2+
--> $DIR/struct-field-as-captured-argument.rs:11:15
3+
|
4+
LL | format!("{foo.field}");
5+
| ^^^^^^^^^ not supported in format string
6+
|
7+
help: consider using a positional formatting argument instead
8+
|
9+
LL | format!("{0}", foo.field);
10+
| ~ +++++++++++
11+
12+
error: invalid format string: field access isn't supported
13+
--> $DIR/struct-field-as-captured-argument.rs:12:15
14+
|
15+
LL | format!("{foo.field} {} {bar}", "aa");
16+
| ^^^^^^^^^ not supported in format string
17+
|
18+
help: consider using a positional formatting argument instead
19+
|
20+
LL | format!("{1} {} {bar}", "aa", foo.field);
21+
| ~ +++++++++++
22+
23+
error: invalid format string: field access isn't supported
24+
--> $DIR/struct-field-as-captured-argument.rs:13:15
25+
|
26+
LL | format!("{foo.field} {} {1} {bar}", "aa", "bb");
27+
| ^^^^^^^^^ not supported in format string
28+
|
29+
help: consider using a positional formatting argument instead
30+
|
31+
LL | format!("{2} {} {1} {bar}", "aa", "bb", foo.field);
32+
| ~ +++++++++++
33+
34+
error: invalid format string: field access isn't supported
35+
--> $DIR/struct-field-as-captured-argument.rs:14:15
36+
|
37+
LL | format!("{foo.field} {} {baz}", "aa", baz = 3);
38+
| ^^^^^^^^^ not supported in format string
39+
|
40+
help: consider using a positional formatting argument instead
41+
|
42+
LL | format!("{1} {} {baz}", "aa", foo.field, baz = 3);
43+
| ~ +++++++++++
44+
45+
error: invalid format string: field access isn't supported
46+
--> $DIR/struct-field-as-captured-argument.rs:15:15
47+
|
48+
LL | format!("{foo.field:?} {} {baz}", "aa", baz = 3);
49+
| ^^^^^^^^^ not supported in format string
50+
|
51+
help: consider using a positional formatting argument instead
52+
|
53+
LL | format!("{1:?} {} {baz}", "aa", foo.field, baz = 3);
54+
| ~ +++++++++++
55+
56+
error: invalid format string: field access isn't supported
57+
--> $DIR/struct-field-as-captured-argument.rs:16:15
58+
|
59+
LL | format!("{foo.field:#?} {} {baz}", "aa", baz = 3);
60+
| ^^^^^^^^^ not supported in format string
61+
|
62+
help: consider using a positional formatting argument instead
63+
|
64+
LL | format!("{1:#?} {} {baz}", "aa", foo.field, baz = 3);
65+
| ~ +++++++++++
66+
67+
error: invalid format string: field access isn't supported
68+
--> $DIR/struct-field-as-captured-argument.rs:17:15
69+
|
70+
LL | format!("{foo.field:.3} {} {baz}", "aa", baz = 3);
71+
| ^^^^^^^^^ not supported in format string
72+
|
73+
help: consider using a positional formatting argument instead
74+
|
75+
LL | format!("{1:.3} {} {baz}", "aa", foo.field, baz = 3);
76+
| ~ +++++++++++
77+
78+
error: aborting due to 7 previous errors
79+

0 commit comments

Comments
 (0)