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_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] = &[
};
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
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 {
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,
}
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`
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>)
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,
)
}
+/// 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.
///
/// ```
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
impl<'tcx> FormatParam<'tcx> {
fn new(
mut kind: FormatParamKind,
+ usage: FormatParamUsage,
position: usize,
inner: rpf::InnerSpan,
values: &FormatArgsValues<'tcx>,
kind = FormatParamKind::NamedInline(name);
}
- Some(Self { value, kind, span })
+ Some(Self {
+ value,
+ kind,
+ usage,
+ span,
+ })
}
}
impl<'tcx> Count<'tcx> {
fn new(
+ usage: FormatParamUsage,
count: rpf::Count<'_>,
position: Option<usize>,
inner: Option<rpf::InnerSpan>,
) -> 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,
})
}
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,
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))
// NamedInline is handled by `FormatParam::new()`
rpf::Position::ArgumentNamed(name) => FormatParamKind::Named(Symbol::intern(name)),
},
+ FormatParamUsage::Argument,
position.value,
parsed_arg.position_span,
&values,
})
.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
}
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
}
}
+ /// 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