Merge pull request #21242 from ChayimFriedman2/fmt-fix

fix: Support the new lowering of `format_args!()`
This commit is contained in:
Lukas Wirth 2025-12-12 17:45:43 +00:00 committed by GitHub
commit 731ae4d91e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1151 additions and 717 deletions

View file

@ -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,

File diff suppressed because it is too large Load diff

View file

@ -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;
@ -343,6 +343,59 @@ fn main() {
.assert_eq(&body.pretty_print(&db, def, Edition::CURRENT))
}
#[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(
@ -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,
)
}
},

View file

@ -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