mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-23 13:13:34 +00:00
Replace remaining usage of FormatArgsExpn
This commit is contained in:
parent
84e42fb363
commit
6589d79492
6 changed files with 132 additions and 803 deletions
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::macros::FormatArgsExpn;
|
||||
use clippy_utils::macros::{find_format_args, format_args_inputs_span};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::{is_expn_of, match_function_call, paths};
|
||||
use if_chain::if_chain;
|
||||
|
@ -8,7 +8,7 @@ use rustc_hir::def::Res;
|
|||
use rustc_hir::{BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::sym;
|
||||
use rustc_span::{sym, ExpnId};
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
|
@ -43,23 +43,22 @@ declare_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]);
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
if_chain! {
|
||||
// match call to unwrap
|
||||
if let ExprKind::MethodCall(unwrap_fun, write_call, [], _) = expr.kind;
|
||||
if unwrap_fun.ident.name == sym::unwrap;
|
||||
// match call to unwrap
|
||||
if let ExprKind::MethodCall(unwrap_fun, write_call, [], _) = expr.kind
|
||||
&& unwrap_fun.ident.name == sym::unwrap
|
||||
// match call to write_fmt
|
||||
if let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = look_in_block(cx, &write_call.kind);
|
||||
if write_fun.ident.name == sym!(write_fmt);
|
||||
&& let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = look_in_block(cx, &write_call.kind)
|
||||
&& write_fun.ident.name == sym!(write_fmt)
|
||||
// match calls to std::io::stdout() / std::io::stderr ()
|
||||
if let Some(dest_name) = if match_function_call(cx, write_recv, &paths::STDOUT).is_some() {
|
||||
&& let Some(dest_name) = if match_function_call(cx, write_recv, &paths::STDOUT).is_some() {
|
||||
Some("stdout")
|
||||
} else if match_function_call(cx, write_recv, &paths::STDERR).is_some() {
|
||||
Some("stderr")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(format_args) = FormatArgsExpn::parse(cx, write_arg);
|
||||
then {
|
||||
}
|
||||
{
|
||||
find_format_args(cx, write_arg, ExpnId::root(), |format_args| {
|
||||
let calling_macro =
|
||||
// ordering is important here, since `writeln!` uses `write!` internally
|
||||
if is_expn_of(write_call.span, "writeln").is_some() {
|
||||
|
@ -92,7 +91,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
|
|||
let mut applicability = Applicability::MachineApplicable;
|
||||
let inputs_snippet = snippet_with_applicability(
|
||||
cx,
|
||||
format_args.inputs_span(),
|
||||
format_args_inputs_span(format_args),
|
||||
"..",
|
||||
&mut applicability,
|
||||
);
|
||||
|
@ -104,8 +103,8 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
|
|||
"try this",
|
||||
format!("{prefix}{sugg_mac}!({inputs_snippet})"),
|
||||
applicability,
|
||||
)
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
|
||||
use clippy_utils::source::snippet_with_context;
|
||||
use clippy_utils::macros::{find_format_arg_expr, find_format_args, root_macro_call_first_node};
|
||||
use clippy_utils::source::{snippet_opt, snippet_with_context};
|
||||
use clippy_utils::sugg::Sugg;
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::{FormatArgsPiece, FormatOptions, FormatTrait};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_middle::ty;
|
||||
use rustc_session::{declare_lint_pass, declare_tool_lint};
|
||||
use rustc_span::symbol::kw;
|
||||
use rustc_span::{sym, Span};
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
@ -44,55 +43,53 @@ declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
|
|||
|
||||
impl<'tcx> LateLintPass<'tcx> for UselessFormat {
|
||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
|
||||
let (format_args, call_site) = if_chain! {
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
|
||||
if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id);
|
||||
if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, macro_call.expn);
|
||||
then {
|
||||
(format_args, macro_call.span)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
};
|
||||
let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
|
||||
if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
if format_args.args.is_empty() {
|
||||
match *format_args.format_string.parts {
|
||||
[] => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability),
|
||||
[_] => {
|
||||
find_format_args(cx, expr, macro_call.expn, |format_args| {
|
||||
let mut applicability = Applicability::MachineApplicable;
|
||||
let call_site = macro_call.span;
|
||||
|
||||
match (format_args.arguments.all_args(), &format_args.template[..]) {
|
||||
([], []) => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability),
|
||||
([], [_]) => {
|
||||
// Simulate macro expansion, converting {{ and }} to { and }.
|
||||
let s_expand = format_args.format_string.snippet.replace("{{", "{").replace("}}", "}");
|
||||
let Some(snippet) = snippet_opt(cx, format_args.span) else { return };
|
||||
let s_expand = snippet.replace("{{", "{").replace("}}", "}");
|
||||
let sugg = format!("{s_expand}.to_string()");
|
||||
span_useless_format(cx, call_site, sugg, applicability);
|
||||
},
|
||||
[..] => {},
|
||||
([arg], [piece]) => {
|
||||
if let Ok(value) = find_format_arg_expr(expr, arg)
|
||||
&& let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& placeholder.format_trait == FormatTrait::Display
|
||||
&& placeholder.format_options == FormatOptions::default()
|
||||
&& match cx.typeck_results().expr_ty(value).peel_refs().kind() {
|
||||
ty::Adt(adt, _) => Some(adt.did()) == cx.tcx.lang_items().string(),
|
||||
ty::Str => true,
|
||||
_ => false,
|
||||
}
|
||||
{
|
||||
let is_new_string = match value.kind {
|
||||
ExprKind::Binary(..) => true,
|
||||
ExprKind::MethodCall(path, ..) => path.ident.name == sym::to_string,
|
||||
_ => false,
|
||||
};
|
||||
let sugg = if is_new_string {
|
||||
snippet_with_context(cx, value.span, call_site.ctxt(), "..", &mut applicability).0.into_owned()
|
||||
} else {
|
||||
let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "<arg>", &mut applicability);
|
||||
format!("{}.to_string()", sugg.maybe_par())
|
||||
};
|
||||
span_useless_format(cx, call_site, sugg, applicability);
|
||||
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
} else if let [arg] = &*format_args.args {
|
||||
let value = arg.param.value;
|
||||
if_chain! {
|
||||
if format_args.format_string.parts == [kw::Empty];
|
||||
if arg.format.is_default();
|
||||
if match cx.typeck_results().expr_ty(value).peel_refs().kind() {
|
||||
ty::Adt(adt, _) => Some(adt.did()) == cx.tcx.lang_items().string(),
|
||||
ty::Str => true,
|
||||
_ => false,
|
||||
};
|
||||
then {
|
||||
let is_new_string = match value.kind {
|
||||
ExprKind::Binary(..) => true,
|
||||
ExprKind::MethodCall(path, ..) => path.ident.name == sym::to_string,
|
||||
_ => false,
|
||||
};
|
||||
let sugg = if is_new_string {
|
||||
snippet_with_context(cx, value.span, call_site.ctxt(), "..", &mut applicability).0.into_owned()
|
||||
} else {
|
||||
let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "<arg>", &mut applicability);
|
||||
format!("{}.to_string()", sugg.maybe_par())
|
||||
};
|
||||
span_useless_format(cx, call_site, sugg, applicability);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
|
||||
use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArg, FormatArgsExpn};
|
||||
use clippy_utils::macros::{find_format_arg_expr, find_format_args, is_format_macro, root_macro_call_first_node};
|
||||
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
|
||||
use if_chain::if_chain;
|
||||
use rustc_ast::{FormatArgsPiece, FormatTrait};
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
||||
use rustc_span::Span;
|
||||
use rustc_span::{sym, symbol::kw, Symbol};
|
||||
|
||||
declare_clippy_lint! {
|
||||
|
@ -89,7 +91,7 @@ declare_clippy_lint! {
|
|||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct FormatTrait {
|
||||
struct FormatTraitNames {
|
||||
/// e.g. `sym::Display`
|
||||
name: Symbol,
|
||||
/// `f` in `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {}`
|
||||
|
@ -99,7 +101,7 @@ struct FormatTrait {
|
|||
#[derive(Default)]
|
||||
pub struct FormatImpl {
|
||||
// Whether we are inside Display or Debug trait impl - None for neither
|
||||
format_trait_impl: Option<FormatTrait>,
|
||||
format_trait_impl: Option<FormatTraitNames>,
|
||||
}
|
||||
|
||||
impl FormatImpl {
|
||||
|
@ -161,43 +163,57 @@ fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTrait) {
|
||||
fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTraitNames) {
|
||||
// Check each arg in format calls - do we ever use Display on self (directly or via deref)?
|
||||
if_chain! {
|
||||
if let Some(outer_macro) = root_macro_call_first_node(cx, expr);
|
||||
if let macro_def_id = outer_macro.def_id;
|
||||
if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, outer_macro.expn);
|
||||
if is_format_macro(cx, macro_def_id);
|
||||
then {
|
||||
for arg in format_args.args {
|
||||
if arg.format.r#trait != impl_trait.name {
|
||||
continue;
|
||||
if let Some(outer_macro) = root_macro_call_first_node(cx, expr)
|
||||
&& let macro_def_id = outer_macro.def_id
|
||||
&& is_format_macro(cx, macro_def_id)
|
||||
{
|
||||
find_format_args(cx, expr, outer_macro.expn, |format_args| {
|
||||
for piece in &format_args.template {
|
||||
if let FormatArgsPiece::Placeholder(placeholder) = piece
|
||||
&& let trait_name = match placeholder.format_trait {
|
||||
FormatTrait::Display => sym::Display,
|
||||
FormatTrait::Debug => sym::Debug,
|
||||
FormatTrait::LowerExp => sym!(LowerExp),
|
||||
FormatTrait::UpperExp => sym!(UpperExp),
|
||||
FormatTrait::Octal => sym!(Octal),
|
||||
FormatTrait::Pointer => sym::Pointer,
|
||||
FormatTrait::Binary => sym!(Binary),
|
||||
FormatTrait::LowerHex => sym!(LowerHex),
|
||||
FormatTrait::UpperHex => sym!(UpperHex),
|
||||
}
|
||||
&& trait_name == impl_trait.name
|
||||
&& let Ok(index) = placeholder.argument.index
|
||||
&& let Some(arg) = format_args.arguments.all_args().get(index)
|
||||
&& let Ok(arg_expr) = find_format_arg_expr(expr, arg)
|
||||
{
|
||||
check_format_arg_self(cx, expr.span, arg_expr, impl_trait);
|
||||
}
|
||||
check_format_arg_self(cx, expr, &arg, impl_trait);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArg<'_>, impl_trait: FormatTrait) {
|
||||
fn check_format_arg_self(cx: &LateContext<'_>, span: Span, arg: &Expr<'_>, impl_trait: FormatTraitNames) {
|
||||
// Handle multiple dereferencing of references e.g. &&self
|
||||
// Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl)
|
||||
// Since the argument to fmt is itself a reference: &self
|
||||
let reference = peel_ref_operators(cx, arg.param.value);
|
||||
let reference = peel_ref_operators(cx, arg);
|
||||
let map = cx.tcx.hir();
|
||||
// Is the reference self?
|
||||
if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
|
||||
let FormatTrait { name, .. } = impl_trait;
|
||||
let FormatTraitNames { name, .. } = impl_trait;
|
||||
span_lint(
|
||||
cx,
|
||||
RECURSIVE_FORMAT_IMPL,
|
||||
expr.span,
|
||||
span,
|
||||
&format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTrait) {
|
||||
fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTraitNames) {
|
||||
if_chain! {
|
||||
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
|
||||
if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id);
|
||||
|
@ -227,7 +243,7 @@ fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait:
|
|||
}
|
||||
}
|
||||
|
||||
fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTrait> {
|
||||
fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTraitNames> {
|
||||
if_chain! {
|
||||
if impl_item.ident.name == sym::fmt;
|
||||
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
|
||||
|
@ -241,7 +257,7 @@ fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Optio
|
|||
.and_then(|param| param.pat.simple_ident())
|
||||
.map(|ident| ident.name);
|
||||
|
||||
Some(FormatTrait {
|
||||
Some(FormatTraitNames {
|
||||
name,
|
||||
formatter_name,
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_sugg;
|
||||
use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
|
||||
use clippy_utils::macros::{find_format_args, format_args_inputs_span, root_macro_call_first_node};
|
||||
use clippy_utils::source::snippet_with_applicability;
|
||||
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
|
||||
use rustc_errors::Applicability;
|
||||
|
@ -136,18 +136,19 @@ pub(super) fn check<'tcx>(
|
|||
if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
|
||||
return;
|
||||
}
|
||||
let Some(format_args) = FormatArgsExpn::find_nested(cx, arg_root, macro_call.expn) else { return };
|
||||
let span = format_args.inputs_span();
|
||||
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPECT_FUN_CALL,
|
||||
span_replace_word,
|
||||
&format!("use of `{name}` followed by a function call"),
|
||||
"try this",
|
||||
format!("unwrap_or_else({closure_args} panic!({sugg}))"),
|
||||
applicability,
|
||||
);
|
||||
find_format_args(cx, arg_root, macro_call.expn, |format_args| {
|
||||
let span = format_args_inputs_span(format_args);
|
||||
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
|
||||
span_lint_and_sugg(
|
||||
cx,
|
||||
EXPECT_FUN_CALL,
|
||||
span_replace_word,
|
||||
&format!("use of `{name}` followed by a function call"),
|
||||
"try this",
|
||||
format!("unwrap_or_else({closure_args} panic!({sugg}))"),
|
||||
applicability,
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@ extern crate rustc_lexer;
|
|||
extern crate rustc_lint;
|
||||
extern crate rustc_middle;
|
||||
extern crate rustc_mir_dataflow;
|
||||
extern crate rustc_parse_format;
|
||||
extern crate rustc_session;
|
||||
extern crate rustc_span;
|
||||
extern crate rustc_target;
|
||||
|
|
|
@ -1,24 +1,16 @@
|
|||
#![allow(clippy::similar_names)] // `expr` and `expn`
|
||||
|
||||
use crate::source::snippet_opt;
|
||||
use crate::visitors::{for_each_expr, Descend};
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use itertools::{izip, Either, Itertools};
|
||||
use rustc_ast::ast::LitKind;
|
||||
use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_hir::intravisit::{walk_expr, Visitor};
|
||||
use rustc_hir::{self as hir, Expr, ExprField, ExprKind, HirId, LangItem, Node, QPath, TyKind};
|
||||
use rustc_lexer::unescape::unescape_literal;
|
||||
use rustc_lexer::{tokenize, unescape, LiteralKind, TokenKind};
|
||||
use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
|
||||
use rustc_lint::LateContext;
|
||||
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 rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, Symbol};
|
||||
use std::cell::RefCell;
|
||||
use std::iter::{once, zip};
|
||||
use std::ops::ControlFlow;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
|
@ -226,11 +218,11 @@ pub enum PanicExpn<'a> {
|
|||
/// A single argument that implements `Display` - `panic!("{}", object)`
|
||||
Display(&'a Expr<'a>),
|
||||
/// Anything else - `panic!("error {}: {}", a, b)`
|
||||
Format(FormatArgsExpn<'a>),
|
||||
Format(&'a Expr<'a>),
|
||||
}
|
||||
|
||||
impl<'a> PanicExpn<'a> {
|
||||
pub fn parse(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Self> {
|
||||
pub fn parse(expr: &'a Expr<'a>) -> Option<Self> {
|
||||
let ExprKind::Call(callee, [arg, rest @ ..]) = &expr.kind else { return None };
|
||||
let ExprKind::Path(QPath::Resolved(_, path)) = &callee.kind else { return None };
|
||||
let result = match path.segments.last().unwrap().ident.as_str() {
|
||||
|
@ -240,7 +232,7 @@ impl<'a> PanicExpn<'a> {
|
|||
let ExprKind::AddrOf(_, _, e) = &arg.kind else { return None };
|
||||
Self::Display(e)
|
||||
},
|
||||
"panic_fmt" => Self::Format(FormatArgsExpn::parse(cx, arg)?),
|
||||
"panic_fmt" => Self::Format(arg),
|
||||
// Since Rust 1.52, `assert_{eq,ne}` macros expand to use:
|
||||
// `core::panicking::assert_failed(.., left_val, right_val, None | Some(format_args!(..)));`
|
||||
"assert_failed" => {
|
||||
|
@ -252,7 +244,7 @@ impl<'a> PanicExpn<'a> {
|
|||
// `msg_arg` is either `None` (no custom message) or `Some(format_args!(..))` (custom message)
|
||||
let msg_arg = &rest[2];
|
||||
match msg_arg.kind {
|
||||
ExprKind::Call(_, [fmt_arg]) => Self::Format(FormatArgsExpn::parse(cx, fmt_arg)?),
|
||||
ExprKind::Call(_, [fmt_arg]) => Self::Format(fmt_arg),
|
||||
_ => Self::Empty,
|
||||
}
|
||||
},
|
||||
|
@ -304,7 +296,7 @@ fn find_assert_args_inner<'a, const N: usize>(
|
|||
let mut args = ArrayVec::new();
|
||||
let panic_expn = for_each_expr(expr, |e| {
|
||||
if args.is_full() {
|
||||
match PanicExpn::parse(cx, e) {
|
||||
match PanicExpn::parse(e) {
|
||||
Some(expn) => ControlFlow::Break(expn),
|
||||
None => ControlFlow::Continue(Descend::Yes),
|
||||
}
|
||||
|
@ -452,6 +444,21 @@ pub fn format_placeholder_format_span(placeholder: &FormatPlaceholder) -> Option
|
|||
))
|
||||
}
|
||||
|
||||
/// Span covering the format string and values
|
||||
///
|
||||
/// ```ignore
|
||||
/// format("{}.{}", 10, 11)
|
||||
/// // ^^^^^^^^^^^^^^^
|
||||
/// ```
|
||||
pub fn format_args_inputs_span(format_args: &FormatArgs) -> Span {
|
||||
match format_args.arguments.explicit_args() {
|
||||
[] => format_args.span,
|
||||
[.., last] => format_args
|
||||
.span
|
||||
.to(hygiene::walk_chain(last.expr.span, format_args.span.ctxt())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`Span`] of the value at `index` extended to the previous comma, e.g. for the value
|
||||
/// `10`
|
||||
///
|
||||
|
@ -473,251 +480,6 @@ pub fn format_arg_removal_span(format_args: &FormatArgs, index: usize) -> Option
|
|||
Some(current.with_lo(prev.hi()))
|
||||
}
|
||||
|
||||
/// The format string doesn't exist in the HIR, so we reassemble it from source code
|
||||
#[derive(Debug)]
|
||||
pub struct FormatString {
|
||||
/// Span of the whole format string literal, including `[r#]"`.
|
||||
pub span: Span,
|
||||
/// Snippet of the whole format string literal, including `[r#]"`.
|
||||
pub snippet: String,
|
||||
/// If the string is raw `r"..."`/`r#""#`, how many `#`s does it have on each side.
|
||||
pub style: Option<usize>,
|
||||
/// The unescaped value of the format string, e.g. `"val – {}"` for the literal
|
||||
/// `"val \u{2013} {}"`.
|
||||
pub unescaped: String,
|
||||
/// The format string split by format args like `{..}`.
|
||||
pub parts: Vec<Symbol>,
|
||||
}
|
||||
|
||||
impl FormatString {
|
||||
fn new(cx: &LateContext<'_>, pieces: &Expr<'_>) -> Option<Self> {
|
||||
// format_args!(r"a {} b \", 1);
|
||||
//
|
||||
// expands to
|
||||
//
|
||||
// ::core::fmt::Arguments::new_v1(&["a ", " b \\"],
|
||||
// &[::core::fmt::ArgumentV1::new_display(&1)]);
|
||||
//
|
||||
// where `pieces` is the expression `&["a ", " b \\"]`. It has the span of `r"a {} b \"`
|
||||
let span = pieces.span;
|
||||
let snippet = snippet_opt(cx, span)?;
|
||||
|
||||
let (inner, style) = match tokenize(&snippet).next()?.kind {
|
||||
TokenKind::Literal { kind, .. } => {
|
||||
let style = match kind {
|
||||
LiteralKind::Str { .. } => None,
|
||||
LiteralKind::RawStr { n_hashes: Some(n), .. } => Some(n.into()),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let start = style.map_or(1, |n| 2 + n);
|
||||
let end = snippet.len() - style.map_or(1, |n| 1 + n);
|
||||
|
||||
(&snippet[start..end], style)
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let mode = if style.is_some() {
|
||||
unescape::Mode::RawStr
|
||||
} else {
|
||||
unescape::Mode::Str
|
||||
};
|
||||
|
||||
let mut unescaped = String::with_capacity(inner.len());
|
||||
// Sometimes the original string comes from a macro which accepts a malformed string, such as in a
|
||||
// #[display(""somestring)] attribute (accepted by the `displaythis` crate). Reconstructing the
|
||||
// string from the span will not be possible, so we will just return None here.
|
||||
let mut unparsable = false;
|
||||
unescape_literal(inner, mode, &mut |_, ch| match ch {
|
||||
Ok(ch) => unescaped.push(ch),
|
||||
Err(e) if !e.is_fatal() => (),
|
||||
Err(_) => unparsable = true,
|
||||
});
|
||||
if unparsable {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut parts = Vec::new();
|
||||
let _: Option<!> = for_each_expr(pieces, |expr| {
|
||||
if let ExprKind::Lit(lit) = &expr.kind
|
||||
&& let LitKind::Str(symbol, _) = lit.node
|
||||
{
|
||||
parts.push(symbol);
|
||||
}
|
||||
ControlFlow::Continue(())
|
||||
});
|
||||
|
||||
Some(Self {
|
||||
span,
|
||||
snippet,
|
||||
style,
|
||||
unescaped,
|
||||
parts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct FormatArgsValues<'tcx> {
|
||||
/// 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`
|
||||
pos_to_value_index: Vec<usize>,
|
||||
/// Used to check if a value is declared inline & to resolve `InnerSpan`s.
|
||||
format_string_span: SpanData,
|
||||
}
|
||||
|
||||
impl<'tcx> FormatArgsValues<'tcx> {
|
||||
fn new_empty(format_string_span: SpanData) -> Self {
|
||||
Self {
|
||||
value_args: Vec::new(),
|
||||
pos_to_value_index: Vec::new(),
|
||||
format_string_span,
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
let _: Option<!> = for_each_expr(args, |expr| {
|
||||
if expr.span.ctxt() == args.span.ctxt() {
|
||||
// ArgumentV1::new_<format_trait>(<val>)
|
||||
// ArgumentV1::from_usize(<val>)
|
||||
if let ExprKind::Call(callee, [val]) = expr.kind
|
||||
&& let ExprKind::Path(QPath::TypeRelative(ty, _)) = callee.kind
|
||||
&& let TyKind::Path(QPath::LangItem(LangItem::FormatArgument, _, _)) = ty.kind
|
||||
{
|
||||
let val_idx = if val.span.ctxt() == expr.span.ctxt()
|
||||
&& let ExprKind::Field(_, field) = val.kind
|
||||
&& let Ok(idx) = field.name.as_str().parse()
|
||||
{
|
||||
// tuple index
|
||||
idx
|
||||
} else {
|
||||
// assume the value expression is passed directly
|
||||
pos_to_value_index.len()
|
||||
};
|
||||
|
||||
pos_to_value_index.push(val_idx);
|
||||
}
|
||||
ControlFlow::Continue(Descend::Yes)
|
||||
} else {
|
||||
// assume that any expr with a differing span is a value
|
||||
value_args.push(expr);
|
||||
ControlFlow::Continue(Descend::No)
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
value_args,
|
||||
pos_to_value_index,
|
||||
format_string_span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The positions of a format argument's value, precision and width
|
||||
///
|
||||
/// A position is an index into the second argument of `Arguments::new_v1[_formatted]`
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
struct ParamPosition {
|
||||
/// The position stored in `rt::v1::Argument::position`.
|
||||
value: usize,
|
||||
/// The position stored in `rt::v1::FormatSpec::width` if it is a `Count::Param`.
|
||||
width: Option<usize>,
|
||||
/// The position stored in `rt::v1::FormatSpec::precision` if it is a `Count::Param`.
|
||||
precision: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'tcx> Visitor<'tcx> for ParamPosition {
|
||||
fn visit_expr_field(&mut self, field: &'tcx ExprField<'tcx>) {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::TypeRelative(_, path)) = ctor.kind
|
||||
&& path.ident.name == sym::Param
|
||||
&& let ExprKind::Lit(lit) = &val.kind
|
||||
&& let LitKind::Int(pos, _) = lit.node
|
||||
{
|
||||
Some(pos as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 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| {
|
||||
if let ExprKind::Call(f, args) = spec.kind
|
||||
&& let ExprKind::Path(QPath::TypeRelative(ty, f)) = f.kind
|
||||
&& let TyKind::Path(QPath::LangItem(LangItem::FormatPlaceholder, _, _)) = ty.kind
|
||||
&& f.ident.name == sym::new
|
||||
&& let [position, _fill, _align, _flags, precision, width] = args
|
||||
&& let ExprKind::Lit(position) = &position.kind
|
||||
&& let LitKind::Int(position, _) = position.node {
|
||||
ParamPosition {
|
||||
value: position as usize,
|
||||
width: parse_count(width),
|
||||
precision: parse_count(precision),
|
||||
}
|
||||
} else {
|
||||
ParamPosition::default()
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// `Span::from_inner`, but for `rustc_parse_format`'s `InnerSpan`
|
||||
fn span_from_inner(base: SpanData, inner: rpf::InnerSpan) -> Span {
|
||||
Span::new(
|
||||
base.lo + BytePos::from_usize(inner.start),
|
||||
base.lo + BytePos::from_usize(inner.end),
|
||||
base.ctxt,
|
||||
base.parent,
|
||||
)
|
||||
}
|
||||
|
||||
/// 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, 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 {
|
||||
|
@ -729,451 +491,6 @@ pub enum FormatParamUsage {
|
|||
Precision,
|
||||
}
|
||||
|
||||
/// A `FormatParam` is any place in a `FormatArgument` that refers to a supplied value, e.g.
|
||||
///
|
||||
/// ```
|
||||
/// let precision = 2;
|
||||
/// format!("{:.precision$}", 0.1234);
|
||||
/// ```
|
||||
///
|
||||
/// has two `FormatParam`s, a [`FormatParamKind::Implicit`] `.kind` with a `.value` of `0.1234`
|
||||
/// and a [`FormatParamKind::NamedInline("precision")`] `.kind` with a `.value` of `2`
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct FormatParam<'tcx> {
|
||||
/// The expression this parameter refers to.
|
||||
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
|
||||
/// format!("{}, { }, {0}, {name}", ...);
|
||||
/// ^ ~~ ~ ~~~~
|
||||
/// ```
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl<'tcx> FormatParam<'tcx> {
|
||||
fn new(
|
||||
mut kind: FormatParamKind,
|
||||
usage: FormatParamUsage,
|
||||
position: usize,
|
||||
inner: rpf::InnerSpan,
|
||||
values: &FormatArgsValues<'tcx>,
|
||||
) -> Option<Self> {
|
||||
let value_index = *values.pos_to_value_index.get(position)?;
|
||||
let value = *values.value_args.get(value_index)?;
|
||||
let span = span_from_inner(values.format_string_span, inner);
|
||||
|
||||
// if a param is declared inline, e.g. `format!("{x}")`, the generated expr's span points
|
||||
// into the format string
|
||||
if let FormatParamKind::Named(name) = kind && values.format_string_span.contains(value.span.data()) {
|
||||
kind = FormatParamKind::NamedInline(name);
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
value,
|
||||
kind,
|
||||
usage,
|
||||
span,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Used by [width](https://doc.rust-lang.org/std/fmt/#width) and
|
||||
/// [precision](https://doc.rust-lang.org/std/fmt/#precision) specifiers.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Count<'tcx> {
|
||||
/// Specified with a literal number, stores the value.
|
||||
Is(usize, Span),
|
||||
/// Specified using `$` and `*` syntaxes. The `*` format is still considered to be
|
||||
/// `FormatParamKind::Numbered`.
|
||||
Param(FormatParam<'tcx>),
|
||||
/// Not specified.
|
||||
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?),
|
||||
rpf::Count::CountIsName(name, _) => Self::Param(FormatParam::new(
|
||||
FormatParamKind::Named(Symbol::intern(name)),
|
||||
usage,
|
||||
position?,
|
||||
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::CountImplied => Self::Implied(span),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_implied(self) -> bool {
|
||||
matches!(self, Count::Implied(_))
|
||||
}
|
||||
|
||||
pub fn param(self) -> Option<FormatParam<'tcx>> {
|
||||
match self {
|
||||
Count::Param(param) => Some(param),
|
||||
_ => 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
|
||||
/// <https://doc.rust-lang.org/std/fmt/index.html#formatting-parameters> for the precise meanings.
|
||||
#[derive(Debug)]
|
||||
pub struct FormatSpec<'tcx> {
|
||||
/// Optionally specified character to fill alignment with.
|
||||
pub fill: Option<char>,
|
||||
/// Optionally specified alignment.
|
||||
pub align: Alignment,
|
||||
/// Whether all flag options are set to default (no flags specified).
|
||||
pub no_flags: bool,
|
||||
/// Represents either the maximum width or the integer precision.
|
||||
pub precision: Count<'tcx>,
|
||||
/// The minimum width, will be padded according to `width`/`align`
|
||||
pub width: Count<'tcx>,
|
||||
/// The formatting trait used by the argument, e.g. `sym::Display` for `{}`, `sym::Debug` for
|
||||
/// `{:?}`.
|
||||
pub r#trait: Symbol,
|
||||
pub trait_span: Option<Span>,
|
||||
}
|
||||
|
||||
impl<'tcx> FormatSpec<'tcx> {
|
||||
fn new(spec: rpf::FormatSpec<'_>, positions: ParamPosition, values: &FormatArgsValues<'tcx>) -> Option<Self> {
|
||||
Some(Self {
|
||||
fill: spec.fill,
|
||||
align: spec.align,
|
||||
no_flags: spec.sign.is_none() && !spec.alternate && !spec.zero_pad && spec.debug_hex.is_none(),
|
||||
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,
|
||||
"o" => sym!(Octal),
|
||||
"x" => sym!(LowerHex),
|
||||
"X" => sym!(UpperHex),
|
||||
"p" => sym::Pointer,
|
||||
"b" => sym!(Binary),
|
||||
"e" => sym!(LowerExp),
|
||||
"E" => sym!(UpperExp),
|
||||
_ => return None,
|
||||
},
|
||||
trait_span: spec
|
||||
.ty_span
|
||||
.map(|span| span_from_inner(values.format_string_span, span)),
|
||||
})
|
||||
}
|
||||
|
||||
/// 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.no_flags
|
||||
}
|
||||
}
|
||||
|
||||
/// A format argument, such as `{}`, `{foo:?}`.
|
||||
#[derive(Debug)]
|
||||
pub struct FormatArg<'tcx> {
|
||||
/// The parameter the argument refers to.
|
||||
pub param: FormatParam<'tcx>,
|
||||
/// How to format `param`.
|
||||
pub format: FormatSpec<'tcx>,
|
||||
/// span of the whole argument, `{..}`.
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// A parsed `format_args!` expansion.
|
||||
#[derive(Debug)]
|
||||
pub struct FormatArgsExpn<'tcx> {
|
||||
/// The format string literal.
|
||||
pub format_string: FormatString,
|
||||
/// 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,
|
||||
/// 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)`.
|
||||
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))
|
||||
.find(|&name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))?;
|
||||
let newline = macro_name == sym::format_args_nl;
|
||||
|
||||
// ::core::fmt::Arguments::new_const(pieces)
|
||||
// ::core::fmt::Arguments::new_v1(pieces, args)
|
||||
// ::core::fmt::Arguments::new_v1_formatted(pieces, args, fmt, _unsafe_arg)
|
||||
if let ExprKind::Call(callee, [pieces, rest @ ..]) = expr.kind
|
||||
&& let ExprKind::Path(QPath::TypeRelative(ty, seg)) = callee.kind
|
||||
&& let TyKind::Path(QPath::LangItem(LangItem::FormatArguments, _, _)) = ty.kind
|
||||
&& matches!(seg.ident.as_str(), "new_const" | "new_v1" | "new_v1_formatted")
|
||||
{
|
||||
let format_string = FormatString::new(cx, pieces)?;
|
||||
|
||||
let mut parser = rpf::Parser::new(
|
||||
&format_string.unescaped,
|
||||
format_string.style,
|
||||
Some(format_string.snippet.clone()),
|
||||
// `format_string.unescaped` does not contain the appended newline
|
||||
false,
|
||||
rpf::ParseMode::Format,
|
||||
);
|
||||
|
||||
let parsed_args = parser
|
||||
.by_ref()
|
||||
.filter_map(|piece| match piece {
|
||||
rpf::Piece::NextArgument(a) => Some(a),
|
||||
rpf::Piece::String(_) => None,
|
||||
})
|
||||
.collect_vec();
|
||||
if !parser.errors.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let positions = if let Some(fmt_arg) = rest.get(1) {
|
||||
// If the argument contains format specs, `new_v1_formatted(_, _, fmt, _)`, parse
|
||||
// them.
|
||||
|
||||
Either::Left(parse_rt_fmt(fmt_arg)?)
|
||||
} else {
|
||||
// If no format specs are given, the positions are in the given order and there are
|
||||
// no `precision`/`width`s to consider.
|
||||
|
||||
Either::Right((0..).map(|n| ParamPosition {
|
||||
value: n,
|
||||
width: None,
|
||||
precision: None,
|
||||
}))
|
||||
};
|
||||
|
||||
let values = if let Some(args) = rest.first() {
|
||||
FormatArgsValues::new(args, format_string.span.data())
|
||||
} else {
|
||||
FormatArgsValues::new_empty(format_string.span.data())
|
||||
};
|
||||
|
||||
let args = izip!(positions, parsed_args, parser.arg_places)
|
||||
.map(|(position, parsed_arg, arg_span)| {
|
||||
Some(FormatArg {
|
||||
param: FormatParam::new(
|
||||
match parsed_arg.position {
|
||||
rpf::Position::ArgumentImplicitlyIs(_) => FormatParamKind::Implicit,
|
||||
rpf::Position::ArgumentIs(_) => FormatParamKind::Numbered,
|
||||
// NamedInline is handled by `FormatParam::new()`
|
||||
rpf::Position::ArgumentNamed(name) => FormatParamKind::Named(Symbol::intern(name)),
|
||||
},
|
||||
FormatParamUsage::Argument,
|
||||
position.value,
|
||||
parsed_arg.position_span,
|
||||
&values,
|
||||
)?,
|
||||
format: FormatSpec::new(parsed_arg.format, position, &values)?,
|
||||
span: span_from_inner(values.format_string_span, arg_span),
|
||||
})
|
||||
})
|
||||
.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,
|
||||
newline,
|
||||
comma_spans,
|
||||
explicit_values,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_nested(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expn_id: ExpnId) -> Option<Self> {
|
||||
for_each_expr(expr, |e| {
|
||||
let e_ctxt = e.span.ctxt();
|
||||
if e_ctxt == expr.span.ctxt() {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Source callsite span of all inputs
|
||||
pub fn inputs_span(&self) -> Span {
|
||||
match *self.explicit_values {
|
||||
[] => self.format_string.span,
|
||||
[.., last] => self
|
||||
.format_string
|
||||
.span
|
||||
.to(hygiene::walk_chain(last.span, self.format_string.span.ctxt())),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
.iter()
|
||||
.flat_map(|arg| [Some(arg.param), arg.format.precision.param(), arg.format.width.param()])
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
/// A node with a `HirId` and a `Span`
|
||||
pub trait HirNode {
|
||||
fn hir_id(&self) -> HirId;
|
||||
|
|
Loading…
Reference in a new issue