From a769b30a9345799a496e800e6907d75af36cfe29 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Fri, 13 Jan 2023 16:35:47 +0100 Subject: [PATCH 01/16] Flatten nested format_args!() into one. --- compiler/rustc_ast/src/format.rs | 4 +- compiler/rustc_ast_lowering/src/format.rs | 102 +++++++++++++++++++++- 2 files changed, 102 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_ast/src/format.rs b/compiler/rustc_ast/src/format.rs index d021bea5ecacb..356b9bb6371e2 100644 --- a/compiler/rustc_ast/src/format.rs +++ b/compiler/rustc_ast/src/format.rs @@ -131,8 +131,8 @@ impl FormatArguments { &self.arguments[..] } - pub fn all_args_mut(&mut self) -> &mut [FormatArgument] { - &mut self.arguments[..] + pub fn all_args_mut(&mut self) -> &mut Vec { + &mut self.arguments } } diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index 4095e225a8019..9954df49ce942 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -7,15 +7,113 @@ use rustc_hir as hir; use rustc_span::{ sym, symbol::{kw, Ident}, - Span, + Span, Symbol, }; +use std::borrow::Cow; impl<'hir> LoweringContext<'_, 'hir> { pub(crate) fn lower_format_args(&mut self, sp: Span, fmt: &FormatArgs) -> hir::ExprKind<'hir> { - expand_format_args(self, sp, fmt) + let fmt = flatten_format_args(fmt); + expand_format_args(self, sp, &fmt) } } +/// Flattens nested `format_args!()` into one. +/// +/// Turns +/// +/// `format_args!("a {} {} {}.", 1, format_args!("b{}!", 2), 3)` +/// +/// into +/// +/// `format_args!("a {} b{}! {}.", 1, 2, 3)`. +fn flatten_format_args(fmt: &FormatArgs) -> Cow<'_, FormatArgs> { + let mut fmt = Cow::Borrowed(fmt); + let mut i = 0; + while i < fmt.template.len() { + if let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i] + && let FormatTrait::Display | FormatTrait::Debug = &placeholder.format_trait + && let Ok(arg_index) = placeholder.argument.index + && let arg = &fmt.arguments.all_args()[arg_index].expr + && let ExprKind::FormatArgs(_) = &arg.kind + // Check that this argument is not used by any other placeholders. + && fmt.template.iter().enumerate().all(|(j, p)| + i == j || + !matches!(p, FormatArgsPiece::Placeholder(placeholder) + if placeholder.argument.index == Ok(arg_index)) + ) + { + // Now we need to mutate the outer FormatArgs. + // If this is the first time, this clones the outer FormatArgs. + let fmt = fmt.to_mut(); + + // Take the inner FormatArgs out of the outer arguments, and + // replace it by the inner arguments. (We can't just put those at + // the end, because we need to preserve the order of evaluation.) + + let args = fmt.arguments.all_args_mut(); + let remaining_args = args.split_off(arg_index + 1); + let old_arg_offset = args.len(); + let fmt2 = args.pop().unwrap().expr.into_inner(); // The inner FormatArgs. + let ExprKind::FormatArgs(fmt2) = fmt2.kind else { unreachable!() }; + let mut fmt2 = fmt2.into_inner(); + + args.append(fmt2.arguments.all_args_mut()); + let new_arg_offset = args.len(); + args.extend(remaining_args); + + // Correct the indexes that refer to the arguments after the newly inserted arguments. + for piece in &mut fmt.template { + if let FormatArgsPiece::Placeholder(placeholder) = piece + && let Ok(index) = &mut placeholder.argument.index + && *index >= old_arg_offset + { + *index -= old_arg_offset; + *index += new_arg_offset; + } + } + + // Now merge the placeholders: + + let mut rest = fmt.template.split_off(i + 1); + fmt.template.pop(); // remove the placeholder for the nested fmt args. + + // Coalesce adjacent literals. + if let Some(FormatArgsPiece::Literal(s1)) = fmt.template.last() && + let Some(FormatArgsPiece::Literal(s2)) = fmt2.template.first_mut() + { + *s2 = Symbol::intern(&(s1.as_str().to_owned() + s2.as_str())); + fmt.template.pop(); + } + if let Some(FormatArgsPiece::Literal(s1)) = fmt2.template.last() && + let Some(FormatArgsPiece::Literal(s2)) = rest.first_mut() + { + *s2 = Symbol::intern(&(s1.as_str().to_owned() + s2.as_str())); + fmt2.template.pop(); + } + + for piece in fmt2.template { + match piece { + FormatArgsPiece::Literal(s) => fmt.template.push(FormatArgsPiece::Literal(s)), + FormatArgsPiece::Placeholder(mut p) => { + // Correct the index to refer to the right place into the outer argument list. + if let Ok(n) = &mut p.argument.index { + *n += arg_index; + } + fmt.template.push(FormatArgsPiece::Placeholder(p)); + } + } + } + fmt.template.extend(rest); + + // Don't increment `i` here, so we recurse into the newly added pieces. + } else { + i += 1; + } + } + fmt +} + #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] enum ArgumentType { Format(FormatTrait), From 94ad7e881dbf28784e44198dcf7dd865661b5424 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Fri, 13 Jan 2023 16:45:51 +0100 Subject: [PATCH 02/16] Coalesce adjacent literal pieces in expand_format_args. --- compiler/rustc_ast_lowering/src/format.rs | 32 +++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index 9954df49ce942..63e74261d7c53 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -75,23 +75,9 @@ fn flatten_format_args(fmt: &FormatArgs) -> Cow<'_, FormatArgs> { // Now merge the placeholders: - let mut rest = fmt.template.split_off(i + 1); + let rest = fmt.template.split_off(i + 1); fmt.template.pop(); // remove the placeholder for the nested fmt args. - // Coalesce adjacent literals. - if let Some(FormatArgsPiece::Literal(s1)) = fmt.template.last() && - let Some(FormatArgsPiece::Literal(s2)) = fmt2.template.first_mut() - { - *s2 = Symbol::intern(&(s1.as_str().to_owned() + s2.as_str())); - fmt.template.pop(); - } - if let Some(FormatArgsPiece::Literal(s1)) = fmt2.template.last() && - let Some(FormatArgsPiece::Literal(s2)) = rest.first_mut() - { - *s2 = Symbol::intern(&(s1.as_str().to_owned() + s2.as_str())); - fmt2.template.pop(); - } - for piece in fmt2.template { match piece { FormatArgsPiece::Literal(s) => fmt.template.push(FormatArgsPiece::Literal(s)), @@ -288,10 +274,24 @@ fn expand_format_args<'hir>( macsp: Span, fmt: &FormatArgs, ) -> hir::ExprKind<'hir> { + let mut incomplete_lit = String::new(); let lit_pieces = ctx.arena.alloc_from_iter(fmt.template.iter().enumerate().filter_map(|(i, piece)| { match piece { - &FormatArgsPiece::Literal(s) => Some(ctx.expr_str(fmt.span, s)), + &FormatArgsPiece::Literal(s) => { + // Coalesce adjacent literal pieces. + if let Some(FormatArgsPiece::Literal(_)) = fmt.template.get(i + 1) { + incomplete_lit.push_str(s.as_str()); + None + } else if !incomplete_lit.is_empty() { + incomplete_lit.push_str(s.as_str()); + let s = Symbol::intern(&incomplete_lit); + incomplete_lit.clear(); + Some(ctx.expr_str(fmt.span, s)) + } else { + Some(ctx.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(_)) { From 85ef2f0cfe7c176755d4ec542c38df868bca8bcb Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Fri, 13 Jan 2023 17:32:15 +0100 Subject: [PATCH 03/16] Inline string literals into format_args!(). --- compiler/rustc_ast_lowering/src/format.rs | 64 +++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index 63e74261d7c53..3db4d061f212e 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -13,7 +13,8 @@ use std::borrow::Cow; impl<'hir> LoweringContext<'_, 'hir> { pub(crate) fn lower_format_args(&mut self, sp: Span, fmt: &FormatArgs) -> hir::ExprKind<'hir> { - let fmt = flatten_format_args(fmt); + let fmt = flatten_format_args(Cow::Borrowed(fmt)); + let fmt = inline_literals(fmt); expand_format_args(self, sp, &fmt) } } @@ -27,8 +28,7 @@ impl<'hir> LoweringContext<'_, 'hir> { /// into /// /// `format_args!("a {} b{}! {}.", 1, 2, 3)`. -fn flatten_format_args(fmt: &FormatArgs) -> Cow<'_, FormatArgs> { - let mut fmt = Cow::Borrowed(fmt); +fn flatten_format_args(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { let mut i = 0; while i < fmt.template.len() { if let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i] @@ -100,6 +100,64 @@ fn flatten_format_args(fmt: &FormatArgs) -> Cow<'_, FormatArgs> { fmt } +/// Inline literals into the format string. +/// +/// Turns +/// +/// `format_args!("Hello, {}! {}", "World", 123)` +/// +/// into +/// +/// `format_args!("Hello, World! {}", 123)`. +fn inline_literals(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { + // None: Not sure yet. + // Some(true): Remove, because it was inlined. (Might be set to false later if it is used in another way.) + // Some(false): Do not remove, because some non-inlined placeholder uses it. + let mut remove = vec![None; fmt.arguments.all_args().len()]; + + for i in 0..fmt.template.len() { + let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i] else { continue }; + let Ok(arg_index) = placeholder.argument.index else { continue }; + if let FormatTrait::Display = placeholder.format_trait + && let ExprKind::Lit(lit) = fmt.arguments.all_args()[arg_index].expr.kind + && let token::LitKind::Str | token::LitKind::StrRaw(_) = lit.kind + && let Ok(LitKind::Str(s, _)) = LitKind::from_token_lit(lit) + { + // Now we need to mutate the outer FormatArgs. + // If this is the first time, this clones the outer FormatArgs. + let fmt = fmt.to_mut(); + // Replace the placeholder with the literal. + fmt.template[i] = FormatArgsPiece::Literal(s); + // Only remove it wasn't set to 'do not remove'. + remove[arg_index].get_or_insert(true); + } else { + // Never remove an argument that's used by a non-inlined placeholder, + // even if this argument is inlined in another place. + remove[arg_index] = Some(false); + } + } + + // Remove the arguments that were inlined. + if remove.iter().any(|&x| x == Some(true)) { + let fmt = fmt.to_mut(); + // Drop all the arguments that are marked for removal. + let mut remove_it = remove.iter(); + fmt.arguments.all_args_mut().retain(|_| remove_it.next() != Some(&Some(true))); + // Correct the indexes that refer to arguments that have shifted position. + for piece in &mut fmt.template { + let FormatArgsPiece::Placeholder(placeholder) = piece else { continue }; + let Ok(arg_index) = &mut placeholder.argument.index else { continue }; + for i in 0..*arg_index { + if remove[i] == Some(true) { + *arg_index -= 1; + } + } + } + } + + fmt +} + #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] enum ArgumentType { Format(FormatTrait), From caa6ba9e86281eb1fbdd87d5100cc7e92ba07f5c Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Fri, 13 Jan 2023 18:00:56 +0100 Subject: [PATCH 04/16] Support flattening/inlining format_args through & and (). E.g. format_args!("{}", &(format_args!("abc"))). --- compiler/rustc_ast/src/ast.rs | 9 +++++++++ compiler/rustc_ast_lowering/src/format.rs | 18 ++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 6503bf2bab762..5d164bc4b3cf5 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1184,6 +1184,15 @@ impl Expr { expr } + pub fn peel_parens_and_refs(&self) -> &Expr { + let mut expr = self; + while let ExprKind::Paren(inner) | ExprKind::AddrOf(BorrowKind::Ref, _, inner) = &expr.kind + { + expr = inner; + } + expr + } + /// Attempts to reparse as `Ty` (for diagnostic purposes). pub fn to_ty(&self) -> Option> { let kind = match &self.kind { diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index 3db4d061f212e..c21fb3a4cfb73 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -34,7 +34,7 @@ fn flatten_format_args(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { if let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i] && let FormatTrait::Display | FormatTrait::Debug = &placeholder.format_trait && let Ok(arg_index) = placeholder.argument.index - && let arg = &fmt.arguments.all_args()[arg_index].expr + && let arg = fmt.arguments.all_args()[arg_index].expr.peel_parens_and_refs() && let ExprKind::FormatArgs(_) = &arg.kind // Check that this argument is not used by any other placeholders. && fmt.template.iter().enumerate().all(|(j, p)| @@ -54,9 +54,14 @@ fn flatten_format_args(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { let args = fmt.arguments.all_args_mut(); let remaining_args = args.split_off(arg_index + 1); let old_arg_offset = args.len(); - let fmt2 = args.pop().unwrap().expr.into_inner(); // The inner FormatArgs. - let ExprKind::FormatArgs(fmt2) = fmt2.kind else { unreachable!() }; - let mut fmt2 = fmt2.into_inner(); + let mut fmt2 = &mut args.pop().unwrap().expr; // The inner FormatArgs. + let fmt2 = loop { // Unwrap the Expr to get to the FormatArgs. + match &mut fmt2.kind { + ExprKind::Paren(inner) | ExprKind::AddrOf(BorrowKind::Ref, _, inner) => fmt2 = inner, + ExprKind::FormatArgs(fmt2) => break fmt2, + _ => unreachable!(), + } + }; args.append(fmt2.arguments.all_args_mut()); let new_arg_offset = args.len(); @@ -78,7 +83,7 @@ fn flatten_format_args(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { let rest = fmt.template.split_off(i + 1); fmt.template.pop(); // remove the placeholder for the nested fmt args. - for piece in fmt2.template { + for piece in fmt2.template.drain(..) { match piece { FormatArgsPiece::Literal(s) => fmt.template.push(FormatArgsPiece::Literal(s)), FormatArgsPiece::Placeholder(mut p) => { @@ -119,7 +124,8 @@ fn inline_literals(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i] else { continue }; let Ok(arg_index) = placeholder.argument.index else { continue }; if let FormatTrait::Display = placeholder.format_trait - && let ExprKind::Lit(lit) = fmt.arguments.all_args()[arg_index].expr.kind + && let arg = fmt.arguments.all_args()[arg_index].expr.peel_parens_and_refs() + && let ExprKind::Lit(lit) = arg.kind && let token::LitKind::Str | token::LitKind::StrRaw(_) = lit.kind && let Ok(LitKind::Str(s, _)) = LitKind::from_token_lit(lit) { From b7678d48b8c168bbbc0c34911bb38c8cd484e0ee Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Fri, 13 Jan 2023 18:59:21 +0100 Subject: [PATCH 05/16] Only inline `{}` string literals in format_args. Placeholders like {:123} would incorrectly get inlined. --- compiler/rustc_ast_lowering/src/format.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index c21fb3a4cfb73..96ca81d823bd6 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -124,6 +124,7 @@ fn inline_literals(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i] else { continue }; let Ok(arg_index) = placeholder.argument.index else { continue }; if let FormatTrait::Display = placeholder.format_trait + && placeholder.format_options == Default::default() && let arg = fmt.arguments.all_args()[arg_index].expr.peel_parens_and_refs() && let ExprKind::Lit(lit) = arg.kind && let token::LitKind::Str | token::LitKind::StrRaw(_) = lit.kind From b6c988b041194136ee20407a920903d49808fb06 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Fri, 13 Jan 2023 19:01:19 +0100 Subject: [PATCH 06/16] Fix argument index remapping in format_args flattening. --- compiler/rustc_ast_lowering/src/format.rs | 64 +++++++++++++---------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index 96ca81d823bd6..672c503f5b271 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -68,33 +68,21 @@ fn flatten_format_args(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { args.extend(remaining_args); // Correct the indexes that refer to the arguments after the newly inserted arguments. - for piece in &mut fmt.template { - if let FormatArgsPiece::Placeholder(placeholder) = piece - && let Ok(index) = &mut placeholder.argument.index - && *index >= old_arg_offset - { + for_all_argument_indexes(&mut fmt.template, |index| { + if *index >= old_arg_offset { *index -= old_arg_offset; *index += new_arg_offset; } - } + }); // Now merge the placeholders: let rest = fmt.template.split_off(i + 1); fmt.template.pop(); // remove the placeholder for the nested fmt args. - - for piece in fmt2.template.drain(..) { - match piece { - FormatArgsPiece::Literal(s) => fmt.template.push(FormatArgsPiece::Literal(s)), - FormatArgsPiece::Placeholder(mut p) => { - // Correct the index to refer to the right place into the outer argument list. - if let Ok(n) = &mut p.argument.index { - *n += arg_index; - } - fmt.template.push(FormatArgsPiece::Placeholder(p)); - } - } - } + // Insert the pieces from the nested format args, but correct any + // placeholders to point to the correct argument index. + for_all_argument_indexes(&mut fmt2.template, |index| *index += arg_index); + fmt.template.append(&mut fmt2.template); fmt.template.extend(rest); // Don't increment `i` here, so we recurse into the newly added pieces. @@ -150,16 +138,17 @@ fn inline_literals(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { // Drop all the arguments that are marked for removal. let mut remove_it = remove.iter(); fmt.arguments.all_args_mut().retain(|_| remove_it.next() != Some(&Some(true))); + // Calculate the mapping of old to new indexes for the remaining arguments. + let index_map: Vec = remove + .into_iter() + .scan(0, |i, remove| { + let mapped = *i; + *i += (remove != Some(true)) as usize; + Some(mapped) + }) + .collect(); // Correct the indexes that refer to arguments that have shifted position. - for piece in &mut fmt.template { - let FormatArgsPiece::Placeholder(placeholder) = piece else { continue }; - let Ok(arg_index) = &mut placeholder.argument.index else { continue }; - for i in 0..*arg_index { - if remove[i] == Some(true) { - *arg_index -= 1; - } - } - } + for_all_argument_indexes(&mut fmt.template, |index| *index = index_map[*index]); } fmt @@ -572,3 +561,22 @@ fn may_contain_yield_point(e: &ast::Expr) -> bool { visitor.visit_expr(e); visitor.0 } + +fn for_all_argument_indexes(template: &mut [FormatArgsPiece], mut f: impl FnMut(&mut usize)) { + for piece in template { + let FormatArgsPiece::Placeholder(placeholder) = piece else { continue }; + if let Ok(index) = &mut placeholder.argument.index { + f(index); + } + if let Some(FormatCount::Argument(FormatArgPosition { index: Ok(index), .. })) = + &mut placeholder.format_options.width + { + f(index); + } + if let Some(FormatCount::Argument(FormatArgPosition { index: Ok(index), .. })) = + &mut placeholder.format_options.precision + { + f(index); + } + } +} From 0554401fcc7bc552b45a900781434e856f917404 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Fri, 13 Jan 2023 19:01:59 +0100 Subject: [PATCH 07/16] Remove unreachable branch in format_args ast lowering. --- compiler/rustc_ast_lowering/src/format.rs | 27 +++++++---------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index 672c503f5b271..2c434ceb09bee 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -442,25 +442,14 @@ fn expand_format_args<'hir>( let args_ident = Ident::new(sym::args, macsp); let (args_pat, args_hir_id) = ctx.pat_ident(macsp, args_ident); let args = ctx.arena.alloc_from_iter(argmap.iter().map(|&(arg_index, ty)| { - if let Some(arg) = arguments.get(arg_index) { - let sp = arg.expr.span.with_ctxt(macsp.ctxt()); - let args_ident_expr = ctx.expr_ident(macsp, args_ident, args_hir_id); - let arg = ctx.arena.alloc(ctx.expr( - sp, - hir::ExprKind::Field( - args_ident_expr, - Ident::new(sym::integer(arg_index), macsp), - ), - )); - make_argument(ctx, sp, arg, ty) - } else { - ctx.expr( - macsp, - hir::ExprKind::Err( - ctx.tcx.sess.delay_span_bug(macsp, format!("no arg at {arg_index}")), - ), - ) - } + let arg = &arguments[arg_index]; + let sp = arg.expr.span.with_ctxt(macsp.ctxt()); + let args_ident_expr = ctx.expr_ident(macsp, args_ident, args_hir_id); + let arg = ctx.arena.alloc(ctx.expr( + sp, + hir::ExprKind::Field(args_ident_expr, Ident::new(sym::integer(arg_index), macsp)), + )); + make_argument(ctx, sp, arg, ty) })); let elements: Vec<_> = arguments .iter() From df8c14ca61833335fa6229b4d828fcc481a57432 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Fri, 13 Jan 2023 19:19:47 +0100 Subject: [PATCH 08/16] Check all arg indexes before removing inlined format args. --- compiler/rustc_ast_lowering/src/format.rs | 28 ++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index 2c434ceb09bee..acfc0099db7f0 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -103,10 +103,8 @@ fn flatten_format_args(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { /// /// `format_args!("Hello, World! {}", 123)`. fn inline_literals(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { - // None: Not sure yet. - // Some(true): Remove, because it was inlined. (Might be set to false later if it is used in another way.) - // Some(false): Do not remove, because some non-inlined placeholder uses it. - let mut remove = vec![None; fmt.arguments.all_args().len()]; + let mut was_inlined = vec![false; fmt.arguments.all_args().len()]; + let mut inlined_anything = false; for i in 0..fmt.template.len() { let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i] else { continue }; @@ -123,30 +121,34 @@ fn inline_literals(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { let fmt = fmt.to_mut(); // Replace the placeholder with the literal. fmt.template[i] = FormatArgsPiece::Literal(s); - // Only remove it wasn't set to 'do not remove'. - remove[arg_index].get_or_insert(true); - } else { - // Never remove an argument that's used by a non-inlined placeholder, - // even if this argument is inlined in another place. - remove[arg_index] = Some(false); + was_inlined[arg_index] = true; + inlined_anything = true; } } // Remove the arguments that were inlined. - if remove.iter().any(|&x| x == Some(true)) { + if inlined_anything { let fmt = fmt.to_mut(); + + let mut remove = was_inlined; + + // Don't remove anything that's still used. + for_all_argument_indexes(&mut fmt.template, |index| remove[*index] = false); + // Drop all the arguments that are marked for removal. let mut remove_it = remove.iter(); - fmt.arguments.all_args_mut().retain(|_| remove_it.next() != Some(&Some(true))); + fmt.arguments.all_args_mut().retain(|_| remove_it.next() != Some(&true)); + // Calculate the mapping of old to new indexes for the remaining arguments. let index_map: Vec = remove .into_iter() .scan(0, |i, remove| { let mapped = *i; - *i += (remove != Some(true)) as usize; + *i += !remove as usize; Some(mapped) }) .collect(); + // Correct the indexes that refer to arguments that have shifted position. for_all_argument_indexes(&mut fmt.template, |index| *index = index_map[*index]); } From 6a535dfff409438c4266a2b222f1dbdfeda9d372 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Fri, 13 Jan 2023 19:21:23 +0100 Subject: [PATCH 09/16] Also inline integer literals into format_args!(). --- compiler/rustc_ast_lowering/src/format.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index acfc0099db7f0..8a8318312d2ea 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -97,11 +97,11 @@ fn flatten_format_args(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { /// /// Turns /// -/// `format_args!("Hello, {}! {}", "World", 123)` +/// `format_args!("Hello, {}! {} {}", "World", 123, x)` /// /// into /// -/// `format_args!("Hello, World! {}", 123)`. +/// `format_args!("Hello, World! 123 {}", x)`. fn inline_literals(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { let mut was_inlined = vec![false; fmt.arguments.all_args().len()]; let mut inlined_anything = false; @@ -109,18 +109,31 @@ fn inline_literals(mut fmt: Cow<'_, FormatArgs>) -> Cow<'_, FormatArgs> { for i in 0..fmt.template.len() { let FormatArgsPiece::Placeholder(placeholder) = &fmt.template[i] else { continue }; let Ok(arg_index) = placeholder.argument.index else { continue }; + + let mut literal = None; + if let FormatTrait::Display = placeholder.format_trait && placeholder.format_options == Default::default() && let arg = fmt.arguments.all_args()[arg_index].expr.peel_parens_and_refs() && let ExprKind::Lit(lit) = arg.kind - && let token::LitKind::Str | token::LitKind::StrRaw(_) = lit.kind - && let Ok(LitKind::Str(s, _)) = LitKind::from_token_lit(lit) { + if let token::LitKind::Str | token::LitKind::StrRaw(_) = lit.kind + && let Ok(LitKind::Str(s, _)) = LitKind::from_token_lit(lit) + { + literal = Some(s); + } else if let token::LitKind::Integer = lit.kind + && let Ok(LitKind::Int(n, _)) = LitKind::from_token_lit(lit) + { + literal = Some(Symbol::intern(&n.to_string())); + } + } + + if let Some(literal) = literal { // Now we need to mutate the outer FormatArgs. // If this is the first time, this clones the outer FormatArgs. let fmt = fmt.to_mut(); // Replace the placeholder with the literal. - fmt.template[i] = FormatArgsPiece::Literal(s); + fmt.template[i] = FormatArgsPiece::Literal(literal); was_inlined[arg_index] = true; inlined_anything = true; } From 96d252160ea80dfa1cb26acc174a31cf94a09520 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Fri, 13 Jan 2023 19:21:36 +0100 Subject: [PATCH 10/16] Update format_args!() test to account for inlining. --- library/core/tests/fmt/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/library/core/tests/fmt/mod.rs b/library/core/tests/fmt/mod.rs index 61807635813c4..c1c80c46c78b7 100644 --- a/library/core/tests/fmt/mod.rs +++ b/library/core/tests/fmt/mod.rs @@ -22,11 +22,11 @@ fn test_pointer_formats_data_pointer() { #[test] fn test_estimated_capacity() { assert_eq!(format_args!("").estimated_capacity(), 0); - assert_eq!(format_args!("{}", "").estimated_capacity(), 0); + assert_eq!(format_args!("{}", {""}).estimated_capacity(), 0); assert_eq!(format_args!("Hello").estimated_capacity(), 5); - assert_eq!(format_args!("Hello, {}!", "").estimated_capacity(), 16); - assert_eq!(format_args!("{}, hello!", "World").estimated_capacity(), 0); - assert_eq!(format_args!("{}. 16-bytes piece", "World").estimated_capacity(), 32); + assert_eq!(format_args!("Hello, {}!", {""}).estimated_capacity(), 16); + assert_eq!(format_args!("{}, hello!", {"World"}).estimated_capacity(), 0); + assert_eq!(format_args!("{}. 16-bytes piece", {"World"}).estimated_capacity(), 32); } #[test] From f2f6bcc4998c6f2a2e044bac428a7ca16028c148 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 1 Feb 2023 17:39:14 +0100 Subject: [PATCH 11/16] Don't allow new const panic through format flattening. panic!("a {}", "b") is still not allowed in const, even if the hir flattens to panic!("a b"). --- compiler/rustc_ast_lowering/src/format.rs | 18 +++++++++++++- compiler/rustc_span/src/symbol.rs | 1 + library/core/src/fmt/mod.rs | 26 ++++++++++++++++++-- library/core/src/panicking.rs | 4 ++-- tests/ui/borrowck/issue-64453.stderr | 2 +- tests/ui/consts/const-eval/format.rs | 3 ++- tests/ui/consts/const-eval/format.stderr | 29 +++++++++++++++-------- 7 files changed, 66 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index 8a8318312d2ea..e094457961ede 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -13,9 +13,12 @@ use std::borrow::Cow; impl<'hir> LoweringContext<'_, 'hir> { pub(crate) fn lower_format_args(&mut self, sp: Span, fmt: &FormatArgs) -> hir::ExprKind<'hir> { + // Never call the const constructor of `fmt::Arguments` if the + // format_args!() had any arguments _before_ flattening/inlining. + let allow_const = fmt.arguments.all_args().is_empty(); let fmt = flatten_format_args(Cow::Borrowed(fmt)); let fmt = inline_literals(fmt); - expand_format_args(self, sp, &fmt) + expand_format_args(self, sp, &fmt, allow_const) } } @@ -342,6 +345,7 @@ fn expand_format_args<'hir>( ctx: &mut LoweringContext<'_, 'hir>, macsp: Span, fmt: &FormatArgs, + allow_const: bool, ) -> hir::ExprKind<'hir> { let mut incomplete_lit = String::new(); let lit_pieces = @@ -411,6 +415,18 @@ fn expand_format_args<'hir>( let arguments = fmt.arguments.all_args(); + if allow_const && arguments.is_empty() && argmap.is_empty() { + // Generate: + // ::new_const(lit_pieces) + let new = ctx.arena.alloc(ctx.expr_lang_item_type_relative( + macsp, + hir::LangItem::FormatArguments, + sym::new_const, + )); + let new_args = ctx.arena.alloc_from_iter([lit_pieces]); + return hir::ExprKind::Call(new, new_args); + } + // 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, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 0154c719ef608..abe5af8f9e0cf 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -984,6 +984,7 @@ symbols! { never_type_fallback, new, new_binary, + new_const, new_debug, new_display, new_lower_exp, diff --git a/library/core/src/fmt/mod.rs b/library/core/src/fmt/mod.rs index c9821bf8109a7..6d764237dc8ae 100644 --- a/library/core/src/fmt/mod.rs +++ b/library/core/src/fmt/mod.rs @@ -392,8 +392,31 @@ enum FlagV1 { } impl<'a> Arguments<'a> { + #[doc(hidden)] + #[inline] + #[unstable(feature = "fmt_internals", issue = "none")] + #[rustc_const_unstable(feature = "const_fmt_arguments_new", issue = "none")] + pub const fn new_const(pieces: &'a [&'static str]) -> Self { + if pieces.len() > 1 { + panic!("invalid args"); + } + Arguments { pieces, fmt: None, args: &[] } + } + /// When using the format_args!() macro, this function is used to generate the /// Arguments structure. + #[cfg(not(bootstrap))] + #[doc(hidden)] + #[inline] + #[unstable(feature = "fmt_internals", reason = "internal to format_args!", issue = "none")] + pub fn new_v1(pieces: &'a [&'static str], args: &'a [ArgumentV1<'a>]) -> Arguments<'a> { + if pieces.len() < args.len() || pieces.len() > args.len() + 1 { + panic!("invalid args"); + } + Arguments { pieces, fmt: None, args } + } + + #[cfg(bootstrap)] #[doc(hidden)] #[inline] #[unstable(feature = "fmt_internals", reason = "internal to format_args!", issue = "none")] @@ -417,8 +440,7 @@ impl<'a> Arguments<'a> { #[doc(hidden)] #[inline] #[unstable(feature = "fmt_internals", reason = "internal to format_args!", issue = "none")] - #[rustc_const_unstable(feature = "const_fmt_arguments_new", issue = "none")] - pub const fn new_v1_formatted( + pub fn new_v1_formatted( pieces: &'a [&'static str], args: &'a [ArgumentV1<'a>], fmt: &'a [rt::v1::Argument], diff --git a/library/core/src/panicking.rs b/library/core/src/panicking.rs index 805a1e51ae9c0..dd0105c0eb4fa 100644 --- a/library/core/src/panicking.rs +++ b/library/core/src/panicking.rs @@ -111,7 +111,7 @@ pub const fn panic(expr: &'static str) -> ! { // truncation and padding (even though none is used here). Using // Arguments::new_v1 may allow the compiler to omit Formatter::pad from the // output binary, saving up to a few kilobytes. - panic_fmt(fmt::Arguments::new_v1(&[expr], &[])); + panic_fmt(fmt::Arguments::new_const(&[expr])); } /// Like `panic`, but without unwinding and track_caller to reduce the impact on codesize. @@ -120,7 +120,7 @@ pub const fn panic(expr: &'static str) -> ! { #[lang = "panic_nounwind"] // needed by codegen for non-unwinding panics #[rustc_nounwind] pub fn panic_nounwind(expr: &'static str) -> ! { - panic_nounwind_fmt(fmt::Arguments::new_v1(&[expr], &[])); + panic_nounwind_fmt(fmt::Arguments::new_const(&[expr])); } #[inline] diff --git a/tests/ui/borrowck/issue-64453.stderr b/tests/ui/borrowck/issue-64453.stderr index 245c3a40e0506..f032ea779dd48 100644 --- a/tests/ui/borrowck/issue-64453.stderr +++ b/tests/ui/borrowck/issue-64453.stderr @@ -1,4 +1,4 @@ -error: `Arguments::<'a>::new_v1` is not yet stable as a const fn +error: `Arguments::<'a>::new_const` is not yet stable as a const fn --> $DIR/issue-64453.rs:4:31 | LL | static settings_dir: String = format!(""); diff --git a/tests/ui/consts/const-eval/format.rs b/tests/ui/consts/const-eval/format.rs index 0d8b7c12d8abb..5bdb2bf195439 100644 --- a/tests/ui/consts/const-eval/format.rs +++ b/tests/ui/consts/const-eval/format.rs @@ -1,12 +1,13 @@ const fn failure() { panic!("{:?}", 0); //~^ ERROR cannot call non-const formatting macro in constant functions + //~| ERROR cannot call non-const fn `Arguments::<'_>::new_v1` in constant functions } const fn print() { println!("{:?}", 0); //~^ ERROR cannot call non-const formatting macro in constant functions - //~| ERROR `Arguments::<'a>::new_v1` is not yet stable as a const fn + //~| ERROR cannot call non-const fn `Arguments::<'_>::new_v1` in constant functions //~| ERROR cannot call non-const fn `_print` in constant functions } diff --git a/tests/ui/consts/const-eval/format.stderr b/tests/ui/consts/const-eval/format.stderr index 4bf39db58746c..c39920d444def 100644 --- a/tests/ui/consts/const-eval/format.stderr +++ b/tests/ui/consts/const-eval/format.stderr @@ -7,8 +7,17 @@ LL | panic!("{:?}", 0); = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants = note: this error originates in the macro `$crate::const_format_args` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) +error[E0015]: cannot call non-const fn `Arguments::<'_>::new_v1` in constant functions + --> $DIR/format.rs:2:5 + | +LL | panic!("{:?}", 0); + | ^^^^^^^^^^^^^^^^^ + | + = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants + = note: this error originates in the macro `$crate::const_format_args` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0015]: cannot call non-const formatting macro in constant functions - --> $DIR/format.rs:7:22 + --> $DIR/format.rs:8:22 | LL | println!("{:?}", 0); | ^ @@ -16,17 +25,17 @@ LL | println!("{:?}", 0); = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) -error: `Arguments::<'a>::new_v1` is not yet stable as a const fn - --> $DIR/format.rs:7:5 +error[E0015]: cannot call non-const fn `Arguments::<'_>::new_v1` in constant functions + --> $DIR/format.rs:8:5 | LL | println!("{:?}", 0); | ^^^^^^^^^^^^^^^^^^^ | - = help: add `#![feature(const_fmt_arguments_new)]` to the crate attributes to enable + = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0015]: cannot call non-const fn `_print` in constant functions - --> $DIR/format.rs:7:5 + --> $DIR/format.rs:8:5 | LL | println!("{:?}", 0); | ^^^^^^^^^^^^^^^^^^^ @@ -63,19 +72,19 @@ LL | panic!("{:?}", 0); = note: this note originates in the macro `$crate::const_format_args` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) note: erroneous constant used - --> $DIR/format.rs:7:14 + --> $DIR/format.rs:8:14 | LL | println!("{:?}", 0); | ^^^^^^ note: erroneous constant used - --> $DIR/format.rs:7:14 + --> $DIR/format.rs:8:14 | LL | println!("{:?}", 0); | ^^^^^^ note: erroneous constant used - --> $DIR/format.rs:7:22 + --> $DIR/format.rs:8:22 | LL | println!("{:?}", 0); | ^ @@ -83,13 +92,13 @@ LL | println!("{:?}", 0); = note: this note originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) note: erroneous constant used - --> $DIR/format.rs:7:22 + --> $DIR/format.rs:8:22 | LL | println!("{:?}", 0); | ^ | = note: this note originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) -error: aborting due to 4 previous errors +error: aborting due to 5 previous errors For more information about this error, try `rustc --explain E0015`. From 1d59081bfdc82074677654955e63833153114b04 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 1 Feb 2023 18:38:14 +0100 Subject: [PATCH 12/16] Bless pretty tests. --- tests/pretty/issue-4264.pp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/pretty/issue-4264.pp b/tests/pretty/issue-4264.pp index e0fa1fe28246e..4020a433d6254 100644 --- a/tests/pretty/issue-4264.pp +++ b/tests/pretty/issue-4264.pp @@ -32,13 +32,11 @@ ({ let res = ((::alloc::fmt::format as - for<'a> fn(Arguments<'a>) -> String {format})(((<#[lang = "format_arguments"]>::new_v1 + for<'a> fn(Arguments<'a>) -> String {format})(((<#[lang = "format_arguments"]>::new_const as - fn(&[&'static str], &[core::fmt::ArgumentV1<'_>]) -> Arguments<'_> {Arguments::<'_>::new_v1})((&([("test" - as &str)] as [&str; 1]) as &[&str; 1]), - (&([] as [core::fmt::ArgumentV1<'_>; 0]) as - &[core::fmt::ArgumentV1<'_>; 0])) as Arguments<'_>)) as - String); + fn(&[&'static str]) -> Arguments<'_> {Arguments::<'_>::new_const})((&([("test" + as &str)] as [&str; 1]) as &[&str; 1])) as Arguments<'_>)) + as String); (res as String) } as String); } as ()) From 995e57b89efc00e967100d6e7c36b55315bf3cd7 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 16 Mar 2023 11:13:07 +0100 Subject: [PATCH 13/16] Gate fmt args flattening behind -Zflatten-format-args. --- compiler/rustc_ast_lowering/src/format.rs | 7 +++++-- compiler/rustc_interface/src/tests.rs | 1 + compiler/rustc_session/src/options.rs | 3 +++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/format.rs b/compiler/rustc_ast_lowering/src/format.rs index e094457961ede..72352b138cbf4 100644 --- a/compiler/rustc_ast_lowering/src/format.rs +++ b/compiler/rustc_ast_lowering/src/format.rs @@ -16,8 +16,11 @@ impl<'hir> LoweringContext<'_, 'hir> { // Never call the const constructor of `fmt::Arguments` if the // format_args!() had any arguments _before_ flattening/inlining. let allow_const = fmt.arguments.all_args().is_empty(); - let fmt = flatten_format_args(Cow::Borrowed(fmt)); - let fmt = inline_literals(fmt); + let mut fmt = Cow::Borrowed(fmt); + if self.tcx.sess.opts.unstable_opts.flatten_format_args { + fmt = flatten_format_args(fmt); + fmt = inline_literals(fmt); + } expand_format_args(self, sp, &fmt, allow_const) } } diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 18d84a7023aa1..014810dba9cce 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -744,6 +744,7 @@ fn test_unstable_options_tracking_hash() { tracked!(emit_thin_lto, false); tracked!(export_executable_symbols, true); tracked!(fewer_names, Some(true)); + tracked!(flatten_format_args, true); tracked!(force_unstable_if_unmarked, true); tracked!(fuel, Some(("abc".to_string(), 99))); tracked!(function_sections, Some(false)); diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index b466a3fcdee91..0548379dc2fc1 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -1422,6 +1422,9 @@ options! { fewer_names: Option = (None, parse_opt_bool, [TRACKED], "reduce memory use by retaining fewer names within compilation artifacts (LLVM-IR) \ (default: no)"), + flatten_format_args: bool = (false, parse_bool, [TRACKED], + "flatten nested format_args!() and literals into a simplified format_args!() call \ + (default: no)"), force_unstable_if_unmarked: bool = (false, parse_bool, [TRACKED], "force all crates to be `rustc_private` unstable (default: no)"), fuel: Option<(String, u64)> = (None, parse_optimization_fuel, [TRACKED], From 653124ac44d30244262fc3b9e2f8d10850ff079f Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 16 Mar 2023 12:08:07 +0100 Subject: [PATCH 14/16] Fix clippy. --- src/tools/clippy/clippy_utils/src/macros.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/tools/clippy/clippy_utils/src/macros.rs b/src/tools/clippy/clippy_utils/src/macros.rs index e135bd9feee54..c0e32068ecacc 100644 --- a/src/tools/clippy/clippy_utils/src/macros.rs +++ b/src/tools/clippy/clippy_utils/src/macros.rs @@ -533,6 +533,14 @@ struct FormatArgsValues<'tcx> { } impl<'tcx> FormatArgsValues<'tcx> { + fn new_empty(format_string_span: SpanData) -> Self { + Self { + value_args: Vec::new(), + pos_to_value_index: Vec::new(), + format_string_span, + } + } + fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self { let mut pos_to_value_index = Vec::new(); let mut value_args = Vec::new(); @@ -997,12 +1005,13 @@ impl<'tcx> FormatArgsExpn<'tcx> { .find(|&name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))?; let newline = macro_name == sym::format_args_nl; + // ::core::fmt::Arguments::new_const(pieces) // ::core::fmt::Arguments::new_v1(pieces, args) // ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg) - if let ExprKind::Call(callee, [pieces, args, rest @ ..]) = expr.kind + if let ExprKind::Call(callee, [pieces, rest @ ..]) = expr.kind && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind && let TyKind::Path(QPath::LangItem(LangItem::FormatArguments, _, _)) = ty.kind - && matches!(seg.ident.as_str(), "new_v1" | "new_v1_formatted") + && matches!(seg.ident.as_str(), "new_const" | "new_v1" | "new_v1_formatted") { let format_string = FormatString::new(cx, pieces)?; @@ -1026,7 +1035,7 @@ impl<'tcx> FormatArgsExpn<'tcx> { return None; } - let positions = if let Some(fmt_arg) = rest.first() { + let positions = if let Some(fmt_arg) = rest.get(1) { // If the argument contains format specs, `new_v1_formatted(_, _, fmt, _)`, parse // them. @@ -1042,7 +1051,11 @@ impl<'tcx> FormatArgsExpn<'tcx> { })) }; - let values = FormatArgsValues::new(args, format_string.span.data()); + let values = if let Some(args) = rest.first() { + FormatArgsValues::new(args, format_string.span.data()) + } else { + FormatArgsValues::new_empty(format_string.span.data()) + }; let args = izip!(positions, parsed_args, parser.arg_places) .map(|(position, parsed_arg, arg_span)| { From 4d840223b004aac41a2307fdd4aba07082164731 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 16 Mar 2023 12:16:27 +0100 Subject: [PATCH 15/16] Add test for -Zflatten-format-args=yes. --- tests/ui/unpretty/flattened-format-args.rs | 8 ++++++++ tests/ui/unpretty/flattened-format-args.stdout | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/ui/unpretty/flattened-format-args.rs create mode 100644 tests/ui/unpretty/flattened-format-args.stdout diff --git a/tests/ui/unpretty/flattened-format-args.rs b/tests/ui/unpretty/flattened-format-args.rs new file mode 100644 index 0000000000000..705dded169a19 --- /dev/null +++ b/tests/ui/unpretty/flattened-format-args.rs @@ -0,0 +1,8 @@ +// compile-flags: -Zunpretty=hir -Zflatten-format-args=yes +// check-pass + +fn main() { + let x = 1; + // Should flatten to println!("a 123 b {x} xyz\n"): + println!("a {} {}", format_args!("{} b {x}", 123), "xyz"); +} diff --git a/tests/ui/unpretty/flattened-format-args.stdout b/tests/ui/unpretty/flattened-format-args.stdout new file mode 100644 index 0000000000000..a8fe8da002472 --- /dev/null +++ b/tests/ui/unpretty/flattened-format-args.stdout @@ -0,0 +1,16 @@ +#[prelude_import] +use ::std::prelude::rust_2015::*; +#[macro_use] +extern crate std; +// compile-flags: -Zunpretty=hir -Zflatten-format-args=yes +// check-pass + +fn main() { + let x = 1; + // Should flatten to println!("a 123 b {x} xyz\n"): + { + ::std::io::_print(<#[lang = "format_arguments"]>::new_v1(&["a 123 b ", + " xyz\n"], + &[<#[lang = "format_argument"]>::new_display(&x)])); + }; + } From a08016514847d39ae3096ff41f98759abf59bee8 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Thu, 16 Mar 2023 12:26:20 +0100 Subject: [PATCH 16/16] Bless -Zhelp output test. --- tests/rustdoc-ui/z-help.stdout | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/rustdoc-ui/z-help.stdout b/tests/rustdoc-ui/z-help.stdout index 79e6b94f1aca5..5ad38e4fd9821 100644 --- a/tests/rustdoc-ui/z-help.stdout +++ b/tests/rustdoc-ui/z-help.stdout @@ -44,6 +44,7 @@ -Z export-executable-symbols=val -- export symbols from executables, as if they were dynamic libraries -Z extra-const-ub-checks=val -- turns on more checks to detect const UB, which can be slow (default: no) -Z fewer-names=val -- reduce memory use by retaining fewer names within compilation artifacts (LLVM-IR) (default: no) + -Z flatten-format-args=val -- flatten nested format_args!() and literals into a simplified format_args!() call (default: no) -Z force-unstable-if-unmarked=val -- force all crates to be `rustc_private` unstable (default: no) -Z fuel=val -- set the optimization fuel quota for a crate -Z function-sections=val -- whether each function should go in its own section