Support the new lowering of format_args!()

This commit is contained in:
Chayim Refael Friedman 2025-12-10 18:42:22 +02:00
parent fd8f92d94a
commit c0bf494d24
4 changed files with 523 additions and 105 deletions

View file

@ -432,6 +432,8 @@ pub struct ExprCollector<'db> {
awaitable_context: Option<Awaitable>,
krate: base_db::Crate,
name_generator_index: usize,
}
#[derive(Clone, Debug)]
@ -537,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()))
@ -1638,9 +1647,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();
@ -1768,7 +1776,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 },
@ -1829,7 +1837,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,
@ -1847,7 +1855,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 });
@ -2628,9 +2636,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)
}
}

View file

@ -19,6 +19,7 @@ use crate::{
FormatPlaceholder, FormatSign, FormatTrait,
},
},
lang_item::LangItemTarget,
type_ref::{Mutability, Rawness},
};
@ -94,6 +95,347 @@ impl<'db> ExprCollector<'db> {
),
};
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();
@ -160,8 +502,14 @@ impl<'db> ExprCollector<'db> {
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)
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,
@ -170,14 +518,7 @@ impl<'db> ExprCollector<'db> {
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`
@ -238,17 +579,10 @@ impl<'db> ExprCollector<'db> {
// )
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));
self.ty_rel_lang_path_desugared_expr(lang_items.FormatArguments, sym::new_v1_formatted);
let unsafe_arg_new =
self.alloc_expr_desugared(unsafe_arg_new.map_or(Expr::Missing, Expr::Path));
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 {
@ -284,7 +618,7 @@ impl<'db> ExprCollector<'db> {
/// `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(
fn collect_format_args_after_1_89_0_impl(
&mut self,
syntax_ptr: AstPtr<ast::Expr>,
fmt: FormatArgs,
@ -422,12 +756,10 @@ impl<'db> ExprCollector<'db> {
// )
// }
let new_v1_formatted = self.ty_rel_lang_path(
let new_v1_formatted = self.ty_rel_lang_path_desugared_expr(
self.lang_items().FormatArguments,
Name::new_symbol_root(sym::new_v1_formatted),
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() });
@ -499,8 +831,8 @@ impl<'db> ExprCollector<'db> {
debug_hex,
} = &placeholder.format_options;
let precision_expr = self.make_count(precision, argmap);
let width_expr = self.make_count(width, argmap);
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.
@ -542,16 +874,8 @@ impl<'db> ExprCollector<'db> {
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(),
}
};
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)
@ -564,21 +888,15 @@ impl<'db> ExprCollector<'db> {
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(),
}
};
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]),
@ -605,7 +923,7 @@ impl<'db> ExprCollector<'db> {
/// ```text
/// <core::fmt::rt::Count>::Implied
/// ```
fn make_count(
fn make_count_before_1_93_0(
&mut self,
count: &Option<FormatCount>,
argmap: &mut FxIndexSet<(usize, ArgumentType)>,
@ -618,12 +936,8 @@ impl<'db> ExprCollector<'db> {
// 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(),
};
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)) => {
@ -634,12 +948,8 @@ impl<'db> ExprCollector<'db> {
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(),
};
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]),
@ -650,9 +960,7 @@ impl<'db> ExprCollector<'db> {
self.missing_expr()
}
}
None => match self
.ty_rel_lang_path(lang_items.FormatCount, Name::new_symbol_root(sym::Implied))
{
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(),
},
@ -670,9 +978,9 @@ impl<'db> ExprCollector<'db> {
use ArgumentType::*;
use FormatTrait::*;
let new_fn = match self.ty_rel_lang_path(
let new_fn = self.ty_rel_lang_path_desugared_expr(
self.lang_items().FormatArgument,
Name::new_symbol_root(match ty {
match ty {
Format(Display) => sym::new_display,
Format(Debug) => sym::new_debug,
Format(LowerExp) => sym::new_lower_exp,
@ -683,13 +991,18 @@ impl<'db> ExprCollector<'db> {
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]) })
}
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)]

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