From 43105c10d22cb1943d73b38423336e9441c47f6d Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 11 Jan 2023 21:35:46 +0100 Subject: [PATCH 01/20] Add some helper functions to LoweringContext. --- compiler/rustc_ast_lowering/src/expr.rs | 73 ++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 14f082be9ba8c..e21082894bde2 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -16,7 +16,7 @@ use rustc_hir::def::Res; use rustc_hir::definitions::DefPathData; use rustc_session::errors::report_lit_error; use rustc_span::source_map::{respan, DesugaringKind, Span, Spanned}; -use rustc_span::symbol::{sym, Ident}; +use rustc_span::symbol::{sym, Ident, Symbol}; use rustc_span::DUMMY_SP; use thin_vec::thin_vec; @@ -1757,6 +1757,43 @@ impl<'hir> LoweringContext<'_, 'hir> { self.arena.alloc(self.expr(sp, hir::ExprKind::Tup(&[]))) } + pub(super) fn expr_usize(&mut self, sp: Span, value: usize) -> hir::Expr<'hir> { + self.expr( + sp, + hir::ExprKind::Lit(hir::Lit { + span: sp, + node: ast::LitKind::Int( + value as u128, + ast::LitIntType::Unsigned(ast::UintTy::Usize), + ), + }), + ) + } + + pub(super) fn expr_u32(&mut self, sp: Span, value: u32) -> hir::Expr<'hir> { + self.expr( + sp, + hir::ExprKind::Lit(hir::Lit { + span: sp, + node: ast::LitKind::Int(value.into(), ast::LitIntType::Unsigned(ast::UintTy::U32)), + }), + ) + } + + pub(super) fn expr_char(&mut self, sp: Span, value: char) -> hir::Expr<'hir> { + self.expr(sp, hir::ExprKind::Lit(hir::Lit { span: sp, node: ast::LitKind::Char(value) })) + } + + pub(super) fn expr_str(&mut self, sp: Span, value: Symbol) -> hir::Expr<'hir> { + self.expr( + sp, + hir::ExprKind::Lit(hir::Lit { + span: sp, + node: ast::LitKind::Str(value, ast::StrStyle::Cooked), + }), + ) + } + fn expr_call_mut( &mut self, span: Span, @@ -1808,6 +1845,27 @@ impl<'hir> LoweringContext<'_, 'hir> { ) } + /// `::name` + pub(super) fn expr_lang_item_type_relative( + &mut self, + span: Span, + lang_item: hir::LangItem, + name: Symbol, + ) -> hir::Expr<'hir> { + let path = hir::ExprKind::Path(hir::QPath::TypeRelative( + self.arena.alloc(self.ty( + span, + hir::TyKind::Path(hir::QPath::LangItem(lang_item, self.lower_span(span), None)), + )), + self.arena.alloc(hir::PathSegment::new( + Ident::new(name, span), + self.next_id(), + Res::Err, + )), + )); + self.expr(span, path) + } + pub(super) fn expr_ident( &mut self, sp: Span, @@ -1866,6 +1924,19 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr(b.span, hir::ExprKind::Block(b, None)) } + pub(super) fn expr_array_ref( + &mut self, + span: Span, + elements: &'hir [hir::Expr<'hir>], + ) -> hir::Expr<'hir> { + let addrof = hir::ExprKind::AddrOf( + hir::BorrowKind::Ref, + hir::Mutability::Not, + self.arena.alloc(self.expr(span, hir::ExprKind::Array(elements))), + ); + self.expr(span, addrof) + } + pub(super) fn expr(&mut self, span: Span, kind: hir::ExprKind<'hir>) -> hir::Expr<'hir> { let hir_id = self.next_id(); hir::Expr { hir_id, kind, span: self.lower_span(span) } From 3c4517659213c67e3ca6f3ef50ef9065385dca54 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 11 Jan 2023 21:41:34 +0100 Subject: [PATCH 02/20] Expose some LoweringContext methods. --- compiler/rustc_ast_lowering/src/expr.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index e21082894bde2..51f290bc587e9 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -1729,7 +1729,7 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr(span, hir::ExprKind::DropTemps(expr)) } - fn expr_match( + pub(super) fn expr_match( &mut self, span: Span, arg: &'hir hir::Expr<'hir>, @@ -1794,7 +1794,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ) } - fn expr_call_mut( + pub(super) fn expr_call_mut( &mut self, span: Span, e: &'hir hir::Expr<'hir>, @@ -1803,7 +1803,7 @@ impl<'hir> LoweringContext<'_, 'hir> { self.expr(span, hir::ExprKind::Call(e, args)) } - fn expr_call( + pub(super) fn expr_call( &mut self, span: Span, e: &'hir hir::Expr<'hir>, @@ -1942,7 +1942,7 @@ impl<'hir> LoweringContext<'_, 'hir> { hir::Expr { hir_id, kind, span: self.lower_span(span) } } - fn expr_field( + pub(super) fn expr_field( &mut self, ident: Ident, expr: &'hir hir::Expr<'hir>, @@ -1957,7 +1957,11 @@ impl<'hir> LoweringContext<'_, 'hir> { } } - fn arm(&mut self, pat: &'hir hir::Pat<'hir>, expr: &'hir hir::Expr<'hir>) -> hir::Arm<'hir> { + pub(super) fn arm( + &mut self, + pat: &'hir hir::Pat<'hir>, + expr: &'hir hir::Expr<'hir>, + ) -> hir::Arm<'hir> { hir::Arm { hir_id: self.next_id(), pat, From bebf9fe0635ad58bff020b754cd0f24c35291d51 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 11 Jan 2023 21:36:46 +0100 Subject: [PATCH 03/20] Turn format arguments types into lang items. --- compiler/rustc_hir/src/lang_items.rs | 8 ++++++++ compiler/rustc_span/src/symbol.rs | 6 ++++++ library/core/src/fmt/mod.rs | 4 +++- library/core/src/fmt/rt/v1.rs | 4 ++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_hir/src/lang_items.rs b/compiler/rustc_hir/src/lang_items.rs index 3474fab34f00b..cb62317909c4c 100644 --- a/compiler/rustc_hir/src/lang_items.rs +++ b/compiler/rustc_hir/src/lang_items.rs @@ -244,6 +244,14 @@ language_item_table! { /// libstd panic entry point. Necessary for const eval to be able to catch it BeginPanic, sym::begin_panic, begin_panic_fn, Target::Fn, GenericRequirement::None; + // Lang items needed for `format_args!()`. + FormatAlignment, sym::format_alignment, format_alignment, Target::Enum, GenericRequirement::None; + FormatArgument, sym::format_argument, format_argument, Target::Struct, GenericRequirement::None; + FormatArguments, sym::format_arguments, format_arguments, Target::Struct, GenericRequirement::None; + FormatCount, sym::format_count, format_count, Target::Enum, GenericRequirement::None; + FormatPlaceholder, sym::format_placeholder, format_placeholder, Target::Struct, GenericRequirement::None; + FormatUnsafeArg, sym::format_unsafe_arg, format_unsafe_arg, Target::Struct, GenericRequirement::None; + ExchangeMalloc, sym::exchange_malloc, exchange_malloc_fn, Target::Fn, GenericRequirement::None; BoxFree, sym::box_free, box_free_fn, Target::Fn, GenericRequirement::Minimum(1); DropInPlace, sym::drop_in_place, drop_in_place_fn, Target::Fn, GenericRequirement::Minimum(1); diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index fbb12701d96ab..f8cd5ba03dbe0 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -721,11 +721,17 @@ symbols! { forbid, forget, format, + format_alignment, format_args, format_args_capture, format_args_macro, format_args_nl, + format_argument, + format_arguments, + format_count, format_macro, + format_placeholder, + format_unsafe_arg, freeze, freg, frem_fast, diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index 51e6a76cea848..ae2f7f049b64e 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -262,6 +262,7 @@ extern "C" { /// family of functions. It contains a function to format the given value. At /// compile time it is ensured that the function and the value have the correct /// types, and then this struct is used to canonicalize arguments to one type. +#[cfg_attr(not(bootstrap), lang = "format_argument")] #[derive(Copy, Clone)] #[allow(missing_debug_implementations)] #[unstable(feature = "fmt_internals", reason = "internal to format_args!", issue = "none")] @@ -274,6 +275,7 @@ pub struct ArgumentV1<'a> { /// This struct represents the unsafety of constructing an `Arguments`. /// It exists, rather than an unsafe function, in order to simplify the expansion /// of `format_args!(..)` and reduce the scope of the `unsafe` block. +#[cfg_attr(not(bootstrap), lang = "format_unsafe_arg")] #[allow(missing_debug_implementations)] #[doc(hidden)] #[unstable(feature = "fmt_internals", reason = "internal to format_args!", issue = "none")] @@ -468,8 +470,8 @@ impl<'a> Arguments<'a> { /// ``` /// /// [`format()`]: ../../std/fmt/fn.format.html +#[cfg_attr(not(bootstrap), lang = "format_arguments")] #[stable(feature = "rust1", since = "1.0.0")] -#[cfg_attr(not(test), rustc_diagnostic_item = "Arguments")] #[derive(Copy, Clone)] pub struct Arguments<'a> { // Format string pieces to print. diff --git a/library/core/src/fmt/rt/v1.rs b/library/core/src/fmt/rt/v1.rs index 37202b2774dc6..8a3de89e18b76 100644 --- a/library/core/src/fmt/rt/v1.rs +++ b/library/core/src/fmt/rt/v1.rs @@ -5,7 +5,9 @@ //! these can be statically allocated and are slightly optimized for the runtime #![allow(missing_debug_implementations)] +#[cfg_attr(not(bootstrap), lang = "format_placeholder")] #[derive(Copy, Clone)] +// FIXME: Rename this to Placeholder pub struct Argument { pub position: usize, pub format: FormatSpec, @@ -21,6 +23,7 @@ pub struct FormatSpec { } /// Possible alignments that can be requested as part of a formatting directive. +#[cfg_attr(not(bootstrap), lang = "format_alignment")] #[derive(Copy, Clone, PartialEq, Eq)] pub enum Alignment { /// Indication that contents should be left-aligned. @@ -34,6 +37,7 @@ pub enum Alignment { } /// Used by [width](https://doc.rust-lang.org/std/fmt/#width) and [precision](https://doc.rust-lang.org/std/fmt/#precision) specifiers. +#[cfg_attr(not(bootstrap), lang = "format_count")] #[derive(Copy, Clone)] pub enum Count { /// Specified with a literal number, stores the value From e83945150f65eaf8b644a4042229fcac4c82596b Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 11 Jan 2023 21:37:27 +0100 Subject: [PATCH 04/20] Add new fn to core::fmt::rt::v1::Argument. --- library/core/src/fmt/rt/v1.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/library/core/src/fmt/rt/v1.rs b/library/core/src/fmt/rt/v1.rs index 8a3de89e18b76..e61b89fad5d3b 100644 --- a/library/core/src/fmt/rt/v1.rs +++ b/library/core/src/fmt/rt/v1.rs @@ -22,6 +22,19 @@ pub struct FormatSpec { pub width: Count, } +impl Argument { + pub fn new( + position: usize, + fill: char, + align: Alignment, + flags: u32, + precision: Count, + width: Count, + ) -> Self { + Self { position, format: FormatSpec { fill, align, flags, precision, width } } + } +} + /// Possible alignments that can be requested as part of a formatting directive. #[cfg_attr(not(bootstrap), lang = "format_alignment")] #[derive(Copy, Clone, PartialEq, Eq)] From a4dbcb525b2f36f66c89df6919a7506cd99041cc Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 11 Jan 2023 21:41:13 +0100 Subject: [PATCH 05/20] Expand format_args!() in rust_ast_lowering. --- Cargo.lock | 1 + compiler/rustc_ast/src/ast.rs | 5 + .../format/ast.rs => rustc_ast/src/format.rs} | 36 +- compiler/rustc_ast/src/lib.rs | 2 + compiler/rustc_ast/src/mut_visit.rs | 14 + compiler/rustc_ast/src/util/parser.rs | 4 +- compiler/rustc_ast/src/visit.rs | 13 + compiler/rustc_ast_lowering/src/expr.rs | 1 + compiler/rustc_ast_lowering/src/format.rs | 356 ++++++++++++++++++ compiler/rustc_ast_lowering/src/lib.rs | 1 + compiler/rustc_ast_pretty/Cargo.toml | 3 +- .../rustc_ast_pretty/src/pprust/state/expr.rs | 102 +++++ .../src/assert/context.rs | 1 + compiler/rustc_builtin_macros/src/format.rs | 14 +- .../rustc_builtin_macros/src/format/expand.rs | 353 ----------------- compiler/rustc_hir/src/hir.rs | 3 + .../src/thir/pattern/check_match.rs | 6 +- compiler/rustc_passes/src/check_const.rs | 2 +- compiler/rustc_passes/src/hir_stats.rs | 2 +- 19 files changed, 535 insertions(+), 384 deletions(-) rename compiler/{rustc_builtin_macros/src/format/ast.rs => rustc_ast/src/format.rs} (87%) create mode 100644 compiler/rustc_ast_lowering/src/format.rs delete mode 100644 compiler/rustc_builtin_macros/src/format/expand.rs diff --git a/Cargo.lock b/Cargo.lock index 2a88152b5194a..faf4fd05c354f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3699,6 +3699,7 @@ name = "rustc_ast_pretty" version = "0.0.0" dependencies = [ "rustc_ast", + "rustc_parse_format", "rustc_span", ] diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index e656fb3740bbd..5b1722dffb90e 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -18,6 +18,7 @@ //! - [`Attribute`]: Metadata associated with item. //! - [`UnOp`], [`BinOp`], and [`BinOpKind`]: Unary and binary operators. +pub use crate::format::*; pub use crate::util::parser::ExprPrecedence; pub use GenericArgs::*; pub use UnsafeSource::*; @@ -1269,6 +1270,7 @@ impl Expr { ExprKind::Try(..) => ExprPrecedence::Try, ExprKind::Yield(..) => ExprPrecedence::Yield, ExprKind::Yeet(..) => ExprPrecedence::Yeet, + ExprKind::FormatArgs(..) => ExprPrecedence::FormatArgs, ExprKind::Err => ExprPrecedence::Err, } } @@ -1498,6 +1500,9 @@ pub enum ExprKind { /// with a `ByteStr` literal. IncludedBytes(Lrc<[u8]>), + /// A `format_args!()` expression. + FormatArgs(P), + /// Placeholder for an expression that wasn't syntactically well formed in some way. Err, } diff --git a/compiler/rustc_builtin_macros/src/format/ast.rs b/compiler/rustc_ast/src/format.rs similarity index 87% rename from compiler/rustc_builtin_macros/src/format/ast.rs rename to compiler/rustc_ast/src/format.rs index 01dbffa21b8aa..ce99c2b58b570 100644 --- a/compiler/rustc_builtin_macros/src/format/ast.rs +++ b/compiler/rustc_ast/src/format.rs @@ -1,5 +1,5 @@ -use rustc_ast::ptr::P; -use rustc_ast::Expr; +use crate::ptr::P; +use crate::Expr; use rustc_data_structures::fx::FxHashMap; use rustc_span::symbol::{Ident, Symbol}; use rustc_span::Span; @@ -39,7 +39,7 @@ use rustc_span::Span; /// Basically the "AST" for a complete `format_args!()`. /// /// E.g., `format_args!("hello {name}");`. -#[derive(Clone, Debug)] +#[derive(Clone, Encodable, Decodable, Debug)] pub struct FormatArgs { pub span: Span, pub template: Vec, @@ -49,7 +49,7 @@ pub struct FormatArgs { /// A piece of a format template string. /// /// E.g. "hello" or "{name}". -#[derive(Clone, Debug)] +#[derive(Clone, Encodable, Decodable, Debug)] pub enum FormatArgsPiece { Literal(Symbol), Placeholder(FormatPlaceholder), @@ -59,7 +59,7 @@ pub enum FormatArgsPiece { /// /// E.g. `1, 2, name="ferris", n=3`, /// but also implicit captured arguments like `x` in `format_args!("{x}")`. -#[derive(Clone, Debug)] +#[derive(Clone, Encodable, Decodable, Debug)] pub struct FormatArguments { arguments: Vec, num_unnamed_args: usize, @@ -121,18 +121,22 @@ impl FormatArguments { &self.arguments[..self.num_explicit_args] } - pub fn into_vec(self) -> Vec { - self.arguments + pub fn all_args(&self) -> &[FormatArgument] { + &self.arguments[..] + } + + pub fn all_args_mut(&mut self) -> &mut [FormatArgument] { + &mut self.arguments[..] } } -#[derive(Clone, Debug)] +#[derive(Clone, Encodable, Decodable, Debug)] pub struct FormatArgument { pub kind: FormatArgumentKind, pub expr: P, } -#[derive(Clone, Debug)] +#[derive(Clone, Encodable, Decodable, Debug)] pub enum FormatArgumentKind { /// `format_args(…, arg)` Normal, @@ -152,7 +156,7 @@ impl FormatArgumentKind { } } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Encodable, Decodable, Debug, PartialEq, Eq)] pub struct FormatPlaceholder { /// Index into [`FormatArgs::arguments`]. pub argument: FormatArgPosition, @@ -164,7 +168,7 @@ pub struct FormatPlaceholder { pub format_options: FormatOptions, } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Encodable, Decodable, Debug, PartialEq, Eq)] pub struct FormatArgPosition { /// Which argument this position refers to (Ok), /// or would've referred to if it existed (Err). @@ -175,7 +179,7 @@ pub struct FormatArgPosition { pub span: Option, } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Encodable, Decodable, Debug, PartialEq, Eq)] pub enum FormatArgPositionKind { /// `{}` or `{:.*}` Implicit, @@ -185,7 +189,7 @@ pub enum FormatArgPositionKind { Named, } -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Copy, Clone, Encodable, Decodable, Debug, PartialEq, Eq, Hash)] pub enum FormatTrait { /// `{}` Display, @@ -207,7 +211,7 @@ pub enum FormatTrait { UpperHex, } -#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Encodable, Decodable, Default, Debug, PartialEq, Eq)] pub struct FormatOptions { /// The width. E.g. `{:5}` or `{:width$}`. pub width: Option, @@ -221,7 +225,7 @@ pub struct FormatOptions { pub flags: u32, } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Encodable, Decodable, Debug, PartialEq, Eq)] pub enum FormatAlignment { /// `{:<}` Left, @@ -231,7 +235,7 @@ pub enum FormatAlignment { Center, } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Encodable, Decodable, Debug, PartialEq, Eq)] pub enum FormatCount { /// `{:5}` or `{:.5}` Literal(usize), diff --git a/compiler/rustc_ast/src/lib.rs b/compiler/rustc_ast/src/lib.rs index 9c1dfeb1a6142..0f8ebcfdc1508 100644 --- a/compiler/rustc_ast/src/lib.rs +++ b/compiler/rustc_ast/src/lib.rs @@ -42,6 +42,7 @@ pub mod ast_traits; pub mod attr; pub mod entry; pub mod expand; +pub mod format; pub mod mut_visit; pub mod node_id; pub mod ptr; @@ -51,6 +52,7 @@ pub mod visit; pub use self::ast::*; pub use self::ast_traits::{AstDeref, AstNodeWrapper, HasAttrs, HasNodeId, HasSpan, HasTokens}; +pub use self::format::*; use rustc_data_structures::stable_hasher::{HashStable, StableHasher}; diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index c572171e8f443..561274cc6f919 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -297,6 +297,10 @@ pub trait MutVisitor: Sized { fn visit_inline_asm_sym(&mut self, sym: &mut InlineAsmSym) { noop_visit_inline_asm_sym(sym, self) } + + fn visit_format_args(&mut self, fmt: &mut FormatArgs) { + noop_visit_format_args(fmt, self) + } } /// Use a map-style function (`FnOnce(T) -> T`) to overwrite a `&mut T`. Useful @@ -1284,6 +1288,15 @@ pub fn noop_visit_inline_asm_sym( vis.visit_path(path); } +pub fn noop_visit_format_args(fmt: &mut FormatArgs, vis: &mut T) { + for arg in fmt.arguments.all_args_mut() { + if let FormatArgumentKind::Named(name) = &mut arg.kind { + vis.visit_ident(name); + } + vis.visit_expr(&mut arg.expr); + } +} + pub fn noop_visit_expr( Expr { kind, id, span, attrs, tokens }: &mut Expr, vis: &mut T, @@ -1423,6 +1436,7 @@ pub fn noop_visit_expr( visit_opt(expr, |expr| vis.visit_expr(expr)); } ExprKind::InlineAsm(asm) => vis.visit_inline_asm(asm), + ExprKind::FormatArgs(fmt) => vis.visit_format_args(fmt), ExprKind::MacCall(mac) => vis.visit_mac_call(mac), ExprKind::Struct(se) => { let StructExpr { qself, path, fields, rest } = se.deref_mut(); diff --git a/compiler/rustc_ast/src/util/parser.rs b/compiler/rustc_ast/src/util/parser.rs index 819f1884a0692..2db2ab5e81157 100644 --- a/compiler/rustc_ast/src/util/parser.rs +++ b/compiler/rustc_ast/src/util/parser.rs @@ -271,6 +271,7 @@ pub enum ExprPrecedence { Try, InlineAsm, Mac, + FormatArgs, Array, Repeat, @@ -335,7 +336,8 @@ impl ExprPrecedence { | ExprPrecedence::Index | ExprPrecedence::Try | ExprPrecedence::InlineAsm - | ExprPrecedence::Mac => PREC_POSTFIX, + | ExprPrecedence::Mac + | ExprPrecedence::FormatArgs => PREC_POSTFIX, // Never need parens ExprPrecedence::Array diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index df7145a722a46..cb5c17084ec2a 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -242,6 +242,9 @@ pub trait Visitor<'ast>: Sized { fn visit_inline_asm(&mut self, asm: &'ast InlineAsm) { walk_inline_asm(self, asm) } + fn visit_format_args(&mut self, fmt: &'ast FormatArgs) { + walk_format_args(self, fmt) + } fn visit_inline_asm_sym(&mut self, sym: &'ast InlineAsmSym) { walk_inline_asm_sym(self, sym) } @@ -756,6 +759,15 @@ pub fn walk_inline_asm_sym<'a, V: Visitor<'a>>(visitor: &mut V, sym: &'a InlineA visitor.visit_path(&sym.path, sym.id); } +pub fn walk_format_args<'a, V: Visitor<'a>>(visitor: &mut V, fmt: &'a FormatArgs) { + for arg in fmt.arguments.all_args() { + if let FormatArgumentKind::Named(name) = arg.kind { + visitor.visit_ident(name); + } + visitor.visit_expr(&arg.expr); + } +} + pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) { walk_list!(visitor, visit_attribute, expression.attrs.iter()); @@ -895,6 +907,7 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) { ExprKind::MacCall(mac) => visitor.visit_mac_call(mac), ExprKind::Paren(subexpression) => visitor.visit_expr(subexpression), ExprKind::InlineAsm(asm) => visitor.visit_inline_asm(asm), + ExprKind::FormatArgs(f) => visitor.visit_format_args(f), ExprKind::Yield(optional_expression) => { walk_list!(visitor, visit_expr, optional_expression); } diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 51f290bc587e9..5e0f1b9b61ffc 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -292,6 +292,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ExprKind::InlineAsm(asm) => { hir::ExprKind::InlineAsm(self.lower_inline_asm(e.span, asm)) } + ExprKind::FormatArgs(fmt) => self.lower_format_args(e.span, fmt), ExprKind::Struct(se) => { let rest = match &se.rest { StructRest::Base(e) => Some(self.lower_expr(e)), diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs new file mode 100644 index 0000000000000..f8ed164b356f1 --- /dev/null +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -0,0 +1,356 @@ +use super::LoweringContext; +use rustc_ast as ast; +use rustc_ast::visit::{self, Visitor}; +use rustc_ast::*; +use rustc_data_structures::fx::FxIndexSet; +use rustc_hir as hir; +use rustc_span::{ + sym, + symbol::{kw, Ident}, + Span, +}; + +impl<'hir> LoweringContext<'_, 'hir> { + pub(crate) fn lower_format_args(&mut self, sp: Span, fmt: &FormatArgs) -> hir::ExprKind<'hir> { + expand_format_args(self, sp, fmt) + } +} + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +enum ArgumentType { + Format(FormatTrait), + Usize, +} + +fn make_argument<'hir>( + ctx: &mut LoweringContext<'_, 'hir>, + sp: Span, + arg: &'hir hir::Expr<'hir>, + ty: ArgumentType, +) -> hir::Expr<'hir> { + // Generate: + // ::core::fmt::ArgumentV1::new_…(arg) + use ArgumentType::*; + use FormatTrait::*; + let new_fn = ctx.arena.alloc(ctx.expr_lang_item_type_relative( + sp, + hir::LangItem::FormatArgument, + match ty { + Format(Display) => sym::new_display, + Format(Debug) => sym::new_debug, + Format(LowerExp) => sym::new_lower_exp, + Format(UpperExp) => sym::new_upper_exp, + Format(Octal) => sym::new_octal, + Format(Pointer) => sym::new_pointer, + Format(Binary) => sym::new_binary, + Format(LowerHex) => sym::new_lower_hex, + Format(UpperHex) => sym::new_upper_hex, + Usize => sym::from_usize, + }, + )); + ctx.expr_call_mut(sp, new_fn, std::slice::from_ref(arg)) +} + +fn make_count<'hir>( + ctx: &mut LoweringContext<'_, 'hir>, + sp: Span, + count: &Option, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, +) -> hir::Expr<'hir> { + // Generate: + // ::core::fmt::rt::v1::Count::…(…) + match count { + Some(FormatCount::Literal(n)) => { + let count_is = ctx.arena.alloc(ctx.expr_lang_item_type_relative( + sp, + hir::LangItem::FormatCount, + sym::Is, + )); + let value = ctx.arena.alloc_from_iter([ctx.expr_usize(sp, *n)]); + ctx.expr_call_mut(sp, count_is, value) + } + Some(FormatCount::Argument(arg)) => { + if let Ok(arg_index) = arg.index { + let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize)); + let count_param = ctx.arena.alloc(ctx.expr_lang_item_type_relative( + sp, + hir::LangItem::FormatCount, + sym::Param, + )); + let value = ctx.arena.alloc_from_iter([ctx.expr_usize(sp, i)]); + ctx.expr_call_mut(sp, count_param, value) + } else { + ctx.expr(sp, hir::ExprKind::Err) + } + } + None => ctx.expr_lang_item_type_relative(sp, hir::LangItem::FormatCount, sym::Implied), + } +} + +fn make_format_spec<'hir>( + ctx: &mut LoweringContext<'_, 'hir>, + sp: Span, + placeholder: &FormatPlaceholder, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, +) -> hir::Expr<'hir> { + // Generate: + // ::core::fmt::rt::v1::Argument { + // position: 0usize, + // format: ::core::fmt::rt::v1::FormatSpec { + // fill: ' ', + // align: ::core::fmt::rt::v1::Alignment::Unknown, + // flags: 0u32, + // precision: ::core::fmt::rt::v1::Count::Implied, + // width: ::core::fmt::rt::v1::Count::Implied, + // }, + // } + let position = match placeholder.argument.index { + Ok(arg_index) => { + let (i, _) = + argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait))); + ctx.expr_usize(sp, i) + } + Err(_) => ctx.expr(sp, hir::ExprKind::Err), + }; + let fill = ctx.expr_char(sp, placeholder.format_options.fill.unwrap_or(' ')); + let align = ctx.expr_lang_item_type_relative( + sp, + hir::LangItem::FormatAlignment, + match placeholder.format_options.alignment { + Some(FormatAlignment::Left) => sym::Left, + Some(FormatAlignment::Right) => sym::Right, + Some(FormatAlignment::Center) => sym::Center, + None => sym::Unknown, + }, + ); + let flags = ctx.expr_u32(sp, placeholder.format_options.flags); + let prec = make_count(ctx, sp, &placeholder.format_options.precision, argmap); + let width = make_count(ctx, sp, &placeholder.format_options.width, argmap); + let format_placeholder_new = ctx.arena.alloc(ctx.expr_lang_item_type_relative( + sp, + hir::LangItem::FormatPlaceholder, + sym::new, + )); + let args = ctx.arena.alloc_from_iter([position, fill, align, flags, prec, width]); + ctx.expr_call_mut(sp, format_placeholder_new, args) +} + +fn expand_format_args<'hir>( + ctx: &mut LoweringContext<'_, 'hir>, + macsp: Span, + fmt: &FormatArgs, +) -> hir::ExprKind<'hir> { + let lit_pieces = + ctx.arena.alloc_from_iter(fmt.template.iter().enumerate().filter_map(|(i, piece)| { + match piece { + &FormatArgsPiece::Literal(s) => Some(ctx.expr_str(fmt.span, s)), + &FormatArgsPiece::Placeholder(_) => { + // Inject empty string before placeholders when not already preceded by a literal piece. + if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_)) { + Some(ctx.expr_str(fmt.span, kw::Empty)) + } else { + None + } + } + } + })); + let lit_pieces = ctx.expr_array_ref(fmt.span, lit_pieces); + + // Whether we'll use the `Arguments::new_v1_formatted` form (true), + // or the `Arguments::new_v1` form (false). + let mut use_format_options = false; + + // Create a list of all _unique_ (argument, format trait) combinations. + // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)] + let mut argmap = FxIndexSet::default(); + for piece in &fmt.template { + let FormatArgsPiece::Placeholder(placeholder) = piece else { continue }; + if placeholder.format_options != Default::default() { + // Can't use basic form if there's any formatting options. + use_format_options = true; + } + if let Ok(index) = placeholder.argument.index { + if !argmap.insert((index, ArgumentType::Format(placeholder.format_trait))) { + // Duplicate (argument, format trait) combination, + // which we'll only put once in the args array. + use_format_options = true; + } + } + } + + let format_options = use_format_options.then(|| { + // Generate: + // &[format_spec_0, format_spec_1, format_spec_2] + let elements: Vec<_> = fmt + .template + .iter() + .filter_map(|piece| { + let FormatArgsPiece::Placeholder(placeholder) = piece else { return None }; + Some(make_format_spec(ctx, macsp, placeholder, &mut argmap)) + }) + .collect(); + ctx.expr_array_ref(macsp, ctx.arena.alloc_from_iter(elements)) + }); + + let arguments = fmt.arguments.all_args(); + + // If the args array contains exactly all the original arguments once, + // in order, we can use a simple array instead of a `match` construction. + // However, if there's a yield point in any argument except the first one, + // we don't do this, because an ArgumentV1 cannot be kept across yield points. + let use_simple_array = argmap.len() == arguments.len() + && argmap.iter().enumerate().all(|(i, &(j, _))| i == j) + && arguments.iter().skip(1).all(|arg| !may_contain_yield_point(&arg.expr)); + + let args = if use_simple_array { + // Generate: + // &[ + // ::core::fmt::ArgumentV1::new_display(&arg0), + // ::core::fmt::ArgumentV1::new_lower_hex(&arg1), + // ::core::fmt::ArgumentV1::new_debug(&arg2), + // ] + let elements: Vec<_> = arguments + .iter() + .zip(argmap) + .map(|(arg, (_, ty))| { + let sp = arg.expr.span.with_ctxt(macsp.ctxt()); + let arg = ctx.lower_expr(&arg.expr); + let ref_arg = ctx.arena.alloc(ctx.expr( + sp, + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, arg), + )); + make_argument(ctx, sp, ref_arg, ty) + }) + .collect(); + ctx.expr_array_ref(macsp, ctx.arena.alloc_from_iter(elements)) + } else { + // Generate: + // &match (&arg0, &arg1, &arg2) { + // args => [ + // ::core::fmt::ArgumentV1::new_display(args.0), + // ::core::fmt::ArgumentV1::new_lower_hex(args.1), + // ::core::fmt::ArgumentV1::new_debug(args.0), + // ] + // } + let args_ident = Ident::new(sym::args, macsp); + let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident); + let args = ctx.arena.alloc_from_iter(argmap.iter().map(|&(arg_index, ty)| { + if let Some(arg) = arguments.get(arg_index) { + let sp = arg.expr.span.with_ctxt(macsp.ctxt()); + let args_ident_expr = ctx.expr_ident(macsp, args_ident, args_hir_id); + let arg = ctx.arena.alloc(ctx.expr( + sp, + hir::ExprKind::Field( + args_ident_expr, + Ident::new(sym::integer(arg_index), macsp), + ), + )); + make_argument(ctx, sp, arg, ty) + } else { + ctx.expr(macsp, hir::ExprKind::Err) + } + })); + let elements: Vec<_> = arguments + .iter() + .map(|arg| { + let arg_expr = ctx.lower_expr(&arg.expr); + ctx.expr( + arg.expr.span.with_ctxt(macsp.ctxt()), + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, arg_expr), + ) + }) + .collect(); + let args_tuple = ctx + .arena + .alloc(ctx.expr(macsp, hir::ExprKind::Tup(ctx.arena.alloc_from_iter(elements)))); + let array = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(args))); + let match_arms = ctx.arena.alloc_from_iter([ctx.arm(args_pat, array)]); + let match_expr = ctx.arena.alloc(ctx.expr_match( + macsp, + args_tuple, + match_arms, + hir::MatchSource::FormatArgs, + )); + ctx.expr( + macsp, + hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, match_expr), + ) + }; + + if let Some(format_options) = format_options { + // Generate: + // ::core::fmt::Arguments::new_v1_formatted( + // lit_pieces, + // args, + // format_options, + // unsafe { ::core::fmt::UnsafeArg::new() } + // ) + let new_v1_formatted = ctx.arena.alloc(ctx.expr_lang_item_type_relative( + macsp, + hir::LangItem::FormatArguments, + sym::new_v1_formatted, + )); + let unsafe_arg_new = ctx.arena.alloc(ctx.expr_lang_item_type_relative( + macsp, + hir::LangItem::FormatUnsafeArg, + sym::new, + )); + let unsafe_arg_new_call = ctx.expr_call(macsp, unsafe_arg_new, &[]); + let hir_id = ctx.next_id(); + let unsafe_arg = ctx.expr_block(ctx.arena.alloc(hir::Block { + stmts: &[], + expr: Some(unsafe_arg_new_call), + hir_id, + rules: hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::CompilerGenerated), + span: macsp, + targeted_by_break: false, + })); + let args = ctx.arena.alloc_from_iter([lit_pieces, args, format_options, unsafe_arg]); + hir::ExprKind::Call(new_v1_formatted, args) + } else { + // Generate: + // ::core::fmt::Arguments::new_v1( + // lit_pieces, + // args, + // ) + let new_v1 = ctx.arena.alloc(ctx.expr_lang_item_type_relative( + macsp, + hir::LangItem::FormatArguments, + sym::new_v1, + )); + let new_args = ctx.arena.alloc_from_iter([lit_pieces, args]); + hir::ExprKind::Call(new_v1, new_args) + } +} + +fn may_contain_yield_point(e: &ast::Expr) -> bool { + struct MayContainYieldPoint(bool); + + impl Visitor<'_> for MayContainYieldPoint { + fn visit_expr(&mut self, e: &ast::Expr) { + if let ast::ExprKind::Await(_) | ast::ExprKind::Yield(_) = e.kind { + self.0 = true; + } else { + visit::walk_expr(self, e); + } + } + + fn visit_mac_call(&mut self, _: &ast::MacCall) { + self.0 = true; + } + + fn visit_attribute(&mut self, _: &ast::Attribute) { + // Conservatively assume this may be a proc macro attribute in + // expression position. + self.0 = true; + } + + fn visit_item(&mut self, _: &ast::Item) { + // Do not recurse into nested items. + } + } + + let mut visitor = MayContainYieldPoint(false); + visitor.visit_expr(e); + visitor.0 +} diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 2e135aafb1e0f..01aba94fe39f4 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -80,6 +80,7 @@ mod asm; mod block; mod errors; mod expr; +mod format; mod index; mod item; mod lifetime_collector; diff --git a/compiler/rustc_ast_pretty/Cargo.toml b/compiler/rustc_ast_pretty/Cargo.toml index a3e3e823b08eb..b4900dc39a8af 100644 --- a/compiler/rustc_ast_pretty/Cargo.toml +++ b/compiler/rustc_ast_pretty/Cargo.toml @@ -6,5 +6,6 @@ edition = "2021" [lib] [dependencies] -rustc_span = { path = "../rustc_span" } rustc_ast = { path = "../rustc_ast" } +rustc_parse_format = { path = "../rustc_parse_format" } +rustc_span = { path = "../rustc_span" } diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 3b17f6dd627eb..03beae3a45bb1 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -6,6 +6,8 @@ use rustc_ast::token; use rustc_ast::util::literal::escape_byte_str_symbol; use rustc_ast::util::parser::{self, AssocOp, Fixity}; use rustc_ast::{self as ast, BlockCheckMode}; +use rustc_ast::{FormatAlignment, FormatArgPosition, FormatArgsPiece, FormatCount, FormatTrait}; +use std::fmt::Write; impl<'a> State<'a> { fn print_else(&mut self, els: Option<&ast::Expr>) { @@ -528,6 +530,18 @@ impl<'a> State<'a> { self.word("asm!"); self.print_inline_asm(a); } + ast::ExprKind::FormatArgs(fmt) => { + self.word("format_args!"); + self.popen(); + self.rbox(0, Inconsistent); + self.word(reconstruct_format_args_template_string(&fmt.template)); + for arg in fmt.arguments.all_args() { + self.word_space(","); + self.print_expr(&arg.expr); + } + self.end(); + self.pclose(); + } ast::ExprKind::MacCall(m) => self.print_mac(m), ast::ExprKind::Paren(e) => { self.popen(); @@ -627,3 +641,91 @@ impl<'a> State<'a> { } } } + +pub fn reconstruct_format_args_template_string(pieces: &[FormatArgsPiece]) -> String { + let mut template = "\"".to_string(); + for piece in pieces { + match piece { + FormatArgsPiece::Literal(s) => { + for c in s.as_str().escape_debug() { + template.push(c); + if let '{' | '}' = c { + template.push(c); + } + } + } + FormatArgsPiece::Placeholder(p) => { + template.push('{'); + let (Ok(n) | Err(n)) = p.argument.index; + write!(template, "{n}").unwrap(); + if p.format_options != Default::default() || p.format_trait != FormatTrait::Display + { + template.push_str(":"); + } + if let Some(fill) = p.format_options.fill { + template.push(fill); + } + match p.format_options.alignment { + Some(FormatAlignment::Left) => template.push_str("<"), + Some(FormatAlignment::Right) => template.push_str(">"), + Some(FormatAlignment::Center) => template.push_str("^"), + None => {} + } + let flags = p.format_options.flags; + if flags >> (rustc_parse_format::FlagSignPlus as usize) & 1 != 0 { + template.push('+'); + } + if flags >> (rustc_parse_format::FlagSignMinus as usize) & 1 != 0 { + template.push('-'); + } + if flags >> (rustc_parse_format::FlagAlternate as usize) & 1 != 0 { + template.push('#'); + } + if flags >> (rustc_parse_format::FlagSignAwareZeroPad as usize) & 1 != 0 { + template.push('0'); + } + if let Some(width) = &p.format_options.width { + match width { + FormatCount::Literal(n) => write!(template, "{n}").unwrap(), + FormatCount::Argument(FormatArgPosition { + index: Ok(n) | Err(n), .. + }) => { + write!(template, "{n}$").unwrap(); + } + } + } + if let Some(precision) = &p.format_options.precision { + template.push('.'); + match precision { + FormatCount::Literal(n) => write!(template, "{n}").unwrap(), + FormatCount::Argument(FormatArgPosition { + index: Ok(n) | Err(n), .. + }) => { + write!(template, "{n}$").unwrap(); + } + } + } + if flags >> (rustc_parse_format::FlagDebugLowerHex as usize) & 1 != 0 { + template.push('X'); + } + if flags >> (rustc_parse_format::FlagDebugUpperHex as usize) & 1 != 0 { + template.push('x'); + } + template.push_str(match p.format_trait { + FormatTrait::Display => "", + FormatTrait::Debug => "?", + FormatTrait::LowerExp => "e", + FormatTrait::UpperExp => "E", + FormatTrait::Octal => "o", + FormatTrait::Pointer => "p", + FormatTrait::Binary => "b", + FormatTrait::LowerHex => "x", + FormatTrait::UpperHex => "X", + }); + template.push('}'); + } + } + } + template.push('"'); + template +} diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index 93b07801e035d..342b1735661df 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -297,6 +297,7 @@ impl<'cx, 'a> Context<'cx, 'a> { | ExprKind::Continue(_) | ExprKind::Err | ExprKind::Field(_, _) + | ExprKind::FormatArgs(_) | ExprKind::ForLoop(_, _, _, _) | ExprKind::If(_, _, _) | ExprKind::IncludedBytes(..) diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index b2b7b9d75bd37..47b63a7fa122f 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -1,7 +1,11 @@ use rustc_ast::ptr::P; use rustc_ast::token; use rustc_ast::tokenstream::TokenStream; -use rustc_ast::Expr; +use rustc_ast::{ + Expr, ExprKind, FormatAlignment, FormatArgPosition, FormatArgPositionKind, FormatArgs, + FormatArgsPiece, FormatArgument, FormatArgumentKind, FormatArguments, FormatCount, + FormatOptions, FormatPlaceholder, FormatTrait, +}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::{pluralize, Applicability, MultiSpan, PResult}; use rustc_expand::base::{self, *}; @@ -12,12 +16,6 @@ use rustc_span::{BytePos, InnerSpan, Span}; use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY; use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, LintId}; -mod ast; -use ast::*; - -mod expand; -use expand::expand_parsed_format_args; - // The format_args!() macro is expanded in three steps: // 1. First, `parse_args` will parse the `(literal, arg, arg, name=arg, name=arg)` syntax, // but doesn't parse the template (the literal) itself. @@ -850,7 +848,7 @@ fn expand_format_args_impl<'cx>( match parse_args(ecx, sp, tts) { Ok((efmt, args)) => { if let Ok(format_args) = make_format_args(ecx, efmt, args, nl) { - MacEager::expr(expand_parsed_format_args(ecx, format_args)) + MacEager::expr(ecx.expr(sp, ExprKind::FormatArgs(P(format_args)))) } else { MacEager::expr(DummyResult::raw_expr(sp, true)) } diff --git a/compiler/rustc_builtin_macros/src/format/expand.rs b/compiler/rustc_builtin_macros/src/format/expand.rs deleted file mode 100644 index 9dde5efcb28b7..0000000000000 --- a/compiler/rustc_builtin_macros/src/format/expand.rs +++ /dev/null @@ -1,353 +0,0 @@ -use super::*; -use rustc_ast as ast; -use rustc_ast::visit::{self, Visitor}; -use rustc_ast::{BlockCheckMode, UnsafeSource}; -use rustc_data_structures::fx::FxIndexSet; -use rustc_span::{sym, symbol::kw}; - -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] -enum ArgumentType { - Format(FormatTrait), - Usize, -} - -fn make_argument(ecx: &ExtCtxt<'_>, sp: Span, arg: P, ty: ArgumentType) -> P { - // Generate: - // ::core::fmt::ArgumentV1::new_…(arg) - use ArgumentType::*; - use FormatTrait::*; - ecx.expr_call_global( - sp, - ecx.std_path(&[ - sym::fmt, - sym::ArgumentV1, - match ty { - Format(Display) => sym::new_display, - Format(Debug) => sym::new_debug, - Format(LowerExp) => sym::new_lower_exp, - Format(UpperExp) => sym::new_upper_exp, - Format(Octal) => sym::new_octal, - Format(Pointer) => sym::new_pointer, - Format(Binary) => sym::new_binary, - Format(LowerHex) => sym::new_lower_hex, - Format(UpperHex) => sym::new_upper_hex, - Usize => sym::from_usize, - }, - ]), - vec![arg], - ) -} - -fn make_count( - ecx: &ExtCtxt<'_>, - sp: Span, - count: &Option, - argmap: &mut FxIndexSet<(usize, ArgumentType)>, -) -> P { - // Generate: - // ::core::fmt::rt::v1::Count::…(…) - match count { - Some(FormatCount::Literal(n)) => ecx.expr_call_global( - sp, - ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Is]), - vec![ecx.expr_usize(sp, *n)], - ), - Some(FormatCount::Argument(arg)) => { - if let Ok(arg_index) = arg.index { - let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize)); - ecx.expr_call_global( - sp, - ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Param]), - vec![ecx.expr_usize(sp, i)], - ) - } else { - DummyResult::raw_expr(sp, true) - } - } - None => ecx.expr_path(ecx.path_global( - sp, - ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Implied]), - )), - } -} - -fn make_format_spec( - ecx: &ExtCtxt<'_>, - sp: Span, - placeholder: &FormatPlaceholder, - argmap: &mut FxIndexSet<(usize, ArgumentType)>, -) -> P { - // Generate: - // ::core::fmt::rt::v1::Argument { - // position: 0usize, - // format: ::core::fmt::rt::v1::FormatSpec { - // fill: ' ', - // align: ::core::fmt::rt::v1::Alignment::Unknown, - // flags: 0u32, - // precision: ::core::fmt::rt::v1::Count::Implied, - // width: ::core::fmt::rt::v1::Count::Implied, - // }, - // } - let position = match placeholder.argument.index { - Ok(arg_index) => { - let (i, _) = - argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait))); - ecx.expr_usize(sp, i) - } - Err(_) => DummyResult::raw_expr(sp, true), - }; - let fill = ecx.expr_char(sp, placeholder.format_options.fill.unwrap_or(' ')); - let align = ecx.expr_path(ecx.path_global( - sp, - ecx.std_path(&[ - sym::fmt, - sym::rt, - sym::v1, - sym::Alignment, - match placeholder.format_options.alignment { - Some(FormatAlignment::Left) => sym::Left, - Some(FormatAlignment::Right) => sym::Right, - Some(FormatAlignment::Center) => sym::Center, - None => sym::Unknown, - }, - ]), - )); - let flags = ecx.expr_u32(sp, placeholder.format_options.flags); - let prec = make_count(ecx, sp, &placeholder.format_options.precision, argmap); - let width = make_count(ecx, sp, &placeholder.format_options.width, argmap); - ecx.expr_struct( - sp, - ecx.path_global(sp, ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Argument])), - vec![ - ecx.field_imm(sp, Ident::new(sym::position, sp), position), - ecx.field_imm( - sp, - Ident::new(sym::format, sp), - ecx.expr_struct( - sp, - ecx.path_global( - sp, - ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::FormatSpec]), - ), - vec![ - ecx.field_imm(sp, Ident::new(sym::fill, sp), fill), - ecx.field_imm(sp, Ident::new(sym::align, sp), align), - ecx.field_imm(sp, Ident::new(sym::flags, sp), flags), - ecx.field_imm(sp, Ident::new(sym::precision, sp), prec), - ecx.field_imm(sp, Ident::new(sym::width, sp), width), - ], - ), - ), - ], - ) -} - -pub fn expand_parsed_format_args(ecx: &mut ExtCtxt<'_>, fmt: FormatArgs) -> P { - let macsp = ecx.with_def_site_ctxt(ecx.call_site()); - - let lit_pieces = ecx.expr_array_ref( - fmt.span, - fmt.template - .iter() - .enumerate() - .filter_map(|(i, piece)| match piece { - &FormatArgsPiece::Literal(s) => Some(ecx.expr_str(fmt.span, s)), - &FormatArgsPiece::Placeholder(_) => { - // Inject empty string before placeholders when not already preceded by a literal piece. - if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_)) { - Some(ecx.expr_str(fmt.span, kw::Empty)) - } else { - None - } - } - }) - .collect(), - ); - - // Whether we'll use the `Arguments::new_v1_formatted` form (true), - // or the `Arguments::new_v1` form (false). - let mut use_format_options = false; - - // Create a list of all _unique_ (argument, format trait) combinations. - // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)] - let mut argmap = FxIndexSet::default(); - for piece in &fmt.template { - let FormatArgsPiece::Placeholder(placeholder) = piece else { continue }; - if placeholder.format_options != Default::default() { - // Can't use basic form if there's any formatting options. - use_format_options = true; - } - if let Ok(index) = placeholder.argument.index { - if !argmap.insert((index, ArgumentType::Format(placeholder.format_trait))) { - // Duplicate (argument, format trait) combination, - // which we'll only put once in the args array. - use_format_options = true; - } - } - } - - let format_options = use_format_options.then(|| { - // Generate: - // &[format_spec_0, format_spec_1, format_spec_2] - ecx.expr_array_ref( - macsp, - fmt.template - .iter() - .filter_map(|piece| { - let FormatArgsPiece::Placeholder(placeholder) = piece else { return None }; - Some(make_format_spec(ecx, macsp, placeholder, &mut argmap)) - }) - .collect(), - ) - }); - - let arguments = fmt.arguments.into_vec(); - - // If the args array contains exactly all the original arguments once, - // in order, we can use a simple array instead of a `match` construction. - // However, if there's a yield point in any argument except the first one, - // we don't do this, because an ArgumentV1 cannot be kept across yield points. - let use_simple_array = argmap.len() == arguments.len() - && argmap.iter().enumerate().all(|(i, &(j, _))| i == j) - && arguments.iter().skip(1).all(|arg| !may_contain_yield_point(&arg.expr)); - - let args = if use_simple_array { - // Generate: - // &[ - // ::core::fmt::ArgumentV1::new_display(&arg0), - // ::core::fmt::ArgumentV1::new_lower_hex(&arg1), - // ::core::fmt::ArgumentV1::new_debug(&arg2), - // ] - ecx.expr_array_ref( - macsp, - arguments - .into_iter() - .zip(argmap) - .map(|(arg, (_, ty))| { - let sp = arg.expr.span.with_ctxt(macsp.ctxt()); - make_argument(ecx, sp, ecx.expr_addr_of(sp, arg.expr), ty) - }) - .collect(), - ) - } else { - // Generate: - // match (&arg0, &arg1, &arg2) { - // args => &[ - // ::core::fmt::ArgumentV1::new_display(args.0), - // ::core::fmt::ArgumentV1::new_lower_hex(args.1), - // ::core::fmt::ArgumentV1::new_debug(args.0), - // ] - // } - let args_ident = Ident::new(sym::args, macsp); - let args = argmap - .iter() - .map(|&(arg_index, ty)| { - if let Some(arg) = arguments.get(arg_index) { - let sp = arg.expr.span.with_ctxt(macsp.ctxt()); - make_argument( - ecx, - sp, - ecx.expr_field( - sp, - ecx.expr_ident(macsp, args_ident), - Ident::new(sym::integer(arg_index), macsp), - ), - ty, - ) - } else { - DummyResult::raw_expr(macsp, true) - } - }) - .collect(); - ecx.expr_addr_of( - macsp, - ecx.expr_match( - macsp, - ecx.expr_tuple( - macsp, - arguments - .into_iter() - .map(|arg| { - ecx.expr_addr_of(arg.expr.span.with_ctxt(macsp.ctxt()), arg.expr) - }) - .collect(), - ), - vec![ecx.arm(macsp, ecx.pat_ident(macsp, args_ident), ecx.expr_array(macsp, args))], - ), - ) - }; - - if let Some(format_options) = format_options { - // Generate: - // ::core::fmt::Arguments::new_v1_formatted( - // lit_pieces, - // args, - // format_options, - // unsafe { ::core::fmt::UnsafeArg::new() } - // ) - ecx.expr_call_global( - macsp, - ecx.std_path(&[sym::fmt, sym::Arguments, sym::new_v1_formatted]), - vec![ - lit_pieces, - args, - format_options, - ecx.expr_block(P(ast::Block { - stmts: vec![ecx.stmt_expr(ecx.expr_call_global( - macsp, - ecx.std_path(&[sym::fmt, sym::UnsafeArg, sym::new]), - Vec::new(), - ))], - id: ast::DUMMY_NODE_ID, - rules: BlockCheckMode::Unsafe(UnsafeSource::CompilerGenerated), - span: macsp, - tokens: None, - could_be_bare_literal: false, - })), - ], - ) - } else { - // Generate: - // ::core::fmt::Arguments::new_v1( - // lit_pieces, - // args, - // ) - ecx.expr_call_global( - macsp, - ecx.std_path(&[sym::fmt, sym::Arguments, sym::new_v1]), - vec![lit_pieces, args], - ) - } -} - -fn may_contain_yield_point(e: &ast::Expr) -> bool { - struct MayContainYieldPoint(bool); - - impl Visitor<'_> for MayContainYieldPoint { - fn visit_expr(&mut self, e: &ast::Expr) { - if let ast::ExprKind::Await(_) | ast::ExprKind::Yield(_) = e.kind { - self.0 = true; - } else { - visit::walk_expr(self, e); - } - } - - fn visit_mac_call(&mut self, _: &ast::MacCall) { - self.0 = true; - } - - fn visit_attribute(&mut self, _: &ast::Attribute) { - // Conservatively assume this may be a proc macro attribute in - // expression position. - self.0 = true; - } - - fn visit_item(&mut self, _: &ast::Item) { - // Do not recurse into nested items. - } - } - - let mut visitor = MayContainYieldPoint(false); - visitor.visit_expr(e); - visitor.0 -} diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index bc897ed8112e5..c827600179def 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -2108,6 +2108,8 @@ pub enum MatchSource { TryDesugar, /// A desugared `.await`. AwaitDesugar, + /// A desugared `format_args!()`. + FormatArgs, } impl MatchSource { @@ -2119,6 +2121,7 @@ impl MatchSource { ForLoopDesugar => "for", TryDesugar => "?", AwaitDesugar => ".await", + FormatArgs => "format_args!()", } } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index e7ee0d9e908e0..2b52c410ecd2a 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -208,9 +208,9 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> { // Don't report arm reachability of desugared `match $iter.into_iter() { iter => .. }` // when the iterator is an uninhabited type. unreachable_code will trigger instead. hir::MatchSource::ForLoopDesugar if arms.len() == 1 => {} - hir::MatchSource::ForLoopDesugar | hir::MatchSource::Normal => { - report_arm_reachability(&cx, &report) - } + hir::MatchSource::ForLoopDesugar + | hir::MatchSource::Normal + | hir::MatchSource::FormatArgs => report_arm_reachability(&cx, &report), // Unreachable patterns in try and await expressions occur when one of // the arms are an uninhabited type. Which is OK. hir::MatchSource::AwaitDesugar | hir::MatchSource::TryDesugar => {} diff --git a/compiler/rustc_passes/src/check_const.rs b/compiler/rustc_passes/src/check_const.rs index aa726d6cd92aa..dd8c646a43c82 100644 --- a/compiler/rustc_passes/src/check_const.rs +++ b/compiler/rustc_passes/src/check_const.rs @@ -48,7 +48,7 @@ impl NonConstExpr { Self::Match(TryDesugar) => &[sym::const_try], // All other expressions are allowed. - Self::Loop(Loop | While) | Self::Match(Normal) => &[], + Self::Loop(Loop | While) | Self::Match(Normal | FormatArgs) => &[], }; Some(gates) diff --git a/compiler/rustc_passes/src/hir_stats.rs b/compiler/rustc_passes/src/hir_stats.rs index b86d2316820ce..d1b896e940e6e 100644 --- a/compiler/rustc_passes/src/hir_stats.rs +++ b/compiler/rustc_passes/src/hir_stats.rs @@ -567,7 +567,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { Box, Array, ConstBlock, Call, MethodCall, Tup, Binary, Unary, Lit, Cast, Type, Let, If, While, ForLoop, Loop, Match, Closure, Block, Async, Await, TryBlock, Assign, AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret, - InlineAsm, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, IncludedBytes, Err + InlineAsm, FormatArgs, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, IncludedBytes, Err ] ); ast_visit::walk_expr(self, e) From 525b0bb77a6d0bf88772a47b13275a553e625797 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 11 Jan 2023 21:41:58 +0100 Subject: [PATCH 06/20] Bless tests. --- .../ui/attributes/key-value-expansion.stderr | 7 +--- ...ming-methods-have-optimized-codegen.stdout | 36 +++++++++---------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/tests/ui/attributes/key-value-expansion.stderr b/tests/ui/attributes/key-value-expansion.stderr index 1b776322aaa64..aaa8b169583fd 100644 --- a/tests/ui/attributes/key-value-expansion.stderr +++ b/tests/ui/attributes/key-value-expansion.stderr @@ -15,12 +15,7 @@ LL | bug!(); | = note: this error originates in the macro `bug` (in Nightly builds, run with -Z macro-backtrace for more info) -error: unexpected expression: `{ - let res = - ::alloc::fmt::format(::core::fmt::Arguments::new_v1(&[""], - &[::core::fmt::ArgumentV1::new_display(&"u8")])); - res - }.as_str()` +error: unexpected expression: `{ let res = ::alloc::fmt::format(format_args!("{0}", "u8")); res }.as_str()` --> $DIR/key-value-expansion.rs:48:23 | LL | doc_comment! {format!("{coor}", coor = stringify!($t1)).as_str()} diff --git a/tests/ui/macros/rfc-2011-nicer-assert-messages/non-consuming-methods-have-optimized-codegen.stdout b/tests/ui/macros/rfc-2011-nicer-assert-messages/non-consuming-methods-have-optimized-codegen.stdout index 90f858f80e6b5..ad97f7a4a7540 100644 --- a/tests/ui/macros/rfc-2011-nicer-assert-messages/non-consuming-methods-have-optimized-codegen.stdout +++ b/tests/ui/macros/rfc-2011-nicer-assert-messages/non-consuming-methods-have-optimized-codegen.stdout @@ -25,8 +25,8 @@ fn arbitrary_consuming_method_for_demonstration_purposes() { { - ::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem as usize\nWith captures:\n elem = ", - "\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)])) + ::std::rt::panic_fmt(format_args!("Assertion failed: elem as usize\nWith captures:\n elem = {0:?}\n", + __capture0)) } } }; @@ -41,8 +41,8 @@ fn addr_of() { if ::core::intrinsics::unlikely(!&*__local_bind0) { (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0); { - ::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: &elem\nWith captures:\n elem = ", - "\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)])) + ::std::rt::panic_fmt(format_args!("Assertion failed: &elem\nWith captures:\n elem = {0:?}\n", + __capture0)) } } }; @@ -57,8 +57,8 @@ fn binary() { if ::core::intrinsics::unlikely(!(*__local_bind0 == 1)) { (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0); { - ::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem == 1\nWith captures:\n elem = ", - "\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)])) + ::std::rt::panic_fmt(format_args!("Assertion failed: elem == 1\nWith captures:\n elem = {0:?}\n", + __capture0)) } } }; @@ -70,8 +70,8 @@ fn binary() { if ::core::intrinsics::unlikely(!(*__local_bind0 >= 1)) { (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0); { - ::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem >= 1\nWith captures:\n elem = ", - "\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)])) + ::std::rt::panic_fmt(format_args!("Assertion failed: elem >= 1\nWith captures:\n elem = {0:?}\n", + __capture0)) } } }; @@ -83,8 +83,8 @@ fn binary() { if ::core::intrinsics::unlikely(!(*__local_bind0 > 0)) { (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0); { - ::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem > 0\nWith captures:\n elem = ", - "\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)])) + ::std::rt::panic_fmt(format_args!("Assertion failed: elem > 0\nWith captures:\n elem = {0:?}\n", + __capture0)) } } }; @@ -96,8 +96,8 @@ fn binary() { if ::core::intrinsics::unlikely(!(*__local_bind0 < 3)) { (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0); { - ::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem < 3\nWith captures:\n elem = ", - "\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)])) + ::std::rt::panic_fmt(format_args!("Assertion failed: elem < 3\nWith captures:\n elem = {0:?}\n", + __capture0)) } } }; @@ -109,8 +109,8 @@ fn binary() { if ::core::intrinsics::unlikely(!(*__local_bind0 <= 3)) { (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0); { - ::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem <= 3\nWith captures:\n elem = ", - "\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)])) + ::std::rt::panic_fmt(format_args!("Assertion failed: elem <= 3\nWith captures:\n elem = {0:?}\n", + __capture0)) } } }; @@ -122,8 +122,8 @@ fn binary() { if ::core::intrinsics::unlikely(!(*__local_bind0 != 3)) { (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0); { - ::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: elem != 3\nWith captures:\n elem = ", - "\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)])) + ::std::rt::panic_fmt(format_args!("Assertion failed: elem != 3\nWith captures:\n elem = {0:?}\n", + __capture0)) } } }; @@ -138,8 +138,8 @@ fn unary() { if ::core::intrinsics::unlikely(!**__local_bind0) { (&::core::asserting::Wrapper(__local_bind0)).try_capture(&mut __capture0); { - ::std::rt::panic_fmt(::core::fmt::Arguments::new_v1(&["Assertion failed: *elem\nWith captures:\n elem = ", - "\n"], &[::core::fmt::ArgumentV1::new_debug(&__capture0)])) + ::std::rt::panic_fmt(format_args!("Assertion failed: *elem\nWith captures:\n elem = {0:?}\n", + __capture0)) } } }; From c878caf835e8b8da1dc8cc2eb03d553e8fc5374f Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 11 Jan 2023 21:42:08 +0100 Subject: [PATCH 07/20] Make clippy compile. --- src/tools/clippy/clippy_utils/src/sugg.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs index e7879bb196e42..2d1044af17e8c 100644 --- a/src/tools/clippy/clippy_utils/src/sugg.rs +++ b/src/tools/clippy/clippy_utils/src/sugg.rs @@ -219,6 +219,7 @@ impl<'a> Sugg<'a> { | ast::ExprKind::Repeat(..) | ast::ExprKind::Ret(..) | ast::ExprKind::Yeet(..) + | ast::ExprKind::FormatArgs(..) | ast::ExprKind::Struct(..) | ast::ExprKind::Try(..) | ast::ExprKind::TryBlock(..) From a1725fa1d51d92b427edb1e92e62badcfe7787dd Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 11 Jan 2023 22:25:29 +0100 Subject: [PATCH 08/20] Update rustfmt for ast::ExprKind::FormatArgs. Rustfmt doesn't expand macros, so that's easy: FormatArgs nodes do not occur in the unexpanded AST. --- src/tools/rustfmt/src/expr.rs | 5 ++++- src/tools/rustfmt/src/utils.rs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/tools/rustfmt/src/expr.rs b/src/tools/rustfmt/src/expr.rs index 414e767690bd0..cdbdcedd58fea 100644 --- a/src/tools/rustfmt/src/expr.rs +++ b/src/tools/rustfmt/src/expr.rs @@ -399,7 +399,10 @@ pub(crate) fn format_expr( } } ast::ExprKind::Underscore => Some("_".to_owned()), - ast::ExprKind::IncludedBytes(..) => unreachable!(), + ast::ExprKind::FormatArgs(..) | ast::ExprKind::IncludedBytes(..) => { + // These do not occur in the AST because macros aren't expanded. + unreachable!() + } ast::ExprKind::Err => None, }; diff --git a/src/tools/rustfmt/src/utils.rs b/src/tools/rustfmt/src/utils.rs index 3e884419f1a32..80dce525e3957 100644 --- a/src/tools/rustfmt/src/utils.rs +++ b/src/tools/rustfmt/src/utils.rs @@ -462,6 +462,7 @@ pub(crate) fn first_line_ends_with(s: &str, c: char) -> bool { pub(crate) fn is_block_expr(context: &RewriteContext<'_>, expr: &ast::Expr, repr: &str) -> bool { match expr.kind { ast::ExprKind::MacCall(..) + | ast::ExprKind::FormatArgs(..) | ast::ExprKind::Call(..) | ast::ExprKind::MethodCall(..) | ast::ExprKind::Array(..) From 7a3d5fe8422242af734766c4a5001ad6e14059a7 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 11 Jan 2023 22:33:13 +0100 Subject: [PATCH 09/20] Bless pretty tests. --- tests/pretty/dollar-crate.pp | 4 +--- tests/pretty/issue-4264.pp | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/pretty/dollar-crate.pp b/tests/pretty/dollar-crate.pp index 3af37955f2380..60fddb630d964 100644 --- a/tests/pretty/dollar-crate.pp +++ b/tests/pretty/dollar-crate.pp @@ -8,6 +8,4 @@ // pretty-mode:expanded // pp-exact:dollar-crate.pp -fn main() { - { ::std::io::_print(::core::fmt::Arguments::new_v1(&["rust\n"], &[])); }; -} +fn main() { { ::std::io::_print(format_args!("rust\n")); }; } diff --git a/tests/pretty/issue-4264.pp b/tests/pretty/issue-4264.pp index 18e6d75b1d5ad..44d21625a2d10 100644 --- a/tests/pretty/issue-4264.pp +++ b/tests/pretty/issue-4264.pp @@ -32,7 +32,7 @@ ({ let res = ((::alloc::fmt::format as - for<'a> fn(Arguments<'a>) -> String {format})(((::core::fmt::Arguments::new_v1 + for<'a> fn(Arguments<'a>) -> String {format})(((<#[lang = "format_arguments"]>::new_v1 as fn(&[&'static str], &[ArgumentV1<'_>]) -> Arguments<'_> {Arguments::<'_>::new_v1})((&([("test" as &str)] as [&str; 1]) as &[&str; 1]), From 36fe3b971dbf23bbb362f79006f7c8571818eb5d Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 12 Jan 2023 00:25:09 +0100 Subject: [PATCH 10/20] Update clippy for new format_args!() lang items. --- .../clippy/clippy_lints/src/format_args.rs | 6 +-- src/tools/clippy/clippy_utils/src/macros.rs | 54 +++++++++++-------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/tools/clippy/clippy_lints/src/format_args.rs b/src/tools/clippy/clippy_lints/src/format_args.rs index 043112bbc9596..70a80d40f464b 100644 --- a/src/tools/clippy/clippy_lints/src/format_args.rs +++ b/src/tools/clippy/clippy_lints/src/format_args.rs @@ -7,14 +7,14 @@ use clippy_utils::macros::{ }; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::snippet_opt; -use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; +use clippy_utils::ty::{implements_trait, is_type_lang_item}; use if_chain::if_chain; use itertools::Itertools; use rustc_errors::{ Applicability, SuggestionStyle::{CompletelyHidden, ShowCode}, }; -use rustc_hir::{Expr, ExprKind, HirId, QPath}; +use rustc_hir::{Expr, ExprKind, HirId, LangItem, QPath}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::adjustment::{Adjust, Adjustment}; use rustc_middle::ty::Ty; @@ -237,7 +237,7 @@ fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) { ); } - if is_type_diagnostic_item(cx, param_ty, sym::Arguments) && !arg.format.is_default_for_trait() { + if is_type_lang_item(cx, param_ty, LangItem::FormatArguments) && !arg.format.is_default_for_trait() { span_lint_and_then( cx, UNUSED_FORMAT_SPECS, diff --git a/src/tools/clippy/clippy_utils/src/macros.rs b/src/tools/clippy/clippy_utils/src/macros.rs index 77c5f1155423c..a8f8da67b5171 100644 --- a/src/tools/clippy/clippy_utils/src/macros.rs +++ b/src/tools/clippy/clippy_utils/src/macros.rs @@ -1,6 +1,5 @@ #![allow(clippy::similar_names)] // `expr` and `expn` -use crate::is_path_diagnostic_item; use crate::source::snippet_opt; use crate::visitors::{for_each_expr, Descend}; @@ -8,7 +7,7 @@ use arrayvec::ArrayVec; use itertools::{izip, Either, Itertools}; use rustc_ast::ast::LitKind; use rustc_hir::intravisit::{walk_expr, Visitor}; -use rustc_hir::{self as hir, Expr, ExprField, ExprKind, HirId, Node, QPath}; +use rustc_hir::{self as hir, Expr, ExprField, ExprKind, HirId, LangItem, Node, QPath, TyKind}; use rustc_lexer::unescape::unescape_literal; use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind}; use rustc_lint::LateContext; @@ -439,8 +438,7 @@ impl<'tcx> FormatArgsValues<'tcx> { // ArgumentV1::from_usize() if let ExprKind::Call(callee, [val]) = expr.kind && let ExprKind::Path(QPath::TypeRelative(ty, _)) = callee.kind - && let hir::TyKind::Path(QPath::Resolved(_, path)) = ty.kind - && path.segments.last().unwrap().ident.name == sym::ArgumentV1 + && let TyKind::Path(QPath::LangItem(LangItem::FormatArgument, _, _)) = ty.kind { let val_idx = if val.span.ctxt() == expr.span.ctxt() && let ExprKind::Field(_, field) = val.kind @@ -486,20 +484,6 @@ struct ParamPosition { impl<'tcx> Visitor<'tcx> for ParamPosition { fn visit_expr_field(&mut self, field: &'tcx ExprField<'tcx>) { - fn parse_count(expr: &Expr<'_>) -> Option { - // ::core::fmt::rt::v1::Count::Param(1usize), - if let ExprKind::Call(ctor, [val]) = expr.kind - && let ExprKind::Path(QPath::Resolved(_, path)) = ctor.kind - && path.segments.last()?.ident.name == sym::Param - && let ExprKind::Lit(lit) = &val.kind - && let LitKind::Int(pos, _) = lit.node - { - Some(pos as usize) - } else { - None - } - } - match field.ident.name { sym::position => { if let ExprKind::Lit(lit) = &field.expr.kind @@ -519,15 +503,41 @@ impl<'tcx> Visitor<'tcx> for ParamPosition { } } +fn parse_count(expr: &Expr<'_>) -> Option { + // <::core::fmt::rt::v1::Count>::Param(1usize), + if let ExprKind::Call(ctor, [val]) = expr.kind + && let ExprKind::Path(QPath::TypeRelative(_, path)) = ctor.kind + && path.ident.name == sym::Param + && let ExprKind::Lit(lit) = &val.kind + && let LitKind::Int(pos, _) = lit.node + { + Some(pos as usize) + } else { + None + } +} + /// Parses the `fmt` arg of `Arguments::new_v1_formatted(pieces, args, fmt, _)` fn parse_rt_fmt<'tcx>(fmt_arg: &'tcx Expr<'tcx>) -> Option + 'tcx> { if let ExprKind::AddrOf(.., array) = fmt_arg.kind && let ExprKind::Array(specs) = array.kind { Some(specs.iter().map(|spec| { - let mut position = ParamPosition::default(); - position.visit_expr(spec); - position + if let ExprKind::Call(f, args) = spec.kind + && let ExprKind::Path(QPath::TypeRelative(ty, f)) = f.kind + && let TyKind::Path(QPath::LangItem(LangItem::FormatPlaceholder, _, _)) = ty.kind + && f.ident.name == sym::new + && let [position, _fill, _align, _flags, precision, width] = args + && let ExprKind::Lit(position) = &position.kind + && let LitKind::Int(position, _) = position.node { + ParamPosition { + value: position as usize, + width: parse_count(width), + precision: parse_count(precision), + } + } else { + ParamPosition::default() + } })) } else { None @@ -890,7 +900,7 @@ impl<'tcx> FormatArgsExpn<'tcx> { // ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg) if let ExprKind::Call(callee, [pieces, args, rest @ ..]) = expr.kind && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind - && is_path_diagnostic_item(cx, ty, sym::Arguments) + && let TyKind::Path(QPath::LangItem(LangItem::FormatArguments, _, _)) = ty.kind && matches!(seg.ident.as_str(), "new_v1" | "new_v1_formatted") { let format_string = FormatString::new(cx, pieces)?; From 9e6785430b2253315a4ce31d03464a30c250ac38 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 12 Jan 2023 00:31:17 +0100 Subject: [PATCH 11/20] Make core::fmt::rt::v1::Argument::new const+inline. --- library/core/src/fmt/rt/v1.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/core/src/fmt/rt/v1.rs b/library/core/src/fmt/rt/v1.rs index e61b89fad5d3b..11a50951a75da 100644 --- a/library/core/src/fmt/rt/v1.rs +++ b/library/core/src/fmt/rt/v1.rs @@ -23,7 +23,8 @@ pub struct FormatSpec { } impl Argument { - pub fn new( + #[inline(always)] + pub const fn new( position: usize, fill: char, align: Alignment, From bcf388f4ac3261d1cc0490415a81d5e4939c7915 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 12 Jan 2023 00:39:46 +0100 Subject: [PATCH 12/20] Update outdated comment. --- compiler/rustc_ast_lowering/src/format.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index f8ed164b356f1..f21c0f23bc114 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -94,16 +94,14 @@ fn make_format_spec<'hir>( argmap: &mut FxIndexSet<(usize, ArgumentType)>, ) -> hir::Expr<'hir> { // Generate: - // ::core::fmt::rt::v1::Argument { - // position: 0usize, - // format: ::core::fmt::rt::v1::FormatSpec { - // fill: ' ', - // align: ::core::fmt::rt::v1::Alignment::Unknown, - // flags: 0u32, - // precision: ::core::fmt::rt::v1::Count::Implied, - // width: ::core::fmt::rt::v1::Count::Implied, - // }, - // } + // ::core::fmt::rt::v1::Argument::new( + // 0usize, // position + // ' ', // fill + // ::core::fmt::rt::v1::Alignment::Unknown, + // 0u32, // flags + // ::core::fmt::rt::v1::Count::Implied, // width + // ::core::fmt::rt::v1::Count::Implied, // precision + // ) let position = match placeholder.argument.index { Ok(arg_index) => { let (i, _) = From 56593104acef6afc0f3d29c765801bb44ec02c65 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 12 Jan 2023 00:45:29 +0100 Subject: [PATCH 13/20] Update comment explaining format_args!() expansion. --- compiler/rustc_builtin_macros/src/format.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 47b63a7fa122f..a246aefc806a2 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -20,11 +20,11 @@ use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, LintId}; // 1. First, `parse_args` will parse the `(literal, arg, arg, name=arg, name=arg)` syntax, // but doesn't parse the template (the literal) itself. // 2. Second, `make_format_args` will parse the template, the format options, resolve argument references, -// produce diagnostics, and turn the whole thing into a `FormatArgs` structure. -// 3. Finally, `expand_parsed_format_args` will turn that `FormatArgs` structure -// into the expression that the macro expands to. +// produce diagnostics, and turn the whole thing into a `FormatArgs` AST node. +// 3. Much later, in AST lowering (rustc_ast_lowering), that `FormatArgs` structure will be turned +// into the expression of type `core::fmt::Arguments`. -// See format/ast.rs for the FormatArgs structure and glossary. +// See rustc_ast/src/format.rs for the FormatArgs structure and glossary. // Only used in parse_args and report_invalid_references, // to indicate how a referred argument was used. From 298e160dc88dab90a83799973e121a18fed2ea38 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 12 Jan 2023 19:58:49 +0100 Subject: [PATCH 14/20] Assume there are no macros left in ast lowering. --- compiler/rustc_ast_lowering/src/format.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index f21c0f23bc114..693b9a399cfb4 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -334,13 +334,8 @@ fn may_contain_yield_point(e: &ast::Expr) -> bool { } fn visit_mac_call(&mut self, _: &ast::MacCall) { - self.0 = true; - } - - fn visit_attribute(&mut self, _: &ast::Attribute) { - // Conservatively assume this may be a proc macro attribute in - // expression position. - self.0 = true; + // Macros should be expanded at this point. + unreachable!("unexpanded macro in ast lowering"); } fn visit_item(&mut self, _: &ast::Item) { From 0a1934a32c51609be92259dca32af394087989d3 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 12 Jan 2023 19:59:03 +0100 Subject: [PATCH 15/20] Add FIXME comments about asm and format_args ast_pretty. --- compiler/rustc_ast_pretty/src/pprust/state/expr.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 03beae3a45bb1..500dc9da8aef8 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -527,10 +527,12 @@ impl<'a> State<'a> { } } ast::ExprKind::InlineAsm(a) => { + // FIXME: This should have its own syntax, distinct from a macro invocation. self.word("asm!"); self.print_inline_asm(a); } ast::ExprKind::FormatArgs(fmt) => { + // FIXME: This should have its own syntax, distinct from a macro invocation. self.word("format_args!"); self.popen(); self.rbox(0, Inconsistent); From 8a23ad17f8fa4f11335eb51215234af95cb9821d Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 12 Jan 2023 20:06:38 +0100 Subject: [PATCH 16/20] Update comments in rustc_ast_lowering/src/format.rs. --- compiler/rustc_ast_lowering/src/format.rs | 73 ++++++++++++++++------- 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index 693b9a399cfb4..d5da1a666f90f 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -22,14 +22,19 @@ enum ArgumentType { Usize, } +/// Generate a hir expression representing an argument to a format_args invocation. +/// +/// Generates: +/// +/// ```text +/// ::new_…(arg) +/// ``` fn make_argument<'hir>( ctx: &mut LoweringContext<'_, 'hir>, sp: Span, arg: &'hir hir::Expr<'hir>, ty: ArgumentType, ) -> hir::Expr<'hir> { - // Generate: - // ::core::fmt::ArgumentV1::new_…(arg) use ArgumentType::*; use FormatTrait::*; let new_fn = ctx.arena.alloc(ctx.expr_lang_item_type_relative( @@ -51,14 +56,31 @@ fn make_argument<'hir>( ctx.expr_call_mut(sp, new_fn, std::slice::from_ref(arg)) } +/// Generate a hir expression for a format_args Count. +/// +/// Generates: +/// +/// ```text +/// ::Is(…) +/// ``` +/// +/// or +/// +/// ```text +/// ::Param(…) +/// ``` +/// +/// or +/// +/// ```text +/// ::Implied +/// ``` fn make_count<'hir>( ctx: &mut LoweringContext<'_, 'hir>, sp: Span, count: &Option, argmap: &mut FxIndexSet<(usize, ArgumentType)>, ) -> hir::Expr<'hir> { - // Generate: - // ::core::fmt::rt::v1::Count::…(…) match count { Some(FormatCount::Literal(n)) => { let count_is = ctx.arena.alloc(ctx.expr_lang_item_type_relative( @@ -87,21 +109,26 @@ fn make_count<'hir>( } } +/// Generate a hir expression for a format_args placeholder specification. +/// +/// Generates +/// +/// ```text +/// ::…, // alignment +/// …u32, // flags +/// , // width +/// , // precision +/// ) +/// ``` fn make_format_spec<'hir>( ctx: &mut LoweringContext<'_, 'hir>, sp: Span, placeholder: &FormatPlaceholder, argmap: &mut FxIndexSet<(usize, ArgumentType)>, ) -> hir::Expr<'hir> { - // Generate: - // ::core::fmt::rt::v1::Argument::new( - // 0usize, // position - // ' ', // fill - // ::core::fmt::rt::v1::Alignment::Unknown, - // 0u32, // flags - // ::core::fmt::rt::v1::Count::Implied, // width - // ::core::fmt::rt::v1::Count::Implied, // precision - // ) let position = match placeholder.argument.index { Ok(arg_index) => { let (i, _) = @@ -203,9 +230,10 @@ fn expand_format_args<'hir>( let args = if use_simple_array { // Generate: // &[ - // ::core::fmt::ArgumentV1::new_display(&arg0), - // ::core::fmt::ArgumentV1::new_lower_hex(&arg1), - // ::core::fmt::ArgumentV1::new_debug(&arg2), + // ::new_display(&arg0), + // ::new_lower_hex(&arg1), + // ::new_debug(&arg2), + // … // ] let elements: Vec<_> = arguments .iter() @@ -223,11 +251,12 @@ fn expand_format_args<'hir>( ctx.expr_array_ref(macsp, ctx.arena.alloc_from_iter(elements)) } else { // Generate: - // &match (&arg0, &arg1, &arg2) { + // &match (&arg0, &arg1, &…) { // args => [ - // ::core::fmt::ArgumentV1::new_display(args.0), - // ::core::fmt::ArgumentV1::new_lower_hex(args.1), - // ::core::fmt::ArgumentV1::new_debug(args.0), + // ::new_display(args.0), + // ::new_lower_hex(args.1), + // ::new_debug(args.0), + // … // ] // } let args_ident = Ident::new(sym::args, macsp); @@ -277,7 +306,7 @@ fn expand_format_args<'hir>( if let Some(format_options) = format_options { // Generate: - // ::core::fmt::Arguments::new_v1_formatted( + // ::new_v1_formatted( // lit_pieces, // args, // format_options, @@ -307,7 +336,7 @@ fn expand_format_args<'hir>( hir::ExprKind::Call(new_v1_formatted, args) } else { // Generate: - // ::core::fmt::Arguments::new_v1( + // ::new_v1( // lit_pieces, // args, // ) From cd8ec6c787e147667e3bf43145873dd8e27755d0 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 12 Jan 2023 20:14:31 +0100 Subject: [PATCH 17/20] Add note on optimization in format args ast lowering. --- compiler/rustc_ast_lowering/src/format.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index d5da1a666f90f..8985f8ad85daf 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -223,6 +223,9 @@ fn expand_format_args<'hir>( // in order, we can use a simple array instead of a `match` construction. // However, if there's a yield point in any argument except the first one, // we don't do this, because an ArgumentV1 cannot be kept across yield points. + // + // This is an optimization, speeding up compilation about 1-2% in some cases. + // See https://perf.rust-lang.org/compare.html?start=5dbee4d3a6728eb4530fb66c9775834438ecec74&end=e36affffe97378a0027b4bcfbb18d27356164ed0&stat=instructions:u let use_simple_array = argmap.len() == arguments.len() && argmap.iter().enumerate().all(|(i, &(j, _))| i == j) && arguments.iter().skip(1).all(|arg| !may_contain_yield_point(&arg.expr)); From 9f2ba59647b2cc70ce9467f2731e707459bf388f Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Fri, 13 Jan 2023 22:23:59 +0100 Subject: [PATCH 18/20] Change not-so-permanent link to a more permanent link. --- compiler/rustc_ast_lowering/src/format.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index 8985f8ad85daf..776b532b0de86 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -225,7 +225,7 @@ fn expand_format_args<'hir>( // we don't do this, because an ArgumentV1 cannot be kept across yield points. // // This is an optimization, speeding up compilation about 1-2% in some cases. - // See https://perf.rust-lang.org/compare.html?start=5dbee4d3a6728eb4530fb66c9775834438ecec74&end=e36affffe97378a0027b4bcfbb18d27356164ed0&stat=instructions:u + // See https://github.com/rust-lang/rust/pull/106770#issuecomment-1380790609 let use_simple_array = argmap.len() == arguments.len() && argmap.iter().enumerate().all(|(i, &(j, _))| i == j) && arguments.iter().skip(1).all(|arg| !may_contain_yield_point(&arg.expr)); From 7b077307fd156275001ce5ca7a53e250eabcb0b6 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Sat, 14 Jan 2023 14:16:26 +0100 Subject: [PATCH 19/20] Fix ast_pretty for format_args for {:x?} and {:X?}. They were accidentally swapped. --- compiler/rustc_ast_pretty/src/pprust/state/expr.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 500dc9da8aef8..5cc66ff54eda5 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -708,10 +708,10 @@ pub fn reconstruct_format_args_template_string(pieces: &[FormatArgsPiece]) -> St } } if flags >> (rustc_parse_format::FlagDebugLowerHex as usize) & 1 != 0 { - template.push('X'); + template.push('x'); } if flags >> (rustc_parse_format::FlagDebugUpperHex as usize) & 1 != 0 { - template.push('x'); + template.push('X'); } template.push_str(match p.format_trait { FormatTrait::Display => "", From db731e42b3c139587cd7a87acaa92cd82ab8a11c Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Tue, 24 Jan 2023 16:44:00 +0100 Subject: [PATCH 20/20] Work around issue 106930. --- compiler/rustc_ast/src/format.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/rustc_ast/src/format.rs b/compiler/rustc_ast/src/format.rs index ce99c2b58b570..da05b09b37dcc 100644 --- a/compiler/rustc_ast/src/format.rs +++ b/compiler/rustc_ast/src/format.rs @@ -67,6 +67,12 @@ pub struct FormatArguments { names: FxHashMap, } +// FIXME: Rustdoc has trouble proving Send/Sync for this. See #106930. +#[cfg(parallel_compiler)] +unsafe impl Sync for FormatArguments {} +#[cfg(parallel_compiler)] +unsafe impl Send for FormatArguments {} + impl FormatArguments { pub fn new() -> Self { Self {