Rollup merge of #73670 - davidhewitt:format-args-capture, r=varkor
Add `format_args_capture` feature This is the initial implementation PR for [RFC 2795](https://github.com/rust-lang/rfcs/pull/2795). Note that, as dicussed in the tracking issue (#67984), the feature gate has been called `format_args_capture`. Next up I guess I need to add documentation for this feature. I've not written any docs before for rustc / std so I would appreciate suggestions on where I should add docs.
This commit is contained in:
commit
4a8d9ea80f
@ -0,0 +1,47 @@
|
||||
# `format_args_capture`
|
||||
|
||||
The tracking issue for this feature is: [#67984]
|
||||
|
||||
[#67984]: https://github.com/rust-lang/rust/issues/67984
|
||||
|
||||
------------------------
|
||||
|
||||
Enables `format_args!` (and macros which use `format_args!` in their implementation, such
|
||||
as `format!`, `print!` and `panic!`) to capture variables from the surrounding scope.
|
||||
This avoids the need to pass named parameters when the binding in question
|
||||
already exists in scope.
|
||||
|
||||
```rust
|
||||
#![feature(format_args_capture)]
|
||||
|
||||
let (person, species, name) = ("Charlie Brown", "dog", "Snoopy");
|
||||
|
||||
// captures named argument `person`
|
||||
print!("Hello {person}");
|
||||
|
||||
// captures named arguments `species` and `name`
|
||||
format!("The {species}'s name is {name}.");
|
||||
```
|
||||
|
||||
This also works for formatting parameters such as width and precision:
|
||||
|
||||
```rust
|
||||
#![feature(format_args_capture)]
|
||||
|
||||
let precision = 2;
|
||||
let s = format!("{:.precision$}", 1.324223);
|
||||
|
||||
assert_eq!(&s, "1.32");
|
||||
```
|
||||
|
||||
A non-exhaustive list of macros which benefit from this functionality include:
|
||||
- `format!`
|
||||
- `print!` and `println!`
|
||||
- `eprint!` and `eprintln!`
|
||||
- `write!` and `writeln!`
|
||||
- `panic!`
|
||||
- `unreachable!`
|
||||
- `unimplemented!`
|
||||
- `todo!`
|
||||
- `assert!` and similar
|
||||
- macros in many thirdparty crates, such as `log`
|
@ -107,6 +107,9 @@ struct Context<'a, 'b> {
|
||||
arg_spans: Vec<Span>,
|
||||
/// All the formatting arguments that have formatting flags set, in order for diagnostics.
|
||||
arg_with_formatting: Vec<parse::FormatSpec<'a>>,
|
||||
|
||||
/// Whether this format string came from a string literal, as opposed to a macro.
|
||||
is_literal: bool,
|
||||
}
|
||||
|
||||
/// Parses the arguments from the given list of tokens, returning the diagnostic
|
||||
@ -498,10 +501,55 @@ impl<'a, 'b> Context<'a, 'b> {
|
||||
self.verify_arg_type(Exact(idx), ty)
|
||||
}
|
||||
None => {
|
||||
let msg = format!("there is no argument named `{}`", name);
|
||||
let sp = *self.arg_spans.get(self.curpiece).unwrap_or(&self.fmtsp);
|
||||
let mut err = self.ecx.struct_span_err(sp, &msg[..]);
|
||||
err.emit();
|
||||
let capture_feature_enabled = self
|
||||
.ecx
|
||||
.ecfg
|
||||
.features
|
||||
.map_or(false, |features| features.format_args_capture);
|
||||
|
||||
// For the moment capturing variables from format strings expanded from macros is
|
||||
// disabled (see RFC #2795)
|
||||
let can_capture = capture_feature_enabled && self.is_literal;
|
||||
|
||||
if can_capture {
|
||||
// Treat this name as a variable to capture from the surrounding scope
|
||||
let idx = self.args.len();
|
||||
self.arg_types.push(Vec::new());
|
||||
self.arg_unique_types.push(Vec::new());
|
||||
self.args.push(
|
||||
self.ecx.expr_ident(self.fmtsp, Ident::new(name, self.fmtsp)),
|
||||
);
|
||||
self.names.insert(name, idx);
|
||||
self.verify_arg_type(Exact(idx), ty)
|
||||
} else {
|
||||
let msg = format!("there is no argument named `{}`", name);
|
||||
let sp = if self.is_literal {
|
||||
*self.arg_spans.get(self.curpiece).unwrap_or(&self.fmtsp)
|
||||
} else {
|
||||
self.fmtsp
|
||||
};
|
||||
let mut err = self.ecx.struct_span_err(sp, &msg[..]);
|
||||
|
||||
if capture_feature_enabled && !self.is_literal {
|
||||
err.note(&format!(
|
||||
"did you intend to capture a variable `{}` from \
|
||||
the surrounding scope?",
|
||||
name
|
||||
));
|
||||
err.note(
|
||||
"to avoid ambiguity, `format_args!` cannot capture variables \
|
||||
when the format string is expanded from a macro",
|
||||
);
|
||||
} else if self.ecx.parse_sess().unstable_features.is_nightly_build() {
|
||||
err.help(&format!(
|
||||
"if you intended to capture `{}` from the surrounding scope, add \
|
||||
`#![feature(format_args_capture)]` to the crate attributes",
|
||||
name
|
||||
));
|
||||
}
|
||||
|
||||
err.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -951,6 +999,7 @@ pub fn expand_preparsed_format_args(
|
||||
invalid_refs: Vec::new(),
|
||||
arg_spans,
|
||||
arg_with_formatting: Vec::new(),
|
||||
is_literal: parser.is_literal,
|
||||
};
|
||||
|
||||
// This needs to happen *after* the Parser has consumed all pieces to create all the spans
|
||||
|
@ -567,6 +567,9 @@ declare_features! (
|
||||
/// Be more precise when looking for live drops in a const context.
|
||||
(active, const_precise_live_drops, "1.46.0", Some(73255), None),
|
||||
|
||||
/// Allows capturing variables in scope using format_args!
|
||||
(active, format_args_capture, "1.46.0", Some(67984), None),
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// feature-group-end: actual feature gates
|
||||
// -------------------------------------------------------------------------
|
||||
|
@ -190,7 +190,7 @@ pub struct Parser<'a> {
|
||||
/// Whether the source string is comes from `println!` as opposed to `format!` or `print!`
|
||||
append_newline: bool,
|
||||
/// Whether this formatting string is a literal or it comes from a macro.
|
||||
is_literal: bool,
|
||||
pub is_literal: bool,
|
||||
/// Start position of the current line.
|
||||
cur_line_start: usize,
|
||||
/// Start and end byte offset of every line of the format string. Excludes
|
||||
|
@ -342,6 +342,7 @@ symbols! {
|
||||
forbid,
|
||||
format_args,
|
||||
format_args_nl,
|
||||
format_args_capture,
|
||||
from,
|
||||
From,
|
||||
from_desugaring,
|
||||
|
6
src/test/ui/fmt/feature-gate-format-args-capture.rs
Normal file
6
src/test/ui/fmt/feature-gate-format-args-capture.rs
Normal file
@ -0,0 +1,6 @@
|
||||
fn main() {
|
||||
format!("{foo}"); //~ ERROR: there is no argument named `foo`
|
||||
|
||||
// panic! doesn't hit format_args! unless there are two or more arguments.
|
||||
panic!("{foo} {bar}", bar=1); //~ ERROR: there is no argument named `foo`
|
||||
}
|
18
src/test/ui/fmt/feature-gate-format-args-capture.stderr
Normal file
18
src/test/ui/fmt/feature-gate-format-args-capture.stderr
Normal file
@ -0,0 +1,18 @@
|
||||
error: there is no argument named `foo`
|
||||
--> $DIR/feature-gate-format-args-capture.rs:2:14
|
||||
|
|
||||
LL | format!("{foo}");
|
||||
| ^^^^^
|
||||
|
|
||||
= help: if you intended to capture `foo` from the surrounding scope, add `#![feature(format_args_capture)]` to the crate attributes
|
||||
|
||||
error: there is no argument named `foo`
|
||||
--> $DIR/feature-gate-format-args-capture.rs:5:13
|
||||
|
|
||||
LL | panic!("{foo} {bar}", bar=1);
|
||||
| ^^^^^
|
||||
|
|
||||
= help: if you intended to capture `foo` from the surrounding scope, add `#![feature(format_args_capture)]` to the crate attributes
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
6
src/test/ui/fmt/format-args-capture-macro-hygiene.rs
Normal file
6
src/test/ui/fmt/format-args-capture-macro-hygiene.rs
Normal file
@ -0,0 +1,6 @@
|
||||
#![feature(format_args_capture)]
|
||||
|
||||
fn main() {
|
||||
format!(concat!("{foo}")); //~ ERROR: there is no argument named `foo`
|
||||
format!(concat!("{ba", "r} {}"), 1); //~ ERROR: there is no argument named `bar`
|
||||
}
|
22
src/test/ui/fmt/format-args-capture-macro-hygiene.stderr
Normal file
22
src/test/ui/fmt/format-args-capture-macro-hygiene.stderr
Normal file
@ -0,0 +1,22 @@
|
||||
error: there is no argument named `foo`
|
||||
--> $DIR/format-args-capture-macro-hygiene.rs:4:13
|
||||
|
|
||||
LL | format!(concat!("{foo}"));
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: did you intend to capture a variable `foo` from the surrounding scope?
|
||||
= note: to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: there is no argument named `bar`
|
||||
--> $DIR/format-args-capture-macro-hygiene.rs:5:13
|
||||
|
|
||||
LL | format!(concat!("{ba", "r} {}"), 1);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: did you intend to capture a variable `bar` from the surrounding scope?
|
||||
= note: to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: aborting due to 2 previous errors
|
||||
|
22
src/test/ui/fmt/format-args-capture-missing-variables.rs
Normal file
22
src/test/ui/fmt/format-args-capture-missing-variables.rs
Normal file
@ -0,0 +1,22 @@
|
||||
#![feature(format_args_capture)]
|
||||
|
||||
fn main() {
|
||||
format!("{} {foo} {} {bar} {}", 1, 2, 3);
|
||||
//~^ ERROR: cannot find value `foo` in this scope
|
||||
//~^^ ERROR: cannot find value `bar` in this scope
|
||||
|
||||
format!("{foo}"); //~ ERROR: cannot find value `foo` in this scope
|
||||
|
||||
format!("{valuea} {valueb}", valuea=5, valuec=7);
|
||||
//~^ ERROR cannot find value `valueb` in this scope
|
||||
//~^^ ERROR named argument never used
|
||||
|
||||
format!(r##"
|
||||
|
||||
{foo}
|
||||
|
||||
"##);
|
||||
//~^^^^^ ERROR: cannot find value `foo` in this scope
|
||||
|
||||
panic!("{foo} {bar}", bar=1); //~ ERROR: cannot find value `foo` in this scope
|
||||
}
|
52
src/test/ui/fmt/format-args-capture-missing-variables.stderr
Normal file
52
src/test/ui/fmt/format-args-capture-missing-variables.stderr
Normal file
@ -0,0 +1,52 @@
|
||||
error: named argument never used
|
||||
--> $DIR/format-args-capture-missing-variables.rs:10:51
|
||||
|
|
||||
LL | format!("{valuea} {valueb}", valuea=5, valuec=7);
|
||||
| ------------------- ^ named argument never used
|
||||
| |
|
||||
| formatting specifier missing
|
||||
|
||||
error[E0425]: cannot find value `foo` in this scope
|
||||
--> $DIR/format-args-capture-missing-variables.rs:4:13
|
||||
|
|
||||
LL | format!("{} {foo} {} {bar} {}", 1, 2, 3);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0425]: cannot find value `bar` in this scope
|
||||
--> $DIR/format-args-capture-missing-variables.rs:4:13
|
||||
|
|
||||
LL | format!("{} {foo} {} {bar} {}", 1, 2, 3);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0425]: cannot find value `foo` in this scope
|
||||
--> $DIR/format-args-capture-missing-variables.rs:8:13
|
||||
|
|
||||
LL | format!("{foo}");
|
||||
| ^^^^^^^ not found in this scope
|
||||
|
||||
error[E0425]: cannot find value `valueb` in this scope
|
||||
--> $DIR/format-args-capture-missing-variables.rs:10:13
|
||||
|
|
||||
LL | format!("{valuea} {valueb}", valuea=5, valuec=7);
|
||||
| ^^^^^^^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error[E0425]: cannot find value `foo` in this scope
|
||||
--> $DIR/format-args-capture-missing-variables.rs:14:13
|
||||
|
|
||||
LL | format!(r##"
|
||||
| _____________^
|
||||
LL | |
|
||||
LL | | {foo}
|
||||
LL | |
|
||||
LL | | "##);
|
||||
| |_______^ not found in this scope
|
||||
|
||||
error[E0425]: cannot find value `foo` in this scope
|
||||
--> $DIR/format-args-capture-missing-variables.rs:21:12
|
||||
|
|
||||
LL | panic!("{foo} {bar}", bar=1);
|
||||
| ^^^^^^^^^^^^^ not found in this scope
|
||||
|
||||
error: aborting due to 7 previous errors
|
||||
|
||||
For more information about this error, try `rustc --explain E0425`.
|
64
src/test/ui/fmt/format-args-capture.rs
Normal file
64
src/test/ui/fmt/format-args-capture.rs
Normal file
@ -0,0 +1,64 @@
|
||||
// run-pass
|
||||
// ignore-wasm32
|
||||
// ignore-wasm64
|
||||
#![feature(format_args_capture)]
|
||||
|
||||
fn main() {
|
||||
named_argument_takes_precedence_to_captured();
|
||||
panic_with_single_argument_does_not_get_formatted();
|
||||
panic_with_multiple_arguments_is_formatted();
|
||||
formatting_parameters_can_be_captured();
|
||||
}
|
||||
|
||||
fn named_argument_takes_precedence_to_captured() {
|
||||
let foo = "captured";
|
||||
let s = format!("{foo}", foo="named");
|
||||
assert_eq!(&s, "named");
|
||||
|
||||
let s = format!("{foo}-{foo}-{foo}", foo="named");
|
||||
assert_eq!(&s, "named-named-named");
|
||||
|
||||
let s = format!("{}-{bar}-{foo}", "positional", bar="named");
|
||||
assert_eq!(&s, "positional-named-captured");
|
||||
}
|
||||
|
||||
fn panic_with_single_argument_does_not_get_formatted() {
|
||||
// panic! with a single argument does not perform string formatting.
|
||||
// RFC #2795 suggests that this may need to change so that captured arguments are formatted.
|
||||
// For stability reasons this will need to part of an edition change.
|
||||
|
||||
let msg = std::panic::catch_unwind(|| {
|
||||
panic!("{foo}");
|
||||
}).unwrap_err();
|
||||
|
||||
assert_eq!(msg.downcast_ref::<&str>(), Some(&"{foo}"))
|
||||
}
|
||||
|
||||
fn panic_with_multiple_arguments_is_formatted() {
|
||||
let foo = "captured";
|
||||
|
||||
let msg = std::panic::catch_unwind(|| {
|
||||
panic!("{}-{bar}-{foo}", "positional", bar="named");
|
||||
}).unwrap_err();
|
||||
|
||||
assert_eq!(msg.downcast_ref::<String>(), Some(&"positional-named-captured".to_string()))
|
||||
}
|
||||
|
||||
fn formatting_parameters_can_be_captured() {
|
||||
let width = 9;
|
||||
let precision = 3;
|
||||
|
||||
let x = 7.0;
|
||||
|
||||
let s = format!("{x:width$}");
|
||||
assert_eq!(&s, " 7");
|
||||
|
||||
let s = format!("{x:<width$}");
|
||||
assert_eq!(&s, "7 ");
|
||||
|
||||
let s = format!("{x:-^width$}");
|
||||
assert_eq!(&s, "----7----");
|
||||
|
||||
let s = format!("{x:-^width$.precision$}");
|
||||
assert_eq!(&s, "--7.000--");
|
||||
}
|
@ -63,18 +63,24 @@ error: there is no argument named `foo`
|
||||
|
|
||||
LL | format!("{} {foo} {} {bar} {}", 1, 2, 3);
|
||||
| ^^^^^
|
||||
|
|
||||
= help: if you intended to capture `foo` from the surrounding scope, add `#![feature(format_args_capture)]` to the crate attributes
|
||||
|
||||
error: there is no argument named `bar`
|
||||
--> $DIR/ifmt-bad-arg.rs:27:26
|
||||
|
|
||||
LL | format!("{} {foo} {} {bar} {}", 1, 2, 3);
|
||||
| ^^^^^
|
||||
|
|
||||
= help: if you intended to capture `bar` from the surrounding scope, add `#![feature(format_args_capture)]` to the crate attributes
|
||||
|
||||
error: there is no argument named `foo`
|
||||
--> $DIR/ifmt-bad-arg.rs:31:14
|
||||
|
|
||||
LL | format!("{foo}");
|
||||
| ^^^^^
|
||||
|
|
||||
= help: if you intended to capture `foo` from the surrounding scope, add `#![feature(format_args_capture)]` to the crate attributes
|
||||
|
||||
error: multiple unused formatting arguments
|
||||
--> $DIR/ifmt-bad-arg.rs:32:17
|
||||
@ -155,6 +161,8 @@ error: there is no argument named `valueb`
|
||||
|
|
||||
LL | format!("{valuea} {valueb}", valuea=5, valuec=7);
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= help: if you intended to capture `valueb` from the surrounding scope, add `#![feature(format_args_capture)]` to the crate attributes
|
||||
|
||||
error: named argument never used
|
||||
--> $DIR/ifmt-bad-arg.rs:45:51
|
||||
@ -205,6 +213,8 @@ error: there is no argument named `foo`
|
||||
|
|
||||
LL | {foo}
|
||||
| ^^^^^
|
||||
|
|
||||
= help: if you intended to capture `foo` from the surrounding scope, add `#![feature(format_args_capture)]` to the crate attributes
|
||||
|
||||
error: invalid format string: expected `'}'`, found `'t'`
|
||||
--> $DIR/ifmt-bad-arg.rs:75:1
|
||||
|
Loading…
Reference in New Issue
Block a user