From 33ec1823d758c08fd7f2eaddabe083dfb3c4b26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 15 Jul 2019 20:51:32 -0700 Subject: [PATCH] Specific error for positional args after named args in `format!()` When writing positional arguments after named arguments in the `format!()` and `println!()` macros, provide a targeted diagnostic. --- src/libsyntax_ext/format.rs | 39 ++++++++++++------- src/test/ui/if/ifmt-bad-arg.rs | 2 +- src/test/ui/if/ifmt-bad-arg.stderr | 10 +++-- src/test/ui/macros/format-parse-errors.rs | 10 ++++- src/test/ui/macros/format-parse-errors.stderr | 26 +++++++------ 5 files changed, 53 insertions(+), 34 deletions(-) diff --git a/src/libsyntax_ext/format.rs b/src/libsyntax_ext/format.rs index c3dbd48cc6e..3f3f647c826 100644 --- a/src/libsyntax_ext/format.rs +++ b/src/libsyntax_ext/format.rs @@ -146,16 +146,13 @@ fn parse_args<'a>( if p.token == token::Eof { break; } // accept trailing commas - if named || (p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq)) { + if p.token.is_ident() && p.look_ahead(1, |t| *t == token::Eq) { named = true; let name = if let token::Ident(name, _) = p.token.kind { p.bump(); name } else { - return Err(ecx.struct_span_err( - p.token.span, - "expected ident, positional arguments cannot follow named arguments", - )); + unreachable!(); }; p.expect(&token::Eq)?; @@ -176,6 +173,17 @@ fn parse_args<'a>( args.push(e); } else { let e = p.parse_expr()?; + if named { + let mut err = ecx.struct_span_err( + e.span, + "positional arguments cannot follow named arguments", + ); + err.span_label(e.span, "positional arguments must be before named arguments"); + for (_, pos) in &names { + err.span_label(args[*pos].span, "named argument"); + } + err.emit(); + } args.push(e); } } @@ -721,13 +729,14 @@ pub fn expand_format_args_nl<'cx>( /// Take the various parts of `format_args!(efmt, args..., name=names...)` /// and construct the appropriate formatting expression. -pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt<'_>, - sp: Span, - efmt: P, - args: Vec>, - names: FxHashMap, - append_newline: bool) - -> P { +pub fn expand_preparsed_format_args( + ecx: &mut ExtCtxt<'_>, + sp: Span, + efmt: P, + args: Vec>, + names: FxHashMap, + append_newline: bool, +) -> P { // NOTE: this verbose way of initializing `Vec>` is because // `ArgumentType` does not derive `Clone`. let arg_types: Vec<_> = (0..args.len()).map(|_| Vec::new()).collect(); @@ -906,6 +915,8 @@ fn find_skips(snippet: &str, is_raw: bool) -> Vec { .map(|span| fmt.span.from_inner(*span)) .collect(); + let named_pos: FxHashSet = names.values().cloned().collect(); + let mut cx = Context { ecx, args, @@ -971,14 +982,12 @@ fn find_skips(snippet: &str, is_raw: bool) -> Vec { } // Make sure that all arguments were used and all arguments have types. - let num_pos_args = cx.args.len() - cx.names.len(); - let errs = cx.arg_types .iter() .enumerate() .filter(|(i, ty)| ty.is_empty() && !cx.count_positions.contains_key(&i)) .map(|(i, _)| { - let msg = if i >= num_pos_args { + let msg = if named_pos.contains(&i) { // named argument "named argument never used" } else { diff --git a/src/test/ui/if/ifmt-bad-arg.rs b/src/test/ui/if/ifmt-bad-arg.rs index a57221af916..0ebe1fa2dff 100644 --- a/src/test/ui/if/ifmt-bad-arg.rs +++ b/src/test/ui/if/ifmt-bad-arg.rs @@ -38,7 +38,7 @@ fn main() { format!("{} {}", 1, 2, foo=1, bar=2); //~ ERROR: multiple unused formatting arguments format!("{foo}", foo=1, foo=2); //~ ERROR: duplicate argument - format!("", foo=1, 2); //~ ERROR: positional arguments cannot follow + format!("{foo} {} {}", foo=1, 2); //~ ERROR: positional arguments cannot follow // bad named arguments, #35082 diff --git a/src/test/ui/if/ifmt-bad-arg.stderr b/src/test/ui/if/ifmt-bad-arg.stderr index 65be86eaf25..835b5b6592b 100644 --- a/src/test/ui/if/ifmt-bad-arg.stderr +++ b/src/test/ui/if/ifmt-bad-arg.stderr @@ -146,11 +146,13 @@ note: previously here LL | format!("{foo}", foo=1, foo=2); | ^ -error: expected ident, positional arguments cannot follow named arguments - --> $DIR/ifmt-bad-arg.rs:41:24 +error: positional arguments cannot follow named arguments + --> $DIR/ifmt-bad-arg.rs:41:35 | -LL | format!("", foo=1, 2); - | ^ +LL | format!("{foo} {} {}", foo=1, 2); + | - ^ positional arguments must be before named arguments + | | + | named argument error: there is no argument named `valueb` --> $DIR/ifmt-bad-arg.rs:45:23 diff --git a/src/test/ui/macros/format-parse-errors.rs b/src/test/ui/macros/format-parse-errors.rs index 96aee5e6aee..ffa7a2817ff 100644 --- a/src/test/ui/macros/format-parse-errors.rs +++ b/src/test/ui/macros/format-parse-errors.rs @@ -1,9 +1,15 @@ fn main() { + let foo = ""; + let bar = ""; format!(); //~ ERROR requires at least a format string argument format!(struct); //~ ERROR expected expression format!("s", name =); //~ ERROR expected expression - format!("s", foo = foo, bar); //~ ERROR expected `=` - format!("s", foo = struct); //~ ERROR expected expression + format!( + "s {foo} {} {}", + foo = foo, + bar, //~ ERROR positional arguments cannot follow named arguments + ); + format!("s {foo}", foo = struct); //~ ERROR expected expression format!("s", struct); //~ ERROR expected expression // This error should come after parsing errors to ensure they are non-fatal. diff --git a/src/test/ui/macros/format-parse-errors.stderr b/src/test/ui/macros/format-parse-errors.stderr index fd4f9309194..906738d7382 100644 --- a/src/test/ui/macros/format-parse-errors.stderr +++ b/src/test/ui/macros/format-parse-errors.stderr @@ -1,5 +1,5 @@ error: requires at least a format string argument - --> $DIR/format-parse-errors.rs:2:5 + --> $DIR/format-parse-errors.rs:4:5 | LL | format!(); | ^^^^^^^^^^ @@ -7,37 +7,39 @@ LL | format!(); = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) error: expected expression, found keyword `struct` - --> $DIR/format-parse-errors.rs:3:13 + --> $DIR/format-parse-errors.rs:5:13 | LL | format!(struct); | ^^^^^^ expected expression error: expected expression, found end of macro arguments - --> $DIR/format-parse-errors.rs:4:24 + --> $DIR/format-parse-errors.rs:6:24 | LL | format!("s", name =); | ^ expected expression -error: expected `=`, found end of macro arguments - --> $DIR/format-parse-errors.rs:5:32 +error: positional arguments cannot follow named arguments + --> $DIR/format-parse-errors.rs:10:9 | -LL | format!("s", foo = foo, bar); - | ^ expected `=` +LL | foo = foo, + | --- named argument +LL | bar, + | ^^^ positional arguments must be before named arguments error: expected expression, found keyword `struct` - --> $DIR/format-parse-errors.rs:6:24 + --> $DIR/format-parse-errors.rs:12:30 | -LL | format!("s", foo = struct); - | ^^^^^^ expected expression +LL | format!("s {foo}", foo = struct); + | ^^^^^^ expected expression error: expected expression, found keyword `struct` - --> $DIR/format-parse-errors.rs:7:18 + --> $DIR/format-parse-errors.rs:13:18 | LL | format!("s", struct); | ^^^^^^ expected expression error: format argument must be a string literal - --> $DIR/format-parse-errors.rs:10:13 + --> $DIR/format-parse-errors.rs:16:13 | LL | format!(123); | ^^^