]> git.lizzy.rs Git - rust.git/blobdiff - src/tools/clippy/clippy_utils/src/macros.rs
Merge commit 'ac0e10aa68325235069a842f47499852b2dee79e' into clippyup
[rust.git] / src / tools / clippy / clippy_utils / src / macros.rs
index bd89ff977f87791dd6deacaf193b34e7781ffd62..dd0ce1da65759396a431f269f6da3e1536cd39a5 100644 (file)
@@ -2,13 +2,13 @@
 
 use crate::is_path_diagnostic_item;
 use crate::source::snippet_opt;
-use crate::visitors::expr_visitor_no_bodies;
+use crate::visitors::{for_each_expr, Descend};
 
 use arrayvec::ArrayVec;
 use itertools::{izip, Either, Itertools};
 use rustc_ast::ast::LitKind;
-use rustc_hir::intravisit::Visitor;
-use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
+use rustc_hir::intravisit::{walk_expr, Visitor};
+use rustc_hir::{self as hir, Expr, ExprField, ExprKind, HirId, Node, QPath};
 use rustc_lexer::unescape::unescape_literal;
 use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
 use rustc_lint::LateContext;
@@ -16,6 +16,7 @@
 use rustc_span::def_id::DefId;
 use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
 use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Pos, Span, SpanData, Symbol};
+use std::iter::{once, zip};
 use std::ops::ControlFlow;
 
 const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
@@ -270,20 +271,19 @@ fn find_assert_args_inner<'a, const N: usize>(
     };
     let mut args = ArrayVec::new();
     let mut panic_expn = None;
-    expr_visitor_no_bodies(|e| {
+    let _: Option<!> = for_each_expr(expr, |e| {
         if args.is_full() {
             if panic_expn.is_none() && e.span.ctxt() != expr.span.ctxt() {
                 panic_expn = PanicExpn::parse(cx, e);
             }
-            panic_expn.is_none()
+            ControlFlow::Continue(Descend::from(panic_expn.is_none()))
         } else if is_assert_arg(cx, e, expn) {
             args.push(e);
-            false
+            ControlFlow::Continue(Descend::No)
         } else {
-            true
+            ControlFlow::Continue(Descend::Yes)
         }
-    })
-    .visit_expr(expr);
+    });
     let args = args.into_inner().ok()?;
     // if no `panic!(..)` is found, use `PanicExpn::Empty`
     // to indicate that the default assertion message is used
@@ -297,22 +297,19 @@ fn find_assert_within_debug_assert<'a>(
     expn: ExpnId,
     assert_name: Symbol,
 ) -> Option<(&'a Expr<'a>, ExpnId)> {
-    let mut found = None;
-    expr_visitor_no_bodies(|e| {
-        if found.is_some() || !e.span.from_expansion() {
-            return false;
+    for_each_expr(expr, |e| {
+        if !e.span.from_expansion() {
+            return ControlFlow::Continue(Descend::No);
         }
         let e_expn = e.span.ctxt().outer_expn();
         if e_expn == expn {
-            return true;
-        }
-        if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
-            found = Some((e, e_expn));
+            ControlFlow::Continue(Descend::Yes)
+        } else if e_expn.expn_data().macro_def_id.map(|id| cx.tcx.item_name(id)) == Some(assert_name) {
+            ControlFlow::Break((e, e_expn))
+        } else {
+            ControlFlow::Continue(Descend::No)
         }
-        false
     })
-    .visit_expr(expr);
-    found
 }
 
 fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) -> bool {
@@ -392,20 +389,18 @@ fn new(cx: &LateContext<'_>, pieces: &Expr<'_>) -> Option<Self> {
         unescape_literal(inner, mode, &mut |_, ch| match ch {
             Ok(ch) => unescaped.push(ch),
             Err(e) if !e.is_fatal() => (),
-            Err(e) => panic!("{:?}", e),
+            Err(e) => panic!("{e:?}"),
         });
 
         let mut parts = Vec::new();
-        expr_visitor_no_bodies(|expr| {
-            if let ExprKind::Lit(lit) = &expr.kind {
-                if let LitKind::Str(symbol, _) = lit.node {
-                    parts.push(symbol);
-                }
+        let _: Option<!> = for_each_expr(pieces, |expr| {
+            if let ExprKind::Lit(lit) = &expr.kind
+                && let LitKind::Str(symbol, _) = lit.node
+            {
+                parts.push(symbol);
             }
-
-            true
-        })
-        .visit_expr(pieces);
+            ControlFlow::Continue(())
+        });
 
         Some(Self {
             span,
@@ -418,7 +413,8 @@ fn new(cx: &LateContext<'_>, pieces: &Expr<'_>) -> Option<Self> {
 }
 
 struct FormatArgsValues<'tcx> {
-    /// See `FormatArgsExpn::value_args`
+    /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
+    /// `format!("{x} {} {y}", 1, z + 2)`.
     value_args: Vec<&'tcx Expr<'tcx>>,
     /// Maps an `rt::v1::Argument::position` or an `rt::v1::Count::Param` to its index in
     /// `value_args`
@@ -431,7 +427,7 @@ impl<'tcx> FormatArgsValues<'tcx> {
     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();
-        expr_visitor_no_bodies(|expr| {
+        let _: Option<!> = for_each_expr(args, |expr| {
             if expr.span.ctxt() == args.span.ctxt() {
                 // ArgumentV1::new_<format_trait>(<val>)
                 // ArgumentV1::from_usize(<val>)
@@ -453,16 +449,13 @@ fn new(args: &'tcx Expr<'tcx>, format_string_span: SpanData) -> Self {
 
                     pos_to_value_index.push(val_idx);
                 }
-
-                true
+                ControlFlow::Continue(Descend::Yes)
             } else {
                 // assume that any expr with a differing span is a value
                 value_args.push(expr);
-
-                false
+                ControlFlow::Continue(Descend::No)
             }
-        })
-        .visit_expr(args);
+        });
 
         Self {
             value_args,
@@ -485,64 +478,49 @@ struct ParamPosition {
     precision: Option<usize>,
 }
 
-/// Parses the `fmt` arg of `Arguments::new_v1_formatted(pieces, args, fmt, _)`
-fn parse_rt_fmt<'tcx>(fmt_arg: &'tcx Expr<'tcx>) -> Option<impl Iterator<Item = ParamPosition> + 'tcx> {
-    fn parse_count(expr: &Expr<'_>) -> Option<usize> {
-        // ::core::fmt::rt::v1::Count::Param(1usize),
-        if let ExprKind::Call(ctor, [val]) = expr.kind
-            && let ExprKind::Path(QPath::Resolved(_, path)) = ctor.kind
-            && path.segments.last()?.ident.name == sym::Param
-            && let ExprKind::Lit(lit) = &val.kind
-            && let LitKind::Int(pos, _) = lit.node
-        {
-            Some(pos as usize)
-        } else {
-            None
+impl<'tcx> Visitor<'tcx> for ParamPosition {
+    fn visit_expr_field(&mut self, field: &'tcx ExprField<'tcx>) {
+        fn parse_count(expr: &Expr<'_>) -> Option<usize> {
+            // ::core::fmt::rt::v1::Count::Param(1usize),
+            if let ExprKind::Call(ctor, [val]) = expr.kind
+                && let ExprKind::Path(QPath::Resolved(_, path)) = ctor.kind
+                && path.segments.last()?.ident.name == sym::Param
+                && let ExprKind::Lit(lit) = &val.kind
+                && let LitKind::Int(pos, _) = lit.node
+            {
+                Some(pos as usize)
+            } else {
+                None
+            }
+        }
+
+        match field.ident.name {
+            sym::position => {
+                if let ExprKind::Lit(lit) = &field.expr.kind
+                    && let LitKind::Int(pos, _) = lit.node
+                {
+                    self.value = pos as usize;
+                }
+            },
+            sym::precision => {
+                self.precision = parse_count(field.expr);
+            },
+            sym::width => {
+                self.width = parse_count(field.expr);
+            },
+            _ => walk_expr(self, field.expr),
         }
     }
+}
 
+/// Parses the `fmt` arg of `Arguments::new_v1_formatted(pieces, args, fmt, _)`
+fn parse_rt_fmt<'tcx>(fmt_arg: &'tcx Expr<'tcx>) -> Option<impl Iterator<Item = ParamPosition> + 'tcx> {
     if let ExprKind::AddrOf(.., array) = fmt_arg.kind
         && let ExprKind::Array(specs) = array.kind
     {
         Some(specs.iter().map(|spec| {
             let mut position = ParamPosition::default();
-
-            // ::core::fmt::rt::v1::Argument {
-            //     position: 0usize,
-            //     format: ::core::fmt::rt::v1::FormatSpec {
-            //         ..
-            //         precision: ::core::fmt::rt::v1::Count::Implied,
-            //         width: ::core::fmt::rt::v1::Count::Implied,
-            //     },
-            // }
-
-            // TODO: this can be made much nicer next sync with `Visitor::visit_expr_field`
-            if let ExprKind::Struct(_, fields, _) = spec.kind {
-                for field in fields {
-                    match (field.ident.name, &field.expr.kind) {
-                        (sym::position, ExprKind::Lit(lit)) => {
-                            if let LitKind::Int(pos, _) = lit.node {
-                                position.value = pos as usize;
-                            }
-                        },
-                        (sym::format, &ExprKind::Struct(_, spec_fields, _)) => {
-                            for spec_field in spec_fields {
-                                match spec_field.ident.name {
-                                    sym::precision => {
-                                        position.precision = parse_count(spec_field.expr);
-                                    },
-                                    sym::width => {
-                                        position.width = parse_count(spec_field.expr);
-                                    },
-                                    _ => {},
-                                }
-                            }
-                        },
-                        _ => {},
-                    }
-                }
-            }
-
+            position.visit_expr(spec);
             position
         }))
     } else {
@@ -560,19 +538,32 @@ fn span_from_inner(base: SpanData, inner: rpf::InnerSpan) -> Span {
     )
 }
 
+/// How a format parameter is used in the format string
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 pub enum FormatParamKind {
     /// An implicit parameter , such as `{}` or `{:?}`.
     Implicit,
-    /// A parameter with an explicit number, or an asterisk precision. e.g. `{1}`, `{0:?}`,
-    /// `{:.0$}` or `{:.*}`.
+    /// A parameter with an explicit number, e.g. `{1}`, `{0:?}`, or `{:.0$}`
     Numbered,
+    /// A parameter with an asterisk precision. e.g. `{:.*}`.
+    Starred,
     /// A named parameter with a named `value_arg`, such as the `x` in `format!("{x}", x = 1)`.
     Named(Symbol),
     /// An implicit named parameter, such as the `y` in `format!("{y}")`.
     NamedInline(Symbol),
 }
 
+/// Where a format parameter is being used in the format string
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum FormatParamUsage {
+    /// Appears as an argument, e.g. `format!("{}", foo)`
+    Argument,
+    /// Appears as a width, e.g. `format!("{:width$}", foo, width = 1)`
+    Width,
+    /// Appears as a precision, e.g. `format!("{:.precision$}", foo, precision = 1)`
+    Precision,
+}
+
 /// A `FormatParam` is any place in a `FormatArgument` that refers to a supplied value, e.g.
 ///
 /// ```
@@ -588,6 +579,8 @@ pub struct FormatParam<'tcx> {
     pub value: &'tcx Expr<'tcx>,
     /// How this parameter refers to its `value`.
     pub kind: FormatParamKind,
+    /// Where this format param is being used - argument/width/precision
+    pub usage: FormatParamUsage,
     /// Span of the parameter, may be zero width. Includes the whitespace of implicit parameters.
     ///
     /// ```text
@@ -600,6 +593,7 @@ pub struct FormatParam<'tcx> {
 impl<'tcx> FormatParam<'tcx> {
     fn new(
         mut kind: FormatParamKind,
+        usage: FormatParamUsage,
         position: usize,
         inner: rpf::InnerSpan,
         values: &FormatArgsValues<'tcx>,
@@ -614,7 +608,12 @@ fn new(
             kind = FormatParamKind::NamedInline(name);
         }
 
-        Some(Self { value, kind, span })
+        Some(Self {
+            value,
+            kind,
+            usage,
+            span,
+        })
     }
 }
 
@@ -633,6 +632,7 @@ pub enum Count<'tcx> {
 
 impl<'tcx> Count<'tcx> {
     fn new(
+        usage: FormatParamUsage,
         count: rpf::Count<'_>,
         position: Option<usize>,
         inner: Option<rpf::InnerSpan>,
@@ -640,15 +640,27 @@ fn new(
     ) -> Option<Self> {
         Some(match count {
             rpf::Count::CountIs(val) => Self::Is(val, span_from_inner(values.format_string_span, inner?)),
-            rpf::Count::CountIsName(name, span) => Self::Param(FormatParam::new(
+            rpf::Count::CountIsName(name, _) => Self::Param(FormatParam::new(
                 FormatParamKind::Named(Symbol::intern(name)),
+                usage,
                 position?,
-                span,
+                inner?,
+                values,
+            )?),
+            rpf::Count::CountIsParam(_) => Self::Param(FormatParam::new(
+                FormatParamKind::Numbered,
+                usage,
+                position?,
+                inner?,
+                values,
+            )?),
+            rpf::Count::CountIsStar(_) => Self::Param(FormatParam::new(
+                FormatParamKind::Starred,
+                usage,
+                position?,
+                inner?,
                 values,
             )?),
-            rpf::Count::CountIsParam(_) | rpf::Count::CountIsStar(_) => {
-                Self::Param(FormatParam::new(FormatParamKind::Numbered, position?, inner?, values)?)
-            },
             rpf::Count::CountImplied => Self::Implied,
         })
     }
@@ -691,8 +703,20 @@ fn new(spec: rpf::FormatSpec<'_>, positions: ParamPosition, values: &FormatArgsV
             fill: spec.fill,
             align: spec.align,
             flags: spec.flags,
-            precision: Count::new(spec.precision, positions.precision, spec.precision_span, values)?,
-            width: Count::new(spec.width, positions.width, spec.width_span, values)?,
+            precision: Count::new(
+                FormatParamUsage::Precision,
+                spec.precision,
+                positions.precision,
+                spec.precision_span,
+                values,
+            )?,
+            width: Count::new(
+                FormatParamUsage::Width,
+                spec.width,
+                positions.width,
+                spec.width_span,
+                values,
+            )?,
             r#trait: match spec.ty {
                 "" => sym::Display,
                 "?" => sym::Debug,
@@ -711,9 +735,14 @@ fn new(spec: rpf::FormatSpec<'_>, positions: ParamPosition, values: &FormatArgsV
         })
     }
 
-    /// Returns true if this format spec would change the contents of a string when formatted
-    pub fn has_string_formatting(&self) -> bool {
-        self.r#trait != sym::Display || !self.width.is_implied() || !self.precision.is_implied()
+    /// Returns true if this format spec is unchanged from the default. e.g. returns true for `{}`,
+    /// `{foo}` and `{2}`, but false for `{:?}`, `{foo:5}` and `{3:.5}`
+    pub fn is_default(&self) -> bool {
+        self.r#trait == sym::Display
+            && self.width.is_implied()
+            && self.precision.is_implied()
+            && self.align == Alignment::AlignUnknown
+            && self.flags == 0
     }
 }
 
@@ -733,17 +762,87 @@ pub struct FormatArg<'tcx> {
 pub struct FormatArgsExpn<'tcx> {
     /// The format string literal.
     pub format_string: FormatString,
-    // The format arguments, such as `{:?}`.
+    /// The format arguments, such as `{:?}`.
     pub args: Vec<FormatArg<'tcx>>,
     /// Has an added newline due to `println!()`/`writeln!()`/etc. The last format string part will
     /// include this added newline.
     pub newline: bool,
-    /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
+    /// Spans of the commas between the format string and explicit values, excluding any trailing
+    /// comma
+    ///
+    /// ```ignore
+    /// format!("..", 1, 2, 3,)
+    /// //          ^  ^  ^
+    /// ```
+    comma_spans: Vec<Span>,
+    /// Explicit values passed after the format string, ignoring implicit captures. `[1, z + 2]` for
     /// `format!("{x} {} {y}", 1, z + 2)`.
-    value_args: Vec<&'tcx Expr<'tcx>>,
+    explicit_values: Vec<&'tcx Expr<'tcx>>,
 }
 
 impl<'tcx> FormatArgsExpn<'tcx> {
+    /// Gets the spans of the commas inbetween the format string and explicit args, not including
+    /// any trailing comma
+    ///
+    /// ```ignore
+    /// format!("{} {}", a, b)
+    /// //             ^  ^
+    /// ```
+    ///
+    /// Ensures that the format string and values aren't coming from a proc macro that sets the
+    /// output span to that of its input
+    fn comma_spans(cx: &LateContext<'_>, explicit_values: &[&Expr<'_>], fmt_span: Span) -> Option<Vec<Span>> {
+        // `format!("{} {} {c}", "one", "two", c = "three")`
+        //                       ^^^^^  ^^^^^      ^^^^^^^
+        let value_spans = explicit_values
+            .iter()
+            .map(|val| hygiene::walk_chain(val.span, fmt_span.ctxt()));
+
+        // `format!("{} {} {c}", "one", "two", c = "three")`
+        //                     ^^     ^^     ^^^^^^
+        let between_spans = once(fmt_span)
+            .chain(value_spans)
+            .tuple_windows()
+            .map(|(start, end)| start.between(end));
+
+        let mut comma_spans = Vec::new();
+        for between_span in between_spans {
+            let mut offset = 0;
+            let mut seen_comma = false;
+
+            for token in tokenize(&snippet_opt(cx, between_span)?) {
+                match token.kind {
+                    TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {},
+                    TokenKind::Comma if !seen_comma => {
+                        seen_comma = true;
+
+                        let base = between_span.data();
+                        comma_spans.push(Span::new(
+                            base.lo + BytePos(offset),
+                            base.lo + BytePos(offset + 1),
+                            base.ctxt,
+                            base.parent,
+                        ));
+                    },
+                    // named arguments, `start_val, name = end_val`
+                    //                            ^^^^^^^^^ between_span
+                    TokenKind::Ident | TokenKind::Eq if seen_comma => {},
+                    // An unexpected token usually indicates the format string or a value came from a proc macro output
+                    // that sets the span of its output to an input, e.g. `println!(some_proc_macro!("input"), ..)` that
+                    // emits a string literal with the span set to that of `"input"`
+                    _ => return None,
+                }
+                offset += token.len;
+            }
+
+            if !seen_comma {
+                return None;
+            }
+        }
+
+        Some(comma_spans)
+    }
+
     pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
         let macro_name = macro_backtrace(expr.span)
             .map(|macro_call| cx.tcx.item_name(macro_call.def_id))
@@ -807,6 +906,7 @@ pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
                                 // NamedInline is handled by `FormatParam::new()`
                                 rpf::Position::ArgumentNamed(name) => FormatParamKind::Named(Symbol::intern(name)),
                             },
+                            FormatParamUsage::Argument,
                             position.value,
                             parsed_arg.position_span,
                             &values,
@@ -817,11 +917,22 @@ pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
                 })
                 .collect::<Option<Vec<_>>>()?;
 
+            let mut explicit_values = values.value_args;
+            // remove values generated for implicitly captured vars
+            let len = explicit_values
+                .iter()
+                .take_while(|val| !format_string.span.contains(val.span))
+                .count();
+            explicit_values.truncate(len);
+
+            let comma_spans = Self::comma_spans(cx, &explicit_values, format_string.span)?;
+
             Some(Self {
                 format_string,
                 args,
-                value_args: values.value_args,
                 newline,
+                comma_spans,
+                explicit_values,
             })
         } else {
             None
@@ -829,27 +940,25 @@ pub fn parse(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<Self> {
     }
 
     pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
-        let mut format_args = None;
-        expr_visitor_no_bodies(|e| {
-            if format_args.is_some() {
-                return false;
-            }
+        for_each_expr(expr, |e| {
             let e_ctxt = e.span.ctxt();
             if e_ctxt == expr.span.ctxt() {
-                return true;
-            }
-            if e_ctxt.outer_expn().is_descendant_of(expn_id) {
-                format_args = FormatArgsExpn::parse(cx, e);
+                ControlFlow::Continue(Descend::Yes)
+            } else if e_ctxt.outer_expn().is_descendant_of(expn_id) {
+                if let Some(args) = FormatArgsExpn::parse(cx, e) {
+                    ControlFlow::Break(args)
+                } else {
+                    ControlFlow::Continue(Descend::No)
+                }
+            } else {
+                ControlFlow::Continue(Descend::No)
             }
-            false
         })
-        .visit_expr(expr);
-        format_args
     }
 
     /// Source callsite span of all inputs
     pub fn inputs_span(&self) -> Span {
-        match *self.value_args {
+        match *self.explicit_values {
             [] => self.format_string.span,
             [.., last] => self
                 .format_string
@@ -858,6 +967,22 @@ pub fn inputs_span(&self) -> Span {
         }
     }
 
+    /// Get the span of a value expanded to the previous comma, e.g. for the value `10`
+    ///
+    /// ```ignore
+    /// format("{}.{}", 10, 11)
+    /// //            ^^^^
+    /// ```
+    pub fn value_with_prev_comma_span(&self, value_id: HirId) -> Option<Span> {
+        for (comma_span, value) in zip(&self.comma_spans, &self.explicit_values) {
+            if value.hir_id == value_id {
+                return Some(comma_span.to(hygiene::walk_chain(value.span, comma_span.ctxt())));
+            }
+        }
+
+        None
+    }
+
     /// Iterator of all format params, both values and those referenced by `width`/`precision`s.
     pub fn params(&'tcx self) -> impl Iterator<Item = FormatParam<'tcx>> {
         self.args