mirror of
https://github.com/rust-lang/rust.git
synced 2024-10-30 14:01:51 +00:00
suggest a positional formatting argument instead of a captured argument
This commit is contained in:
parent
e4417cf020
commit
4233a13ceb
@ -250,6 +250,11 @@ struct Context<'a, 'b> {
|
||||
unused_names_lint: PositionalNamedArgsLint,
|
||||
}
|
||||
|
||||
pub struct FormatArg {
|
||||
expr: P<ast::Expr>,
|
||||
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<ast::Expr>, Vec<P<ast::Expr>>, FxHashMap<Symbol, (usize, Span)>)> {
|
||||
let mut args = Vec::<P<ast::Expr>>::new();
|
||||
) -> PResult<'a, (P<ast::Expr>, Vec<FormatArg>, FxHashMap<Symbol, (usize, Span)>)> {
|
||||
let mut args = Vec::<FormatArg>::new();
|
||||
let mut names = FxHashMap::<Symbol, (usize, Span)>::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<ast::Expr>,
|
||||
args: Vec<P<ast::Expr>>,
|
||||
args: Vec<FormatArg>,
|
||||
names: FxHashMap<Symbol, (usize, Span)>,
|
||||
append_newline: bool,
|
||||
) -> P<ast::Expr> {
|
||||
@ -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::<Vec<_>>();
|
||||
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,
|
||||
|
@ -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
|
||||
|
15
src/test/ui/fmt/struct-field-as-captured-argument.fixed
Normal file
15
src/test/ui/fmt/struct-field-as-captured-argument.fixed
Normal file
@ -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
|
||||
}
|
15
src/test/ui/fmt/struct-field-as-captured-argument.rs
Normal file
15
src/test/ui/fmt/struct-field-as-captured-argument.rs
Normal file
@ -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
|
||||
}
|
46
src/test/ui/fmt/struct-field-as-captured-argument.stderr
Normal file
46
src/test/ui/fmt/struct-field-as-captured-argument.stderr
Normal file
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user