Skip to content

Commit

Permalink
Auto merge of rust-lang#90473 - joshtriplett:stabilize-format-args-ca…
Browse files Browse the repository at this point in the history
…pture, 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: rust-lang#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 rust-lang#67984 (comment) ).
- 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.
  • Loading branch information
bors committed Nov 15, 2021
2 parents eab2d75 + afa719e commit c26746a
Show file tree
Hide file tree
Showing 22 changed files with 83 additions and 167 deletions.
2 changes: 1 addition & 1 deletion compiler/rustc_borrowck/src/lib.rs
Expand Up @@ -3,7 +3,7 @@
#![feature(bool_to_option)]
#![feature(box_patterns)]
#![feature(crate_visibility_modifier)]
#![feature(format_args_capture)]
#![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(in_band_lifetimes)]
#![feature(iter_zip)]
#![feature(let_else)]
Expand Down
36 changes: 10 additions & 26 deletions compiler/rustc_builtin_macros/src/format.rs
Expand Up @@ -527,17 +527,9 @@ impl<'a, 'b> Context<'a, 'b> {
self.verify_arg_type(Exact(idx), ty)
}
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
// disabled (see RFC #2795)
let can_capture = capture_feature_enabled && self.is_literal;

if can_capture {
if self.is_literal {
// Treat this name as a variable to capture from the surrounding scope
let idx = self.args.len();
self.arg_types.push(Vec::new());
Expand All @@ -559,23 +551,15 @@ impl<'a, 'b> Context<'a, 'b> {
};
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.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",
);

err.emit();
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_errors/src/lib.rs
Expand Up @@ -6,7 +6,7 @@
#![feature(crate_visibility_modifier)]
#![feature(backtrace)]
#![feature(if_let_guard)]
#![feature(format_args_capture)]
#![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(iter_zip)]
#![feature(let_else)]
#![feature(nll)]
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_expand/src/lib.rs
@@ -1,7 +1,7 @@
#![feature(crate_visibility_modifier)]
#![feature(decl_macro)]
#![feature(destructuring_assignment)]
#![feature(format_args_capture)]
#![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(if_let_guard)]
#![feature(iter_zip)]
#![feature(let_else)]
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/accepted.rs
Expand Up @@ -301,6 +301,8 @@ declare_features! (
(accepted, relaxed_struct_unsize, "1.58.0", Some(81793), None),
/// Allows dereferencing raw pointers during const eval.
(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
Expand Down
3 changes: 0 additions & 3 deletions compiler/rustc_feature/src/active.rs
Expand Up @@ -539,9 +539,6 @@ 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),

/// Allows `if let` guard in match arms.
(active, if_let_guard, "1.47.0", Some(51114), None),

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_lint/src/lib.rs
Expand Up @@ -30,7 +30,7 @@
#![feature(bool_to_option)]
#![feature(box_patterns)]
#![feature(crate_visibility_modifier)]
#![feature(format_args_capture)]
#![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(iter_order_by)]
#![feature(iter_zip)]
#![feature(never_type)]
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_passes/src/lib.rs
Expand Up @@ -7,7 +7,7 @@
#![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
#![feature(crate_visibility_modifier)]
#![feature(in_band_lifetimes)]
#![feature(format_args_capture)]
#![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(iter_zip)]
#![feature(map_try_insert)]
#![feature(min_specialization)]
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_resolve/src/lib.rs
Expand Up @@ -13,7 +13,7 @@
#![feature(drain_filter)]
#![feature(bool_to_option)]
#![feature(crate_visibility_modifier)]
#![feature(format_args_capture)]
#![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(iter_zip)]
#![feature(let_else)]
#![feature(never_type)]
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_typeck/src/lib.rs
Expand Up @@ -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/")]
#![feature(bool_to_option)]
#![feature(crate_visibility_modifier)]
#![feature(format_args_capture)]
#![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(if_let_guard)]
#![feature(in_band_lifetimes)]
#![feature(is_sorted)]
Expand Down
17 changes: 17 additions & 0 deletions library/alloc/src/fmt.rs
Expand Up @@ -17,6 +17,8 @@
//! format!("The number is {}", 1); // => "The number is 1"
//! format!("{:?}", (3, 4)); // => "(3, 4)"
//! format!("{value}", value=4); // => "4"
//! let people = "Rustaceans";
//! format!("Hello {people}!"); // => "Hello Rustaceans!"
//! format!("{} {}", 1, 2); // => "1 2"
//! format!("{:04}", 42); // => "0042" with leading zeros
//! format!("{:#?}", (100, 200)); // => "(
Expand Down Expand Up @@ -80,6 +82,19 @@
//! 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
//! arguments that have names. Like with positional parameters, it is not
//! valid to provide named parameters that are unused by the format string.
Expand All @@ -98,6 +113,8 @@
//! println!("Hello {:1$}!", "x", 5);
//! println!("Hello {1:0$}!", 5, "x");
//! 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.
Expand Down
1 change: 1 addition & 0 deletions library/alloc/src/lib.rs
Expand Up @@ -105,6 +105,7 @@
#![feature(fmt_internals)]
#![feature(fn_traits)]
#![feature(inherent_ascii_escape)]
#![cfg_attr(bootstrap, feature(format_args_capture))]
#![feature(inplace_iteration)]
#![feature(iter_advance_by)]
#![feature(iter_zip)]
Expand Down
47 changes: 0 additions & 47 deletions src/doc/unstable-book/src/library-features/format-args-capture.md

This file was deleted.

6 changes: 0 additions & 6 deletions src/test/ui/fmt/feature-gate-format-args-capture.rs

This file was deleted.

18 changes: 0 additions & 18 deletions src/test/ui/fmt/feature-gate-format-args-capture.stderr

This file was deleted.

2 changes: 0 additions & 2 deletions src/test/ui/fmt/format-args-capture-macro-hygiene.rs
@@ -1,5 +1,3 @@
#![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`
Expand Down
4 changes: 2 additions & 2 deletions src/test/ui/fmt/format-args-capture-macro-hygiene.stderr
@@ -1,5 +1,5 @@
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}"));
| ^^^^^^^^^^^^^^^^
Expand All @@ -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)

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);
| ^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
2 changes: 0 additions & 2 deletions src/test/ui/fmt/format-args-capture-missing-variables.rs
@@ -1,5 +1,3 @@
#![feature(format_args_capture)]

fn main() {
format!("{} {foo} {} {bar} {}", 1, 2, 3);
//~^ ERROR: cannot find value `foo` in this scope
Expand Down
14 changes: 7 additions & 7 deletions src/test/ui/fmt/format-args-capture-missing-variables.stderr
@@ -1,43 +1,43 @@
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);
| ------------------- ^ named argument never used
| |
| formatting specifier missing

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);
| ^^^^^ not found 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);
| ^^^^^ not found 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}");
| ^^^^^ not found 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);
| ^^^^^^^^ not found 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}
| ^^^^^ not found 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);
| ^^^^^ not found in this scope
Expand Down
1 change: 0 additions & 1 deletion src/test/ui/fmt/format-args-capture.rs
@@ -1,5 +1,4 @@
// run-pass
#![feature(format_args_capture)]
#![feature(cfg_panic)]

fn main() {
Expand Down
10 changes: 5 additions & 5 deletions src/test/ui/fmt/ifmt-bad-arg.rs
Expand Up @@ -25,10 +25,10 @@ fn main() {
//~^ ERROR: invalid reference to positional arguments 3, 4 and 5 (there are 3 arguments)

format!("{} {foo} {} {bar} {}", 1, 2, 3);
//~^ ERROR: there is no argument named `foo`
//~^^ ERROR: there is no argument named `bar`
//~^ ERROR: cannot find value `foo` in this scope
//~^^ 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: argument never used
format!("{1}", 1, 2); //~ ERROR: argument never used
Expand All @@ -43,7 +43,7 @@ fn main() {
// bad named arguments, #35082

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

// bad syntax of the format string
Expand All @@ -60,7 +60,7 @@ fn main() {
{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
format!("first number: {}
Expand Down

0 comments on commit c26746a

Please sign in to comment.