diff options
19 files changed, 535 insertions, 384 deletions
| diff --git a/Cargo.lock b/Cargo.lock index 2a88152b519..faf4fd05c35 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 e656fb3740b..5b1722dffb9 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<FormatArgs>), + /// 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 index 01dbffa21b8..ce99c2b58b5 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<FormatArgsPiece>, @@ -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<FormatArgument>, num_unnamed_args: usize, @@ -121,18 +121,22 @@ impl FormatArguments { &self.arguments[..self.num_explicit_args] } - pub fn into_vec(self) -> Vec<FormatArgument> { - 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<Expr>, } -#[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<Span>, } -#[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<FormatCount>, @@ -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 9c1dfeb1a61..0f8ebcfdc15 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 c572171e8f4..561274cc6f9 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<T: MutVisitor>( vis.visit_path(path); } +pub fn noop_visit_format_args<T: MutVisitor>(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<T: MutVisitor>( Expr { kind, id, span, attrs, tokens }: &mut Expr, vis: &mut T, @@ -1423,6 +1436,7 @@ pub fn noop_visit_expr<T: MutVisitor>( 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 819f1884a06..2db2ab5e811 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 df7145a722a..cb5c17084ec 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 51f290bc587..5e0f1b9b61f 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 00000000000..f8ed164b356 --- /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<FormatCount>, + 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 2e135aafb1e..01aba94fe39 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 a3e3e823b08..b4900dc39a8 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 3b17f6dd627..03beae3a45b 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 93b07801e03..342b1735661 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 b2b7b9d75bd..47b63a7fa12 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 9dde5efcb28..00000000000 --- 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<ast::Expr>, ty: ArgumentType) -> P<ast::Expr> { - // 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<FormatCount>, - argmap: &mut FxIndexSet<(usize, ArgumentType)>, -) -> P<ast::Expr> { - // 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<ast::Expr> { - // 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<ast::Expr> { - 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 bc897ed8112..c827600179d 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 `<expr>.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 e7ee0d9e908..2b52c410ecd 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 aa726d6cd92..dd8c646a43c 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 b86d2316820..d1b896e940e 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) | 
