1
Fork 0

Auto merge of #90473 - joshtriplett:stabilize-format-args-capture, r=Mark-Simulacrum

stabilize format args capture

Works as expected, and there are widespread reports of success with it, as well as interest in it.

RFC: rust-lang/rfcs#2795
Tracking issue: https://github.com/rust-lang/rust/issues/67984

Addressing items from the tracking issue:

- We don't support capturing arguments from a non-literal format string like `format_args!(concat!(...))`. We could add that in a future enhancement, or we can decide that it isn't supported (as suggested in https://github.com/rust-lang/rust/issues/67984#issuecomment-801394736 ).
- I've updated the documentation.
- `panic!` now supports capture as well.
- There are potentially opportunities to further improve diagnostics for invalid usage, such as if it looks like the user tried to use an expression rather than a variable. However, such cases are all already caught and provide reasonable syntax errors now, and we can always provided even friendlier diagnostics in the future.
This commit is contained in:
bors 2021-11-15 16:10:19 +00:00
commit c26746af5a
22 changed files with 83 additions and 167 deletions

View file

@ -3,7 +3,7 @@
#![feature(bool_to_option)] #![feature(bool_to_option)]
#![feature(box_patterns)] #![feature(box_patterns)]
#![feature(crate_visibility_modifier)] #![feature(crate_visibility_modifier)]
#![feature(format_args_capture)] #![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(in_band_lifetimes)] #![feature(in_band_lifetimes)]
#![feature(iter_zip)] #![feature(iter_zip)]
#![feature(let_else)] #![feature(let_else)]

View file

@ -527,17 +527,9 @@ impl<'a, 'b> Context<'a, 'b> {
self.verify_arg_type(Exact(idx), ty) self.verify_arg_type(Exact(idx), ty)
} }
None => { None => {
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 // For the moment capturing variables from format strings expanded from macros is
// disabled (see RFC #2795) // disabled (see RFC #2795)
let can_capture = capture_feature_enabled && self.is_literal; if self.is_literal {
if can_capture {
// Treat this name as a variable to capture from the surrounding scope // Treat this name as a variable to capture from the surrounding scope
let idx = self.args.len(); let idx = self.args.len();
self.arg_types.push(Vec::new()); self.arg_types.push(Vec::new());
@ -559,23 +551,15 @@ impl<'a, 'b> Context<'a, 'b> {
}; };
let mut err = self.ecx.struct_span_err(sp, &msg[..]); let mut err = self.ecx.struct_span_err(sp, &msg[..]);
if capture_feature_enabled && !self.is_literal { err.note(&format!(
err.note(&format!( "did you intend to capture a variable `{}` from \
"did you intend to capture a variable `{}` from \ the surrounding scope?",
the surrounding scope?", name
name ));
)); err.note(
err.note( "to avoid ambiguity, `format_args!` cannot capture variables \
"to avoid ambiguity, `format_args!` cannot capture variables \ when the format string is expanded from a macro",
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(); err.emit();
} }

View file

@ -6,7 +6,7 @@
#![feature(crate_visibility_modifier)] #![feature(crate_visibility_modifier)]
#![feature(backtrace)] #![feature(backtrace)]
#![feature(if_let_guard)] #![feature(if_let_guard)]
#![feature(format_args_capture)] #![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(iter_zip)] #![feature(iter_zip)]
#![feature(let_else)] #![feature(let_else)]
#![feature(nll)] #![feature(nll)]

View file

@ -1,7 +1,7 @@
#![feature(crate_visibility_modifier)] #![feature(crate_visibility_modifier)]
#![feature(decl_macro)] #![feature(decl_macro)]
#![feature(destructuring_assignment)] #![feature(destructuring_assignment)]
#![feature(format_args_capture)] #![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(if_let_guard)] #![feature(if_let_guard)]
#![feature(iter_zip)] #![feature(iter_zip)]
#![feature(let_else)] #![feature(let_else)]

View file

@ -301,6 +301,8 @@ declare_features! (
(accepted, relaxed_struct_unsize, "1.58.0", Some(81793), None), (accepted, relaxed_struct_unsize, "1.58.0", Some(81793), None),
/// Allows dereferencing raw pointers during const eval. /// Allows dereferencing raw pointers during const eval.
(accepted, const_raw_ptr_deref, "1.58.0", Some(51911), None), (accepted, const_raw_ptr_deref, "1.58.0", Some(51911), None),
/// Allows capturing variables in scope using format_args!
(accepted, format_args_capture, "1.58.0", Some(67984), None),
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// feature-group-end: accepted features // feature-group-end: accepted features

View file

@ -539,9 +539,6 @@ declare_features! (
/// Be more precise when looking for live drops in a const context. /// Be more precise when looking for live drops in a const context.
(active, const_precise_live_drops, "1.46.0", Some(73255), None), (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),
/// Allows `if let` guard in match arms. /// Allows `if let` guard in match arms.
(active, if_let_guard, "1.47.0", Some(51114), None), (active, if_let_guard, "1.47.0", Some(51114), None),

View file

@ -30,7 +30,7 @@
#![feature(bool_to_option)] #![feature(bool_to_option)]
#![feature(box_patterns)] #![feature(box_patterns)]
#![feature(crate_visibility_modifier)] #![feature(crate_visibility_modifier)]
#![feature(format_args_capture)] #![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(iter_order_by)] #![feature(iter_order_by)]
#![feature(iter_zip)] #![feature(iter_zip)]
#![feature(never_type)] #![feature(never_type)]

View file

@ -7,7 +7,7 @@
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
#![feature(crate_visibility_modifier)] #![feature(crate_visibility_modifier)]
#![feature(in_band_lifetimes)] #![feature(in_band_lifetimes)]
#![feature(format_args_capture)] #![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(iter_zip)] #![feature(iter_zip)]
#![feature(map_try_insert)] #![feature(map_try_insert)]
#![feature(min_specialization)] #![feature(min_specialization)]

View file

@ -13,7 +13,7 @@
#![feature(drain_filter)] #![feature(drain_filter)]
#![feature(bool_to_option)] #![feature(bool_to_option)]
#![feature(crate_visibility_modifier)] #![feature(crate_visibility_modifier)]
#![feature(format_args_capture)] #![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(iter_zip)] #![feature(iter_zip)]
#![feature(let_else)] #![feature(let_else)]
#![feature(never_type)] #![feature(never_type)]

View file

@ -58,7 +58,7 @@ This API is completely unstable and subject to change.
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")] #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
#![feature(bool_to_option)] #![feature(bool_to_option)]
#![feature(crate_visibility_modifier)] #![feature(crate_visibility_modifier)]
#![feature(format_args_capture)] #![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(if_let_guard)] #![feature(if_let_guard)]
#![feature(in_band_lifetimes)] #![feature(in_band_lifetimes)]
#![feature(is_sorted)] #![feature(is_sorted)]

View file

@ -17,6 +17,8 @@
//! format!("The number is {}", 1); // => "The number is 1" //! format!("The number is {}", 1); // => "The number is 1"
//! format!("{:?}", (3, 4)); // => "(3, 4)" //! format!("{:?}", (3, 4)); // => "(3, 4)"
//! format!("{value}", value=4); // => "4" //! format!("{value}", value=4); // => "4"
//! let people = "Rustaceans";
//! format!("Hello {people}!"); // => "Hello Rustaceans!"
//! format!("{} {}", 1, 2); // => "1 2" //! format!("{} {}", 1, 2); // => "1 2"
//! format!("{:04}", 42); // => "0042" with leading zeros //! format!("{:04}", 42); // => "0042" with leading zeros
//! format!("{:#?}", (100, 200)); // => "( //! format!("{:#?}", (100, 200)); // => "(
@ -80,6 +82,19 @@
//! format!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" //! format!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b"
//! ``` //! ```
//! //!
//! If a named parameter does not appear in the argument list, `format!` will
//! reference a variable with that name in the current scope.
//!
//! ```
//! let argument = 2 + 2;
//! format!("{argument}"); // => "4"
//!
//! fn make_string(a: u32, b: &str) -> String {
//! format!("{b} {a}")
//! }
//! make_string(927, "label"); // => "label 927"
//! ```
//!
//! It is not valid to put positional parameters (those without names) after //! It is not valid to put positional parameters (those without names) after
//! arguments that have names. Like with positional parameters, it is not //! arguments that have names. Like with positional parameters, it is not
//! valid to provide named parameters that are unused by the format string. //! valid to provide named parameters that are unused by the format string.
@ -98,6 +113,8 @@
//! println!("Hello {:1$}!", "x", 5); //! println!("Hello {:1$}!", "x", 5);
//! println!("Hello {1:0$}!", 5, "x"); //! println!("Hello {1:0$}!", 5, "x");
//! println!("Hello {:width$}!", "x", width = 5); //! println!("Hello {:width$}!", "x", width = 5);
//! let width = 5;
//! println!("Hello {:width$}!", "x");
//! ``` //! ```
//! //!
//! This is a parameter for the "minimum width" that the format should take up. //! This is a parameter for the "minimum width" that the format should take up.

View file

@ -105,6 +105,7 @@
#![feature(fmt_internals)] #![feature(fmt_internals)]
#![feature(fn_traits)] #![feature(fn_traits)]
#![feature(inherent_ascii_escape)] #![feature(inherent_ascii_escape)]
#![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(inplace_iteration)] #![feature(inplace_iteration)]
#![feature(iter_advance_by)] #![feature(iter_advance_by)]
#![feature(iter_zip)] #![feature(iter_zip)]

View file

@ -1,47 +0,0 @@
# `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`

View file

@ -1,6 +0,0 @@
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`
}

View file

@ -1,18 +0,0 @@
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

View file

@ -1,5 +1,3 @@
#![feature(format_args_capture)]
fn main() { fn main() {
format!(concat!("{foo}")); //~ ERROR: there is no argument named `foo` format!(concat!("{foo}")); //~ ERROR: there is no argument named `foo`
format!(concat!("{ba", "r} {}"), 1); //~ ERROR: there is no argument named `bar` format!(concat!("{ba", "r} {}"), 1); //~ ERROR: there is no argument named `bar`

View file

@ -1,5 +1,5 @@
error: there is no argument named `foo` error: there is no argument named `foo`
--> $DIR/format-args-capture-macro-hygiene.rs:4:13 --> $DIR/format-args-capture-macro-hygiene.rs:2:13
| |
LL | format!(concat!("{foo}")); LL | format!(concat!("{foo}"));
| ^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^
@ -9,7 +9,7 @@ LL | format!(concat!("{foo}"));
= note: this error originates in the macro `concat` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the macro `concat` (in Nightly builds, run with -Z macro-backtrace for more info)
error: there is no argument named `bar` error: there is no argument named `bar`
--> $DIR/format-args-capture-macro-hygiene.rs:5:13 --> $DIR/format-args-capture-macro-hygiene.rs:3:13
| |
LL | format!(concat!("{ba", "r} {}"), 1); LL | format!(concat!("{ba", "r} {}"), 1);
| ^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -1,5 +1,3 @@
#![feature(format_args_capture)]
fn main() { fn main() {
format!("{} {foo} {} {bar} {}", 1, 2, 3); format!("{} {foo} {} {bar} {}", 1, 2, 3);
//~^ ERROR: cannot find value `foo` in this scope //~^ ERROR: cannot find value `foo` in this scope

View file

@ -1,5 +1,5 @@
error: named argument never used error: named argument never used
--> $DIR/format-args-capture-missing-variables.rs:10:51 --> $DIR/format-args-capture-missing-variables.rs:8:51
| |
LL | format!("{valuea} {valueb}", valuea=5, valuec=7); LL | format!("{valuea} {valueb}", valuea=5, valuec=7);
| ------------------- ^ named argument never used | ------------------- ^ named argument never used
@ -7,37 +7,37 @@ LL | format!("{valuea} {valueb}", valuea=5, valuec=7);
| formatting specifier missing | formatting specifier missing
error[E0425]: cannot find value `foo` in this scope error[E0425]: cannot find value `foo` in this scope
--> $DIR/format-args-capture-missing-variables.rs:4:17 --> $DIR/format-args-capture-missing-variables.rs:2:17
| |
LL | format!("{} {foo} {} {bar} {}", 1, 2, 3); LL | format!("{} {foo} {} {bar} {}", 1, 2, 3);
| ^^^^^ not found in this scope | ^^^^^ not found in this scope
error[E0425]: cannot find value `bar` in this scope error[E0425]: cannot find value `bar` in this scope
--> $DIR/format-args-capture-missing-variables.rs:4:26 --> $DIR/format-args-capture-missing-variables.rs:2:26
| |
LL | format!("{} {foo} {} {bar} {}", 1, 2, 3); LL | format!("{} {foo} {} {bar} {}", 1, 2, 3);
| ^^^^^ not found in this scope | ^^^^^ not found in this scope
error[E0425]: cannot find value `foo` in this scope error[E0425]: cannot find value `foo` in this scope
--> $DIR/format-args-capture-missing-variables.rs:8:14 --> $DIR/format-args-capture-missing-variables.rs:6:14
| |
LL | format!("{foo}"); LL | format!("{foo}");
| ^^^^^ not found in this scope | ^^^^^ not found in this scope
error[E0425]: cannot find value `valueb` in this scope error[E0425]: cannot find value `valueb` in this scope
--> $DIR/format-args-capture-missing-variables.rs:10:23 --> $DIR/format-args-capture-missing-variables.rs:8:23
| |
LL | format!("{valuea} {valueb}", valuea=5, valuec=7); LL | format!("{valuea} {valueb}", valuea=5, valuec=7);
| ^^^^^^^^ not found in this scope | ^^^^^^^^ not found in this scope
error[E0425]: cannot find value `foo` in this scope error[E0425]: cannot find value `foo` in this scope
--> $DIR/format-args-capture-missing-variables.rs:16:9 --> $DIR/format-args-capture-missing-variables.rs:14:9
| |
LL | {foo} LL | {foo}
| ^^^^^ not found in this scope | ^^^^^ not found in this scope
error[E0425]: cannot find value `foo` in this scope error[E0425]: cannot find value `foo` in this scope
--> $DIR/format-args-capture-missing-variables.rs:21:13 --> $DIR/format-args-capture-missing-variables.rs:19:13
| |
LL | panic!("{foo} {bar}", bar=1); LL | panic!("{foo} {bar}", bar=1);
| ^^^^^ not found in this scope | ^^^^^ not found in this scope

View file

@ -1,5 +1,4 @@
// run-pass // run-pass
#![feature(format_args_capture)]
#![feature(cfg_panic)] #![feature(cfg_panic)]
fn main() { fn main() {

View file

@ -25,10 +25,10 @@ fn main() {
//~^ ERROR: invalid reference to positional arguments 3, 4 and 5 (there are 3 arguments) //~^ ERROR: invalid reference to positional arguments 3, 4 and 5 (there are 3 arguments)
format!("{} {foo} {} {bar} {}", 1, 2, 3); format!("{} {foo} {} {bar} {}", 1, 2, 3);
//~^ ERROR: there is no argument named `foo` //~^ ERROR: cannot find value `foo` in this scope
//~^^ ERROR: there is no argument named `bar` //~^^ ERROR: cannot find value `bar` in this scope
format!("{foo}"); //~ ERROR: no argument named `foo` format!("{foo}"); //~ ERROR: cannot find value `foo` in this scope
format!("", 1, 2); //~ ERROR: multiple unused formatting arguments format!("", 1, 2); //~ ERROR: multiple unused formatting arguments
format!("{}", 1, 2); //~ ERROR: argument never used format!("{}", 1, 2); //~ ERROR: argument never used
format!("{1}", 1, 2); //~ ERROR: argument never used format!("{1}", 1, 2); //~ ERROR: argument never used
@ -43,7 +43,7 @@ fn main() {
// bad named arguments, #35082 // bad named arguments, #35082
format!("{valuea} {valueb}", valuea=5, valuec=7); format!("{valuea} {valueb}", valuea=5, valuec=7);
//~^ ERROR there is no argument named `valueb` //~^ ERROR cannot find value `valueb` in this scope
//~^^ ERROR named argument never used //~^^ ERROR named argument never used
// bad syntax of the format string // bad syntax of the format string
@ -60,7 +60,7 @@ fn main() {
{foo} {foo}
"##); "##);
//~^^^ ERROR: there is no argument named `foo` //~^^^ ERROR: cannot find value `foo` in this scope
// bad syntax in format string with multiple newlines, #53836 // bad syntax in format string with multiple newlines, #53836
format!("first number: {} format!("first number: {}

View file

@ -58,30 +58,6 @@ LL | format!("{name} {value} {} {} {} {} {} {}", 0, name=1, value=2);
| |
= note: positional arguments are zero-based = note: positional arguments are zero-based
error: there is no argument named `foo`
--> $DIR/ifmt-bad-arg.rs:27:17
|
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 error: multiple unused formatting arguments
--> $DIR/ifmt-bad-arg.rs:32:17 --> $DIR/ifmt-bad-arg.rs:32:17
| |
@ -156,14 +132,6 @@ LL | format!("{foo} {} {}", foo=1, 2);
| | | |
| named argument | named argument
error: there is no argument named `valueb`
--> $DIR/ifmt-bad-arg.rs:45:23
|
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 error: named argument never used
--> $DIR/ifmt-bad-arg.rs:45:51 --> $DIR/ifmt-bad-arg.rs:45:51
| |
@ -208,14 +176,6 @@ LL | format!("foo %s baz", "bar");
| |
= note: printf formatting not supported; see the documentation for `std::fmt` = note: printf formatting not supported; see the documentation for `std::fmt`
error: there is no argument named `foo`
--> $DIR/ifmt-bad-arg.rs:60:9
|
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'` error: invalid format string: expected `'}'`, found `'t'`
--> $DIR/ifmt-bad-arg.rs:75:1 --> $DIR/ifmt-bad-arg.rs:75:1
| |
@ -302,6 +262,36 @@ LL | println!("{:.*}");
= note: positional arguments are zero-based = note: positional arguments are zero-based
= note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html
error[E0425]: cannot find value `foo` in this scope
--> $DIR/ifmt-bad-arg.rs:27:17
|
LL | format!("{} {foo} {} {bar} {}", 1, 2, 3);
| ^^^^^ not found in this scope
error[E0425]: cannot find value `bar` in this scope
--> $DIR/ifmt-bad-arg.rs:27:26
|
LL | format!("{} {foo} {} {bar} {}", 1, 2, 3);
| ^^^^^ not found in this scope
error[E0425]: cannot find value `foo` in this scope
--> $DIR/ifmt-bad-arg.rs:31:14
|
LL | format!("{foo}");
| ^^^^^ not found in this scope
error[E0425]: cannot find value `valueb` in this scope
--> $DIR/ifmt-bad-arg.rs:45:23
|
LL | format!("{valuea} {valueb}", valuea=5, valuec=7);
| ^^^^^^^^ not found in this scope
error[E0425]: cannot find value `foo` in this scope
--> $DIR/ifmt-bad-arg.rs:60:9
|
LL | {foo}
| ^^^^^ not found in this scope
error[E0308]: mismatched types error[E0308]: mismatched types
--> $DIR/ifmt-bad-arg.rs:78:32 --> $DIR/ifmt-bad-arg.rs:78:32
| |
@ -324,4 +314,5 @@ LL | println!("{} {:07$.*} {}", 1, 3.2, 4);
error: aborting due to 36 previous errors error: aborting due to 36 previous errors
For more information about this error, try `rustc --explain E0308`. Some errors have detailed explanations: E0308, E0425.
For more information about an error, try `rustc --explain E0308`.