Allow any identifier as format arg name

Previously:

    error: invalid format string: invalid argument name `_x`
     --> src/main.rs:2:16
      |
    2 |     println!("{_x}", a=0);
      |                ^^ invalid argument name in format string
      |
      = note: argument names cannot start with an underscore

Not supporting identifiers starting with underscore appears to have been
an arbitrary limitation from 2013 in code that was most likely never
reviewed:
https://github.com/rust-lang/rust/pull/8245/files#diff-0347868ef389c805e97636623e4a4ea6R277

The error message was dutifully improved in #50610 but is there any
reason that leading underscore would be a special case?

This commit updates the format_args parser to accept identifiers with
leading underscores.
This commit is contained in:
David Tolnay 2019-11-28 10:49:13 -08:00
parent f453d1127d
commit 423a5d3e85
No known key found for this signature in database
GPG Key ID: F9BA143B95FF6D82
4 changed files with 51 additions and 38 deletions

View File

@ -442,20 +442,9 @@ impl<'a> Parser<'a> {
Some(ArgumentIs(i))
} else {
match self.cur.peek() {
Some(&(_, c)) if c.is_alphabetic() => {
Some(&(_, c)) if rustc_lexer::is_id_start(c) => {
Some(ArgumentNamed(Symbol::intern(self.word())))
}
Some(&(pos, c)) if c == '_' => {
let invalid_name = self.string(pos);
self.err_with_note(format!("invalid argument name `{}`", invalid_name),
"invalid argument name",
"argument names cannot start with an underscore",
self.to_span_index(pos).to(
self.to_span_index(pos + invalid_name.len())
),
);
Some(ArgumentNamed(Symbol::intern(invalid_name)))
},
// This is an `ArgumentNext`.
// Record the fact and do the resolution after parsing the
@ -611,22 +600,34 @@ impl<'a> Parser<'a> {
/// Rust identifier, except that it can't start with `_` character.
fn word(&mut self) -> &'a str {
let start = match self.cur.peek() {
Some(&(pos, c)) if c != '_' && rustc_lexer::is_id_start(c) => {
Some(&(pos, c)) if rustc_lexer::is_id_start(c) => {
self.cur.next();
pos
}
_ => {
return &self.input[..0];
return "";
}
};
let mut end = None;
while let Some(&(pos, c)) = self.cur.peek() {
if rustc_lexer::is_id_continue(c) {
self.cur.next();
} else {
return &self.input[start..pos];
end = Some(pos);
break;
}
}
&self.input[start..self.input.len()]
let end = end.unwrap_or(self.input.len());
let word = &self.input[start..end];
if word == "_" {
self.err_with_note(
"invalid argument name `_`",
"invalid argument name",
"argument name cannot be a single underscore",
self.to_span_index(start).to(self.to_span_index(end)),
);
}
word
}
/// Optionally parses an integer at the current position. This doesn't deal

View File

@ -6,10 +6,12 @@ fn main() {
println!("{{}}");
println!("}");
//~^ ERROR invalid format string: unmatched `}` found
let _ = format!("{_foo}", _foo = 6usize);
//~^ ERROR invalid format string: invalid argument name `_foo`
let _ = format!("{_}", _ = 6usize);
//~^ ERROR invalid format string: invalid argument name `_`
let _ = format!("{a:_}", a = "", _ = 0);
//~^ ERROR invalid format string: invalid argument name `_`
let _ = format!("{a:._$}", a = "", _ = 0);
//~^ ERROR invalid format string: invalid argument name `_`
let _ = format!("{");
//~^ ERROR invalid format string: expected `'}'` but string was terminated
let _ = format!("}");

View File

@ -16,24 +16,32 @@ LL | println!("}");
|
= note: if you intended to print `}`, you can escape it using `}}`
error: invalid format string: invalid argument name `_foo`
--> $DIR/format-string-error.rs:9:23
|
LL | let _ = format!("{_foo}", _foo = 6usize);
| ^^^^ invalid argument name in format string
|
= note: argument names cannot start with an underscore
error: invalid format string: invalid argument name `_`
--> $DIR/format-string-error.rs:11:23
--> $DIR/format-string-error.rs:9:23
|
LL | let _ = format!("{_}", _ = 6usize);
| ^ invalid argument name in format string
|
= note: argument names cannot start with an underscore
= note: argument name cannot be a single underscore
error: invalid format string: invalid argument name `_`
--> $DIR/format-string-error.rs:11:25
|
LL | let _ = format!("{a:_}", a = "", _ = 0);
| ^ invalid argument name in format string
|
= note: argument name cannot be a single underscore
error: invalid format string: invalid argument name `_`
--> $DIR/format-string-error.rs:13:26
|
LL | let _ = format!("{a:._$}", a = "", _ = 0);
| ^ invalid argument name in format string
|
= note: argument name cannot be a single underscore
error: invalid format string: expected `'}'` but string was terminated
--> $DIR/format-string-error.rs:13:23
--> $DIR/format-string-error.rs:15:23
|
LL | let _ = format!("{");
| -^ expected `'}'` in format string
@ -43,7 +51,7 @@ LL | let _ = format!("{");
= note: if you intended to print `{`, you can escape it using `{{`
error: invalid format string: unmatched `}` found
--> $DIR/format-string-error.rs:15:22
--> $DIR/format-string-error.rs:17:22
|
LL | let _ = format!("}");
| ^ unmatched `}` in format string
@ -51,7 +59,7 @@ LL | let _ = format!("}");
= note: if you intended to print `}`, you can escape it using `}}`
error: invalid format string: expected `'}'`, found `'\'`
--> $DIR/format-string-error.rs:17:23
--> $DIR/format-string-error.rs:19:23
|
LL | let _ = format!("{\}");
| -^ expected `}` in format string
@ -61,7 +69,7 @@ LL | let _ = format!("{\}");
= note: if you intended to print `{`, you can escape it using `{{`
error: invalid format string: expected `'}'` but string was terminated
--> $DIR/format-string-error.rs:19:35
--> $DIR/format-string-error.rs:21:35
|
LL | let _ = format!("\n\n\n{\n\n\n");
| - ^ expected `'}'` in format string
@ -71,7 +79,7 @@ LL | let _ = format!("\n\n\n{\n\n\n");
= note: if you intended to print `{`, you can escape it using `{{`
error: invalid format string: expected `'}'` but string was terminated
--> $DIR/format-string-error.rs:25:3
--> $DIR/format-string-error.rs:27:3
|
LL | {"###);
| -^ expected `'}'` in format string
@ -81,7 +89,7 @@ LL | {"###);
= note: if you intended to print `{`, you can escape it using `{{`
error: invalid format string: expected `'}'` but string was terminated
--> $DIR/format-string-error.rs:33:1
--> $DIR/format-string-error.rs:35:1
|
LL | {
| - because of this opening brace
@ -92,7 +100,7 @@ LL | "###);
= note: if you intended to print `{`, you can escape it using `{{`
error: invalid format string: unmatched `}` found
--> $DIR/format-string-error.rs:39:2
--> $DIR/format-string-error.rs:41:2
|
LL | }
| ^ unmatched `}` in format string
@ -100,7 +108,7 @@ LL | }
= note: if you intended to print `}`, you can escape it using `}}`
error: invalid format string: unmatched `}` found
--> $DIR/format-string-error.rs:47:9
--> $DIR/format-string-error.rs:49:9
|
LL | }
| ^ unmatched `}` in format string
@ -108,10 +116,10 @@ LL | }
= note: if you intended to print `}`, you can escape it using `}}`
error: 3 positional arguments in format string, but there are 2 arguments
--> $DIR/format-string-error.rs:51:15
--> $DIR/format-string-error.rs:53:15
|
LL | println!("{} {} {}", 1, 2);
| ^^ ^^ ^^ - -
error: aborting due to 13 previous errors
error: aborting due to 14 previous errors

View File

@ -90,6 +90,7 @@ pub fn main() {
t!(format!("{foo} {bar}", foo=0, bar=1), "0 1");
t!(format!("{foo} {1} {bar} {0}", 0, 1, foo=2, bar=3), "2 1 3 0");
t!(format!("{} {0}", "a"), "a a");
t!(format!("{_foo}", _foo = 6usize), "6");
t!(format!("{foo_bar}", foo_bar=1), "1");
t!(format!("{}", 5 + 5), "10");
t!(format!("{:#4}", C), "☃123");
@ -125,6 +126,7 @@ pub fn main() {
t!(format!("{:.*}", 4, "aaaaaaaaaaaaaaaaaa"), "aaaa");
t!(format!("{:.1$}", "aaaaaaaaaaaaaaaaaa", 4), "aaaa");
t!(format!("{:.a$}", "aaaaaaaaaaaaaaaaaa", a=4), "aaaa");
t!(format!("{:._a$}", "aaaaaaaaaaaaaaaaaa", _a=4), "aaaa");
t!(format!("{:1$}", "a", 4), "a ");
t!(format!("{1:0$}", 4, "a"), "a ");
t!(format!("{:a$}", "a", a=4), "a ");