Skip to content

Commit af89685

Browse files
committed
Do not deduplicate captured args while expanding format_args!
1 parent c4dc70e commit af89685

File tree

8 files changed

+104
-20
lines changed

8 files changed

+104
-20
lines changed

compiler/rustc_ast/src/format.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,14 @@ pub struct FormatArguments {
7777
arguments: Vec<FormatArgument>,
7878
num_unnamed_args: usize,
7979
num_explicit_args: usize,
80-
names: FxHashMap<Symbol, usize>,
80+
explicit_names: FxHashMap<Symbol, usize>,
8181
}
8282

8383
impl FormatArguments {
8484
pub fn new() -> Self {
8585
Self {
8686
arguments: Vec::new(),
87-
names: FxHashMap::default(),
87+
explicit_names: FxHashMap::default(),
8888
num_unnamed_args: 0,
8989
num_explicit_args: 0,
9090
}
@@ -93,13 +93,16 @@ impl FormatArguments {
9393
pub fn add(&mut self, arg: FormatArgument) -> usize {
9494
let index = self.arguments.len();
9595
if let Some(name) = arg.kind.ident() {
96-
self.names.insert(name.name, index);
97-
} else if self.names.is_empty() {
96+
self.explicit_names.insert(name.name, index);
97+
} else if let FormatArgumentKind::Normal = arg.kind
98+
&& self.explicit_names.is_empty()
99+
&& self.arguments.len() == self.num_unnamed_args
100+
{
98101
// Only count the unnamed args before the first named arg.
99102
// (Any later ones are errors.)
100103
self.num_unnamed_args += 1;
101104
}
102-
if !matches!(arg.kind, FormatArgumentKind::Captured(..)) {
105+
if !matches!(arg.kind, FormatArgumentKind::Captured) {
103106
// This is an explicit argument.
104107
// Make sure that all arguments so far are explicit.
105108
assert_eq!(
@@ -113,8 +116,8 @@ impl FormatArguments {
113116
index
114117
}
115118

116-
pub fn by_name(&self, name: Symbol) -> Option<(usize, &FormatArgument)> {
117-
let i = *self.names.get(&name)?;
119+
pub fn by_explicit_name(&self, name: Symbol) -> Option<(usize, &FormatArgument)> {
120+
let i = *self.explicit_names.get(&name)?;
118121
Some((i, &self.arguments[i]))
119122
}
120123

@@ -156,15 +159,15 @@ pub enum FormatArgumentKind {
156159
/// `format_args(…, arg = 1)`
157160
Named(Ident),
158161
/// `format_args("… {arg} …")`
159-
Captured(Ident),
162+
Captured,
160163
}
161164

162165
impl FormatArgumentKind {
163166
pub fn ident(&self) -> Option<Ident> {
164167
match self {
165168
&Self::Normal => None,
166169
&Self::Named(id) => Some(id),
167-
&Self::Captured(id) => Some(id),
170+
&Self::Captured => None,
168171
}
169172
}
170173
}

compiler/rustc_ast_lowering/src/format.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ fn expand_format_args<'hir>(
462462
let placeholder_span =
463463
placeholder_span.unwrap_or(arg.expr.span).with_ctxt(macsp.ctxt());
464464
let arg_span = match arg.kind {
465-
FormatArgumentKind::Captured(_) => placeholder_span,
465+
FormatArgumentKind::Captured => placeholder_span,
466466
_ => arg.expr.span.with_ctxt(macsp.ctxt()),
467467
};
468468
let args_ident_expr = ctx.expr_ident(macsp, args_ident, args_hir_id);

compiler/rustc_builtin_macros/src/format.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ fn parse_args<'a>(ecx: &ExtCtxt<'a>, sp: Span, tts: TokenStream) -> PResult<'a,
121121
p.bump();
122122
p.expect(exp!(Eq))?;
123123
let expr = p.parse_expr()?;
124-
if let Some((_, prev)) = args.by_name(ident.name) {
124+
if let Some((_, prev)) = args.by_explicit_name(ident.name) {
125125
ecx.dcx().emit_err(errors::FormatDuplicateArg {
126126
span: ident.span,
127127
prev: prev.kind.ident().unwrap().span,
@@ -374,12 +374,11 @@ fn make_format_args(
374374
}
375375
Name(name, span) => {
376376
let name = Symbol::intern(name);
377-
if let Some((index, _)) = args.by_name(name) {
377+
if let Some((index, _)) = args.by_explicit_name(name) {
378378
// Name found in `args`, so we resolve it to its index.
379-
if index < args.explicit_args().len() {
380-
// Mark it as used, if it was an explicit argument.
381-
used[index] = true;
382-
}
379+
assert!(index < args.explicit_args().len());
380+
// Mark it as used, as this is an explicit argument.
381+
used[index] = true;
383382
Ok(index)
384383
} else {
385384
// Name not found in `args`, so we add it as an implicitly captured argument.
@@ -394,7 +393,7 @@ fn make_format_args(
394393
unnamed_arg_after_named_arg = true;
395394
DummyResult::raw_expr(span, Some(guar))
396395
};
397-
Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr }))
396+
Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured, expr }))
398397
}
399398
}
400399
};

src/tools/clippy/clippy_lints/src/format_args.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
447447
let index = pos.index.unwrap();
448448
let arg = &self.format_args.arguments.all_args()[index];
449449

450-
if !matches!(arg.kind, FormatArgumentKind::Captured(_))
450+
if !matches!(arg.kind, FormatArgumentKind::Captured)
451451
&& let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind
452452
&& let [segment] = path.segments.as_slice()
453453
&& segment.args.is_none()
@@ -467,7 +467,7 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
467467
// * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)`
468468
// * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already
469469
pos.kind != FormatArgPositionKind::Number
470-
&& (!self.ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_)))
470+
&& (!self.ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured))
471471
}
472472
}
473473

src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ impl<'tcx> Visitor<'tcx> for UnitVariableCollector<'_, 'tcx> {
202202
{
203203
if let Some(macro_call) = self.macro_call
204204
&& macro_call.arguments.all_args().iter().any(|arg| {
205-
matches!(arg.kind, FormatArgumentKind::Captured(_)) && find_format_arg_expr(ex, arg).is_some()
205+
matches!(arg.kind, FormatArgumentKind::Captured) && find_format_arg_expr(ex, arg).is_some()
206206
})
207207
{
208208
self.spans.push(VariableUsage::FormatCapture);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#[attr = MacroUse {arguments: UseAll}]
2+
extern crate std;
3+
#[prelude_import]
4+
use ::std::prelude::rust_2015::*;
5+
//@ pretty-compare-only
6+
//@ pretty-mode:hir
7+
//@ pp-exact:hir-format-args-duplicated-captures.pp
8+
9+
const X: i32 = 42;
10+
11+
fn main() {
12+
let _ =
13+
{
14+
super let args = (&X, &X);
15+
super let args =
16+
[format_argument::new_display(args.0),
17+
format_argument::new_display(args.1)];
18+
unsafe { format_arguments::new(b"\xc0\x01 \xc0\x00", &args) }
19+
};
20+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//@ pretty-compare-only
2+
//@ pretty-mode:hir
3+
//@ pp-exact:hir-format-args-duplicated-captures.pp
4+
5+
const X: i32 = 42;
6+
7+
fn main() {
8+
let _ = format_args!("{X} {X}");
9+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//! A regression test for https://github.com/rust-lang/rust/issues/145739.
2+
//! `format_args!` should not deduplicate implicitly captured arguments.
3+
//! They must be evaluated once per occurrence, whereas explicitly
4+
//! provided arguments are evaluated only once, regardless of how many
5+
//! times they appear in the format string.
6+
//@ run-pass
7+
8+
use std::sync::atomic::{AtomicUsize, Ordering};
9+
10+
static DROP_COUNTER: AtomicUsize = AtomicUsize::new(0);
11+
12+
#[derive(Debug)]
13+
struct Foo;
14+
15+
impl Drop for Foo {
16+
fn drop(&mut self) {
17+
DROP_COUNTER.fetch_add(1, Ordering::Relaxed);
18+
}
19+
}
20+
21+
fn main() {
22+
assert_eq!(DROP_COUNTER.load(Ordering::Relaxed), 0);
23+
24+
{
25+
let _x = format_args!("{Foo:?}");
26+
}
27+
assert_eq!(DROP_COUNTER.load(Ordering::Relaxed), 1);
28+
29+
{
30+
let _x = format_args!("{Foo:?}{Foo:?}");
31+
}
32+
// Increased by 2, as `Foo` is constructed for each captured `Foo`.
33+
assert_eq!(DROP_COUNTER.load(Ordering::Relaxed), 3);
34+
35+
{
36+
let _x = format_args!("{:?}{0:?}", Foo);
37+
}
38+
// Increased by 1, as `foo` is constructed just once as an explicit argument.
39+
assert_eq!(DROP_COUNTER.load(Ordering::Relaxed), 4);
40+
41+
{
42+
let _x = format_args!("{foo:?}{foo:?}{bar:?}{Foo:?}{Foo:?}", foo = Foo, bar = Foo);
43+
}
44+
// Increased by 4, as `foo` is constructed twice for captured `Foo`, and once for each `foo` and
45+
// `bar`.
46+
assert_eq!(DROP_COUNTER.load(Ordering::Relaxed), 8);
47+
48+
{
49+
let _x = format_args!("{Foo:?}{Foo:?}", Foo = Foo);
50+
}
51+
// Increased by 1, as `Foo` is shadowed by an explicit argument.
52+
assert_eq!(DROP_COUNTER.load(Ordering::Relaxed), 9);
53+
}

0 commit comments

Comments
 (0)