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 }