Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-def/src/expr_store/lower/format_args.rs')
| -rw-r--r-- | crates/hir-def/src/expr_store/lower/format_args.rs | 1012 |
1 files changed, 1012 insertions, 0 deletions
diff --git a/crates/hir-def/src/expr_store/lower/format_args.rs b/crates/hir-def/src/expr_store/lower/format_args.rs new file mode 100644 index 0000000000..4bbfc5b144 --- /dev/null +++ b/crates/hir-def/src/expr_store/lower/format_args.rs @@ -0,0 +1,1012 @@ +//! Lowering of `format_args!()`. + +use base_db::FxIndexSet; +use hir_expand::name::{AsName, Name}; +use intern::{Symbol, sym}; +use syntax::{ + AstPtr, AstToken as _, + ast::{self, HasName}, +}; + +use crate::{ + builtin_type::BuiltinUint, + expr_store::{HygieneId, lower::ExprCollector, path::Path}, + hir::{ + Array, BindingAnnotation, Expr, ExprId, Literal, Pat, RecordLitField, Statement, + format_args::{ + self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind, + FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions, + FormatPlaceholder, FormatSign, FormatTrait, + }, + }, + lang_item::LangItemTarget, + type_ref::{Mutability, Rawness}, +}; + +impl<'db> ExprCollector<'db> { + pub(super) fn collect_format_args( + &mut self, + f: ast::FormatArgsExpr, + syntax_ptr: AstPtr<ast::Expr>, + ) -> ExprId { + let mut args = FormatArgumentsCollector::default(); + f.args().for_each(|arg| { + args.add(FormatArgument { + kind: match arg.name() { + Some(name) => FormatArgumentKind::Named(name.as_name()), + None => FormatArgumentKind::Normal, + }, + expr: self.collect_expr_opt(arg.expr()), + }); + }); + let template = f.template(); + let fmt_snippet = template.as_ref().and_then(|it| match it { + ast::Expr::Literal(literal) => match literal.kind() { + ast::LiteralKind::String(s) => Some(s.text().to_owned()), + _ => None, + }, + _ => None, + }); + let mut mappings = vec![]; + let (fmt, hygiene) = match template.and_then(|template| { + self.expand_macros_to_string(template.clone()).map(|it| (it, template)) + }) { + Some(((s, is_direct_literal), template)) => { + let call_ctx = self.expander.call_syntax_ctx(); + let hygiene = self.hygiene_id_for(s.syntax().text_range()); + let fmt = format_args::parse( + &s, + fmt_snippet, + args, + is_direct_literal, + |name, range| { + let expr_id = self.alloc_expr_desugared(Expr::Path(Path::from(name))); + if let Some(range) = range { + self.store + .template_map + .get_or_insert_with(Default::default) + .implicit_capture_to_source + .insert( + expr_id, + self.expander.in_file((AstPtr::new(&template), range)), + ); + } + if !hygiene.is_root() { + self.store.ident_hygiene.insert(expr_id.into(), hygiene); + } + expr_id + }, + |name, span| { + if let Some(span) = span { + mappings.push((span, name)) + } + }, + call_ctx, + ); + (fmt, hygiene) + } + None => ( + FormatArgs { + template: Default::default(), + arguments: args.finish(), + orphans: Default::default(), + }, + HygieneId::ROOT, + ), + }; + + let idx = if self.lang_items().FormatCount.is_none() { + self.collect_format_args_after_1_93_0_impl(syntax_ptr, fmt) + } else { + self.collect_format_args_before_1_93_0_impl(syntax_ptr, fmt) + }; + + self.store + .template_map + .get_or_insert_with(Default::default) + .format_args_to_captures + .insert(idx, (hygiene, mappings)); + idx + } + + fn collect_format_args_after_1_93_0_impl( + &mut self, + syntax_ptr: AstPtr<ast::Expr>, + fmt: FormatArgs, + ) -> ExprId { + let lang_items = self.lang_items(); + + // Create a list of all _unique_ (argument, format trait) combinations. + // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)] + // + // We use usize::MAX for arguments that don't exist, because that can never be a valid index + // into the arguments array. + let mut argmap = FxIndexSet::default(); + + let mut incomplete_lit = String::new(); + + let mut implicit_arg_index = 0; + + let mut bytecode = Vec::new(); + + let template = if fmt.template.is_empty() { + // Treat empty templates as a single literal piece (with an empty string), + // so we produce `from_str("")` for those. + &[FormatArgsPiece::Literal(sym::__empty)][..] + } else { + &fmt.template[..] + }; + + // See library/core/src/fmt/mod.rs for the format string encoding format. + + for (i, piece) in template.iter().enumerate() { + match piece { + FormatArgsPiece::Literal(sym) => { + // Coalesce adjacent literal pieces. + if let Some(FormatArgsPiece::Literal(_)) = template.get(i + 1) { + incomplete_lit.push_str(sym.as_str()); + continue; + } + let mut s = if incomplete_lit.is_empty() { + sym.as_str() + } else { + incomplete_lit.push_str(sym.as_str()); + &incomplete_lit + }; + + // If this is the last piece and was the only piece, that means + // there are no placeholders and the entire format string is just a literal. + // + // In that case, we can just use `from_str`. + if i + 1 == template.len() && bytecode.is_empty() { + // Generate: + // <core::fmt::Arguments>::from_str("meow") + let from_str = self.ty_rel_lang_path_desugared_expr( + lang_items.FormatArguments, + sym::from_str, + ); + let sym = + if incomplete_lit.is_empty() { sym.clone() } else { Symbol::intern(s) }; + let s = self.alloc_expr_desugared(Expr::Literal(Literal::String(sym))); + let from_str = self.alloc_expr( + Expr::Call { callee: from_str, args: Box::new([s]) }, + syntax_ptr, + ); + return if !fmt.arguments.arguments.is_empty() { + // With an incomplete format string (e.g. only an opening `{`), it's possible for `arguments` + // to be non-empty when reaching this code path. + self.alloc_expr( + Expr::Block { + id: None, + statements: fmt + .arguments + .arguments + .iter() + .map(|arg| Statement::Expr { + expr: arg.expr, + has_semi: true, + }) + .collect(), + tail: Some(from_str), + label: None, + }, + syntax_ptr, + ) + } else { + from_str + }; + } + + // Encode the literal in chunks of up to u16::MAX bytes, split at utf-8 boundaries. + while !s.is_empty() { + let len = s.floor_char_boundary(usize::from(u16::MAX)); + if len < 0x80 { + bytecode.push(len as u8); + } else { + bytecode.push(0x80); + bytecode.extend_from_slice(&(len as u16).to_le_bytes()); + } + bytecode.extend(&s.as_bytes()[..len]); + s = &s[len..]; + } + + incomplete_lit.clear(); + } + FormatArgsPiece::Placeholder(p) => { + // Push the start byte and remember its index so we can set the option bits later. + let i = bytecode.len(); + bytecode.push(0xC0); + + let position = match &p.argument.index { + &Ok(it) => it, + Err(_) => usize::MAX, + }; + let position = argmap + .insert_full((position, ArgumentType::Format(p.format_trait))) + .0 as u64; + + // This needs to match the constants in library/core/src/fmt/mod.rs. + let o = &p.format_options; + let align = match o.alignment { + Some(FormatAlignment::Left) => 0, + Some(FormatAlignment::Right) => 1, + Some(FormatAlignment::Center) => 2, + None => 3, + }; + let default_flags = 0x6000_0020; + let flags: u32 = o.fill.unwrap_or(' ') as u32 + | ((o.sign == Some(FormatSign::Plus)) as u32) << 21 + | ((o.sign == Some(FormatSign::Minus)) as u32) << 22 + | (o.alternate as u32) << 23 + | (o.zero_pad as u32) << 24 + | ((o.debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25 + | ((o.debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26 + | (o.width.is_some() as u32) << 27 + | (o.precision.is_some() as u32) << 28 + | align << 29; + if flags != default_flags { + bytecode[i] |= 1; + bytecode.extend_from_slice(&flags.to_le_bytes()); + if let Some(val) = &o.width { + let (indirect, val) = self.make_count_after_1_93_0(val, &mut argmap); + // Only encode if nonzero; zero is the default. + if indirect || val != 0 { + bytecode[i] |= 1 << 1 | (indirect as u8) << 4; + bytecode.extend_from_slice(&val.to_le_bytes()); + } + } + if let Some(val) = &o.precision { + let (indirect, val) = self.make_count_after_1_93_0(val, &mut argmap); + // Only encode if nonzero; zero is the default. + if indirect || val != 0 { + bytecode[i] |= 1 << 2 | (indirect as u8) << 5; + bytecode.extend_from_slice(&val.to_le_bytes()); + } + } + } + if implicit_arg_index != position { + bytecode[i] |= 1 << 3; + bytecode.extend_from_slice(&(position as u16).to_le_bytes()); + } + implicit_arg_index = position + 1; + } + } + } + + assert!(incomplete_lit.is_empty()); + + // Zero terminator. + bytecode.push(0); + + // Ensure all argument indexes actually fit in 16 bits, as we truncated them to 16 bits before. + if argmap.len() > u16::MAX as usize { + // FIXME: Emit an error. + // ctx.dcx().span_err(macsp, "too many format arguments"); + } + + let arguments = &fmt.arguments.arguments[..]; + + let (mut statements, args) = if arguments.is_empty() { + // Generate: + // [] + ( + Vec::new(), + self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements: Box::new([]), + })), + ) + } else { + // Generate: + // super let args = (&arg0, &arg1, &…); + let args_name = self.generate_new_name(); + let args_path = Path::from(args_name.clone()); + let args_binding = self.alloc_binding( + args_name.clone(), + BindingAnnotation::Unannotated, + HygieneId::ROOT, + ); + let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None }); + self.add_definition_to_binding(args_binding, args_pat); + let elements = arguments + .iter() + .map(|arg| { + self.alloc_expr_desugared(Expr::Ref { + expr: arg.expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + }) + .collect(); + let args_tuple = self.alloc_expr_desugared(Expr::Tuple { exprs: elements }); + // FIXME: Make this a `super let` when we have this statement. + let let_statement_1 = Statement::Let { + pat: args_pat, + type_ref: None, + initializer: Some(args_tuple), + else_branch: None, + }; + + // Generate: + // super let args = [ + // <core::fmt::Argument>::new_display(args.0), + // <core::fmt::Argument>::new_lower_hex(args.1), + // <core::fmt::Argument>::new_debug(args.0), + // … + // ]; + let args = argmap + .iter() + .map(|&(arg_index, ty)| { + let args_ident_expr = self.alloc_expr_desugared(Expr::Path(args_path.clone())); + let arg = self.alloc_expr_desugared(Expr::Field { + expr: args_ident_expr, + name: Name::new_tuple_field(arg_index), + }); + self.make_argument(arg, ty) + }) + .collect(); + let args = + self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args })); + let args_binding = + self.alloc_binding(args_name, BindingAnnotation::Unannotated, HygieneId::ROOT); + let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None }); + self.add_definition_to_binding(args_binding, args_pat); + // FIXME: Make this a `super let` when we have this statement. + let let_statement_2 = Statement::Let { + pat: args_pat, + type_ref: None, + initializer: Some(args), + else_branch: None, + }; + ( + vec![let_statement_1, let_statement_2], + self.alloc_expr_desugared(Expr::Path(args_path)), + ) + }; + + // Generate: + // unsafe { + // <core::fmt::Arguments>::new(b"…", &args) + // } + let template = self + .alloc_expr_desugared(Expr::Literal(Literal::ByteString(bytecode.into_boxed_slice()))); + let call = { + let new = self.ty_rel_lang_path_desugared_expr(lang_items.FormatArguments, sym::new); + let args = self.alloc_expr_desugared(Expr::Ref { + expr: args, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + self.alloc_expr_desugared(Expr::Call { callee: new, args: Box::new([template, args]) }) + }; + let call = self.alloc_expr( + Expr::Unsafe { id: None, statements: Box::new([]), tail: Some(call) }, + syntax_ptr, + ); + + // We collect the unused expressions here so that we still infer them instead of + // dropping them out of the expression tree. We cannot store them in the `Unsafe` + // block because then unsafe blocks within them will get a false "unused unsafe" + // diagnostic (rustc has a notion of builtin unsafe blocks, but we don't). + statements + .extend(fmt.orphans.into_iter().map(|expr| Statement::Expr { expr, has_semi: true })); + + if !statements.is_empty() { + // Generate: + // { + // super let … + // super let … + // <core::fmt::Arguments>::new(…) + // } + self.alloc_expr( + Expr::Block { + id: None, + statements: statements.into_boxed_slice(), + tail: Some(call), + label: None, + }, + syntax_ptr, + ) + } else { + call + } + } + + /// Get the value for a `width` or `precision` field. + /// + /// Returns the value and whether it is indirect (an indexed argument) or not. + fn make_count_after_1_93_0( + &self, + count: &FormatCount, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, + ) -> (bool, u16) { + match count { + FormatCount::Literal(n) => (false, *n), + FormatCount::Argument(arg) => { + let index = match &arg.index { + &Ok(it) => it, + Err(_) => usize::MAX, + }; + (true, argmap.insert_full((index, ArgumentType::Usize)).0 as u16) + } + } + } + + fn collect_format_args_before_1_93_0_impl( + &mut self, + syntax_ptr: AstPtr<ast::Expr>, + fmt: FormatArgs, + ) -> ExprId { + // 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.iter() { + let FormatArgsPiece::Placeholder(placeholder) = piece else { continue }; + if let Ok(index) = placeholder.argument.index { + argmap.insert((index, ArgumentType::Format(placeholder.format_trait))); + } + } + + let lit_pieces = fmt + .template + .iter() + .enumerate() + .filter_map(|(i, piece)| { + match piece { + FormatArgsPiece::Literal(s) => { + Some(self.alloc_expr_desugared(Expr::Literal(Literal::String(s.clone())))) + } + &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(self.alloc_expr_desugared(Expr::Literal(Literal::String( + Symbol::empty(), + )))) + } else { + None + } + } + } + }) + .collect(); + let lit_pieces = + self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: lit_pieces })); + let lit_pieces = self.alloc_expr_desugared(Expr::Ref { + expr: lit_pieces, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + let format_options = { + // Generate: + // &[format_spec_0, format_spec_1, format_spec_2] + let elements = fmt + .template + .iter() + .filter_map(|piece| { + let FormatArgsPiece::Placeholder(placeholder) = piece else { return None }; + Some(self.make_format_spec(placeholder, &mut argmap)) + }) + .collect(); + let array = self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements })); + self.alloc_expr_desugared(Expr::Ref { + expr: array, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + }; + + // Assume that rustc version >= 1.89.0 iff lang item `format_arguments` exists + // but `format_unsafe_arg` does not + let lang_items = self.lang_items(); + let fmt_args = lang_items.FormatArguments; + let fmt_unsafe_arg = lang_items.FormatUnsafeArg; + let use_format_args_since_1_89_0 = fmt_args.is_some() && fmt_unsafe_arg.is_none(); + + if use_format_args_since_1_89_0 { + self.collect_format_args_after_1_89_0_impl( + syntax_ptr, + fmt, + argmap, + lit_pieces, + format_options, + ) + } else { + self.collect_format_args_before_1_89_0_impl( + syntax_ptr, + fmt, + argmap, + lit_pieces, + format_options, + ) + } + } + + /// `format_args!` expansion implementation for rustc versions < `1.89.0` + fn collect_format_args_before_1_89_0_impl( + &mut self, + syntax_ptr: AstPtr<ast::Expr>, + fmt: FormatArgs, + argmap: FxIndexSet<(usize, ArgumentType)>, + lit_pieces: ExprId, + format_options: ExprId, + ) -> ExprId { + let arguments = &*fmt.arguments.arguments; + + let args = if arguments.is_empty() { + let expr = self + .alloc_expr_desugared(Expr::Array(Array::ElementList { elements: Box::default() })); + self.alloc_expr_desugared(Expr::Ref { + expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + } else { + // Generate: + // &match (&arg0, &arg1, &…) { + // args => [ + // <core::fmt::Argument>::new_display(args.0), + // <core::fmt::Argument>::new_lower_hex(args.1), + // <core::fmt::Argument>::new_debug(args.0), + // … + // ] + // } + let args = argmap + .iter() + .map(|&(arg_index, ty)| { + let arg = self.alloc_expr_desugared(Expr::Ref { + expr: arguments[arg_index].expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + self.make_argument(arg, ty) + }) + .collect(); + let array = + self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args })); + self.alloc_expr_desugared(Expr::Ref { + expr: array, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + }; + + // Generate: + // <core::fmt::Arguments>::new_v1_formatted( + // lit_pieces, + // args, + // format_options, + // unsafe { ::core::fmt::UnsafeArg::new() } + // ) + + let lang_items = self.lang_items(); + let new_v1_formatted = + self.ty_rel_lang_path_desugared_expr(lang_items.FormatArguments, sym::new_v1_formatted); + let unsafe_arg_new = + self.ty_rel_lang_path_desugared_expr(lang_items.FormatUnsafeArg, sym::new); + let unsafe_arg_new = + self.alloc_expr_desugared(Expr::Call { callee: unsafe_arg_new, args: Box::default() }); + let mut unsafe_arg_new = self.alloc_expr_desugared(Expr::Unsafe { + id: None, + statements: Box::new([]), + tail: Some(unsafe_arg_new), + }); + if !fmt.orphans.is_empty() { + unsafe_arg_new = self.alloc_expr_desugared(Expr::Block { + id: None, + // We collect the unused expressions here so that we still infer them instead of + // dropping them out of the expression tree. We cannot store them in the `Unsafe` + // block because then unsafe blocks within them will get a false "unused unsafe" + // diagnostic (rustc has a notion of builtin unsafe blocks, but we don't). + statements: fmt + .orphans + .into_iter() + .map(|expr| Statement::Expr { expr, has_semi: true }) + .collect(), + tail: Some(unsafe_arg_new), + label: None, + }); + } + + self.alloc_expr( + Expr::Call { + callee: new_v1_formatted, + args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]), + }, + syntax_ptr, + ) + } + + /// `format_args!` expansion implementation for rustc versions >= `1.89.0`, + /// especially since [this PR](https://github.com/rust-lang/rust/pull/140748) + fn collect_format_args_after_1_89_0_impl( + &mut self, + syntax_ptr: AstPtr<ast::Expr>, + fmt: FormatArgs, + argmap: FxIndexSet<(usize, ArgumentType)>, + lit_pieces: ExprId, + format_options: ExprId, + ) -> ExprId { + let arguments = &*fmt.arguments.arguments; + + let (let_stmts, args) = if arguments.is_empty() { + ( + // Generate: + // [] + vec![], + self.alloc_expr_desugared(Expr::Array(Array::ElementList { + elements: Box::default(), + })), + ) + } else if argmap.len() == 1 && arguments.len() == 1 { + // Only one argument, so we don't need to make the `args` tuple. + // + // Generate: + // super let args = [<core::fmt::Arguments>::new_display(&arg)]; + let args = argmap + .iter() + .map(|&(arg_index, ty)| { + let ref_arg = self.alloc_expr_desugared(Expr::Ref { + expr: arguments[arg_index].expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + self.make_argument(ref_arg, ty) + }) + .collect(); + let args = + self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args })); + let args_name = self.generate_new_name(); + let args_binding = self.alloc_binding( + args_name.clone(), + BindingAnnotation::Unannotated, + HygieneId::ROOT, + ); + let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None }); + self.add_definition_to_binding(args_binding, args_pat); + // TODO: We don't have `super let` yet. + let let_stmt = Statement::Let { + pat: args_pat, + type_ref: None, + initializer: Some(args), + else_branch: None, + }; + (vec![let_stmt], self.alloc_expr_desugared(Expr::Path(args_name.into()))) + } else { + // Generate: + // super let args = (&arg0, &arg1, &...); + let args_name = self.generate_new_name(); + let args_binding = self.alloc_binding( + args_name.clone(), + BindingAnnotation::Unannotated, + HygieneId::ROOT, + ); + let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None }); + self.add_definition_to_binding(args_binding, args_pat); + let elements = arguments + .iter() + .map(|arg| { + self.alloc_expr_desugared(Expr::Ref { + expr: arg.expr, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }) + }) + .collect(); + let args_tuple = self.alloc_expr_desugared(Expr::Tuple { exprs: elements }); + // TODO: We don't have `super let` yet + let let_stmt1 = Statement::Let { + pat: args_pat, + type_ref: None, + initializer: Some(args_tuple), + else_branch: None, + }; + + // Generate: + // super let args = [ + // <core::fmt::Argument>::new_display(args.0), + // <core::fmt::Argument>::new_lower_hex(args.1), + // <core::fmt::Argument>::new_debug(args.0), + // … + // ]; + let args = argmap + .iter() + .map(|&(arg_index, ty)| { + let args_ident_expr = + self.alloc_expr_desugared(Expr::Path(args_name.clone().into())); + let arg = self.alloc_expr_desugared(Expr::Field { + expr: args_ident_expr, + name: Name::new_tuple_field(arg_index), + }); + self.make_argument(arg, ty) + }) + .collect(); + let array = + self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args })); + let args_binding = self.alloc_binding( + args_name.clone(), + BindingAnnotation::Unannotated, + HygieneId::ROOT, + ); + let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None }); + self.add_definition_to_binding(args_binding, args_pat); + let let_stmt2 = Statement::Let { + pat: args_pat, + type_ref: None, + initializer: Some(array), + else_branch: None, + }; + (vec![let_stmt1, let_stmt2], self.alloc_expr_desugared(Expr::Path(args_name.into()))) + }; + + // Generate: + // &args + let args = self.alloc_expr_desugared(Expr::Ref { + expr: args, + rawness: Rawness::Ref, + mutability: Mutability::Shared, + }); + + let call_block = { + // Generate: + // unsafe { + // <core::fmt::Arguments>::new_v1_formatted( + // lit_pieces, + // args, + // format_options, + // ) + // } + + let new_v1_formatted = self.ty_rel_lang_path_desugared_expr( + self.lang_items().FormatArguments, + sym::new_v1_formatted, + ); + let args = [lit_pieces, args, format_options]; + let call = self + .alloc_expr_desugared(Expr::Call { callee: new_v1_formatted, args: args.into() }); + + Expr::Unsafe { id: None, statements: Box::default(), tail: Some(call) } + }; + + if !let_stmts.is_empty() { + // Generate: + // { + // super let … + // super let … + // <core::fmt::Arguments>::new_…(…) + // } + let call = self.alloc_expr_desugared(call_block); + self.alloc_expr( + Expr::Block { + id: None, + statements: let_stmts.into(), + tail: Some(call), + label: None, + }, + syntax_ptr, + ) + } else { + self.alloc_expr(call_block, syntax_ptr) + } + } + + /// Generate a hir expression for a format_args placeholder specification. + /// + /// Generates + /// + /// ```text + /// <core::fmt::rt::Placeholder::new( + /// …usize, // position + /// '…', // fill + /// <core::fmt::rt::Alignment>::…, // alignment + /// …u32, // flags + /// <core::fmt::rt::Count::…>, // width + /// <core::fmt::rt::Count::…>, // precision + /// ) + /// ``` + fn make_format_spec( + &mut self, + placeholder: &FormatPlaceholder, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, + ) -> ExprId { + let lang_items = self.lang_items(); + let position = match placeholder.argument.index { + Ok(arg_index) => { + let (i, _) = + argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait))); + self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + i as u128, + Some(BuiltinUint::Usize), + ))) + } + Err(_) => self.missing_expr(), + }; + let &FormatOptions { + ref width, + ref precision, + alignment, + fill, + sign, + alternate, + zero_pad, + debug_hex, + } = &placeholder.format_options; + + let precision_expr = self.make_count_before_1_93_0(precision, argmap); + let width_expr = self.make_count_before_1_93_0(width, argmap); + + if self.krate.workspace_data(self.db).is_atleast_187() { + // These need to match the constants in library/core/src/fmt/rt.rs. + let align = match alignment { + Some(FormatAlignment::Left) => 0, + Some(FormatAlignment::Right) => 1, + Some(FormatAlignment::Center) => 2, + None => 3, + }; + // This needs to match `Flag` in library/core/src/fmt/rt.rs. + let flags = fill.unwrap_or(' ') as u32 + | ((sign == Some(FormatSign::Plus)) as u32) << 21 + | ((sign == Some(FormatSign::Minus)) as u32) << 22 + | (alternate as u32) << 23 + | (zero_pad as u32) << 24 + | ((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25 + | ((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26 + | (width.is_some() as u32) << 27 + | (precision.is_some() as u32) << 28 + | align << 29 + | 1 << 31; // Highest bit always set. + let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + flags as u128, + Some(BuiltinUint::U32), + ))); + + let position = + RecordLitField { name: Name::new_symbol_root(sym::position), expr: position }; + let flags = RecordLitField { name: Name::new_symbol_root(sym::flags), expr: flags }; + let precision = RecordLitField { + name: Name::new_symbol_root(sym::precision), + expr: precision_expr, + }; + let width = + RecordLitField { name: Name::new_symbol_root(sym::width), expr: width_expr }; + self.alloc_expr_desugared(Expr::RecordLit { + path: self.lang_path(lang_items.FormatPlaceholder).map(Box::new), + fields: Box::new([position, flags, precision, width]), + spread: None, + }) + } else { + let format_placeholder_new = + self.ty_rel_lang_path_desugared_expr(lang_items.FormatPlaceholder, sym::new); + // This needs to match `Flag` in library/core/src/fmt/rt.rs. + let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32) + | (((sign == Some(FormatSign::Minus)) as u32) << 1) + | ((alternate as u32) << 2) + | ((zero_pad as u32) << 3) + | (((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 4) + | (((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 5); + let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + flags as u128, + Some(BuiltinUint::U32), + ))); + let fill = self.alloc_expr_desugared(Expr::Literal(Literal::Char(fill.unwrap_or(' ')))); + let align = self.ty_rel_lang_path_desugared_expr( + lang_items.FormatAlignment, + match alignment { + Some(FormatAlignment::Left) => sym::Left, + Some(FormatAlignment::Right) => sym::Right, + Some(FormatAlignment::Center) => sym::Center, + None => sym::Unknown, + }, + ); + self.alloc_expr_desugared(Expr::Call { + callee: format_placeholder_new, + args: Box::new([position, fill, align, flags, precision_expr, width_expr]), + }) + } + } + + /// Generate a hir expression for a format_args Count. + /// + /// Generates: + /// + /// ```text + /// <core::fmt::rt::Count>::Is(…) + /// ``` + /// + /// or + /// + /// ```text + /// <core::fmt::rt::Count>::Param(…) + /// ``` + /// + /// or + /// + /// ```text + /// <core::fmt::rt::Count>::Implied + /// ``` + fn make_count_before_1_93_0( + &mut self, + count: &Option<FormatCount>, + argmap: &mut FxIndexSet<(usize, ArgumentType)>, + ) -> ExprId { + let lang_items = self.lang_items(); + match count { + Some(FormatCount::Literal(n)) => { + let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + *n as u128, + // FIXME: Change this to Some(BuiltinUint::U16) once we drop support for toolchains < 1.88 + None, + ))); + let count_is = + self.ty_rel_lang_path_desugared_expr(lang_items.FormatCount, sym::Is); + self.alloc_expr_desugared(Expr::Call { callee: count_is, args: Box::new([args]) }) + } + Some(FormatCount::Argument(arg)) => { + if let Ok(arg_index) = arg.index { + let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize)); + + let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint( + i as u128, + Some(BuiltinUint::Usize), + ))); + let count_param = + self.ty_rel_lang_path_desugared_expr(lang_items.FormatCount, sym::Param); + self.alloc_expr_desugared(Expr::Call { + callee: count_param, + args: Box::new([args]), + }) + } else { + // FIXME: This drops arg causing it to potentially not be resolved/type checked + // when typing? + self.missing_expr() + } + } + None => match self.ty_rel_lang_path(lang_items.FormatCount, sym::Implied) { + Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)), + None => self.missing_expr(), + }, + } + } + + /// Generate a hir expression representing an argument to a format_args invocation. + /// + /// Generates: + /// + /// ```text + /// <core::fmt::Argument>::new_…(arg) + /// ``` + fn make_argument(&mut self, arg: ExprId, ty: ArgumentType) -> ExprId { + use ArgumentType::*; + use FormatTrait::*; + + let new_fn = self.ty_rel_lang_path_desugared_expr( + self.lang_items().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, + }, + ); + self.alloc_expr_desugared(Expr::Call { callee: new_fn, args: Box::new([arg]) }) + } + + fn ty_rel_lang_path_desugared_expr( + &mut self, + lang: Option<impl Into<LangItemTarget>>, + relative_name: Symbol, + ) -> ExprId { + self.alloc_expr_desugared(self.ty_rel_lang_path_expr(lang, relative_name)) + } +} + +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +enum ArgumentType { + Format(FormatTrait), + Usize, +} |