]> git.proxmox.com Git - rustc.git/blobdiff - compiler/rustc_builtin_macros/src/format/expand.rs
New upstream version 1.66.0+dfsg1
[rustc.git] / compiler / rustc_builtin_macros / src / format / expand.rs
diff --git a/compiler/rustc_builtin_macros/src/format/expand.rs b/compiler/rustc_builtin_macros/src/format/expand.rs
new file mode 100644 (file)
index 0000000..9dde5ef
--- /dev/null
@@ -0,0 +1,353 @@
+use super::*;
+use rustc_ast as ast;
+use rustc_ast::visit::{self, Visitor};
+use rustc_ast::{BlockCheckMode, UnsafeSource};
+use rustc_data_structures::fx::FxIndexSet;
+use rustc_span::{sym, symbol::kw};
+
+#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
+enum ArgumentType {
+    Format(FormatTrait),
+    Usize,
+}
+
+fn make_argument(ecx: &ExtCtxt<'_>, sp: Span, arg: P<ast::Expr>, ty: ArgumentType) -> P<ast::Expr> {
+    // Generate:
+    //     ::core::fmt::ArgumentV1::new_…(arg)
+    use ArgumentType::*;
+    use FormatTrait::*;
+    ecx.expr_call_global(
+        sp,
+        ecx.std_path(&[
+            sym::fmt,
+            sym::ArgumentV1,
+            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,
+            },
+        ]),
+        vec![arg],
+    )
+}
+
+fn make_count(
+    ecx: &ExtCtxt<'_>,
+    sp: Span,
+    count: &Option<FormatCount>,
+    argmap: &mut FxIndexSet<(usize, ArgumentType)>,
+) -> P<ast::Expr> {
+    // Generate:
+    //     ::core::fmt::rt::v1::Count::…(…)
+    match count {
+        Some(FormatCount::Literal(n)) => ecx.expr_call_global(
+            sp,
+            ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Is]),
+            vec![ecx.expr_usize(sp, *n)],
+        ),
+        Some(FormatCount::Argument(arg)) => {
+            if let Ok(arg_index) = arg.index {
+                let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize));
+                ecx.expr_call_global(
+                    sp,
+                    ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Param]),
+                    vec![ecx.expr_usize(sp, i)],
+                )
+            } else {
+                DummyResult::raw_expr(sp, true)
+            }
+        }
+        None => ecx.expr_path(ecx.path_global(
+            sp,
+            ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Count, sym::Implied]),
+        )),
+    }
+}
+
+fn make_format_spec(
+    ecx: &ExtCtxt<'_>,
+    sp: Span,
+    placeholder: &FormatPlaceholder,
+    argmap: &mut FxIndexSet<(usize, ArgumentType)>,
+) -> P<ast::Expr> {
+    // Generate:
+    //     ::core::fmt::rt::v1::Argument {
+    //         position: 0usize,
+    //         format: ::core::fmt::rt::v1::FormatSpec {
+    //             fill: ' ',
+    //             align: ::core::fmt::rt::v1::Alignment::Unknown,
+    //             flags: 0u32,
+    //             precision: ::core::fmt::rt::v1::Count::Implied,
+    //             width: ::core::fmt::rt::v1::Count::Implied,
+    //         },
+    //     }
+    let position = match placeholder.argument.index {
+        Ok(arg_index) => {
+            let (i, _) =
+                argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait)));
+            ecx.expr_usize(sp, i)
+        }
+        Err(_) => DummyResult::raw_expr(sp, true),
+    };
+    let fill = ecx.expr_char(sp, placeholder.format_options.fill.unwrap_or(' '));
+    let align = ecx.expr_path(ecx.path_global(
+        sp,
+        ecx.std_path(&[
+            sym::fmt,
+            sym::rt,
+            sym::v1,
+            sym::Alignment,
+            match placeholder.format_options.alignment {
+                Some(FormatAlignment::Left) => sym::Left,
+                Some(FormatAlignment::Right) => sym::Right,
+                Some(FormatAlignment::Center) => sym::Center,
+                None => sym::Unknown,
+            },
+        ]),
+    ));
+    let flags = ecx.expr_u32(sp, placeholder.format_options.flags);
+    let prec = make_count(ecx, sp, &placeholder.format_options.precision, argmap);
+    let width = make_count(ecx, sp, &placeholder.format_options.width, argmap);
+    ecx.expr_struct(
+        sp,
+        ecx.path_global(sp, ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::Argument])),
+        vec![
+            ecx.field_imm(sp, Ident::new(sym::position, sp), position),
+            ecx.field_imm(
+                sp,
+                Ident::new(sym::format, sp),
+                ecx.expr_struct(
+                    sp,
+                    ecx.path_global(
+                        sp,
+                        ecx.std_path(&[sym::fmt, sym::rt, sym::v1, sym::FormatSpec]),
+                    ),
+                    vec![
+                        ecx.field_imm(sp, Ident::new(sym::fill, sp), fill),
+                        ecx.field_imm(sp, Ident::new(sym::align, sp), align),
+                        ecx.field_imm(sp, Ident::new(sym::flags, sp), flags),
+                        ecx.field_imm(sp, Ident::new(sym::precision, sp), prec),
+                        ecx.field_imm(sp, Ident::new(sym::width, sp), width),
+                    ],
+                ),
+            ),
+        ],
+    )
+}
+
+pub fn expand_parsed_format_args(ecx: &mut ExtCtxt<'_>, fmt: FormatArgs) -> P<ast::Expr> {
+    let macsp = ecx.with_def_site_ctxt(ecx.call_site());
+
+    let lit_pieces = ecx.expr_array_ref(
+        fmt.span,
+        fmt.template
+            .iter()
+            .enumerate()
+            .filter_map(|(i, piece)| match piece {
+                &FormatArgsPiece::Literal(s) => Some(ecx.expr_str(fmt.span, s)),
+                &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(ecx.expr_str(fmt.span, kw::Empty))
+                    } else {
+                        None
+                    }
+                }
+            })
+            .collect(),
+    );
+
+    // Whether we'll use the `Arguments::new_v1_formatted` form (true),
+    // or the `Arguments::new_v1` form (false).
+    let mut use_format_options = false;
+
+    // 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 {
+        let FormatArgsPiece::Placeholder(placeholder) = piece else { continue };
+        if placeholder.format_options != Default::default() {
+            // Can't use basic form if there's any formatting options.
+            use_format_options = true;
+        }
+        if let Ok(index) = placeholder.argument.index {
+            if !argmap.insert((index, ArgumentType::Format(placeholder.format_trait))) {
+                // Duplicate (argument, format trait) combination,
+                // which we'll only put once in the args array.
+                use_format_options = true;
+            }
+        }
+    }
+
+    let format_options = use_format_options.then(|| {
+        // Generate:
+        //     &[format_spec_0, format_spec_1, format_spec_2]
+        ecx.expr_array_ref(
+            macsp,
+            fmt.template
+                .iter()
+                .filter_map(|piece| {
+                    let FormatArgsPiece::Placeholder(placeholder) = piece else { return None };
+                    Some(make_format_spec(ecx, macsp, placeholder, &mut argmap))
+                })
+                .collect(),
+        )
+    });
+
+    let arguments = fmt.arguments.into_vec();
+
+    // If the args array contains exactly all the original arguments once,
+    // in order, we can use a simple array instead of a `match` construction.
+    // However, if there's a yield point in any argument except the first one,
+    // we don't do this, because an ArgumentV1 cannot be kept across yield points.
+    let use_simple_array = argmap.len() == arguments.len()
+        && argmap.iter().enumerate().all(|(i, &(j, _))| i == j)
+        && arguments.iter().skip(1).all(|arg| !may_contain_yield_point(&arg.expr));
+
+    let args = if use_simple_array {
+        // Generate:
+        //     &[
+        //         ::core::fmt::ArgumentV1::new_display(&arg0),
+        //         ::core::fmt::ArgumentV1::new_lower_hex(&arg1),
+        //         ::core::fmt::ArgumentV1::new_debug(&arg2),
+        //     ]
+        ecx.expr_array_ref(
+            macsp,
+            arguments
+                .into_iter()
+                .zip(argmap)
+                .map(|(arg, (_, ty))| {
+                    let sp = arg.expr.span.with_ctxt(macsp.ctxt());
+                    make_argument(ecx, sp, ecx.expr_addr_of(sp, arg.expr), ty)
+                })
+                .collect(),
+        )
+    } else {
+        // Generate:
+        //     match (&arg0, &arg1, &arg2) {
+        //         args => &[
+        //             ::core::fmt::ArgumentV1::new_display(args.0),
+        //             ::core::fmt::ArgumentV1::new_lower_hex(args.1),
+        //             ::core::fmt::ArgumentV1::new_debug(args.0),
+        //         ]
+        //     }
+        let args_ident = Ident::new(sym::args, macsp);
+        let args = argmap
+            .iter()
+            .map(|&(arg_index, ty)| {
+                if let Some(arg) = arguments.get(arg_index) {
+                    let sp = arg.expr.span.with_ctxt(macsp.ctxt());
+                    make_argument(
+                        ecx,
+                        sp,
+                        ecx.expr_field(
+                            sp,
+                            ecx.expr_ident(macsp, args_ident),
+                            Ident::new(sym::integer(arg_index), macsp),
+                        ),
+                        ty,
+                    )
+                } else {
+                    DummyResult::raw_expr(macsp, true)
+                }
+            })
+            .collect();
+        ecx.expr_addr_of(
+            macsp,
+            ecx.expr_match(
+                macsp,
+                ecx.expr_tuple(
+                    macsp,
+                    arguments
+                        .into_iter()
+                        .map(|arg| {
+                            ecx.expr_addr_of(arg.expr.span.with_ctxt(macsp.ctxt()), arg.expr)
+                        })
+                        .collect(),
+                ),
+                vec![ecx.arm(macsp, ecx.pat_ident(macsp, args_ident), ecx.expr_array(macsp, args))],
+            ),
+        )
+    };
+
+    if let Some(format_options) = format_options {
+        // Generate:
+        //     ::core::fmt::Arguments::new_v1_formatted(
+        //         lit_pieces,
+        //         args,
+        //         format_options,
+        //         unsafe { ::core::fmt::UnsafeArg::new() }
+        //     )
+        ecx.expr_call_global(
+            macsp,
+            ecx.std_path(&[sym::fmt, sym::Arguments, sym::new_v1_formatted]),
+            vec![
+                lit_pieces,
+                args,
+                format_options,
+                ecx.expr_block(P(ast::Block {
+                    stmts: vec![ecx.stmt_expr(ecx.expr_call_global(
+                        macsp,
+                        ecx.std_path(&[sym::fmt, sym::UnsafeArg, sym::new]),
+                        Vec::new(),
+                    ))],
+                    id: ast::DUMMY_NODE_ID,
+                    rules: BlockCheckMode::Unsafe(UnsafeSource::CompilerGenerated),
+                    span: macsp,
+                    tokens: None,
+                    could_be_bare_literal: false,
+                })),
+            ],
+        )
+    } else {
+        // Generate:
+        //     ::core::fmt::Arguments::new_v1(
+        //         lit_pieces,
+        //         args,
+        //     )
+        ecx.expr_call_global(
+            macsp,
+            ecx.std_path(&[sym::fmt, sym::Arguments, sym::new_v1]),
+            vec![lit_pieces, args],
+        )
+    }
+}
+
+fn may_contain_yield_point(e: &ast::Expr) -> bool {
+    struct MayContainYieldPoint(bool);
+
+    impl Visitor<'_> for MayContainYieldPoint {
+        fn visit_expr(&mut self, e: &ast::Expr) {
+            if let ast::ExprKind::Await(_) | ast::ExprKind::Yield(_) = e.kind {
+                self.0 = true;
+            } else {
+                visit::walk_expr(self, e);
+            }
+        }
+
+        fn visit_mac_call(&mut self, _: &ast::MacCall) {
+            self.0 = true;
+        }
+
+        fn visit_attribute(&mut self, _: &ast::Attribute) {
+            // Conservatively assume this may be a proc macro attribute in
+            // expression position.
+            self.0 = true;
+        }
+
+        fn visit_item(&mut self, _: &ast::Item) {
+            // Do not recurse into nested items.
+        }
+    }
+
+    let mut visitor = MayContainYieldPoint(false);
+    visitor.visit_expr(e);
+    visitor.0
+}