about summary refs log tree commit diff
path: root/compiler/rustc_builtin_macros/src/format.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_builtin_macros/src/format.rs')
-rw-r--r--compiler/rustc_builtin_macros/src/format.rs247
1 files changed, 187 insertions, 60 deletions
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
index ce897abb766..f5eb984c138 100644
--- a/compiler/rustc_builtin_macros/src/format.rs
+++ b/compiler/rustc_builtin_macros/src/format.rs
@@ -11,14 +11,14 @@ use rustc_errors::{pluralize, Applicability, MultiSpan, PResult};
 use rustc_expand::base::{self, *};
 use rustc_parse_format as parse;
 use rustc_span::symbol::{sym, Ident, Symbol};
-use rustc_span::{InnerSpan, Span};
+use rustc_span::{BytePos, InnerSpan, Span};
 use smallvec::SmallVec;
 
 use rustc_lint_defs::builtin::NAMED_ARGUMENTS_USED_POSITIONALLY;
 use rustc_lint_defs::{BufferedEarlyLint, BuiltinLintDiagnostics, LintId};
-use rustc_parse_format::Count;
 use std::borrow::Cow;
 use std::collections::hash_map::Entry;
+use std::ops::Deref;
 
 #[derive(PartialEq)]
 enum ArgumentType {
@@ -32,6 +32,105 @@ enum Position {
     Named(Symbol, InnerSpan),
 }
 
+/// Indicates how positional named argument (i.e. an named argument which is used by position
+/// instead of by name) is used in format string
+/// * `Arg` is the actual argument to print
+/// * `Width` is width format argument
+/// * `Precision` is precion format argument
+/// Example: `{Arg:Width$.Precision$}
+#[derive(Debug, Eq, PartialEq)]
+enum PositionalNamedArgType {
+    Arg,
+    Width,
+    Precision,
+}
+
+/// Contains information necessary to create a lint for a positional named argument
+#[derive(Debug)]
+struct PositionalNamedArg {
+    ty: PositionalNamedArgType,
+    /// The piece of the using this argument (multiple pieces can use the same argument)
+    cur_piece: usize,
+    /// The InnerSpan for in the string to be replaced with the named argument
+    /// This will be None when the position is implicit
+    inner_span_to_replace: Option<rustc_parse_format::InnerSpan>,
+    /// The name to use instead of the position
+    replacement: Symbol,
+    /// The span for the positional named argument (so the lint can point a message to it)
+    positional_named_arg_span: Span,
+}
+
+impl PositionalNamedArg {
+    /// Determines what span to replace with the name of the named argument
+    fn get_span_to_replace(&self, cx: &Context<'_, '_>) -> Option<Span> {
+        if let Some(inner_span) = &self.inner_span_to_replace {
+            return match self.ty {
+                PositionalNamedArgType::Arg | PositionalNamedArgType::Width => Some(Span::new(
+                    cx.fmtsp.lo() + BytePos(inner_span.start.try_into().unwrap()),
+                    cx.fmtsp.lo() + BytePos(inner_span.end.try_into().unwrap()),
+                    self.positional_named_arg_span.ctxt(),
+                    self.positional_named_arg_span.parent(),
+                )),
+                PositionalNamedArgType::Precision => Some(Span::new(
+                    cx.fmtsp.lo() + BytePos(inner_span.start.try_into().unwrap()) + BytePos(1),
+                    cx.fmtsp.lo() + BytePos(inner_span.end.try_into().unwrap()),
+                    self.positional_named_arg_span.ctxt(),
+                    self.positional_named_arg_span.parent(),
+                )),
+            };
+        } else if self.ty == PositionalNamedArgType::Arg {
+            // In the case of a named argument whose position is implicit, there will not be a span
+            // to replace. Instead, we insert the name after the `{`, which is the first character
+            // of arg_span.
+            if let Some(arg_span) = cx.arg_spans.get(self.cur_piece).copied() {
+                return Some(Span::new(
+                    arg_span.lo() + BytePos(1),
+                    arg_span.lo() + BytePos(1),
+                    self.positional_named_arg_span.ctxt(),
+                    self.positional_named_arg_span.parent(),
+                ));
+            }
+        }
+
+        None
+    }
+}
+
+/// Encapsulates all the named arguments that have been used positionally
+#[derive(Debug)]
+struct PositionalNamedArgsLint {
+    positional_named_args: Vec<PositionalNamedArg>,
+}
+
+impl PositionalNamedArgsLint {
+    /// Try constructing a PositionalNamedArg struct and pushing it into the vec of positional
+    /// named arguments. If a named arg associated with `format_argument_index` cannot be found,
+    /// a new item will not be added as the lint cannot be emitted in this case.
+    fn maybe_push(
+        &mut self,
+        format_argument_index: usize,
+        ty: PositionalNamedArgType,
+        cur_piece: usize,
+        inner_span: Option<rustc_parse_format::InnerSpan>,
+        names: &FxHashMap<Symbol, (usize, Span)>,
+    ) {
+        let named_arg = names
+            .iter()
+            .find(|name| name.deref().1.0 == format_argument_index)
+            .map(|found| found.clone());
+
+        if let Some(named_arg) = named_arg {
+            self.positional_named_args.push(PositionalNamedArg {
+                ty,
+                cur_piece,
+                inner_span_to_replace: inner_span,
+                replacement: named_arg.0.clone(),
+                positional_named_arg_span: named_arg.1.1.clone(),
+            });
+        }
+    }
+}
+
 struct Context<'a, 'b> {
     ecx: &'a mut ExtCtxt<'b>,
     /// The macro's call site. References to unstable formatting internals must
@@ -118,6 +217,7 @@ struct Context<'a, 'b> {
 
     /// Whether this format string came from a string literal, as opposed to a macro.
     is_literal: bool,
+    unused_names_lint: PositionalNamedArgsLint,
 }
 
 /// Parses the arguments from the given list of tokens, returning the diagnostic
@@ -242,7 +342,7 @@ impl<'a, 'b> Context<'a, 'b> {
         self.args.len() - self.num_captured_args
     }
 
-    fn resolve_name_inplace(&self, p: &mut parse::Piece<'_>) {
+    fn resolve_name_inplace(&mut self, p: &mut parse::Piece<'_>) {
         // NOTE: the `unwrap_or` branch is needed in case of invalid format
         // arguments, e.g., `format_args!("{foo}")`.
         let lookup =
@@ -252,7 +352,7 @@ impl<'a, 'b> Context<'a, 'b> {
             parse::String(_) => {}
             parse::NextArgument(ref mut arg) => {
                 if let parse::ArgumentNamed(s, _) = arg.position {
-                    arg.position = parse::ArgumentIs(lookup(s));
+                    arg.position = parse::ArgumentIs(lookup(s), None);
                 }
                 if let parse::CountIsName(s, _) = arg.format.width {
                     arg.format.width = parse::CountIsParam(lookup(s));
@@ -273,15 +373,50 @@ impl<'a, 'b> Context<'a, 'b> {
             parse::NextArgument(ref arg) => {
                 // width/precision first, if they have implicit positional
                 // parameters it makes more sense to consume them first.
-                self.verify_count(arg.format.width);
-                self.verify_count(arg.format.precision);
+                self.verify_count(
+                    arg.format.width,
+                    &arg.format.width_span,
+                    PositionalNamedArgType::Width,
+                );
+                self.verify_count(
+                    arg.format.precision,
+                    &arg.format.precision_span,
+                    PositionalNamedArgType::Precision,
+                );
 
                 // argument second, if it's an implicit positional parameter
                 // it's written second, so it should come after width/precision.
                 let pos = match arg.position {
-                    parse::ArgumentIs(i) | parse::ArgumentImplicitlyIs(i) => Exact(i),
+                    parse::ArgumentIs(i, arg_end) => {
+                        let start_of_named_args = self.args.len() - self.names.len();
+                        if self.curpiece >= start_of_named_args {
+                            self.unused_names_lint.maybe_push(
+                                i,
+                                PositionalNamedArgType::Arg,
+                                self.curpiece,
+                                arg_end,
+                                &self.names,
+                            );
+                        }
+
+                        Exact(i)
+                    }
+                    parse::ArgumentImplicitlyIs(i) => {
+                        let start_of_named_args = self.args.len() - self.names.len();
+                        if self.curpiece >= start_of_named_args {
+                            self.unused_names_lint.maybe_push(
+                                i,
+                                PositionalNamedArgType::Arg,
+                                self.curpiece,
+                                None,
+                                &self.names,
+                            );
+                        }
+                        Exact(i)
+                    }
                     parse::ArgumentNamed(s, span) => {
-                        Named(Symbol::intern(s), InnerSpan::new(span.start, span.end))
+                        let symbol = Symbol::intern(s);
+                        Named(symbol, InnerSpan::new(span.start, span.end))
                     }
                 };
 
@@ -349,10 +484,25 @@ impl<'a, 'b> Context<'a, 'b> {
         }
     }
 
-    fn verify_count(&mut self, c: parse::Count<'_>) {
+    fn verify_count(
+        &mut self,
+        c: parse::Count<'_>,
+        inner_span: &Option<rustc_parse_format::InnerSpan>,
+        named_arg_type: PositionalNamedArgType,
+    ) {
         match c {
             parse::CountImplied | parse::CountIs(..) => {}
             parse::CountIsParam(i) => {
+                let start_of_named_args = self.args.len() - self.names.len();
+                if i >= start_of_named_args {
+                    self.unused_names_lint.maybe_push(
+                        i,
+                        named_arg_type,
+                        self.curpiece,
+                        inner_span.clone(),
+                        &self.names,
+                    );
+                }
                 self.verify_arg_type(Exact(i), Count);
             }
             parse::CountIsName(s, span) => {
@@ -673,7 +823,7 @@ impl<'a, 'b> Context<'a, 'b> {
                 // Build the position
                 let pos = {
                     match arg.position {
-                        parse::ArgumentIs(i) | parse::ArgumentImplicitlyIs(i) => {
+                        parse::ArgumentIs(i, ..) | parse::ArgumentImplicitlyIs(i) => {
                             // Map to index in final generated argument array
                             // in case of multiple types specified
                             let arg_idx = match arg_index_consumed.get_mut(i) {
@@ -701,7 +851,7 @@ impl<'a, 'b> Context<'a, 'b> {
                         // track the current argument ourselves.
                         let i = self.curarg;
                         self.curarg += 1;
-                        parse::ArgumentIs(i)
+                        parse::ArgumentIs(i, None)
                     },
                     format: parse::FormatSpec {
                         fill: arg.format.fill,
@@ -971,43 +1121,27 @@ pub fn expand_format_args_nl<'cx>(
     expand_format_args_impl(ecx, sp, tts, true)
 }
 
-fn lint_named_arguments_used_positionally(
-    names: FxHashMap<Symbol, (usize, Span)>,
-    cx: &mut Context<'_, '_>,
-    unverified_pieces: Vec<parse::Piece<'_>>,
-) {
-    let mut used_argument_names = FxHashSet::<&str>::default();
-    for piece in unverified_pieces {
-        if let rustc_parse_format::Piece::NextArgument(a) = piece {
-            match a.position {
-                rustc_parse_format::Position::ArgumentNamed(arg_name, _) => {
-                    used_argument_names.insert(arg_name);
-                }
-                _ => {}
-            };
-            if let Count::CountIsName(s, _) = a.format.width {
-                used_argument_names.insert(s);
-            }
-            if let Count::CountIsName(s, _) = a.format.precision {
-                used_argument_names.insert(s);
-            }
-        }
-    }
+fn create_lints_for_named_arguments_used_positionally(cx: &mut Context<'_, '_>) {
+    for named_arg in &cx.unused_names_lint.positional_named_args {
+        let arg_span = named_arg.get_span_to_replace(cx);
 
-    for (symbol, (index, span)) in names {
-        if !used_argument_names.contains(symbol.as_str()) {
-            let msg = format!("named argument `{}` is not used by name", symbol.as_str());
-            let arg_span = cx.arg_spans.get(index).copied();
-            cx.ecx.buffered_early_lint.push(BufferedEarlyLint {
-                span: MultiSpan::from_span(span),
-                msg: msg.clone(),
-                node_id: ast::CRATE_NODE_ID,
-                lint_id: LintId::of(&NAMED_ARGUMENTS_USED_POSITIONALLY),
-                diagnostic: BuiltinLintDiagnostics::NamedArgumentUsedPositionally(
-                    arg_span, span, symbol,
-                ),
-            });
-        }
+        let msg = format!("named argument `{}` is not used by name", named_arg.replacement);
+        let replacement = match named_arg.ty {
+            PositionalNamedArgType::Arg => named_arg.replacement.to_string(),
+            _ => named_arg.replacement.to_string() + "$",
+        };
+
+        cx.ecx.buffered_early_lint.push(BufferedEarlyLint {
+            span: MultiSpan::from_span(named_arg.positional_named_arg_span),
+            msg: msg.clone(),
+            node_id: ast::CRATE_NODE_ID,
+            lint_id: LintId::of(&NAMED_ARGUMENTS_USED_POSITIONALLY),
+            diagnostic: BuiltinLintDiagnostics::NamedArgumentUsedPositionally(
+                arg_span,
+                named_arg.positional_named_arg_span,
+                replacement,
+            ),
+        });
     }
 }
 
@@ -1119,11 +1253,6 @@ pub fn expand_preparsed_format_args(
 
     let named_pos: FxHashSet<usize> = names.values().cloned().map(|(i, _)| i).collect();
 
-    // Clone `names` because `names` in Context get updated by verify_piece, which includes usages
-    // of the names of named arguments, resulting in incorrect errors if a name argument is used
-    // but not declared, such as: `println!("x = {x}");`
-    let named_arguments = names.clone();
-
     let mut cx = Context {
         ecx,
         args,
@@ -1148,13 +1277,12 @@ pub fn expand_preparsed_format_args(
         arg_spans,
         arg_with_formatting: Vec::new(),
         is_literal: parser.is_literal,
+        unused_names_lint: PositionalNamedArgsLint { positional_named_args: vec![] },
     };
 
-    // This needs to happen *after* the Parser has consumed all pieces to create all the spans.
-    // unverified_pieces is used later to check named argument names are used, so clone each piece.
+    // This needs to happen *after* the Parser has consumed all pieces to create all the spans
     let pieces = unverified_pieces
-        .iter()
-        .cloned()
+        .into_iter()
         .map(|mut piece| {
             cx.verify_piece(&piece);
             cx.resolve_name_inplace(&mut piece);
@@ -1164,7 +1292,7 @@ pub fn expand_preparsed_format_args(
 
     let numbered_position_args = pieces.iter().any(|arg: &parse::Piece<'_>| match *arg {
         parse::String(_) => false,
-        parse::NextArgument(arg) => matches!(arg.position, parse::Position::ArgumentIs(_)),
+        parse::NextArgument(arg) => matches!(arg.position, parse::Position::ArgumentIs(..)),
     });
 
     cx.build_index_map();
@@ -1316,11 +1444,10 @@ pub fn expand_preparsed_format_args(
         }
 
         diag.emit();
-    } else if cx.invalid_refs.is_empty() && !named_arguments.is_empty() {
+    } else if cx.invalid_refs.is_empty() && cx.ecx.sess.err_count() == 0 {
         // Only check for unused named argument names if there are no other errors to avoid causing
         // too much noise in output errors, such as when a named argument is entirely unused.
-        // We also only need to perform this check if there are actually named arguments.
-        lint_named_arguments_used_positionally(named_arguments, &mut cx, unverified_pieces);
+        create_lints_for_named_arguments_used_positionally(&mut cx);
     }
 
     cx.into_expr()