about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMara Bos <m-ou.se@m-ou.se>2022-09-06 23:15:13 +0200
committerMara Bos <m-ou.se@m-ou.se>2022-09-27 13:31:52 +0200
commitcf53fef0d6d4d5d8f6733a3d1e98e3b774342819 (patch)
treed136fe75544d3958331708e06e3e50dd695c807d
parent14065639ca9ebf29ae6563f81920d443c26fd1fb (diff)
downloadrust-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.rs119
-rw-r--r--compiler/rustc_builtin_macros/src/format/ast.rs87
-rw-r--r--compiler/rustc_builtin_macros/src/format/expand.rs24
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))],