summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_utils/src/macros.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/clippy/clippy_utils/src/macros.rs')
-rw-r--r--src/tools/clippy/clippy_utils/src/macros.rs400
1 files changed, 278 insertions, 122 deletions
diff --git a/src/tools/clippy/clippy_utils/src/macros.rs b/src/tools/clippy/clippy_utils/src/macros.rs
index bd89ff977..9a682fbe6 100644
--- a/src/tools/clippy/clippy_utils/src/macros.rs
+++ b/src/tools/clippy/clippy_utils/src/macros.rs
@@ -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_parse_format::{self as rpf, Alignment};
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 @@ impl FormatString {
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 @@ impl FormatString {
}
struct FormatArgsValues<'tcx> {
- /// See `FormatArgsExpn::value_args`
+ /// Values passed after the format string and implicit captures. `[1, z + 2, x]` for
+ /// `format!("{x} {} {}", 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 @@ impl<'tcx> FormatArgsValues<'tcx> {
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 @@ impl<'tcx> FormatParam<'tcx> {
kind = FormatParamKind::NamedInline(name);
}
- Some(Self { value, kind, span })
+ Some(Self {
+ value,
+ kind,
+ usage,
+ span,
+ })
}
}
@@ -628,33 +627,48 @@ pub enum Count<'tcx> {
/// `FormatParamKind::Numbered`.
Param(FormatParam<'tcx>),
/// Not specified.
- Implied,
+ Implied(Option<Span>),
}
impl<'tcx> Count<'tcx> {
fn new(
+ usage: FormatParamUsage,
count: rpf::Count<'_>,
position: Option<usize>,
inner: Option<rpf::InnerSpan>,
values: &FormatArgsValues<'tcx>,
) -> Option<Self> {
+ let span = inner.map(|inner| span_from_inner(values.format_string_span, inner));
+
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::CountIs(val) => Self::Is(val, span?),
+ rpf::Count::CountIsName(name, _) => Self::Param(FormatParam::new(
FormatParamKind::Named(Symbol::intern(name)),
+ usage,
position?,
- span,
+ inner?,
values,
)?),
- rpf::Count::CountIsParam(_) | rpf::Count::CountIsStar(_) => {
- Self::Param(FormatParam::new(FormatParamKind::Numbered, position?, inner?, values)?)
- },
- rpf::Count::CountImplied => Self::Implied,
+ 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::CountImplied => Self::Implied(span),
})
}
pub fn is_implied(self) -> bool {
- matches!(self, Count::Implied)
+ matches!(self, Count::Implied(_))
}
pub fn param(self) -> Option<FormatParam<'tcx>> {
@@ -663,6 +677,14 @@ impl<'tcx> Count<'tcx> {
_ => None,
}
}
+
+ pub fn span(self) -> Option<Span> {
+ match self {
+ Count::Is(_, span) => Some(span),
+ Count::Param(param) => Some(param.span),
+ Count::Implied(span) => span,
+ }
+ }
}
/// Specification for the formatting of an argument in the format string. See
@@ -691,8 +713,20 @@ impl<'tcx> FormatSpec<'tcx> {
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 +745,19 @@ impl<'tcx> FormatSpec<'tcx> {
})
}
- /// 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.is_default_for_trait()
+ }
+
+ /// Has no other formatting specifiers than setting the format trait. returns true for `{}`,
+ /// `{foo}`, `{:?}`, but false for `{foo:5}`, `{3:.5?}`
+ pub fn is_default_for_trait(&self) -> bool {
+ self.width.is_implied()
+ && self.precision.is_implied()
+ && self.align == Alignment::AlignUnknown
+ && self.flags == 0
}
}
@@ -728,22 +772,108 @@ pub struct FormatArg<'tcx> {
pub span: Span,
}
+impl<'tcx> FormatArg<'tcx> {
+ /// Span of the `:` and format specifiers
+ ///
+ /// ```ignore
+ /// format!("{:.}"), format!("{foo:.}")
+ /// ^^ ^^
+ /// ```
+ pub fn format_span(&self) -> Span {
+ let base = self.span.data();
+
+ // `base.hi` is `{...}|`, subtract 1 byte (the length of '}') so that it points before the closing
+ // brace `{...|}`
+ Span::new(self.param.span.hi(), base.hi - BytePos(1), base.ctxt, base.parent)
+ }
+}
+
/// A parsed `format_args!` expansion.
#[derive(Debug)]
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 +937,7 @@ impl<'tcx> FormatArgsExpn<'tcx> {
// 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 +948,22 @@ impl<'tcx> FormatArgsExpn<'tcx> {
})
.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 +971,25 @@ impl<'tcx> FormatArgsExpn<'tcx> {
}
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 +998,22 @@ impl<'tcx> FormatArgsExpn<'tcx> {
}
}
+ /// 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