Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #21242 from ChayimFriedman2/fmt-fix
fix: Support the new lowering of `format_args!()`
Lukas Wirth 4 months ago
parent 5502640 · parent 71b3bca · commit eda27bb
-rw-r--r--crates/hir-def/src/expr_store/lower.rs721
-rw-r--r--crates/hir-def/src/expr_store/lower/format_args.rs1012
-rw-r--r--crates/hir-def/src/expr_store/tests/body.rs84
-rw-r--r--crates/test-utils/src/minicore.rs51
4 files changed, 1151 insertions, 717 deletions
diff --git a/crates/hir-def/src/expr_store/lower.rs b/crates/hir-def/src/expr_store/lower.rs
index e72de6d682..42b076abb2 100644
--- a/crates/hir-def/src/expr_store/lower.rs
+++ b/crates/hir-def/src/expr_store/lower.rs
@@ -2,6 +2,7 @@
//! representation.
mod asm;
+mod format_args;
mod generics;
mod path;
@@ -19,7 +20,7 @@ use intern::{Symbol, sym};
use rustc_hash::FxHashMap;
use stdx::never;
use syntax::{
- AstNode, AstPtr, AstToken as _, SyntaxNodePtr,
+ AstNode, AstPtr, SyntaxNodePtr,
ast::{
self, ArrayExprKind, AstChildren, BlockExpr, HasArgList, HasAttrs, HasGenericArgs,
HasGenericParams, HasLoopBody, HasName, HasTypeBounds, IsString, RangeItem,
@@ -34,7 +35,6 @@ use crate::{
AdtId, BlockId, BlockLoc, DefWithBodyId, FunctionId, GenericDefId, ImplId, MacroId,
ModuleDefId, ModuleId, TraitId, TypeAliasId, UnresolvedMacro,
attrs::AttrFlags,
- builtin_type::BuiltinUint,
db::DefDatabase,
expr_store::{
Body, BodySourceMap, ExprPtr, ExpressionStore, ExpressionStoreBuilder,
@@ -47,13 +47,7 @@ use crate::{
hir::{
Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy, ClosureKind,
Expr, ExprId, Item, Label, LabelId, Literal, MatchArm, Movability, OffsetOf, Pat, PatId,
- RecordFieldPat, RecordLitField, Statement,
- format_args::{
- self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind,
- FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions,
- FormatPlaceholder, FormatSign, FormatTrait,
- },
- generics::GenericParams,
+ RecordFieldPat, RecordLitField, Statement, generics::GenericParams,
},
item_scope::BuiltinShadowMode,
item_tree::FieldsShape,
@@ -438,6 +432,8 @@ pub struct ExprCollector<'db> {
awaitable_context: Option<Awaitable>,
krate: base_db::Crate,
+
+ name_generator_index: usize,
}
#[derive(Clone, Debug)]
@@ -543,9 +539,16 @@ impl<'db> ExprCollector<'db> {
current_block_legacy_macro_defs_count: FxHashMap::default(),
outer_impl_trait: false,
krate,
+ name_generator_index: 0,
}
}
+ fn generate_new_name(&mut self) -> Name {
+ let index = self.name_generator_index;
+ self.name_generator_index += 1;
+ Name::generate_new_name(index)
+ }
+
#[inline]
pub(crate) fn lang_items(&self) -> &'db LangItems {
self.lang_items.get_or_init(|| crate::lang_item::lang_items(self.db, self.def_map.krate()))
@@ -1645,9 +1648,8 @@ impl<'db> ExprCollector<'db> {
/// and save the `<new_label>` to use it as a break target for desugaring of the `?` operator.
fn desugar_try_block(&mut self, e: BlockExpr) -> ExprId {
let try_from_output = self.lang_path(self.lang_items().TryTraitFromOutput);
- let label = self.alloc_label_desugared(Label {
- name: Name::generate_new_name(self.store.labels.len()),
- });
+ let label = self.generate_new_name();
+ let label = self.alloc_label_desugared(Label { name: label });
let old_label = self.current_try_block_label.replace(label);
let ptr = AstPtr::new(&e).upcast();
@@ -1775,7 +1777,7 @@ impl<'db> ExprCollector<'db> {
this.collect_expr_opt(e.loop_body().map(|it| it.into()))
}),
};
- let iter_name = Name::generate_new_name(self.store.exprs.len());
+ let iter_name = self.generate_new_name();
let iter_expr = self.alloc_expr(Expr::Path(Path::from(iter_name.clone())), syntax_ptr);
let iter_expr_mut = self.alloc_expr(
Expr::Ref { expr: iter_expr, rawness: Rawness::Ref, mutability: Mutability::Mut },
@@ -1836,7 +1838,7 @@ impl<'db> ExprCollector<'db> {
let try_branch = self.alloc_expr(try_branch.map_or(Expr::Missing, Expr::Path), syntax_ptr);
let expr = self
.alloc_expr(Expr::Call { callee: try_branch, args: Box::new([operand]) }, syntax_ptr);
- let continue_name = Name::generate_new_name(self.store.bindings.len());
+ let continue_name = self.generate_new_name();
let continue_binding = self.alloc_binding(
continue_name.clone(),
BindingAnnotation::Unannotated,
@@ -1854,7 +1856,7 @@ impl<'db> ExprCollector<'db> {
guard: None,
expr: self.alloc_expr(Expr::Path(Path::from(continue_name)), syntax_ptr),
};
- let break_name = Name::generate_new_name(self.store.bindings.len());
+ let break_name = self.generate_new_name();
let break_binding =
self.alloc_binding(break_name.clone(), BindingAnnotation::Unannotated, HygieneId::ROOT);
let break_bpat = self.alloc_pat_desugared(Pat::Bind { id: break_binding, subpat: None });
@@ -2609,7 +2611,6 @@ impl<'db> ExprCollector<'db> {
}
// endregion: labels
- // region: format
fn expand_macros_to_string(&mut self, expr: ast::Expr) -> Option<(ast::String, bool)> {
let m = match expr {
ast::Expr::MacroExpr(m) => m,
@@ -2629,676 +2630,6 @@ impl<'db> ExprCollector<'db> {
Some((exp, false))
}
- 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,
- ),
- };
-
- // 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();
-
- let idx = if use_format_args_since_1_89_0 {
- self.collect_format_args_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,
- )
- };
-
- self.store
- .template_map
- .get_or_insert_with(Default::default)
- .format_args_to_captures
- .insert(idx, (hygiene, mappings));
- idx
- }
-
- /// `format_args!` expansion implementation for rustc versions < `1.89.0`
- fn collect_format_args_before_1_89_0_impl(
- &mut self,
- syntax_ptr: AstPtr<ast::Expr>,
- fmt: FormatArgs,
- argmap: FxIndexSet<(usize, ArgumentType)>,
- lit_pieces: ExprId,
- format_options: ExprId,
- ) -> ExprId {
- let arguments = &*fmt.arguments.arguments;
-
- let 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(
- lang_items.FormatArguments,
- Name::new_symbol_root(sym::new_v1_formatted),
- );
- let unsafe_arg_new =
- self.ty_rel_lang_path(lang_items.FormatUnsafeArg, Name::new_symbol_root(sym::new));
- let new_v1_formatted =
- self.alloc_expr_desugared(new_v1_formatted.map_or(Expr::Missing, Expr::Path));
-
- let unsafe_arg_new =
- self.alloc_expr_desugared(unsafe_arg_new.map_or(Expr::Missing, Expr::Path));
- 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_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 = Name::new_symbol_root(sym::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);
- // 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 = Name::new_symbol_root(sym::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 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(
- self.lang_items().FormatArguments,
- Name::new_symbol_root(sym::new_v1_formatted),
- );
- let new_v1_formatted =
- self.alloc_expr_desugared(new_v1_formatted.map_or(Expr::Missing, Expr::Path));
- let args = [lit_pieces, args, format_options];
- let call = self
- .alloc_expr_desugared(Expr::Call { callee: new_v1_formatted, args: args.into() });
-
- Expr::Unsafe { id: None, statements: Box::default(), tail: Some(call) }
- };
-
- if !let_stmts.is_empty() {
- // Generate:
- // {
- // super let …
- // super let …
- // <core::fmt::Arguments>::new_…(…)
- // }
- let call = self.alloc_expr_desugared(call_block);
- self.alloc_expr(
- Expr::Block {
- id: None,
- statements: let_stmts.into(),
- tail: Some(call),
- label: None,
- },
- syntax_ptr,
- )
- } else {
- self.alloc_expr(call_block, syntax_ptr)
- }
- }
-
- /// Generate a hir expression for a format_args placeholder specification.
- ///
- /// 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(precision, argmap);
- let width_expr = self.make_count(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 = {
- let format_placeholder_new = self.ty_rel_lang_path(
- lang_items.FormatPlaceholder,
- Name::new_symbol_root(sym::new),
- );
- match format_placeholder_new {
- Some(path) => self.alloc_expr_desugared(Expr::Path(path)),
- None => self.missing_expr(),
- }
- };
- // 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 = {
- let align = self.ty_rel_lang_path(
- lang_items.FormatAlignment,
- match alignment {
- Some(FormatAlignment::Left) => Name::new_symbol_root(sym::Left),
- Some(FormatAlignment::Right) => Name::new_symbol_root(sym::Right),
- Some(FormatAlignment::Center) => Name::new_symbol_root(sym::Center),
- None => Name::new_symbol_root(sym::Unknown),
- },
- );
- match align {
- Some(path) => self.alloc_expr_desugared(Expr::Path(path)),
- None => self.missing_expr(),
- }
- };
- 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(
- &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 = match self
- .ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Is))
- {
- Some(count_is) => self.alloc_expr_desugared(Expr::Path(count_is)),
- None => self.missing_expr(),
- };
- 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 = match self
- .ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Param))
- {
- Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)),
- None => self.missing_expr(),
- };
- 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, Name::new_symbol_root(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 = match self.ty_rel_lang_path(
- self.lang_items().FormatArgument,
- Name::new_symbol_root(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,
- }),
- ) {
- Some(new_fn) => self.alloc_expr_desugared(Expr::Path(new_fn)),
- None => self.missing_expr(),
- };
- self.alloc_expr_desugared(Expr::Call { callee: new_fn, args: Box::new([arg]) })
- }
-
- // endregion: format
-
fn lang_path(&self, lang: Option<impl Into<LangItemTarget>>) -> Option<Path> {
Some(Path::LangItem(lang?.into(), None))
}
@@ -3306,9 +2637,17 @@ impl<'db> ExprCollector<'db> {
fn ty_rel_lang_path(
&self,
lang: Option<impl Into<LangItemTarget>>,
- relative_name: Name,
+ relative_name: Symbol,
) -> Option<Path> {
- Some(Path::LangItem(lang?.into(), Some(relative_name)))
+ Some(Path::LangItem(lang?.into(), Some(Name::new_symbol_root(relative_name))))
+ }
+
+ fn ty_rel_lang_path_expr(
+ &self,
+ lang: Option<impl Into<LangItemTarget>>,
+ relative_name: Symbol,
+ ) -> Expr {
+ self.ty_rel_lang_path(lang, relative_name).map_or(Expr::Missing, Expr::Path)
}
}
@@ -3425,12 +2764,6 @@ fn comma_follows_token(t: Option<syntax::SyntaxToken>) -> bool {
.is_some_and(|it| it.kind() == syntax::T![,])
}
-#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
-enum ArgumentType {
- Format(FormatTrait),
- Usize,
-}
-
/// This function find the AST fragment that corresponds to an `AssociatedTypeBinding` in the HIR.
pub fn hir_assoc_type_binding_to_ast(
segment_args: &ast::GenericArgList,
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..7efc9a956c
--- /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 = Name::new_symbol_root(sym::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);
+ // 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 = Name::new_symbol_root(sym::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 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,
+}
diff --git a/crates/hir-def/src/expr_store/tests/body.rs b/crates/hir-def/src/expr_store/tests/body.rs
index 22ade43875..7e48ca8f45 100644
--- a/crates/hir-def/src/expr_store/tests/body.rs
+++ b/crates/hir-def/src/expr_store/tests/body.rs
@@ -161,9 +161,9 @@ fn main() {
match builtin#lang(into_iter)(
0..10,
) {
- mut <ra@gennew>11 => loop {
+ mut <ra@gennew>0 => loop {
match builtin#lang(next)(
- &mut <ra@gennew>11,
+ &mut <ra@gennew>0,
) {
builtin#lang(None) => break,
builtin#lang(Some)(ident) => {
@@ -261,10 +261,10 @@ fn main() {
}
#[test]
-fn desugar_builtin_format_args() {
+fn desugar_builtin_format_args_before_1_93_0() {
let (db, body, def) = lower(
r#"
-//- minicore: fmt
+//- minicore: fmt_before_1_93_0
fn main() {
let are = "are";
let count = 10;
@@ -344,6 +344,59 @@ fn main() {
}
#[test]
+fn desugar_builtin_format_args() {
+ let (db, body, def) = lower(
+ r#"
+//- minicore: fmt
+fn main() {
+ let are = "are";
+ let count = 10;
+ builtin#format_args("\u{1b}hello {count:02} {} friends, we {are:?} {0}{last}", "fancy", orphan = (), last = "!");
+ builtin#format_args("hello world");
+ builtin#format_args("hello world", orphan = ());
+}
+"#,
+ );
+
+ expect![[r#"
+ fn main() {
+ let are = "are";
+ let count = 10;
+ {
+ let <ra@gennew>0 = (&"fancy", &(), &"!", &count, &are, );
+ let <ra@gennew>0 = [
+ builtin#lang(Argument::new_display)(
+ <ra@gennew>0.3,
+ ), builtin#lang(Argument::new_display)(
+ <ra@gennew>0.0,
+ ), builtin#lang(Argument::new_debug)(
+ <ra@gennew>0.4,
+ ), builtin#lang(Argument::new_display)(
+ <ra@gennew>0.2,
+ ),
+ ];
+ ();
+ unsafe {
+ builtin#lang(Arguments::new)(
+ "\x07\x1bhello \xc3 \x00\x00i\x02\x00\x01 \xc0\r friends, we \xc0\x01 \xc8\x01\x00\xc8\x03\x00\x00",
+ &<ra@gennew>0,
+ )
+ }
+ };
+ builtin#lang(Arguments::from_str)(
+ "hello world",
+ );
+ {
+ ();
+ builtin#lang(Arguments::from_str)(
+ "hello world",
+ )
+ };
+ }"#]]
+ .assert_eq(&body.pretty_print(&db, def, Edition::CURRENT))
+}
+
+#[test]
fn test_macro_hygiene() {
let (db, body, def) = lower(
r##"
@@ -382,27 +435,16 @@ impl SsrError {
fn main() {
_ = ra_test_fixture::error::SsrError::new(
{
- let args = [
+ let <ra@gennew>0 = (&node.text(), );
+ let <ra@gennew>0 = [
builtin#lang(Argument::new_display)(
- &node.text(),
+ <ra@gennew>0.0,
),
];
unsafe {
- builtin#lang(Arguments::new_v1_formatted)(
- &[
- "Failed to resolve path `", "`",
- ],
- &args,
- &[
- builtin#lang(Placeholder::new)(
- 0usize,
- ' ',
- builtin#lang(Alignment::Unknown),
- 0u32,
- builtin#lang(Count::Implied),
- builtin#lang(Count::Implied),
- ),
- ],
+ builtin#lang(Arguments::new)(
+ "\x18Failed to resolve path `\xc0\x01`\x00",
+ &<ra@gennew>0,
)
}
},
diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs
index 0fe17e3075..b7c09391ec 100644
--- a/crates/test-utils/src/minicore.rs
+++ b/crates/test-utils/src/minicore.rs
@@ -34,7 +34,8 @@
//! eq: sized
//! error: fmt
//! fmt: option, result, transmute, coerce_unsized, copy, clone, derive
-//! fmt_before_1_89_0: fmt
+//! fmt_before_1_93_0: fmt
+//! fmt_before_1_89_0: fmt_before_1_93_0
//! fn: sized, tuple
//! from: sized, result
//! future: pin
@@ -1259,6 +1260,7 @@ pub mod fmt {
Unknown,
}
+ // region:fmt_before_1_93_0
#[lang = "format_count"]
pub enum Count {
Is(usize),
@@ -1288,6 +1290,7 @@ pub mod fmt {
Placeholder { position, fill, align, flags, precision, width }
}
}
+ // endregion:fmt_before_1_93_0
// region:fmt_before_1_89_0
#[lang = "format_unsafe_arg"]
@@ -1303,6 +1306,7 @@ pub mod fmt {
// endregion:fmt_before_1_89_0
}
+ // region:fmt_before_1_93_0
#[derive(Copy, Clone)]
#[lang = "format_arguments"]
pub struct Arguments<'a> {
@@ -1341,6 +1345,14 @@ pub mod fmt {
}
// endregion:!fmt_before_1_89_0
+ pub fn from_str_nonconst(s: &'static str) -> Arguments<'a> {
+ Self::from_str(s)
+ }
+
+ pub const fn from_str(s: &'static str) -> Arguments<'a> {
+ Arguments { pieces: &[s], fmt: None, args: &[] }
+ }
+
pub const fn as_str(&self) -> Option<&'static str> {
match (self.pieces, self.args) {
([], []) => Some(""),
@@ -1349,6 +1361,41 @@ pub mod fmt {
}
}
}
+ // endregion:fmt_before_1_93_0
+
+ // region:!fmt_before_1_93_0
+ #[lang = "format_arguments"]
+ #[derive(Copy, Clone)]
+ pub struct Arguments<'a> {
+ // This is a non-faithful representation of `core::fmt::Arguments`, because the real one
+ // is too complex for minicore.
+ message: Option<&'a str>,
+ }
+
+ impl<'a> Arguments<'a> {
+ pub unsafe fn new<const N: usize, const M: usize>(
+ _template: &'a [u8; N],
+ _args: &'a [rt::Argument<'a>; M],
+ ) -> Arguments<'a> {
+ Arguments { message: None }
+ }
+
+ pub fn from_str_nonconst(s: &'static str) -> Arguments<'a> {
+ Arguments { message: Some(s) }
+ }
+
+ pub const fn from_str(s: &'static str) -> Arguments<'a> {
+ Arguments { message: Some(s) }
+ }
+
+ pub fn as_str(&self) -> Option<&'static str> {
+ match self.message {
+ Some(s) => unsafe { Some(&*(s as *const str)) },
+ None => None,
+ }
+ }
+ }
+ // endregion:!fmt_before_1_93_0
// region:derive
pub(crate) mod derive {
@@ -1817,7 +1864,7 @@ mod panicking {
#[lang = "panic"]
pub const fn panic(expr: &'static str) -> ! {
- panic_fmt(crate::fmt::Arguments::new_const(&[expr]))
+ panic_fmt(crate::fmt::Arguments::from_str(expr))
}
}
// endregion:panic