]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_ast_lowering/src/format.rs
New upstream version 1.69.0+dfsg1
[rustc.git] / compiler / rustc_ast_lowering / src / format.rs
CommitLineData
9ffffee4
FG
1use super::LoweringContext;
2use rustc_ast as ast;
3use rustc_ast::visit::{self, Visitor};
4use rustc_ast::*;
5use rustc_data_structures::fx::FxIndexSet;
6use rustc_hir as hir;
7use rustc_span::{
8 sym,
9 symbol::{kw, Ident},
10 Span,
11};
12
13impl<'hir> LoweringContext<'_, 'hir> {
14 pub(crate) fn lower_format_args(&mut self, sp: Span, fmt: &FormatArgs) -> hir::ExprKind<'hir> {
15 expand_format_args(self, sp, fmt)
16 }
17}
18
19#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
20enum ArgumentType {
21 Format(FormatTrait),
22 Usize,
23}
24
25/// Generate a hir expression representing an argument to a format_args invocation.
26///
27/// Generates:
28///
29/// ```text
30/// <core::fmt::ArgumentV1>::new_…(arg)
31/// ```
32fn make_argument<'hir>(
33 ctx: &mut LoweringContext<'_, 'hir>,
34 sp: Span,
35 arg: &'hir hir::Expr<'hir>,
36 ty: ArgumentType,
37) -> hir::Expr<'hir> {
38 use ArgumentType::*;
39 use FormatTrait::*;
40 let new_fn = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
41 sp,
42 hir::LangItem::FormatArgument,
43 match ty {
44 Format(Display) => sym::new_display,
45 Format(Debug) => sym::new_debug,
46 Format(LowerExp) => sym::new_lower_exp,
47 Format(UpperExp) => sym::new_upper_exp,
48 Format(Octal) => sym::new_octal,
49 Format(Pointer) => sym::new_pointer,
50 Format(Binary) => sym::new_binary,
51 Format(LowerHex) => sym::new_lower_hex,
52 Format(UpperHex) => sym::new_upper_hex,
53 Usize => sym::from_usize,
54 },
55 ));
56 ctx.expr_call_mut(sp, new_fn, std::slice::from_ref(arg))
57}
58
59/// Generate a hir expression for a format_args Count.
60///
61/// Generates:
62///
63/// ```text
64/// <core::fmt::rt::v1::Count>::Is(…)
65/// ```
66///
67/// or
68///
69/// ```text
70/// <core::fmt::rt::v1::Count>::Param(…)
71/// ```
72///
73/// or
74///
75/// ```text
76/// <core::fmt::rt::v1::Count>::Implied
77/// ```
78fn make_count<'hir>(
79 ctx: &mut LoweringContext<'_, 'hir>,
80 sp: Span,
81 count: &Option<FormatCount>,
82 argmap: &mut FxIndexSet<(usize, ArgumentType)>,
83) -> hir::Expr<'hir> {
84 match count {
85 Some(FormatCount::Literal(n)) => {
86 let count_is = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
87 sp,
88 hir::LangItem::FormatCount,
89 sym::Is,
90 ));
91 let value = ctx.arena.alloc_from_iter([ctx.expr_usize(sp, *n)]);
92 ctx.expr_call_mut(sp, count_is, value)
93 }
94 Some(FormatCount::Argument(arg)) => {
95 if let Ok(arg_index) = arg.index {
96 let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize));
97 let count_param = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
98 sp,
99 hir::LangItem::FormatCount,
100 sym::Param,
101 ));
102 let value = ctx.arena.alloc_from_iter([ctx.expr_usize(sp, i)]);
103 ctx.expr_call_mut(sp, count_param, value)
104 } else {
105 ctx.expr(
106 sp,
107 hir::ExprKind::Err(
108 ctx.tcx.sess.delay_span_bug(sp, "lowered bad format_args count"),
109 ),
110 )
111 }
112 }
113 None => ctx.expr_lang_item_type_relative(sp, hir::LangItem::FormatCount, sym::Implied),
114 }
115}
116
117/// Generate a hir expression for a format_args placeholder specification.
118///
119/// Generates
120///
121/// ```text
122/// <core::fmt::rt::v1::Argument::new(
123/// …usize, // position
124/// '…', // fill
125/// <core::fmt::rt::v1::Alignment>::…, // alignment
126/// …u32, // flags
127/// <core::fmt::rt::v1::Count::…>, // width
128/// <core::fmt::rt::v1::Count::…>, // precision
129/// )
130/// ```
131fn make_format_spec<'hir>(
132 ctx: &mut LoweringContext<'_, 'hir>,
133 sp: Span,
134 placeholder: &FormatPlaceholder,
135 argmap: &mut FxIndexSet<(usize, ArgumentType)>,
136) -> hir::Expr<'hir> {
137 let position = match placeholder.argument.index {
138 Ok(arg_index) => {
139 let (i, _) =
140 argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait)));
141 ctx.expr_usize(sp, i)
142 }
143 Err(_) => ctx.expr(
144 sp,
145 hir::ExprKind::Err(ctx.tcx.sess.delay_span_bug(sp, "lowered bad format_args count")),
146 ),
147 };
148 let &FormatOptions {
149 ref width,
150 ref precision,
151 alignment,
152 fill,
153 sign,
154 alternate,
155 zero_pad,
156 debug_hex,
157 } = &placeholder.format_options;
158 let fill = ctx.expr_char(sp, fill.unwrap_or(' '));
159 let align = ctx.expr_lang_item_type_relative(
160 sp,
161 hir::LangItem::FormatAlignment,
162 match alignment {
163 Some(FormatAlignment::Left) => sym::Left,
164 Some(FormatAlignment::Right) => sym::Right,
165 Some(FormatAlignment::Center) => sym::Center,
166 None => sym::Unknown,
167 },
168 );
169 // This needs to match `FlagV1` in library/core/src/fmt/mod.rs.
170 let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32)
171 | ((sign == Some(FormatSign::Minus)) as u32) << 1
172 | (alternate as u32) << 2
173 | (zero_pad as u32) << 3
174 | ((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 4
175 | ((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 5;
176 let flags = ctx.expr_u32(sp, flags);
177 let precision = make_count(ctx, sp, &precision, argmap);
178 let width = make_count(ctx, sp, &width, argmap);
179 let format_placeholder_new = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
180 sp,
181 hir::LangItem::FormatPlaceholder,
182 sym::new,
183 ));
184 let args = ctx.arena.alloc_from_iter([position, fill, align, flags, precision, width]);
185 ctx.expr_call_mut(sp, format_placeholder_new, args)
186}
187
188fn expand_format_args<'hir>(
189 ctx: &mut LoweringContext<'_, 'hir>,
190 macsp: Span,
191 fmt: &FormatArgs,
192) -> hir::ExprKind<'hir> {
193 let lit_pieces =
194 ctx.arena.alloc_from_iter(fmt.template.iter().enumerate().filter_map(|(i, piece)| {
195 match piece {
196 &FormatArgsPiece::Literal(s) => Some(ctx.expr_str(fmt.span, s)),
197 &FormatArgsPiece::Placeholder(_) => {
198 // Inject empty string before placeholders when not already preceded by a literal piece.
199 if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_)) {
200 Some(ctx.expr_str(fmt.span, kw::Empty))
201 } else {
202 None
203 }
204 }
205 }
206 }));
207 let lit_pieces = ctx.expr_array_ref(fmt.span, lit_pieces);
208
209 // Whether we'll use the `Arguments::new_v1_formatted` form (true),
210 // or the `Arguments::new_v1` form (false).
211 let mut use_format_options = false;
212
213 // Create a list of all _unique_ (argument, format trait) combinations.
214 // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)]
215 let mut argmap = FxIndexSet::default();
216 for piece in &fmt.template {
217 let FormatArgsPiece::Placeholder(placeholder) = piece else { continue };
218 if placeholder.format_options != Default::default() {
219 // Can't use basic form if there's any formatting options.
220 use_format_options = true;
221 }
222 if let Ok(index) = placeholder.argument.index {
223 if !argmap.insert((index, ArgumentType::Format(placeholder.format_trait))) {
224 // Duplicate (argument, format trait) combination,
225 // which we'll only put once in the args array.
226 use_format_options = true;
227 }
228 }
229 }
230
231 let format_options = use_format_options.then(|| {
232 // Generate:
233 // &[format_spec_0, format_spec_1, format_spec_2]
234 let elements: Vec<_> = fmt
235 .template
236 .iter()
237 .filter_map(|piece| {
238 let FormatArgsPiece::Placeholder(placeholder) = piece else { return None };
239 Some(make_format_spec(ctx, macsp, placeholder, &mut argmap))
240 })
241 .collect();
242 ctx.expr_array_ref(macsp, ctx.arena.alloc_from_iter(elements))
243 });
244
245 let arguments = fmt.arguments.all_args();
246
247 // If the args array contains exactly all the original arguments once,
248 // in order, we can use a simple array instead of a `match` construction.
249 // However, if there's a yield point in any argument except the first one,
250 // we don't do this, because an ArgumentV1 cannot be kept across yield points.
251 //
252 // This is an optimization, speeding up compilation about 1-2% in some cases.
253 // See https://github.com/rust-lang/rust/pull/106770#issuecomment-1380790609
254 let use_simple_array = argmap.len() == arguments.len()
255 && argmap.iter().enumerate().all(|(i, &(j, _))| i == j)
256 && arguments.iter().skip(1).all(|arg| !may_contain_yield_point(&arg.expr));
257
258 let args = if use_simple_array {
259 // Generate:
260 // &[
261 // <core::fmt::ArgumentV1>::new_display(&arg0),
262 // <core::fmt::ArgumentV1>::new_lower_hex(&arg1),
263 // <core::fmt::ArgumentV1>::new_debug(&arg2),
264 // …
265 // ]
266 let elements: Vec<_> = arguments
267 .iter()
268 .zip(argmap)
269 .map(|(arg, (_, ty))| {
270 let sp = arg.expr.span.with_ctxt(macsp.ctxt());
271 let arg = ctx.lower_expr(&arg.expr);
272 let ref_arg = ctx.arena.alloc(ctx.expr(
273 sp,
274 hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, arg),
275 ));
276 make_argument(ctx, sp, ref_arg, ty)
277 })
278 .collect();
279 ctx.expr_array_ref(macsp, ctx.arena.alloc_from_iter(elements))
280 } else {
281 // Generate:
282 // &match (&arg0, &arg1, &…) {
283 // args => [
284 // <core::fmt::ArgumentV1>::new_display(args.0),
285 // <core::fmt::ArgumentV1>::new_lower_hex(args.1),
286 // <core::fmt::ArgumentV1>::new_debug(args.0),
287 // …
288 // ]
289 // }
290 let args_ident = Ident::new(sym::args, macsp);
291 let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident);
292 let args = ctx.arena.alloc_from_iter(argmap.iter().map(|&(arg_index, ty)| {
293 if let Some(arg) = arguments.get(arg_index) {
294 let sp = arg.expr.span.with_ctxt(macsp.ctxt());
295 let args_ident_expr = ctx.expr_ident(macsp, args_ident, args_hir_id);
296 let arg = ctx.arena.alloc(ctx.expr(
297 sp,
298 hir::ExprKind::Field(
299 args_ident_expr,
300 Ident::new(sym::integer(arg_index), macsp),
301 ),
302 ));
303 make_argument(ctx, sp, arg, ty)
304 } else {
305 ctx.expr(
306 macsp,
307 hir::ExprKind::Err(
308 ctx.tcx.sess.delay_span_bug(macsp, format!("no arg at {arg_index}")),
309 ),
310 )
311 }
312 }));
313 let elements: Vec<_> = arguments
314 .iter()
315 .map(|arg| {
316 let arg_expr = ctx.lower_expr(&arg.expr);
317 ctx.expr(
318 arg.expr.span.with_ctxt(macsp.ctxt()),
319 hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, arg_expr),
320 )
321 })
322 .collect();
323 let args_tuple = ctx
324 .arena
325 .alloc(ctx.expr(macsp, hir::ExprKind::Tup(ctx.arena.alloc_from_iter(elements))));
326 let array = ctx.arena.alloc(ctx.expr(macsp, hir::ExprKind::Array(args)));
327 let match_arms = ctx.arena.alloc_from_iter([ctx.arm(args_pat, array)]);
328 let match_expr = ctx.arena.alloc(ctx.expr_match(
329 macsp,
330 args_tuple,
331 match_arms,
332 hir::MatchSource::FormatArgs,
333 ));
334 ctx.expr(
335 macsp,
336 hir::ExprKind::AddrOf(hir::BorrowKind::Ref, hir::Mutability::Not, match_expr),
337 )
338 };
339
340 if let Some(format_options) = format_options {
341 // Generate:
342 // <core::fmt::Arguments>::new_v1_formatted(
343 // lit_pieces,
344 // args,
345 // format_options,
346 // unsafe { ::core::fmt::UnsafeArg::new() }
347 // )
348 let new_v1_formatted = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
349 macsp,
350 hir::LangItem::FormatArguments,
351 sym::new_v1_formatted,
352 ));
353 let unsafe_arg_new = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
354 macsp,
355 hir::LangItem::FormatUnsafeArg,
356 sym::new,
357 ));
358 let unsafe_arg_new_call = ctx.expr_call(macsp, unsafe_arg_new, &[]);
359 let hir_id = ctx.next_id();
360 let unsafe_arg = ctx.expr_block(ctx.arena.alloc(hir::Block {
361 stmts: &[],
362 expr: Some(unsafe_arg_new_call),
363 hir_id,
364 rules: hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::CompilerGenerated),
365 span: macsp,
366 targeted_by_break: false,
367 }));
368 let args = ctx.arena.alloc_from_iter([lit_pieces, args, format_options, unsafe_arg]);
369 hir::ExprKind::Call(new_v1_formatted, args)
370 } else {
371 // Generate:
372 // <core::fmt::Arguments>::new_v1(
373 // lit_pieces,
374 // args,
375 // )
376 let new_v1 = ctx.arena.alloc(ctx.expr_lang_item_type_relative(
377 macsp,
378 hir::LangItem::FormatArguments,
379 sym::new_v1,
380 ));
381 let new_args = ctx.arena.alloc_from_iter([lit_pieces, args]);
382 hir::ExprKind::Call(new_v1, new_args)
383 }
384}
385
386fn may_contain_yield_point(e: &ast::Expr) -> bool {
387 struct MayContainYieldPoint(bool);
388
389 impl Visitor<'_> for MayContainYieldPoint {
390 fn visit_expr(&mut self, e: &ast::Expr) {
391 if let ast::ExprKind::Await(_) | ast::ExprKind::Yield(_) = e.kind {
392 self.0 = true;
393 } else {
394 visit::walk_expr(self, e);
395 }
396 }
397
398 fn visit_mac_call(&mut self, _: &ast::MacCall) {
399 // Macros should be expanded at this point.
400 unreachable!("unexpanded macro in ast lowering");
401 }
402
403 fn visit_item(&mut self, _: &ast::Item) {
404 // Do not recurse into nested items.
405 }
406 }
407
408 let mut visitor = MayContainYieldPoint(false);
409 visitor.visit_expr(e);
410 visitor.0
411}