]>
Commit | Line | Data |
---|---|---|
9ffffee4 FG |
1 | use super::LoweringContext; |
2 | use rustc_ast as ast; | |
3 | use rustc_ast::visit::{self, Visitor}; | |
4 | use rustc_ast::*; | |
5 | use rustc_data_structures::fx::FxIndexSet; | |
6 | use rustc_hir as hir; | |
7 | use rustc_span::{ | |
8 | sym, | |
9 | symbol::{kw, Ident}, | |
10 | Span, | |
11 | }; | |
12 | ||
13 | impl<'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)] | |
20 | enum 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 | /// ``` | |
32 | fn 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 | /// ``` | |
78 | fn 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 | /// ``` | |
131 | fn 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 | ||
188 | fn 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 | ||
386 | fn 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 | } |