diff options
| author | Mara Bos <m-ou.se@m-ou.se> | 2022-09-06 23:15:13 +0200 |
|---|---|---|
| committer | Mara Bos <m-ou.se@m-ou.se> | 2022-09-27 13:31:52 +0200 |
| commit | cf53fef0d6d4d5d8f6733a3d1e98e3b774342819 (patch) | |
| tree | d136fe75544d3958331708e06e3e50dd695c807d | |
| parent | 14065639ca9ebf29ae6563f81920d443c26fd1fb (diff) | |
| download | rust-cf53fef0d6d4d5d8f6733a3d1e98e3b774342819.tar.gz rust-cf53fef0d6d4d5d8f6733a3d1e98e3b774342819.zip | |
Turn format arguments Vec into its own struct.
With efficient lookup through a hash map.
| -rw-r--r-- | compiler/rustc_builtin_macros/src/format.rs | 119 | ||||
| -rw-r--r-- | compiler/rustc_builtin_macros/src/format/ast.rs | 87 | ||||
| -rw-r--r-- | compiler/rustc_builtin_macros/src/format/expand.rs | 24 |
3 files changed, 146 insertions, 84 deletions
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 26a9169a30b..d3e8d854c7d 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -45,14 +45,14 @@ use PositionUsedAs::*; /// If parsing succeeds, the return value is: /// /// ```text -/// Some((fmtstr, parsed arguments)) +/// Ok((fmtstr, parsed arguments)) /// ``` fn parse_args<'a>( ecx: &mut ExtCtxt<'a>, sp: Span, tts: TokenStream, -) -> PResult<'a, (P<Expr>, Vec<(P<Expr>, FormatArgKind)>)> { - let mut args = Vec::<(P<Expr>, FormatArgKind)>::new(); +) -> PResult<'a, (P<Expr>, FormatArguments)> { + let mut args = FormatArguments::new(); let mut p = ecx.new_parser_from_tts(tts); @@ -81,7 +81,6 @@ fn parse_args<'a>( }; let mut first = true; - let mut named = false; while p.token != token::Eof { if !p.eat(&token::Comma) { @@ -113,40 +112,40 @@ fn parse_args<'a>( } // accept trailing commas match p.token.ident() { Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => { - named = true; p.bump(); p.expect(&token::Eq)?; - let e = p.parse_expr()?; - if let Some(prev) = - args.iter().rev().map_while(|a| a.1.ident()).find(|n| n.name == ident.name) - { + let expr = p.parse_expr()?; + if let Some((_, prev)) = args.by_name(ident.name) { ecx.struct_span_err( ident.span, &format!("duplicate argument named `{}`", ident), ) - .span_label(prev.span, "previously here") + .span_label(prev.kind.ident().unwrap().span, "previously here") .span_label(ident.span, "duplicate argument") .emit(); continue; } - args.push((e, FormatArgKind::Named(ident))); + args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr }); } _ => { - let e = p.parse_expr()?; - if named { + let expr = p.parse_expr()?; + if !args.named_args().is_empty() { let mut err = ecx.struct_span_err( - e.span, + expr.span, "positional arguments cannot follow named arguments", ); - err.span_label(e.span, "positional arguments must be before named arguments"); - for arg in &args { - if let Some(name) = arg.1.ident() { - err.span_label(name.span.to(arg.0.span), "named argument"); + err.span_label( + expr.span, + "positional arguments must be before named arguments", + ); + for arg in args.named_args() { + if let Some(name) = arg.kind.ident() { + err.span_label(name.span.to(arg.expr.span), "named argument"); } } err.emit(); } - args.push((e, FormatArgKind::Normal)); + args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr }); } } } @@ -156,12 +155,9 @@ fn parse_args<'a>( pub fn make_format_args( ecx: &mut ExtCtxt<'_>, efmt: P<Expr>, - mut args: Vec<(P<Expr>, FormatArgKind)>, + mut args: FormatArguments, append_newline: bool, ) -> Result<FormatArgs, ()> { - let start_of_named_args = - args.iter().position(|arg| arg.1.ident().is_some()).unwrap_or(args.len()); - let msg = "format argument must be a string literal"; let fmt_span = efmt.span; let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) { @@ -172,9 +168,9 @@ pub fn make_format_args( Ok(fmt) => fmt, Err(err) => { if let Some((mut err, suggested)) = err { - let sugg_fmt = match args.len() { + let sugg_fmt = match args.explicit_args().len() { 0 => "{}".to_string(), - _ => format!("{}{{}}", "{} ".repeat(args.len())), + _ => format!("{}{{}}", "{} ".repeat(args.explicit_args().len())), }; if !suggested { err.span_suggestion( @@ -243,14 +239,14 @@ pub fn make_format_args( let captured_arg_span = fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end)); if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) { - let span = match args[..start_of_named_args].last() { - Some(arg) => arg.0.span, + let span = match args.unnamed_args().last() { + Some(arg) => arg.expr.span, None => fmt_span, }; e.multipart_suggestion_verbose( "consider using a positional formatting argument instead", vec![ - (captured_arg_span, start_of_named_args.to_string()), + (captured_arg_span, args.unnamed_args().len().to_string()), (span.shrink_to_hi(), format!(", {}", arg)), ], Applicability::MachineApplicable, @@ -267,8 +263,7 @@ pub fn make_format_args( }) }; - let num_explicit_args = args.len(); - let mut used = vec![false; num_explicit_args]; + let mut used = vec![false; args.explicit_args().len()]; let mut invalid_refs = Vec::new(); let mut numeric_refences_to_named_arg = Vec::new(); @@ -285,32 +280,24 @@ pub fn make_format_args( -> FormatArgPosition { let index = match arg { Index(index) => { - match args.get(index) { - Some((_, FormatArgKind::Normal)) => { - used[index] = true; - Ok(index) - } - Some((_, FormatArgKind::Named(_))) => { - used[index] = true; + if let Some(arg) = args.by_index(index) { + used[index] = true; + if arg.kind.ident().is_some() { + // This was a named argument, but it was used as a positional argument. numeric_refences_to_named_arg.push((index, span, used_as)); - Ok(index) - } - Some((_, FormatArgKind::Captured(_))) | None => { - // Doesn't exist as an explicit argument. - invalid_refs.push((index, span, used_as, kind)); - Err(index) } + Ok(index) + } else { + // Doesn't exist as an explicit argument. + invalid_refs.push((index, span, used_as, kind)); + Err(index) } } Name(name, span) => { let name = Symbol::intern(name); - if let Some(i) = args[start_of_named_args..] - .iter() - .position(|arg| arg.1.ident().is_some_and(|id| id.name == name)) - { - // Name found in `args`, so we resolve it to its index in that Vec. - let index = start_of_named_args + i; - if !matches!(args[index].1, FormatArgKind::Captured(_)) { + if let Some((index, _)) = args.by_name(name) { + // Name found in `args`, so we resolve it to its index. + if index < args.explicit_args().len() { // Mark it as used, if it was an explicit argument. used[index] = true; } @@ -319,7 +306,7 @@ pub fn make_format_args( // Name not found in `args`, so we add it as an implicitly captured argument. let span = span.unwrap_or(fmt_span); let ident = Ident::new(name, span); - let arg = if is_literal { + let expr = if is_literal { ecx.expr_ident(span, ident) } else { // For the moment capturing variables from format strings expanded from macros is @@ -330,8 +317,7 @@ pub fn make_format_args( .emit(); DummyResult::raw_expr(span, true) }; - args.push((arg, FormatArgKind::Captured(ident))); - Ok(args.len() - 1) + Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr })) } } }; @@ -466,15 +452,7 @@ pub fn make_format_args( } if !invalid_refs.is_empty() { - report_invalid_references( - ecx, - &invalid_refs, - &template, - fmt_span, - num_explicit_args, - &args, - parser, - ); + report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser); } let unused = used @@ -482,19 +460,19 @@ pub fn make_format_args( .enumerate() .filter(|&(_, used)| !used) .map(|(i, _)| { - let msg = if let FormatArgKind::Named(_) = args[i].1 { + let msg = if let FormatArgumentKind::Named(_) = args.explicit_args()[i].kind { "named argument never used" } else { "argument never used" }; - (args[i].0.span, msg) + (args.explicit_args()[i].expr.span, msg) }) .collect::<Vec<_>>(); if !unused.is_empty() { // If there's a lot of unused arguments, // let's check if this format arguments looks like another syntax (printf / shell). - let detect_foreign_fmt = unused.len() > num_explicit_args / 2; + let detect_foreign_fmt = unused.len() > args.explicit_args().len() / 2; report_missing_placeholders(ecx, unused, detect_foreign_fmt, str_style, fmt_str, fmt_span); } @@ -511,7 +489,7 @@ pub fn make_format_args( } Width => (span, span), }; - let arg_name = args[index].1.ident().unwrap(); + let arg_name = args.explicit_args()[index].kind.ident().unwrap(); ecx.buffered_early_lint.push(BufferedEarlyLint { span: arg_name.span.into(), msg: format!("named argument `{}` is not used by name", arg_name.name).into(), @@ -695,11 +673,10 @@ fn report_invalid_references( invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)], template: &[FormatArgsPiece], fmt_span: Span, - num_explicit_args: usize, - args: &[(P<Expr>, FormatArgKind)], + args: &FormatArguments, parser: parse::Parser<'_>, ) { - let num_args_desc = match num_explicit_args { + let num_args_desc = match args.explicit_args().len() { 0 => "no arguments were given".to_string(), 1 => "there is 1 argument".to_string(), n => format!("there are {} arguments", n), @@ -785,8 +762,8 @@ fn report_invalid_references( num_args_desc, ), ); - for (arg, _) in &args[..num_explicit_args] { - e.span_label(arg.span, ""); + for arg in args.explicit_args() { + e.span_label(arg.expr.span, ""); } // Point out `{:.*}` placeholders: those take an extra argument. let mut has_precision_star = false; diff --git a/compiler/rustc_builtin_macros/src/format/ast.rs b/compiler/rustc_builtin_macros/src/format/ast.rs index cb76482fe53..139645248a1 100644 --- a/compiler/rustc_builtin_macros/src/format/ast.rs +++ b/compiler/rustc_builtin_macros/src/format/ast.rs @@ -1,5 +1,6 @@ use rustc_ast::ptr::P; use rustc_ast::Expr; +use rustc_data_structures::fx::FxHashMap; use rustc_span::symbol::{Ident, Symbol}; use rustc_span::Span; @@ -42,17 +43,97 @@ use rustc_span::Span; pub struct FormatArgs { pub span: Span, pub template: Vec<FormatArgsPiece>, - pub arguments: Vec<(P<Expr>, FormatArgKind)>, + pub arguments: FormatArguments, } +/// A piece of a format template string. +/// +/// E.g. "hello" or "{name}". #[derive(Clone, Debug)] pub enum FormatArgsPiece { Literal(Symbol), Placeholder(FormatPlaceholder), } +/// The arguments to format_args!(). +/// +/// E.g. `1, 2, name="ferris", n=3`, +/// but also implicit captured arguments like `x` in `format_args!("{x}")`. +#[derive(Clone, Debug)] +pub struct FormatArguments { + arguments: Vec<FormatArgument>, + num_unnamed_args: usize, + num_explicit_args: usize, + names: FxHashMap<Symbol, usize>, +} + +impl FormatArguments { + pub fn new() -> Self { + Self { + arguments: Vec::new(), + names: FxHashMap::default(), + num_unnamed_args: 0, + num_explicit_args: 0, + } + } + + pub fn add(&mut self, arg: FormatArgument) -> usize { + let index = self.arguments.len(); + if let Some(name) = arg.kind.ident() { + self.names.insert(name.name, index); + } else if self.names.is_empty() { + // Only count the unnamed args before the first named arg. + // (Any later ones are errors.) + self.num_unnamed_args += 1; + } + if !matches!(arg.kind, FormatArgumentKind::Captured(..)) { + // This is an explicit argument. + // Make sure that all arguments so far are explcit. + assert_eq!( + self.num_explicit_args, + self.arguments.len(), + "captured arguments must be added last" + ); + self.num_explicit_args += 1; + } + self.arguments.push(arg); + index + } + + pub fn by_name(&self, name: Symbol) -> Option<(usize, &FormatArgument)> { + let i = *self.names.get(&name)?; + Some((i, &self.arguments[i])) + } + + pub fn by_index(&self, i: usize) -> Option<&FormatArgument> { + (i < self.num_explicit_args).then(|| &self.arguments[i]) + } + + pub fn unnamed_args(&self) -> &[FormatArgument] { + &self.arguments[..self.num_unnamed_args] + } + + pub fn named_args(&self) -> &[FormatArgument] { + &self.arguments[self.num_unnamed_args..self.num_explicit_args] + } + + pub fn explicit_args(&self) -> &[FormatArgument] { + &self.arguments[..self.num_explicit_args] + } + + pub fn into_vec(self) -> Vec<FormatArgument> { + self.arguments + } +} + +#[derive(Clone, Debug)] +pub struct FormatArgument { + pub kind: FormatArgumentKind, + pub expr: P<Expr>, +} + #[derive(Clone, Debug)] -pub enum FormatArgKind { +pub enum FormatArgumentKind { /// `format_args(…, arg)` Normal, /// `format_args(…, arg = 1)` @@ -61,7 +142,7 @@ pub enum FormatArgKind { Captured(Ident), } -impl FormatArgKind { +impl FormatArgumentKind { pub fn ident(&self) -> Option<Ident> { match self { &Self::Normal => None, diff --git a/compiler/rustc_builtin_macros/src/format/expand.rs b/compiler/rustc_builtin_macros/src/format/expand.rs index a2a8213dafc..9dde5efcb28 100644 --- a/compiler/rustc_builtin_macros/src/format/expand.rs +++ b/compiler/rustc_builtin_macros/src/format/expand.rs @@ -201,13 +201,15 @@ pub fn expand_parsed_format_args(ecx: &mut ExtCtxt<'_>, fmt: FormatArgs) -> P<as ) }); + let arguments = fmt.arguments.into_vec(); + // If the args array contains exactly all the original arguments once, // in order, we can use a simple array instead of a `match` construction. // However, if there's a yield point in any argument except the first one, // we don't do this, because an ArgumentV1 cannot be kept across yield points. - let use_simple_array = argmap.len() == fmt.arguments.len() + let use_simple_array = argmap.len() == arguments.len() && argmap.iter().enumerate().all(|(i, &(j, _))| i == j) - && fmt.arguments.iter().skip(1).all(|(arg, _)| !may_contain_yield_point(arg)); + && arguments.iter().skip(1).all(|arg| !may_contain_yield_point(&arg.expr)); let args = if use_simple_array { // Generate: @@ -218,12 +220,12 @@ pub fn expand_parsed_format_args(ecx: &mut ExtCtxt<'_>, fmt: FormatArgs) -> P<as // ] ecx.expr_array_ref( macsp, - fmt.arguments + arguments .into_iter() .zip(argmap) - .map(|((arg, _), (_, ty))| { - let sp = arg.span.with_ctxt(macsp.ctxt()); - make_argument(ecx, sp, ecx.expr_addr_of(sp, arg), ty) + .map(|(arg, (_, ty))| { + let sp = arg.expr.span.with_ctxt(macsp.ctxt()); + make_argument(ecx, sp, ecx.expr_addr_of(sp, arg.expr), ty) }) .collect(), ) @@ -240,8 +242,8 @@ pub fn expand_parsed_format_args(ecx: &mut ExtCtxt<'_>, fmt: FormatArgs) -> P<as let args = argmap .iter() .map(|&(arg_index, ty)| { - if let Some((arg, _)) = fmt.arguments.get(arg_index) { - let sp = arg.span.with_ctxt(macsp.ctxt()); + if let Some(arg) = arguments.get(arg_index) { + let sp = arg.expr.span.with_ctxt(macsp.ctxt()); make_argument( ecx, sp, @@ -263,9 +265,11 @@ pub fn expand_parsed_format_args(ecx: &mut ExtCtxt<'_>, fmt: FormatArgs) -> P<as macsp, ecx.expr_tuple( macsp, - fmt.arguments + arguments .into_iter() - .map(|(arg, _)| ecx.expr_addr_of(arg.span.with_ctxt(macsp.ctxt()), arg)) + .map(|arg| { + ecx.expr_addr_of(arg.expr.span.with_ctxt(macsp.ctxt()), arg.expr) + }) .collect(), ), vec![ecx.arm(macsp, ecx.pat_ident(macsp, args_ident), ecx.expr_array(macsp, args))], |
