src/tools/rust-analyzer/crates/hir-def/src/expr_store/lower/format_args.rs RUST 1,012 lines View on github.com → Search inside
1//! Lowering of `format_args!()`.23use base_db::FxIndexSet;4use hir_expand::name::Name;5use intern::{Symbol, sym};6use span::SyntaxContext;7use syntax::{AstPtr, AstToken as _, ast};89use crate::{10    builtin_type::BuiltinUint,11    expr_store::{HygieneId, lower::ExprCollector, path::Path},12    hir::{13        Array, BindingAnnotation, Expr, ExprId, Literal, Pat, RecordLitField, RecordSpread,14        Statement,15        format_args::{16            self, FormatAlignment, FormatArgs, FormatArgsPiece, FormatArgument, FormatArgumentKind,17            FormatArgumentsCollector, FormatCount, FormatDebugHex, FormatOptions,18            FormatPlaceholder, FormatSign, FormatTrait,19        },20    },21    lang_item::LangItemTarget,22    type_ref::{Mutability, Rawness},23};2425impl<'db> ExprCollector<'db> {26    pub(super) fn collect_format_args(27        &mut self,28        f: ast::FormatArgsExpr,29        syntax_ptr: AstPtr<ast::Expr>,30    ) -> ExprId {31        let mut args = FormatArgumentsCollector::default();32        f.args().for_each(|arg| {33            args.add(FormatArgument {34                kind: match arg.arg_name() {35                    Some(name) => FormatArgumentKind::Named(Name::new_root(name.name().text())),36                    None => FormatArgumentKind::Normal,37                },38                expr: self.collect_expr_opt(arg.expr()),39            });40        });41        let template = f.template();42        let fmt_snippet = template.as_ref().and_then(|it| match it {43            ast::Expr::Literal(literal) => match literal.kind() {44                ast::LiteralKind::String(s) => Some(s.text().to_owned()),45                _ => None,46            },47            _ => None,48        });49        let mut mappings = vec![];50        let (fmt, hygiene) = match template.and_then(|template| {51            self.expand_macros_to_string(template.clone()).map(|it| (it, template))52        }) {53            Some(((s, is_direct_literal), template)) => {54                let call_ctx = SyntaxContext::root(self.def_map.edition());55                let hygiene = self.hygiene_id_for(s.syntax().text_range());56                let fmt = format_args::parse(57                    &s,58                    fmt_snippet,59                    args,60                    is_direct_literal,61                    |name, range| {62                        let expr_id = self.alloc_expr_desugared(Expr::Path(Path::from(name)));63                        if let Some(range) = range {64                            self.store65                                .template_map66                                .get_or_insert_with(Default::default)67                                .implicit_capture_to_source68                                .insert(69                                    expr_id,70                                    self.expander.in_file((AstPtr::new(&template), range)),71                                );72                        }73                        if !hygiene.is_root() {74                            self.store.ident_hygiene.insert(expr_id.into(), hygiene);75                        }76                        expr_id77                    },78                    |name, span| {79                        if let Some(span) = span {80                            mappings.push((span, name))81                        }82                    },83                    call_ctx,84                );85                (fmt, hygiene)86            }87            None => (88                FormatArgs {89                    template: Default::default(),90                    arguments: args.finish(),91                    orphans: Default::default(),92                },93                HygieneId::ROOT,94            ),95        };9697        let idx = if self.lang_items().FormatCount.is_none() {98            self.collect_format_args_after_1_93_0_impl(syntax_ptr, fmt)99        } else {100            self.collect_format_args_before_1_93_0_impl(syntax_ptr, fmt)101        };102103        self.store104            .template_map105            .get_or_insert_with(Default::default)106            .format_args_to_captures107            .insert(idx, (hygiene, mappings));108        idx109    }110111    fn collect_format_args_after_1_93_0_impl(112        &mut self,113        syntax_ptr: AstPtr<ast::Expr>,114        fmt: FormatArgs,115    ) -> ExprId {116        let lang_items = self.lang_items();117118        // Create a list of all _unique_ (argument, format trait) combinations.119        // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)]120        //121        // We use usize::MAX for arguments that don't exist, because that can never be a valid index122        // into the arguments array.123        let mut argmap = FxIndexSet::default();124125        let mut incomplete_lit = String::new();126127        let mut implicit_arg_index = 0;128129        let mut bytecode = Vec::new();130131        let template = if fmt.template.is_empty() {132            // Treat empty templates as a single literal piece (with an empty string),133            // so we produce `from_str("")` for those.134            &[FormatArgsPiece::Literal(sym::__empty)][..]135        } else {136            &fmt.template[..]137        };138139        // See library/core/src/fmt/mod.rs for the format string encoding format.140141        for (i, piece) in template.iter().enumerate() {142            match piece {143                FormatArgsPiece::Literal(sym) => {144                    // Coalesce adjacent literal pieces.145                    if let Some(FormatArgsPiece::Literal(_)) = template.get(i + 1) {146                        incomplete_lit.push_str(sym.as_str());147                        continue;148                    }149                    let mut s = if incomplete_lit.is_empty() {150                        sym.as_str()151                    } else {152                        incomplete_lit.push_str(sym.as_str());153                        &incomplete_lit154                    };155156                    // If this is the last piece and was the only piece, that means157                    // there are no placeholders and the entire format string is just a literal.158                    //159                    // In that case, we can just use `from_str`.160                    if i + 1 == template.len() && bytecode.is_empty() {161                        // Generate:162                        //     <core::fmt::Arguments>::from_str("meow")163                        let from_str = self.ty_rel_lang_path_desugared_expr(164                            lang_items.FormatArguments,165                            sym::from_str,166                        );167                        let sym =168                            if incomplete_lit.is_empty() { sym.clone() } else { Symbol::intern(s) };169                        let s = self.alloc_expr_desugared(Expr::Literal(Literal::String(sym)));170                        let from_str = self.alloc_expr(171                            Expr::Call { callee: from_str, args: Box::new([s]) },172                            syntax_ptr,173                        );174                        return if !fmt.arguments.arguments.is_empty() {175                            // With an incomplete format string (e.g. only an opening `{`), it's possible for `arguments`176                            // to be non-empty when reaching this code path.177                            self.alloc_expr(178                                Expr::Block {179                                    id: None,180                                    statements: fmt181                                        .arguments182                                        .arguments183                                        .iter()184                                        .map(|arg| Statement::Expr {185                                            expr: arg.expr,186                                            has_semi: true,187                                        })188                                        .collect(),189                                    tail: Some(from_str),190                                    label: None,191                                },192                                syntax_ptr,193                            )194                        } else {195                            from_str196                        };197                    }198199                    // Encode the literal in chunks of up to u16::MAX bytes, split at utf-8 boundaries.200                    while !s.is_empty() {201                        let len = s.floor_char_boundary(usize::from(u16::MAX));202                        if len < 0x80 {203                            bytecode.push(len as u8);204                        } else {205                            bytecode.push(0x80);206                            bytecode.extend_from_slice(&(len as u16).to_le_bytes());207                        }208                        bytecode.extend(&s.as_bytes()[..len]);209                        s = &s[len..];210                    }211212                    incomplete_lit.clear();213                }214                FormatArgsPiece::Placeholder(p) => {215                    // Push the start byte and remember its index so we can set the option bits later.216                    let i = bytecode.len();217                    bytecode.push(0xC0);218219                    let position = match &p.argument.index {220                        &Ok(it) => it,221                        Err(_) => usize::MAX,222                    };223                    let position = argmap224                        .insert_full((position, ArgumentType::Format(p.format_trait)))225                        .0 as u64;226227                    // This needs to match the constants in library/core/src/fmt/mod.rs.228                    let o = &p.format_options;229                    let align = match o.alignment {230                        Some(FormatAlignment::Left) => 0,231                        Some(FormatAlignment::Right) => 1,232                        Some(FormatAlignment::Center) => 2,233                        None => 3,234                    };235                    let default_flags = 0x6000_0020;236                    let flags: u32 = o.fill.unwrap_or(' ') as u32237                        | ((o.sign == Some(FormatSign::Plus)) as u32) << 21238                        | ((o.sign == Some(FormatSign::Minus)) as u32) << 22239                        | (o.alternate as u32) << 23240                        | (o.zero_pad as u32) << 24241                        | ((o.debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25242                        | ((o.debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26243                        | (o.width.is_some() as u32) << 27244                        | (o.precision.is_some() as u32) << 28245                        | align << 29;246                    if flags != default_flags {247                        bytecode[i] |= 1;248                        bytecode.extend_from_slice(&flags.to_le_bytes());249                        if let Some(val) = &o.width {250                            let (indirect, val) = self.make_count_after_1_93_0(val, &mut argmap);251                            // Only encode if nonzero; zero is the default.252                            if indirect || val != 0 {253                                bytecode[i] |= 1 << 1 | (indirect as u8) << 4;254                                bytecode.extend_from_slice(&val.to_le_bytes());255                            }256                        }257                        if let Some(val) = &o.precision {258                            let (indirect, val) = self.make_count_after_1_93_0(val, &mut argmap);259                            // Only encode if nonzero; zero is the default.260                            if indirect || val != 0 {261                                bytecode[i] |= 1 << 2 | (indirect as u8) << 5;262                                bytecode.extend_from_slice(&val.to_le_bytes());263                            }264                        }265                    }266                    if implicit_arg_index != position {267                        bytecode[i] |= 1 << 3;268                        bytecode.extend_from_slice(&(position as u16).to_le_bytes());269                    }270                    implicit_arg_index = position + 1;271                }272            }273        }274275        assert!(incomplete_lit.is_empty());276277        // Zero terminator.278        bytecode.push(0);279280        // Ensure all argument indexes actually fit in 16 bits, as we truncated them to 16 bits before.281        if argmap.len() > u16::MAX as usize {282            // FIXME: Emit an error.283            // ctx.dcx().span_err(macsp, "too many format arguments");284        }285286        let arguments = &fmt.arguments.arguments[..];287288        let (mut statements, args) = if arguments.is_empty() {289            // Generate:290            //     []291            (292                Vec::new(),293                self.alloc_expr_desugared(Expr::Array(Array::ElementList {294                    elements: Box::new([]),295                })),296            )297        } else {298            // Generate:299            //     super let args = (&arg0, &arg1, &…);300            let args_name = self.generate_new_name();301            let args_path = Path::from(args_name.clone());302            let args_binding = self.alloc_binding(303                args_name.clone(),304                BindingAnnotation::Unannotated,305                HygieneId::ROOT,306            );307            let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });308            self.add_definition_to_binding(args_binding, args_pat);309            let elements = arguments310                .iter()311                .map(|arg| {312                    self.alloc_expr_desugared(Expr::Ref {313                        expr: arg.expr,314                        rawness: Rawness::Ref,315                        mutability: Mutability::Shared,316                    })317                })318                .collect();319            let args_tuple = self.alloc_expr_desugared(Expr::Tuple { exprs: elements });320            // FIXME: Make this a `super let` when we have this statement.321            let let_statement_1 = Statement::Let {322                pat: args_pat,323                type_ref: None,324                initializer: Some(args_tuple),325                else_branch: None,326            };327328            // Generate:329            //     super let args = [330            //         <core::fmt::Argument>::new_display(args.0),331            //         <core::fmt::Argument>::new_lower_hex(args.1),332            //         <core::fmt::Argument>::new_debug(args.0),333            //         …334            //     ];335            let args = argmap336                .iter()337                .map(|&(arg_index, ty)| {338                    let args_ident_expr = self.alloc_expr_desugared(Expr::Path(args_path.clone()));339                    let arg = self.alloc_expr_desugared(Expr::Field {340                        expr: args_ident_expr,341                        name: Name::new_tuple_field(arg_index),342                    });343                    self.make_argument(arg, ty)344                })345                .collect();346            let args =347                self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));348            let args_binding =349                self.alloc_binding(args_name, BindingAnnotation::Unannotated, HygieneId::ROOT);350            let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });351            self.add_definition_to_binding(args_binding, args_pat);352            // FIXME: Make this a `super let` when we have this statement.353            let let_statement_2 = Statement::Let {354                pat: args_pat,355                type_ref: None,356                initializer: Some(args),357                else_branch: None,358            };359            (360                vec![let_statement_1, let_statement_2],361                self.alloc_expr_desugared(Expr::Path(args_path)),362            )363        };364365        // Generate:366        //     unsafe {367        //         <core::fmt::Arguments>::new(b"…", &args)368        //     }369        let template = self370            .alloc_expr_desugared(Expr::Literal(Literal::ByteString(bytecode.into_boxed_slice())));371        let call = {372            let new = self.ty_rel_lang_path_desugared_expr(lang_items.FormatArguments, sym::new);373            let args = self.alloc_expr_desugared(Expr::Ref {374                expr: args,375                rawness: Rawness::Ref,376                mutability: Mutability::Shared,377            });378            self.alloc_expr_desugared(Expr::Call { callee: new, args: Box::new([template, args]) })379        };380        let call = self.alloc_expr(381            Expr::Unsafe { id: None, statements: Box::new([]), tail: Some(call) },382            syntax_ptr,383        );384385        // We collect the unused expressions here so that we still infer them instead of386        // dropping them out of the expression tree. We cannot store them in the `Unsafe`387        // block because then unsafe blocks within them will get a false "unused unsafe"388        // diagnostic (rustc has a notion of builtin unsafe blocks, but we don't).389        statements390            .extend(fmt.orphans.into_iter().map(|expr| Statement::Expr { expr, has_semi: true }));391392        if !statements.is_empty() {393            // Generate:394            //     {395            //         super let …396            //         super let …397            //         <core::fmt::Arguments>::new(…)398            //     }399            self.alloc_expr(400                Expr::Block {401                    id: None,402                    statements: statements.into_boxed_slice(),403                    tail: Some(call),404                    label: None,405                },406                syntax_ptr,407            )408        } else {409            call410        }411    }412413    /// Get the value for a `width` or `precision` field.414    ///415    /// Returns the value and whether it is indirect (an indexed argument) or not.416    fn make_count_after_1_93_0(417        &self,418        count: &FormatCount,419        argmap: &mut FxIndexSet<(usize, ArgumentType)>,420    ) -> (bool, u16) {421        match count {422            FormatCount::Literal(n) => (false, *n),423            FormatCount::Argument(arg) => {424                let index = match &arg.index {425                    &Ok(it) => it,426                    Err(_) => usize::MAX,427                };428                (true, argmap.insert_full((index, ArgumentType::Usize)).0 as u16)429            }430        }431    }432433    fn collect_format_args_before_1_93_0_impl(434        &mut self,435        syntax_ptr: AstPtr<ast::Expr>,436        fmt: FormatArgs,437    ) -> ExprId {438        // Create a list of all _unique_ (argument, format trait) combinations.439        // E.g. "{0} {0:x} {0} {1}" -> [(0, Display), (0, LowerHex), (1, Display)]440        let mut argmap = FxIndexSet::default();441        for piece in fmt.template.iter() {442            let FormatArgsPiece::Placeholder(placeholder) = piece else { continue };443            if let Ok(index) = placeholder.argument.index {444                argmap.insert((index, ArgumentType::Format(placeholder.format_trait)));445            }446        }447448        let lit_pieces = fmt449            .template450            .iter()451            .enumerate()452            .filter_map(|(i, piece)| {453                match piece {454                    FormatArgsPiece::Literal(s) => {455                        Some(self.alloc_expr_desugared(Expr::Literal(Literal::String(s.clone()))))456                    }457                    &FormatArgsPiece::Placeholder(_) => {458                        // Inject empty string before placeholders when not already preceded by a literal piece.459                        if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_))460                        {461                            Some(self.alloc_expr_desugared(Expr::Literal(Literal::String(462                                Symbol::empty(),463                            ))))464                        } else {465                            None466                        }467                    }468                }469            })470            .collect();471        let lit_pieces =472            self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: lit_pieces }));473        let lit_pieces = self.alloc_expr_desugared(Expr::Ref {474            expr: lit_pieces,475            rawness: Rawness::Ref,476            mutability: Mutability::Shared,477        });478        let format_options = {479            // Generate:480            //     &[format_spec_0, format_spec_1, format_spec_2]481            let elements = fmt482                .template483                .iter()484                .filter_map(|piece| {485                    let FormatArgsPiece::Placeholder(placeholder) = piece else { return None };486                    Some(self.make_format_spec(placeholder, &mut argmap))487                })488                .collect();489            let array = self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements }));490            self.alloc_expr_desugared(Expr::Ref {491                expr: array,492                rawness: Rawness::Ref,493                mutability: Mutability::Shared,494            })495        };496497        // Assume that rustc version >= 1.89.0 iff lang item `format_arguments` exists498        // but `format_unsafe_arg` does not499        let lang_items = self.lang_items();500        let fmt_args = lang_items.FormatArguments;501        let fmt_unsafe_arg = lang_items.FormatUnsafeArg;502        let use_format_args_since_1_89_0 = fmt_args.is_some() && fmt_unsafe_arg.is_none();503504        if use_format_args_since_1_89_0 {505            self.collect_format_args_after_1_89_0_impl(506                syntax_ptr,507                fmt,508                argmap,509                lit_pieces,510                format_options,511            )512        } else {513            self.collect_format_args_before_1_89_0_impl(514                syntax_ptr,515                fmt,516                argmap,517                lit_pieces,518                format_options,519            )520        }521    }522523    /// `format_args!` expansion implementation for rustc versions < `1.89.0`524    fn collect_format_args_before_1_89_0_impl(525        &mut self,526        syntax_ptr: AstPtr<ast::Expr>,527        fmt: FormatArgs,528        argmap: FxIndexSet<(usize, ArgumentType)>,529        lit_pieces: ExprId,530        format_options: ExprId,531    ) -> ExprId {532        let arguments = &*fmt.arguments.arguments;533534        let args = if arguments.is_empty() {535            let expr = self536                .alloc_expr_desugared(Expr::Array(Array::ElementList { elements: Box::default() }));537            self.alloc_expr_desugared(Expr::Ref {538                expr,539                rawness: Rawness::Ref,540                mutability: Mutability::Shared,541            })542        } else {543            // Generate:544            //     &match (&arg0, &arg1, &…) {545            //         args => [546            //             <core::fmt::Argument>::new_display(args.0),547            //             <core::fmt::Argument>::new_lower_hex(args.1),548            //             <core::fmt::Argument>::new_debug(args.0),549            //             …550            //         ]551            //     }552            let args = argmap553                .iter()554                .map(|&(arg_index, ty)| {555                    let arg = self.alloc_expr_desugared(Expr::Ref {556                        expr: arguments[arg_index].expr,557                        rawness: Rawness::Ref,558                        mutability: Mutability::Shared,559                    });560                    self.make_argument(arg, ty)561                })562                .collect();563            let array =564                self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));565            self.alloc_expr_desugared(Expr::Ref {566                expr: array,567                rawness: Rawness::Ref,568                mutability: Mutability::Shared,569            })570        };571572        // Generate:573        //     <core::fmt::Arguments>::new_v1_formatted(574        //         lit_pieces,575        //         args,576        //         format_options,577        //         unsafe { ::core::fmt::UnsafeArg::new() }578        //     )579580        let lang_items = self.lang_items();581        let new_v1_formatted =582            self.ty_rel_lang_path_desugared_expr(lang_items.FormatArguments, sym::new_v1_formatted);583        let unsafe_arg_new =584            self.ty_rel_lang_path_desugared_expr(lang_items.FormatUnsafeArg, sym::new);585        let unsafe_arg_new =586            self.alloc_expr_desugared(Expr::Call { callee: unsafe_arg_new, args: Box::default() });587        let mut unsafe_arg_new = self.alloc_expr_desugared(Expr::Unsafe {588            id: None,589            statements: Box::new([]),590            tail: Some(unsafe_arg_new),591        });592        if !fmt.orphans.is_empty() {593            unsafe_arg_new = self.alloc_expr_desugared(Expr::Block {594                id: None,595                // We collect the unused expressions here so that we still infer them instead of596                // dropping them out of the expression tree. We cannot store them in the `Unsafe`597                // block because then unsafe blocks within them will get a false "unused unsafe"598                // diagnostic (rustc has a notion of builtin unsafe blocks, but we don't).599                statements: fmt600                    .orphans601                    .into_iter()602                    .map(|expr| Statement::Expr { expr, has_semi: true })603                    .collect(),604                tail: Some(unsafe_arg_new),605                label: None,606            });607        }608609        self.alloc_expr(610            Expr::Call {611                callee: new_v1_formatted,612                args: Box::new([lit_pieces, args, format_options, unsafe_arg_new]),613            },614            syntax_ptr,615        )616    }617618    /// `format_args!` expansion implementation for rustc versions >= `1.89.0`,619    /// especially since [this PR](https://github.com/rust-lang/rust/pull/140748)620    fn collect_format_args_after_1_89_0_impl(621        &mut self,622        syntax_ptr: AstPtr<ast::Expr>,623        fmt: FormatArgs,624        argmap: FxIndexSet<(usize, ArgumentType)>,625        lit_pieces: ExprId,626        format_options: ExprId,627    ) -> ExprId {628        let arguments = &*fmt.arguments.arguments;629630        let (let_stmts, args) = if arguments.is_empty() {631            (632                // Generate:633                //     []634                vec![],635                self.alloc_expr_desugared(Expr::Array(Array::ElementList {636                    elements: Box::default(),637                })),638            )639        } else if argmap.len() == 1 && arguments.len() == 1 {640            // Only one argument, so we don't need to make the `args` tuple.641            //642            // Generate:643            //     super let args = [<core::fmt::Arguments>::new_display(&arg)];644            let args = argmap645                .iter()646                .map(|&(arg_index, ty)| {647                    let ref_arg = self.alloc_expr_desugared(Expr::Ref {648                        expr: arguments[arg_index].expr,649                        rawness: Rawness::Ref,650                        mutability: Mutability::Shared,651                    });652                    self.make_argument(ref_arg, ty)653                })654                .collect();655            let args =656                self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));657            let args_name = self.generate_new_name();658            let args_binding = self.alloc_binding(659                args_name.clone(),660                BindingAnnotation::Unannotated,661                HygieneId::ROOT,662            );663            let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });664            self.add_definition_to_binding(args_binding, args_pat);665            // TODO: We don't have `super let` yet.666            let let_stmt = Statement::Let {667                pat: args_pat,668                type_ref: None,669                initializer: Some(args),670                else_branch: None,671            };672            (vec![let_stmt], self.alloc_expr_desugared(Expr::Path(args_name.into())))673        } else {674            // Generate:675            //     super let args = (&arg0, &arg1, &...);676            let args_name = self.generate_new_name();677            let args_binding = self.alloc_binding(678                args_name.clone(),679                BindingAnnotation::Unannotated,680                HygieneId::ROOT,681            );682            let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });683            self.add_definition_to_binding(args_binding, args_pat);684            let elements = arguments685                .iter()686                .map(|arg| {687                    self.alloc_expr_desugared(Expr::Ref {688                        expr: arg.expr,689                        rawness: Rawness::Ref,690                        mutability: Mutability::Shared,691                    })692                })693                .collect();694            let args_tuple = self.alloc_expr_desugared(Expr::Tuple { exprs: elements });695            // TODO: We don't have `super let` yet696            let let_stmt1 = Statement::Let {697                pat: args_pat,698                type_ref: None,699                initializer: Some(args_tuple),700                else_branch: None,701            };702703            // Generate:704            //     super let args = [705            //         <core::fmt::Argument>::new_display(args.0),706            //         <core::fmt::Argument>::new_lower_hex(args.1),707            //         <core::fmt::Argument>::new_debug(args.0),708            //         …709            //     ];710            let args = argmap711                .iter()712                .map(|&(arg_index, ty)| {713                    let args_ident_expr =714                        self.alloc_expr_desugared(Expr::Path(args_name.clone().into()));715                    let arg = self.alloc_expr_desugared(Expr::Field {716                        expr: args_ident_expr,717                        name: Name::new_tuple_field(arg_index),718                    });719                    self.make_argument(arg, ty)720                })721                .collect();722            let array =723                self.alloc_expr_desugared(Expr::Array(Array::ElementList { elements: args }));724            let args_binding = self.alloc_binding(725                args_name.clone(),726                BindingAnnotation::Unannotated,727                HygieneId::ROOT,728            );729            let args_pat = self.alloc_pat_desugared(Pat::Bind { id: args_binding, subpat: None });730            self.add_definition_to_binding(args_binding, args_pat);731            let let_stmt2 = Statement::Let {732                pat: args_pat,733                type_ref: None,734                initializer: Some(array),735                else_branch: None,736            };737            (vec![let_stmt1, let_stmt2], self.alloc_expr_desugared(Expr::Path(args_name.into())))738        };739740        // Generate:741        //     &args742        let args = self.alloc_expr_desugared(Expr::Ref {743            expr: args,744            rawness: Rawness::Ref,745            mutability: Mutability::Shared,746        });747748        let call_block = {749            // Generate:750            //     unsafe {751            //         <core::fmt::Arguments>::new_v1_formatted(752            //             lit_pieces,753            //             args,754            //             format_options,755            //         )756            //     }757758            let new_v1_formatted = self.ty_rel_lang_path_desugared_expr(759                self.lang_items().FormatArguments,760                sym::new_v1_formatted,761            );762            let args = [lit_pieces, args, format_options];763            let call = self764                .alloc_expr_desugared(Expr::Call { callee: new_v1_formatted, args: args.into() });765766            Expr::Unsafe { id: None, statements: Box::default(), tail: Some(call) }767        };768769        if !let_stmts.is_empty() {770            // Generate:771            //     {772            //         super let …773            //         super let …774            //         <core::fmt::Arguments>::new_…(…)775            //     }776            let call = self.alloc_expr_desugared(call_block);777            self.alloc_expr(778                Expr::Block {779                    id: None,780                    statements: let_stmts.into(),781                    tail: Some(call),782                    label: None,783                },784                syntax_ptr,785            )786        } else {787            self.alloc_expr(call_block, syntax_ptr)788        }789    }790791    /// Generate a hir expression for a format_args placeholder specification.792    ///793    /// Generates794    ///795    /// ```text796    ///     <core::fmt::rt::Placeholder::new(797    ///         …usize, // position798    ///         '…', // fill799    ///         <core::fmt::rt::Alignment>::…, // alignment800    ///         …u32, // flags801    ///         <core::fmt::rt::Count::…>, // width802    ///         <core::fmt::rt::Count::…>, // precision803    ///     )804    /// ```805    fn make_format_spec(806        &mut self,807        placeholder: &FormatPlaceholder,808        argmap: &mut FxIndexSet<(usize, ArgumentType)>,809    ) -> ExprId {810        let lang_items = self.lang_items();811        let position = match placeholder.argument.index {812            Ok(arg_index) => {813                let (i, _) =814                    argmap.insert_full((arg_index, ArgumentType::Format(placeholder.format_trait)));815                self.alloc_expr_desugared(Expr::Literal(Literal::Uint(816                    i as u128,817                    Some(BuiltinUint::Usize),818                )))819            }820            Err(_) => self.missing_expr(),821        };822        let &FormatOptions {823            ref width,824            ref precision,825            alignment,826            fill,827            sign,828            alternate,829            zero_pad,830            debug_hex,831        } = &placeholder.format_options;832833        let precision_expr = self.make_count_before_1_93_0(precision, argmap);834        let width_expr = self.make_count_before_1_93_0(width, argmap);835836        if self.krate.workspace_data(self.db).is_atleast_187() {837            // These need to match the constants in library/core/src/fmt/rt.rs.838            let align = match alignment {839                Some(FormatAlignment::Left) => 0,840                Some(FormatAlignment::Right) => 1,841                Some(FormatAlignment::Center) => 2,842                None => 3,843            };844            // This needs to match `Flag` in library/core/src/fmt/rt.rs.845            let flags = fill.unwrap_or(' ') as u32846                | ((sign == Some(FormatSign::Plus)) as u32) << 21847                | ((sign == Some(FormatSign::Minus)) as u32) << 22848                | (alternate as u32) << 23849                | (zero_pad as u32) << 24850                | ((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 25851                | ((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 26852                | (width.is_some() as u32) << 27853                | (precision.is_some() as u32) << 28854                | align << 29855                | 1 << 31; // Highest bit always set.856            let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(857                flags as u128,858                Some(BuiltinUint::U32),859            )));860861            let position =862                RecordLitField { name: Name::new_symbol_root(sym::position), expr: position };863            let flags = RecordLitField { name: Name::new_symbol_root(sym::flags), expr: flags };864            let precision = RecordLitField {865                name: Name::new_symbol_root(sym::precision),866                expr: precision_expr,867            };868            let width =869                RecordLitField { name: Name::new_symbol_root(sym::width), expr: width_expr };870            self.alloc_expr_desugared(Expr::RecordLit {871                path: self.lang_path(lang_items.FormatPlaceholder).map(Box::new),872                fields: Box::new([position, flags, precision, width]),873                spread: RecordSpread::None,874            })875        } else {876            let format_placeholder_new =877                self.ty_rel_lang_path_desugared_expr(lang_items.FormatPlaceholder, sym::new);878            // This needs to match `Flag` in library/core/src/fmt/rt.rs.879            let flags: u32 = ((sign == Some(FormatSign::Plus)) as u32)880                | (((sign == Some(FormatSign::Minus)) as u32) << 1)881                | ((alternate as u32) << 2)882                | ((zero_pad as u32) << 3)883                | (((debug_hex == Some(FormatDebugHex::Lower)) as u32) << 4)884                | (((debug_hex == Some(FormatDebugHex::Upper)) as u32) << 5);885            let flags = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(886                flags as u128,887                Some(BuiltinUint::U32),888            )));889            let fill = self.alloc_expr_desugared(Expr::Literal(Literal::Char(fill.unwrap_or(' '))));890            let align = self.ty_rel_lang_path_desugared_expr(891                lang_items.FormatAlignment,892                match alignment {893                    Some(FormatAlignment::Left) => sym::Left,894                    Some(FormatAlignment::Right) => sym::Right,895                    Some(FormatAlignment::Center) => sym::Center,896                    None => sym::Unknown,897                },898            );899            self.alloc_expr_desugared(Expr::Call {900                callee: format_placeholder_new,901                args: Box::new([position, fill, align, flags, precision_expr, width_expr]),902            })903        }904    }905906    /// Generate a hir expression for a format_args Count.907    ///908    /// Generates:909    ///910    /// ```text911    ///     <core::fmt::rt::Count>::Is(…)912    /// ```913    ///914    /// or915    ///916    /// ```text917    ///     <core::fmt::rt::Count>::Param(…)918    /// ```919    ///920    /// or921    ///922    /// ```text923    ///     <core::fmt::rt::Count>::Implied924    /// ```925    fn make_count_before_1_93_0(926        &mut self,927        count: &Option<FormatCount>,928        argmap: &mut FxIndexSet<(usize, ArgumentType)>,929    ) -> ExprId {930        let lang_items = self.lang_items();931        match count {932            Some(FormatCount::Literal(n)) => {933                let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(934                    *n as u128,935                    // FIXME: Change this to Some(BuiltinUint::U16) once we drop support for toolchains < 1.88936                    None,937                )));938                let count_is =939                    self.ty_rel_lang_path_desugared_expr(lang_items.FormatCount, sym::Is);940                self.alloc_expr_desugared(Expr::Call { callee: count_is, args: Box::new([args]) })941            }942            Some(FormatCount::Argument(arg)) => {943                if let Ok(arg_index) = arg.index {944                    let (i, _) = argmap.insert_full((arg_index, ArgumentType::Usize));945946                    let args = self.alloc_expr_desugared(Expr::Literal(Literal::Uint(947                        i as u128,948                        Some(BuiltinUint::Usize),949                    )));950                    let count_param =951                        self.ty_rel_lang_path_desugared_expr(lang_items.FormatCount, sym::Param);952                    self.alloc_expr_desugared(Expr::Call {953                        callee: count_param,954                        args: Box::new([args]),955                    })956                } else {957                    // FIXME: This drops arg causing it to potentially not be resolved/type checked958                    // when typing?959                    self.missing_expr()960                }961            }962            None => match self.ty_rel_lang_path(lang_items.FormatCount, sym::Implied) {963                Some(count_param) => self.alloc_expr_desugared(Expr::Path(count_param)),964                None => self.missing_expr(),965            },966        }967    }968969    /// Generate a hir expression representing an argument to a format_args invocation.970    ///971    /// Generates:972    ///973    /// ```text974    ///     <core::fmt::Argument>::new_…(arg)975    /// ```976    fn make_argument(&mut self, arg: ExprId, ty: ArgumentType) -> ExprId {977        use ArgumentType::*;978        use FormatTrait::*;979980        let new_fn = self.ty_rel_lang_path_desugared_expr(981            self.lang_items().FormatArgument,982            match ty {983                Format(Display) => sym::new_display,984                Format(Debug) => sym::new_debug,985                Format(LowerExp) => sym::new_lower_exp,986                Format(UpperExp) => sym::new_upper_exp,987                Format(Octal) => sym::new_octal,988                Format(Pointer) => sym::new_pointer,989                Format(Binary) => sym::new_binary,990                Format(LowerHex) => sym::new_lower_hex,991                Format(UpperHex) => sym::new_upper_hex,992                Usize => sym::from_usize,993            },994        );995        self.alloc_expr_desugared(Expr::Call { callee: new_fn, args: Box::new([arg]) })996    }997998    fn ty_rel_lang_path_desugared_expr(999        &mut self,1000        lang: Option<impl Into<LangItemTarget>>,1001        relative_name: Symbol,1002    ) -> ExprId {1003        self.alloc_expr_desugared(self.ty_rel_lang_path_expr(lang, relative_name))1004    }1005}10061007#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]1008enum ArgumentType {1009    Format(FormatTrait),1010    Usize,1011}

Code quality findings 25

Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
// unsafe {
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
// block because then unsafe blocks within them will get a false "unused unsafe"
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
// diagnostic (rustc has a notion of builtin unsafe blocks, but we don't).
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
// unsafe { ::core::fmt::UnsafeArg::new() }
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
// block because then unsafe blocks within them will get a false "unused unsafe"
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
// diagnostic (rustc has a notion of builtin unsafe blocks, but we don't).
Critical: Use of 'unsafe' keyword bypasses Rust's safety guarantees. Requires careful auditing, clear justification (FFI, specific optimizations), and minimal scope.
error safety unsafe-block
// unsafe {
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
&[FormatArgsPiece::Literal(sym::__empty)][..]
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
&fmt.template[..]
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
bytecode.extend(&s.as_bytes()[..len]);
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
s = &s[len..];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
bytecode[i] |= 1;
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
bytecode[i] |= 1 << 1 | (indirect as u8) << 4;
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
bytecode[i] |= 1 << 2 | (indirect as u8) << 5;
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
bytecode[i] |= 1 << 3;
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let arguments = &fmt.arguments.arguments[..];
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
if i == 0 || matches!(fmt.template[i - 1], FormatArgsPiece::Placeholder(_))
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
expr: arguments[arg_index].expr,
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
/// especially since [this PR](https://github.com/rust-lang/rust/pull/140748)
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
expr: arguments[arg_index].expr,
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let (fmt, hygiene) = match template.and_then(|template| {
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
bytecode.push(len as u8);
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
bytecode.push(0x80);
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use ArgumentType::*;
Info: Wildcard imports (`use some::path::*;`) can obscure the origin of names and lead to conflicts. Prefer importing specific items explicitly.
info maintainability wildcard-import
use FormatTrait::*;

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.