Skip to content

Commit 4a8d9ea

Browse files
authored
Rollup merge of #73670 - davidhewitt:format-args-capture, r=varkor
Add `format_args_capture` feature This is the initial implementation PR for [RFC 2795](rust-lang/rfcs#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.
2 parents df8f551 + 93d662f commit 4a8d9ea

13 files changed

+305
-5
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# `format_args_capture`
2+
3+
The tracking issue for this feature is: [#67984]
4+
5+
[#67984]: https://github.com/rust-lang/rust/issues/67984
6+
7+
------------------------
8+
9+
Enables `format_args!` (and macros which use `format_args!` in their implementation, such
10+
as `format!`, `print!` and `panic!`) to capture variables from the surrounding scope.
11+
This avoids the need to pass named parameters when the binding in question
12+
already exists in scope.
13+
14+
```rust
15+
#![feature(format_args_capture)]
16+
17+
let (person, species, name) = ("Charlie Brown", "dog", "Snoopy");
18+
19+
// captures named argument `person`
20+
print!("Hello {person}");
21+
22+
// captures named arguments `species` and `name`
23+
format!("The {species}'s name is {name}.");
24+
```
25+
26+
This also works for formatting parameters such as width and precision:
27+
28+
```rust
29+
#![feature(format_args_capture)]
30+
31+
let precision = 2;
32+
let s = format!("{:.precision$}", 1.324223);
33+
34+
assert_eq!(&s, "1.32");
35+
```
36+
37+
A non-exhaustive list of macros which benefit from this functionality include:
38+
- `format!`
39+
- `print!` and `println!`
40+
- `eprint!` and `eprintln!`
41+
- `write!` and `writeln!`
42+
- `panic!`
43+
- `unreachable!`
44+
- `unimplemented!`
45+
- `todo!`
46+
- `assert!` and similar
47+
- macros in many thirdparty crates, such as `log`

src/librustc_builtin_macros/format.rs

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ struct Context<'a, 'b> {
107107
arg_spans: Vec<Span>,
108108
/// All the formatting arguments that have formatting flags set, in order for diagnostics.
109109
arg_with_formatting: Vec<parse::FormatSpec<'a>>,
110+
111+
/// Whether this format string came from a string literal, as opposed to a macro.
112+
is_literal: bool,
110113
}
111114

112115
/// Parses the arguments from the given list of tokens, returning the diagnostic
@@ -498,10 +501,55 @@ impl<'a, 'b> Context<'a, 'b> {
498501
self.verify_arg_type(Exact(idx), ty)
499502
}
500503
None => {
501-
let msg = format!("there is no argument named `{}`", name);
502-
let sp = *self.arg_spans.get(self.curpiece).unwrap_or(&self.fmtsp);
503-
let mut err = self.ecx.struct_span_err(sp, &msg[..]);
504-
err.emit();
504+
let capture_feature_enabled = self
505+
.ecx
506+
.ecfg
507+
.features
508+
.map_or(false, |features| features.format_args_capture);
509+
510+
// For the moment capturing variables from format strings expanded from macros is
511+
// disabled (see RFC #2795)
512+
let can_capture = capture_feature_enabled && self.is_literal;
513+
514+
if can_capture {
515+
// Treat this name as a variable to capture from the surrounding scope
516+
let idx = self.args.len();
517+
self.arg_types.push(Vec::new());
518+
self.arg_unique_types.push(Vec::new());
519+
self.args.push(
520+
self.ecx.expr_ident(self.fmtsp, Ident::new(name, self.fmtsp)),
521+
);
522+
self.names.insert(name, idx);
523+
self.verify_arg_type(Exact(idx), ty)
524+
} else {
525+
let msg = format!("there is no argument named `{}`", name);
526+
let sp = if self.is_literal {
527+
*self.arg_spans.get(self.curpiece).unwrap_or(&self.fmtsp)
528+
} else {
529+
self.fmtsp
530+
};
531+
let mut err = self.ecx.struct_span_err(sp, &msg[..]);
532+
533+
if capture_feature_enabled && !self.is_literal {
534+
err.note(&format!(
535+
"did you intend to capture a variable `{}` from \
536+
the surrounding scope?",
537+
name
538+
));
539+
err.note(
540+
"to avoid ambiguity, `format_args!` cannot capture variables \
541+
when the format string is expanded from a macro",
542+
);
543+
} else if self.ecx.parse_sess().unstable_features.is_nightly_build() {
544+
err.help(&format!(
545+
"if you intended to capture `{}` from the surrounding scope, add \
546+
`#![feature(format_args_capture)]` to the crate attributes",
547+
name
548+
));
549+
}
550+
551+
err.emit();
552+
}
505553
}
506554
}
507555
}
@@ -951,6 +999,7 @@ pub fn expand_preparsed_format_args(
951999
invalid_refs: Vec::new(),
9521000
arg_spans,
9531001
arg_with_formatting: Vec::new(),
1002+
is_literal: parser.is_literal,
9541003
};
9551004

9561005
// This needs to happen *after* the Parser has consumed all pieces to create all the spans

src/librustc_feature/active.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,9 @@ declare_features! (
567567
/// Be more precise when looking for live drops in a const context.
568568
(active, const_precise_live_drops, "1.46.0", Some(73255), None),
569569

570+
/// Allows capturing variables in scope using format_args!
571+
(active, format_args_capture, "1.46.0", Some(67984), None),
572+
570573
// -------------------------------------------------------------------------
571574
// feature-group-end: actual feature gates
572575
// -------------------------------------------------------------------------

src/librustc_parse_format/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ pub struct Parser<'a> {
190190
/// Whether the source string is comes from `println!` as opposed to `format!` or `print!`
191191
append_newline: bool,
192192
/// Whether this formatting string is a literal or it comes from a macro.
193-
is_literal: bool,
193+
pub is_literal: bool,
194194
/// Start position of the current line.
195195
cur_line_start: usize,
196196
/// Start and end byte offset of every line of the format string. Excludes

src/librustc_span/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ symbols! {
342342
forbid,
343343
format_args,
344344
format_args_nl,
345+
format_args_capture,
345346
from,
346347
From,
347348
from_desugaring,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fn main() {
2+
format!("{foo}"); //~ ERROR: there is no argument named `foo`
3+
4+
// panic! doesn't hit format_args! unless there are two or more arguments.
5+
panic!("{foo} {bar}", bar=1); //~ ERROR: there is no argument named `foo`
6+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
error: there is no argument named `foo`
2+
--> $DIR/feature-gate-format-args-capture.rs:2:14
3+
|
4+
LL | format!("{foo}");
5+
| ^^^^^
6+
|
7+
= help: if you intended to capture `foo` from the surrounding scope, add `#![feature(format_args_capture)]` to the crate attributes
8+
9+
error: there is no argument named `foo`
10+
--> $DIR/feature-gate-format-args-capture.rs:5:13
11+
|
12+
LL | panic!("{foo} {bar}", bar=1);
13+
| ^^^^^
14+
|
15+
= help: if you intended to capture `foo` from the surrounding scope, add `#![feature(format_args_capture)]` to the crate attributes
16+
17+
error: aborting due to 2 previous errors
18+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#![feature(format_args_capture)]
2+
3+
fn main() {
4+
format!(concat!("{foo}")); //~ ERROR: there is no argument named `foo`
5+
format!(concat!("{ba", "r} {}"), 1); //~ ERROR: there is no argument named `bar`
6+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: there is no argument named `foo`
2+
--> $DIR/format-args-capture-macro-hygiene.rs:4:13
3+
|
4+
LL | format!(concat!("{foo}"));
5+
| ^^^^^^^^^^^^^^^^
6+
|
7+
= note: did you intend to capture a variable `foo` from the surrounding scope?
8+
= note: to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro
9+
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
10+
11+
error: there is no argument named `bar`
12+
--> $DIR/format-args-capture-macro-hygiene.rs:5:13
13+
|
14+
LL | format!(concat!("{ba", "r} {}"), 1);
15+
| ^^^^^^^^^^^^^^^^^^^^^^^
16+
|
17+
= note: did you intend to capture a variable `bar` from the surrounding scope?
18+
= note: to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro
19+
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
20+
21+
error: aborting due to 2 previous errors
22+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#![feature(format_args_capture)]
2+
3+
fn main() {
4+
format!("{} {foo} {} {bar} {}", 1, 2, 3);
5+
//~^ ERROR: cannot find value `foo` in this scope
6+
//~^^ ERROR: cannot find value `bar` in this scope
7+
8+
format!("{foo}"); //~ ERROR: cannot find value `foo` in this scope
9+
10+
format!("{valuea} {valueb}", valuea=5, valuec=7);
11+
//~^ ERROR cannot find value `valueb` in this scope
12+
//~^^ ERROR named argument never used
13+
14+
format!(r##"
15+
16+
{foo}
17+
18+
"##);
19+
//~^^^^^ ERROR: cannot find value `foo` in this scope
20+
21+
panic!("{foo} {bar}", bar=1); //~ ERROR: cannot find value `foo` in this scope
22+
}

0 commit comments

Comments
 (0)