Merge pull request #20056 from ShoyuVanilla/fmt-args-new
Some checks failed
metrics / build_metrics (push) Has been cancelled
rustdoc / rustdoc (push) Has been cancelled
metrics / generate_final_metrics (push) Has been cancelled
metrics / other_metrics (diesel-1.4.8) (push) Has been cancelled
metrics / other_metrics (hyper-0.14.18) (push) Has been cancelled
metrics / other_metrics (ripgrep-13.0.0) (push) Has been cancelled
metrics / other_metrics (self) (push) Has been cancelled
metrics / other_metrics (webrender-2022) (push) Has been cancelled
release / dist (x86_64-unknown-linux-gnu) (push) Has been cancelled
release / dist (aarch64-pc-windows-msvc) (push) Has been cancelled
release / dist (x86_64-pc-windows-msvc) (push) Has been cancelled
release / dist (i686-pc-windows-msvc) (push) Has been cancelled
autopublish / publish (push) Has been cancelled
release / dist (aarch64-apple-darwin) (push) Has been cancelled
release / dist (x86_64-apple-darwin) (push) Has been cancelled
release / dist (aarch64-unknown-linux-gnu) (push) Has been cancelled
release / dist (arm-unknown-linux-gnueabihf) (push) Has been cancelled
release / dist (x86_64-unknown-linux-musl) (push) Has been cancelled
release / publish (push) Has been cancelled

Minic rustc's new `format_args!` expansion
This commit is contained in:
Chayim Refael Friedman 2025-06-22 04:35:11 +00:00 committed by GitHub
commit 0100bc7373
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 412 additions and 46 deletions

View file

@ -2815,6 +2815,51 @@ impl ExprCollector<'_> {
mutability: Mutability::Shared, mutability: Mutability::Shared,
}) })
}; };
// Assume that rustc version >= 1.89.0 iff lang item `format_arguments` exists
// but `format_unsafe_arg` does not
let fmt_args =
|| crate::lang_item::lang_item(self.db, self.module.krate(), LangItem::FormatArguments);
let fmt_unsafe_arg =
|| crate::lang_item::lang_item(self.db, self.module.krate(), LangItem::FormatUnsafeArg);
let use_format_args_since_1_89_0 = fmt_args().is_some() && fmt_unsafe_arg().is_none();
let idx = if use_format_args_since_1_89_0 {
self.collect_format_args_impl(
syntax_ptr,
fmt,
hygiene,
argmap,
lit_pieces,
format_options,
)
} else {
self.collect_format_args_before_1_89_0_impl(
syntax_ptr,
fmt,
argmap,
lit_pieces,
format_options,
)
};
self.source_map
.template_map
.get_or_insert_with(Default::default)
.format_args_to_captures
.insert(idx, (hygiene, mappings));
idx
}
/// `format_args!` expansion implementation for rustc versions < `1.89.0`
fn collect_format_args_before_1_89_0_impl(
&mut self,
syntax_ptr: AstPtr<ast::Expr>,
fmt: FormatArgs,
argmap: FxIndexSet<(usize, ArgumentType)>,
lit_pieces: ExprId,
format_options: ExprId,
) -> ExprId {
let arguments = &*fmt.arguments.arguments; let arguments = &*fmt.arguments.arguments;
let args = if arguments.is_empty() { let args = if arguments.is_empty() {
@ -2902,19 +2947,181 @@ impl ExprCollector<'_> {
}); });
} }
let idx = self.alloc_expr( self.alloc_expr(
Expr::Call { Expr::Call {
callee: new_v1_formatted, callee: new_v1_formatted,
args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]), args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]),
}, },
syntax_ptr, syntax_ptr,
); )
self.source_map }
.template_map
.get_or_insert_with(Default::default) /// `format_args!` expansion implementation for rustc versions >= `1.89.0`,
.format_args_to_captures /// especially since [this PR](https://github.com/rust-lang/rust/pull/140748)
.insert(idx, (hygiene, mappings)); fn collect_format_args_impl(
idx &mut self,
syntax_ptr: AstPtr<ast::Expr>,
fmt: FormatArgs,
hygiene: HygieneId,
argmap: FxIndexSet<(usize, ArgumentType)>,
lit_pieces: ExprId,
format_options: ExprId,
) -> ExprId {
let arguments = &*fmt.arguments.arguments;
let (let_stmts, args) = if arguments.is_empty() {
(
// Generate:
// []
vec![],
self.alloc_expr_desugared(Expr::Array(Array::ElementList {
elements: Box::default(),
})),
)
} else if argmap.len() == 1 && arguments.len() == 1 {
// Only one argument, so we don't need to make the `args` tuple.
//
// Generate:
// super let args = [<core::fmt::Arguments>::new_display(&arg)];
let args = argmap
.iter()
.map(|&(arg_index, ty)| {
let ref_arg = self.alloc_expr_desugared(Expr::Ref {
expr: arguments[arg_index].expr,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
});
self.make_argument(ref_arg, ty)
})
.collect();
let args =
self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));
let args_name = Name::new_symbol_root(sym::args);
let args_binding =
self.alloc_binding(args_name.clone(), BindingAnnotation::Unannotated, hygiene);
let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
self.add_definition_to_binding(args_binding, args_pat);
// TODO: We don't have `super let` yet.
let let_stmt = Statement::Let {
pat: args_pat,
type_ref: None,
initializer: Some(args),
else_branch: None,
};
(vec![let_stmt], self.alloc_expr_desugared(Expr::Path(Path::from(args_name))))
} else {
// Generate:
// super let args = (&arg0, &arg1, &...);
let args_name = Name::new_symbol_root(sym::args);
let args_binding =
self.alloc_binding(args_name.clone(), BindingAnnotation::Unannotated, hygiene);
let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
self.add_definition_to_binding(args_binding, args_pat);
let elements = arguments
.iter()
.map(|arg| {
self.alloc_expr_desugared(Expr::Ref {
expr: arg.expr,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
})
})
.collect();
let args_tuple = self.alloc_expr_desugared(Expr::Tuple { exprs: elements });
// TODO: We don't have `super let` yet
let let_stmt1 = Statement::Let {
pat: args_pat,
type_ref: None,
initializer: Some(args_tuple),
else_branch: None,
};
// Generate:
// super let args = [
// <core::fmt::Argument>::new_display(args.0),
// <core::fmt::Argument>::new_lower_hex(args.1),
// <core::fmt::Argument>::new_debug(args.0),
// …
// ];
let args = argmap
.iter()
.map(|&(arg_index, ty)| {
let args_ident_expr =
self.alloc_expr_desugared(Expr::Path(args_name.clone().into()));
let arg = self.alloc_expr_desugared(Expr::Field {
expr: args_ident_expr,
name: Name::new_tuple_field(arg_index),
});
self.make_argument(arg, ty)
})
.collect();
let array =
self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));
let args_binding =
self.alloc_binding(args_name.clone(), BindingAnnotation::Unannotated, hygiene);
let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });
self.add_definition_to_binding(args_binding, args_pat);
let let_stmt2 = Statement::Let {
pat: args_pat,
type_ref: None,
initializer: Some(array),
else_branch: None,
};
(vec![let_stmt1, let_stmt2], self.alloc_expr_desugared(Expr::Path(args_name.into())))
};
// Generate:
// &args
let args = self.alloc_expr_desugared(Expr::Ref {
expr: args,
rawness: Rawness::Ref,
mutability: Mutability::Shared,
});
let call_block = {
// Generate:
// unsafe {
// <core::fmt::Arguments>::new_v1_formatted(
// lit_pieces,
// args,
// format_options,
// )
// }
let new_v1_formatted = LangItem::FormatArguments.ty_rel_path(
self.db,
self.module.krate(),
Name::new_symbol_root(sym::new_v1_formatted),
);
let new_v1_formatted =
self.alloc_expr_desugared(new_v1_formatted.map_or(Expr::Missing, Expr::Path));
let args = [lit_pieces, args, format_options];
let call = self
.alloc_expr_desugared(Expr::Call { callee: new_v1_formatted, args: args.into() });
Expr::Unsafe { id: None, statements: Box::default(), tail: Some(call) }
};
if !let_stmts.is_empty() {
// Generate:
// {
// super let …
// super let …
// <core::fmt::Arguments>::new_…(…)
// }
let call = self.alloc_expr_desugared(call_block);
self.alloc_expr(
Expr::Block {
id: None,
statements: let_stmts.into(),
tail: Some(call),
label: None,
},
syntax_ptr,
)
} else {
self.alloc_expr(call_block, syntax_ptr)
}
} }
/// Generate a hir expression for a format_args placeholder specification. /// Generate a hir expression for a format_args placeholder specification.

View file

@ -178,14 +178,14 @@ fn main() {
} }
#[test] #[test]
fn desugar_builtin_format_args() { fn desugar_builtin_format_args_before_1_89_0() {
let (db, body, def) = lower( let (db, body, def) = lower(
r#" r#"
//- minicore: fmt //- minicore: fmt_before_1_89_0
fn main() { fn main() {
let are = "are"; let are = "are";
let count = 10; let count = 10;
builtin#format_args("\u{1b}hello {count:02} {} friends, we {are:?} {0}{last}", "fancy", last = "!"); builtin#format_args("\u{1b}hello {count:02} {} friends, we {are:?} {0}{last}", "fancy", orphan = (), last = "!");
} }
"#, "#,
); );
@ -249,14 +249,100 @@ fn main() {
builtin#lang(Count::Implied), builtin#lang(Count::Implied),
), ),
], ],
unsafe { {
builtin#lang(UnsafeArg::new)() ();
unsafe {
builtin#lang(UnsafeArg::new)()
}
}, },
); );
}"#]] }"#]]
.assert_eq(&body.pretty_print(&db, def, Edition::CURRENT)) .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT))
} }
#[test]
fn desugar_builtin_format_args() {
let (db, body, def) = lower(
r#"
//- minicore: fmt
fn main() {
let are = "are";
let count = 10;
builtin#format_args("\u{1b}hello {count:02} {} friends, we {are:?} {0}{last}", "fancy", orphan = (), last = "!");
}
"#,
);
expect![[r#"
fn main() {
let are = "are";
let count = 10;
{
let args = (&"fancy", &(), &"!", &count, &are, );
let args = [
builtin#lang(Argument::new_display)(
args.3,
), builtin#lang(Argument::new_display)(
args.0,
), builtin#lang(Argument::new_debug)(
args.4,
), builtin#lang(Argument::new_display)(
args.2,
),
];
unsafe {
builtin#lang(Arguments::new_v1_formatted)(
&[
"\u{1b}hello ", " ", " friends, we ", " ", "",
],
&args,
&[
builtin#lang(Placeholder::new)(
0usize,
' ',
builtin#lang(Alignment::Unknown),
8u32,
builtin#lang(Count::Implied),
builtin#lang(Count::Is)(
2,
),
), builtin#lang(Placeholder::new)(
1usize,
' ',
builtin#lang(Alignment::Unknown),
0u32,
builtin#lang(Count::Implied),
builtin#lang(Count::Implied),
), builtin#lang(Placeholder::new)(
2usize,
' ',
builtin#lang(Alignment::Unknown),
0u32,
builtin#lang(Count::Implied),
builtin#lang(Count::Implied),
), builtin#lang(Placeholder::new)(
1usize,
' ',
builtin#lang(Alignment::Unknown),
0u32,
builtin#lang(Count::Implied),
builtin#lang(Count::Implied),
), builtin#lang(Placeholder::new)(
3usize,
' ',
builtin#lang(Alignment::Unknown),
0u32,
builtin#lang(Count::Implied),
builtin#lang(Count::Implied),
),
],
)
}
};
}"#]]
.assert_eq(&body.pretty_print(&db, def, Edition::CURRENT))
}
#[test] #[test]
fn test_macro_hygiene() { fn test_macro_hygiene() {
let (db, body, def) = lower( let (db, body, def) = lower(
@ -295,29 +381,31 @@ impl SsrError {
expect![[r#" expect![[r#"
fn main() { fn main() {
_ = ra_test_fixture::error::SsrError::new( _ = ra_test_fixture::error::SsrError::new(
builtin#lang(Arguments::new_v1_formatted)( {
&[ let args = [
"Failed to resolve path `", "`",
],
&[
builtin#lang(Argument::new_display)( builtin#lang(Argument::new_display)(
&node.text(), &node.text(),
), ),
], ];
&[
builtin#lang(Placeholder::new)(
0usize,
' ',
builtin#lang(Alignment::Unknown),
0u32,
builtin#lang(Count::Implied),
builtin#lang(Count::Implied),
),
],
unsafe { unsafe {
builtin#lang(UnsafeArg::new)() builtin#lang(Arguments::new_v1_formatted)(
}, &[
), "Failed to resolve path `", "`",
],
&args,
&[
builtin#lang(Placeholder::new)(
0usize,
' ',
builtin#lang(Alignment::Unknown),
0u32,
builtin#lang(Count::Implied),
builtin#lang(Count::Implied),
),
],
)
}
},
); );
}"#]] }"#]]
.assert_eq(&body.pretty_print(&db, def, Edition::CURRENT)) .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT))
@ -327,7 +415,7 @@ impl SsrError {
fn regression_10300() { fn regression_10300() {
let (db, body, def) = lower( let (db, body, def) = lower(
r#" r#"
//- minicore: concat, panic //- minicore: concat, panic, fmt_before_1_89_0
mod private { mod private {
pub use core::concat; pub use core::concat;
} }

View file

@ -100,7 +100,9 @@ fn f() { let a: u128 = 1; let b: u128 = todo$0!() }"#,
fn test_complete_todo_with_msg() { fn test_complete_todo_with_msg() {
check_assist( check_assist(
term_search, term_search,
r#"//- minicore: todo, unimplemented // FIXME: Since we are lacking of `super let`, term search fails due to borrowck failure.
// Should implement super let and remove `fmt_before_1_89_0`
r#"//- minicore: todo, unimplemented, fmt_before_1_89_0
fn f() { let a: u128 = 1; let b: u128 = todo$0!("asd") }"#, fn f() { let a: u128 = 1; let b: u128 = todo$0!("asd") }"#,
r#"fn f() { let a: u128 = 1; let b: u128 = a }"#, r#"fn f() { let a: u128 = 1; let b: u128 = a }"#,
) )
@ -110,7 +112,9 @@ fn f() { let a: u128 = 1; let b: u128 = todo$0!("asd") }"#,
fn test_complete_unimplemented_with_msg() { fn test_complete_unimplemented_with_msg() {
check_assist( check_assist(
term_search, term_search,
r#"//- minicore: todo, unimplemented // FIXME: Since we are lacking of `super let`, term search fails due to borrowck failure.
// Should implement super let and remove `fmt_before_1_89_0`
r#"//- minicore: todo, unimplemented, fmt_before_1_89_0
fn f() { let a: u128 = 1; let b: u128 = todo$0!("asd") }"#, fn f() { let a: u128 = 1; let b: u128 = todo$0!("asd") }"#,
r#"fn f() { let a: u128 = 1; let b: u128 = a }"#, r#"fn f() { let a: u128 = 1; let b: u128 = a }"#,
) )
@ -120,7 +124,9 @@ fn f() { let a: u128 = 1; let b: u128 = todo$0!("asd") }"#,
fn test_complete_unimplemented() { fn test_complete_unimplemented() {
check_assist( check_assist(
term_search, term_search,
r#"//- minicore: todo, unimplemented // FIXME: Since we are lacking of `super let`, term search fails due to borrowck failure.
// Should implement super let and remove `fmt_before_1_89_0`
r#"//- minicore: todo, unimplemented, fmt_before_1_89_0
fn f() { let a: u128 = 1; let b: u128 = todo$0!("asd") }"#, fn f() { let a: u128 = 1; let b: u128 = todo$0!("asd") }"#,
r#"fn f() { let a: u128 = 1; let b: u128 = a }"#, r#"fn f() { let a: u128 = 1; let b: u128 = a }"#,
) )

View file

@ -1474,20 +1474,18 @@ fn main() {
} }
"#, "#,
expect![[r#" expect![[r#"
me foo() fn(&self)
sn box Box::new(expr) sn box Box::new(expr)
sn call function(expr) sn call function(expr)
sn const const {} sn const const {}
sn dbg dbg!(expr) sn dbg dbg!(expr)
sn dbgr dbg!(&expr) sn dbgr dbg!(&expr)
sn deref *expr sn deref *expr
sn if if expr {}
sn match match expr {} sn match match expr {}
sn not !expr
sn ref &expr sn ref &expr
sn refm &mut expr sn refm &mut expr
sn return return expr sn return return expr
sn unsafe unsafe {} sn unsafe unsafe {}
sn while while expr {}
"#]], "#]],
); );
} }

View file

@ -628,6 +628,17 @@ fn main() {
#[test] #[test]
fn orphan_unsafe_format_args() { fn orphan_unsafe_format_args() {
// Checks that we don't place orphan arguments for formatting under an unsafe block. // Checks that we don't place orphan arguments for formatting under an unsafe block.
check_diagnostics(
r#"
//- minicore: fmt_before_1_89_0
fn foo() {
let p = 0xDEADBEEF as *const i32;
format_args!("", *p);
// ^^ error: dereference of raw pointer is unsafe and requires an unsafe function or block
}
"#,
);
check_diagnostics( check_diagnostics(
r#" r#"
//- minicore: fmt //- minicore: fmt
@ -958,4 +969,18 @@ impl FooTrait for S2 {
"#, "#,
); );
} }
#[test]
fn no_false_positive_on_format_args_since_1_89_0() {
check_diagnostics(
r#"
//- minicore: fmt
fn test() {
let foo = 10;
let bar = true;
let _x = format_args!("{} {0} {} {last}", foo, bar, last = "!");
}
"#,
);
}
} }

View file

@ -496,6 +496,7 @@ define_symbols! {
vectorcall, vectorcall,
wasm, wasm,
win64, win64,
args,
array, array,
boxed_slice, boxed_slice,
completions, completions,

View file

@ -412,22 +412,36 @@ impl MiniCore {
} }
let mut active_regions = Vec::new(); let mut active_regions = Vec::new();
let mut inactive_regions = Vec::new();
let mut seen_regions = Vec::new(); let mut seen_regions = Vec::new();
for line in lines { for line in lines {
let trimmed = line.trim(); let trimmed = line.trim();
if let Some(region) = trimmed.strip_prefix("// region:") { if let Some(region) = trimmed.strip_prefix("// region:") {
active_regions.push(region); if let Some(region) = region.strip_prefix('!') {
continue; inactive_regions.push(region);
continue;
} else {
active_regions.push(region);
continue;
}
} }
if let Some(region) = trimmed.strip_prefix("// endregion:") { if let Some(region) = trimmed.strip_prefix("// endregion:") {
let prev = active_regions.pop().unwrap(); let (prev, region) = if let Some(region) = region.strip_prefix('!') {
(inactive_regions.pop().unwrap(), region)
} else {
(active_regions.pop().unwrap(), region)
};
assert_eq!(prev, region, "unbalanced region pairs"); assert_eq!(prev, region, "unbalanced region pairs");
continue; continue;
} }
let mut line_region = false; let mut active_line_region = false;
if let Some(idx) = trimmed.find("// :") { let mut inactive_line_region = false;
line_region = true; if let Some(idx) = trimmed.find("// :!") {
inactive_line_region = true;
inactive_regions.push(&trimmed[idx + "// :!".len()..]);
} else if let Some(idx) = trimmed.find("// :") {
active_line_region = true;
active_regions.push(&trimmed[idx + "// :".len()..]); active_regions.push(&trimmed[idx + "// :".len()..]);
} }
@ -438,18 +452,30 @@ impl MiniCore {
seen_regions.push(region); seen_regions.push(region);
keep &= self.has_flag(region); keep &= self.has_flag(region);
} }
for &region in &inactive_regions {
assert!(!region.starts_with(' '), "region marker starts with a space: {region:?}");
self.assert_valid_flag(region);
seen_regions.push(region);
keep &= !self.has_flag(region);
}
if keep { if keep {
buf.push_str(line); buf.push_str(line);
} }
if line_region { if active_line_region {
active_regions.pop().unwrap(); active_regions.pop().unwrap();
} }
if inactive_line_region {
inactive_regions.pop().unwrap();
}
} }
if !active_regions.is_empty() { if !active_regions.is_empty() {
panic!("unclosed regions: {active_regions:?} Add an `endregion` comment"); panic!("unclosed regions: {active_regions:?} Add an `endregion` comment");
} }
if !inactive_regions.is_empty() {
panic!("unclosed regions: {inactive_regions:?} Add an `endregion` comment");
}
for flag in &self.valid_flags { for flag in &self.valid_flags {
if !seen_regions.iter().any(|it| it == flag) { if !seen_regions.iter().any(|it| it == flag) {

View file

@ -31,6 +31,7 @@
//! eq: sized //! eq: sized
//! error: fmt //! error: fmt
//! fmt: option, result, transmute, coerce_unsized, copy, clone, derive //! fmt: option, result, transmute, coerce_unsized, copy, clone, derive
//! fmt_before_1_89_0: fmt
//! fn: tuple //! fn: tuple
//! from: sized, result //! from: sized, result
//! future: pin //! future: pin
@ -1175,6 +1176,7 @@ pub mod fmt {
} }
} }
// region:fmt_before_1_89_0
#[lang = "format_unsafe_arg"] #[lang = "format_unsafe_arg"]
pub struct UnsafeArg { pub struct UnsafeArg {
_private: (), _private: (),
@ -1185,6 +1187,7 @@ pub mod fmt {
UnsafeArg { _private: () } UnsafeArg { _private: () }
} }
} }
// endregion:fmt_before_1_89_0
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -1204,6 +1207,7 @@ pub mod fmt {
Arguments { pieces, fmt: None, args: &[] } Arguments { pieces, fmt: None, args: &[] }
} }
// region:fmt_before_1_89_0
pub fn new_v1_formatted( pub fn new_v1_formatted(
pieces: &'a [&'static str], pieces: &'a [&'static str],
args: &'a [rt::Argument<'a>], args: &'a [rt::Argument<'a>],
@ -1212,6 +1216,17 @@ pub mod fmt {
) -> Arguments<'a> { ) -> Arguments<'a> {
Arguments { pieces, fmt: Some(fmt), args } Arguments { pieces, fmt: Some(fmt), args }
} }
// endregion:fmt_before_1_89_0
// region:!fmt_before_1_89_0
pub unsafe fn new_v1_formatted(
pieces: &'a [&'static str],
args: &'a [rt::Argument<'a>],
fmt: &'a [rt::Placeholder],
) -> Arguments<'a> {
Arguments { pieces, fmt: Some(fmt), args }
}
// endregion:!fmt_before_1_89_0
pub const fn as_str(&self) -> Option<&'static str> { pub const fn as_str(&self) -> Option<&'static str> {
match (self.pieces, self.args) { match (self.pieces, self.args) {