Auto merge of #12567 - Alexendoo:format-args-storage, r=flip1995

Fix `FormatArgs` storage when `-Zthreads` > 1

Fixes #11886

The initial way I thought of was a little gross so I never opened a PR for it, I thought of a nicer way today that no longer involves any `thread_local`s or `static`s

`rustc_data_strucutres::sync::{Lrc, OnceLock}` implement `DynSend` + `DynSync` so we can pass them to the lint passes that need the storage

changelog: none

r? `@flip1995`
This commit is contained in:
bors 2024-05-03 09:21:22 +00:00
commit 993d8ae2a7
10 changed files with 151 additions and 94 deletions

View file

@ -1,12 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::{find_format_args, format_args_inputs_span};
use clippy_utils::macros::{format_args_inputs_span, FormatArgsStorage};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_expn_of, path_def_id};
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::{BindingMode, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_session::impl_lint_pass;
use rustc_span::{sym, ExpnId};
declare_clippy_lint! {
@ -38,7 +38,17 @@ declare_clippy_lint! {
"using the `write!()` family of functions instead of the `print!()` family of functions, when using the latter would work"
}
declare_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]);
pub struct ExplicitWrite {
format_args: FormatArgsStorage,
}
impl ExplicitWrite {
pub fn new(format_args: FormatArgsStorage) -> Self {
Self { format_args }
}
}
impl_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]);
impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
@ -57,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
Some(sym::io_stderr) => ("stderr", "e"),
_ => return,
};
let Some(format_args) = find_format_args(cx, write_arg, ExpnId::root()) else {
let Some(format_args) = self.format_args.get(cx, write_arg, ExpnId::root()) else {
return;
};
@ -83,7 +93,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), "..", &mut applicability);
snippet_with_applicability(cx, format_args_inputs_span(format_args), "..", &mut applicability);
span_lint_and_sugg(
cx,
EXPLICIT_WRITE,

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::{find_format_arg_expr, find_format_args, root_macro_call_first_node};
use clippy_utils::macros::{find_format_arg_expr, root_macro_call_first_node, FormatArgsStorage};
use clippy_utils::source::{snippet_opt, snippet_with_context};
use clippy_utils::sugg::Sugg;
use rustc_ast::{FormatArgsPiece, FormatOptions, FormatTrait};
@ -7,7 +7,7 @@ use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
use rustc_session::impl_lint_pass;
use rustc_span::{sym, Span};
declare_clippy_lint! {
@ -39,13 +39,24 @@ declare_clippy_lint! {
"useless use of `format!`"
}
declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
#[allow(clippy::module_name_repetitions)]
pub struct UselessFormat {
format_args: FormatArgsStorage,
}
impl UselessFormat {
pub fn new(format_args: FormatArgsStorage) -> Self {
Self { format_args }
}
}
impl_lint_pass!(UselessFormat => [USELESS_FORMAT]);
impl<'tcx> LateLintPass<'tcx> for UselessFormat {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
&& let Some(format_args) = find_format_args(cx, expr, macro_call.expn)
&& let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn)
{
let mut applicability = Applicability::MachineApplicable;
let call_site = macro_call.span;

View file

@ -3,8 +3,8 @@ use clippy_config::msrvs::{self, Msrv};
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::is_diag_trait_item;
use clippy_utils::macros::{
find_format_arg_expr, find_format_args, format_arg_removal_span, format_placeholder_format_span, is_assert_macro,
is_format_macro, is_panic, matching_root_macro_call, root_macro_call_first_node, FormatParamUsage, MacroCall,
find_format_arg_expr, format_arg_removal_span, format_placeholder_format_span, is_assert_macro, is_format_macro,
is_panic, matching_root_macro_call, root_macro_call_first_node, FormatArgsStorage, FormatParamUsage, MacroCall,
};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_lang_item};
@ -167,15 +167,18 @@ impl_lint_pass!(FormatArgs => [
UNUSED_FORMAT_SPECS,
]);
#[allow(clippy::struct_field_names)]
pub struct FormatArgs {
format_args: FormatArgsStorage,
msrv: Msrv,
ignore_mixed: bool,
}
impl FormatArgs {
#[must_use]
pub fn new(msrv: Msrv, allow_mixed_uninlined_format_args: bool) -> Self {
pub fn new(format_args: FormatArgsStorage, msrv: Msrv, allow_mixed_uninlined_format_args: bool) -> Self {
Self {
format_args,
msrv,
ignore_mixed: allow_mixed_uninlined_format_args,
}
@ -186,13 +189,13 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
if let Some(macro_call) = root_macro_call_first_node(cx, expr)
&& is_format_macro(cx, macro_call.def_id)
&& let Some(format_args) = find_format_args(cx, expr, macro_call.expn)
&& let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn)
{
let linter = FormatArgsExpr {
cx,
expr,
macro_call: &macro_call,
format_args: &format_args,
format_args,
ignore_mixed: self.ignore_mixed,
};

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
use clippy_utils::macros::{find_format_arg_expr, find_format_args, is_format_macro, root_macro_call_first_node};
use clippy_utils::macros::{find_format_arg_expr, is_format_macro, root_macro_call_first_node, FormatArgsStorage};
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
use rustc_ast::{FormatArgsPiece, FormatTrait};
use rustc_errors::Applicability;
@ -99,13 +99,15 @@ struct FormatTraitNames {
#[derive(Default)]
pub struct FormatImpl {
format_args: FormatArgsStorage,
// Whether we are inside Display or Debug trait impl - None for neither
format_trait_impl: Option<FormatTraitNames>,
}
impl FormatImpl {
pub fn new() -> Self {
pub fn new(format_args: FormatArgsStorage) -> Self {
Self {
format_args,
format_trait_impl: None,
}
}
@ -129,6 +131,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl {
if let Some(format_trait_impl) = self.format_trait_impl {
let linter = FormatImplExpr {
cx,
format_args: &self.format_args,
expr,
format_trait_impl,
};
@ -141,6 +144,7 @@ impl<'tcx> LateLintPass<'tcx> for FormatImpl {
struct FormatImplExpr<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
format_args: &'a FormatArgsStorage,
expr: &'tcx Expr<'tcx>,
format_trait_impl: FormatTraitNames,
}
@ -175,7 +179,7 @@ impl<'a, 'tcx> FormatImplExpr<'a, 'tcx> {
if let Some(outer_macro) = root_macro_call_first_node(self.cx, self.expr)
&& let macro_def_id = outer_macro.def_id
&& is_format_macro(self.cx, macro_def_id)
&& let Some(format_args) = find_format_args(self.cx, self.expr, outer_macro.expn)
&& let Some(format_args) = self.format_args.get(self.cx, self.expr, outer_macro.expn)
{
for piece in &format_args.template {
if let FormatArgsPiece::Placeholder(placeholder) = piece

View file

@ -60,11 +60,6 @@ extern crate clippy_utils;
#[macro_use]
extern crate declare_clippy_lint;
use std::collections::BTreeMap;
use rustc_data_structures::fx::FxHashSet;
use rustc_lint::{Lint, LintId};
#[cfg(feature = "internal")]
pub mod deprecated_lints;
#[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))]
@ -384,6 +379,10 @@ mod zero_sized_map_values;
// end lints modules, do not remove this comment, its used in `update_lints`
use clippy_config::{get_configuration_metadata, Conf};
use clippy_utils::macros::FormatArgsStorage;
use rustc_data_structures::fx::FxHashSet;
use rustc_lint::{Lint, LintId};
use std::collections::BTreeMap;
/// Register all pre expansion lints
///
@ -615,6 +614,14 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
}
}
let format_args_storage = FormatArgsStorage::default();
let format_args = format_args_storage.clone();
store.register_early_pass(move || {
Box::new(utils::format_args_collector::FormatArgsCollector::new(
format_args.clone(),
))
});
// all the internal lints
#[cfg(feature = "internal")]
{
@ -655,7 +662,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
.collect(),
))
});
store.register_early_pass(|| Box::<utils::format_args_collector::FormatArgsCollector>::default());
store.register_late_pass(|_| Box::new(utils::dump_hir::DumpHir));
store.register_late_pass(|_| Box::new(utils::author::Author));
store.register_late_pass(move |_| {
@ -697,6 +703,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions));
store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports));
store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(msrv())));
let format_args = format_args_storage.clone();
store.register_late_pass(move |_| {
Box::new(methods::Methods::new(
avoid_breaking_exported_api,
@ -704,6 +711,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
allow_expect_in_tests,
allow_unwrap_in_tests,
allowed_dotfiles.clone(),
format_args.clone(),
))
});
store.register_late_pass(move |_| Box::new(matches::Matches::new(msrv())));
@ -768,7 +776,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::<regex::Regex>::default());
store.register_late_pass(move |_| Box::new(copies::CopyAndPaste::new(ignore_interior_mutability.clone())));
store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator));
store.register_late_pass(|_| Box::new(format::UselessFormat));
let format_args = format_args_storage.clone();
store.register_late_pass(move |_| Box::new(format::UselessFormat::new(format_args.clone())));
store.register_late_pass(|_| Box::new(swap::Swap));
store.register_late_pass(|_| Box::new(overflow_check_conditional::OverflowCheckConditional));
store.register_late_pass(|_| Box::<new_without_default::NewWithoutDefault>::default());
@ -792,7 +801,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl));
store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount));
store.register_late_pass(move |_| Box::new(large_enum_variant::LargeEnumVariant::new(enum_variant_size_threshold)));
store.register_late_pass(|_| Box::new(explicit_write::ExplicitWrite));
let format_args = format_args_storage.clone();
store.register_late_pass(move |_| Box::new(explicit_write::ExplicitWrite::new(format_args.clone())));
store.register_late_pass(|_| Box::new(needless_pass_by_value::NeedlessPassByValue));
store.register_late_pass(move |tcx| {
Box::new(pass_by_ref_or_value::PassByRefOrValue::new(
@ -834,7 +844,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(mut_key::MutableKeyType::new(ignore_interior_mutability.clone())));
store.register_early_pass(|| Box::new(reference::DerefAddrOf));
store.register_early_pass(|| Box::new(double_parens::DoubleParens));
store.register_late_pass(|_| Box::new(format_impl::FormatImpl::new()));
let format_args = format_args_storage.clone();
store.register_late_pass(move |_| Box::new(format_impl::FormatImpl::new(format_args.clone())));
store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval));
store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse));
store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne));
@ -960,8 +971,14 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
accept_comment_above_attributes,
))
});
store
.register_late_pass(move |_| Box::new(format_args::FormatArgs::new(msrv(), allow_mixed_uninlined_format_args)));
let format_args = format_args_storage.clone();
store.register_late_pass(move |_| {
Box::new(format_args::FormatArgs::new(
format_args.clone(),
msrv(),
allow_mixed_uninlined_format_args,
))
});
store.register_late_pass(|_| Box::new(trailing_empty_array::TrailingEmptyArray));
store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes));
store.register_late_pass(|_| Box::new(needless_late_init::NeedlessLateInit));
@ -972,7 +989,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(|_| Box::new(default_union_representation::DefaultUnionRepresentation));
store.register_late_pass(|_| Box::<only_used_in_recursion::OnlyUsedInRecursion>::default());
store.register_late_pass(move |_| Box::new(dbg_macro::DbgMacro::new(allow_dbg_in_tests)));
store.register_late_pass(move |_| Box::new(write::Write::new(allow_print_in_tests)));
let format_args = format_args_storage.clone();
store.register_late_pass(move |_| Box::new(write::Write::new(format_args.clone(), allow_print_in_tests)));
store.register_late_pass(move |_| {
Box::new(cargo::Cargo {
ignore_publish: cargo_ignore_publish,

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::{find_format_args, format_args_inputs_span, root_macro_call_first_node};
use clippy_utils::macros::{format_args_inputs_span, root_macro_call_first_node, FormatArgsStorage};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use rustc_errors::Applicability;
@ -16,6 +16,7 @@ use super::EXPECT_FUN_CALL;
#[allow(clippy::too_many_lines)]
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
format_args_storage: &FormatArgsStorage,
expr: &hir::Expr<'_>,
method_span: Span,
name: &str,
@ -134,9 +135,9 @@ pub(super) fn check<'tcx>(
// Special handling for `format!` as arg_root
if let Some(macro_call) = root_macro_call_first_node(cx, arg_root) {
if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id)
&& let Some(format_args) = find_format_args(cx, arg_root, macro_call.expn)
&& let Some(format_args) = format_args_storage.get(cx, arg_root, macro_call.expn)
{
let span = format_args_inputs_span(&format_args);
let span = format_args_inputs_span(format_args);
let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
span_lint_and_sugg(
cx,

View file

@ -133,6 +133,7 @@ use bind_instead_of_map::BindInsteadOfMap;
use clippy_config::msrvs::{self, Msrv};
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
use clippy_utils::macros::FormatArgsStorage;
use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_copy, is_type_diagnostic_item};
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, peel_blocks, return_ty};
pub use path_ends_with_ext::DEFAULT_ALLOWED_DOTFILES;
@ -4087,12 +4088,14 @@ declare_clippy_lint! {
suspicious,
"is_empty() called on strings known at compile time"
}
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
allow_expect_in_tests: bool,
allow_unwrap_in_tests: bool,
allowed_dotfiles: FxHashSet<String>,
format_args: FormatArgsStorage,
}
impl Methods {
@ -4103,6 +4106,7 @@ impl Methods {
allow_expect_in_tests: bool,
allow_unwrap_in_tests: bool,
mut allowed_dotfiles: FxHashSet<String>,
format_args: FormatArgsStorage,
) -> Self {
allowed_dotfiles.extend(DEFAULT_ALLOWED_DOTFILES.iter().map(ToString::to_string));
@ -4112,6 +4116,7 @@ impl Methods {
allow_expect_in_tests,
allow_unwrap_in_tests,
allowed_dotfiles,
format_args,
}
}
}
@ -4281,7 +4286,15 @@ impl<'tcx> LateLintPass<'tcx> for Methods {
ExprKind::MethodCall(method_call, receiver, args, _) => {
let method_span = method_call.ident.span;
or_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), receiver, args);
expect_fun_call::check(cx, expr, method_span, method_call.ident.as_str(), receiver, args);
expect_fun_call::check(
cx,
&self.format_args,
expr,
method_span,
method_call.ident.as_str(),
receiver,
args,
);
clone_on_copy::check(cx, expr, method_call.ident.name, receiver, args);
clone_on_ref_ptr::check(cx, expr, method_call.ident.name, receiver, args);
inefficient_to_string::check(cx, expr, method_call.ident.name, receiver, args);

View file

@ -1,4 +1,4 @@
use clippy_utils::macros::AST_FORMAT_ARGS;
use clippy_utils::macros::FormatArgsStorage;
use clippy_utils::source::snippet_opt;
use itertools::Itertools;
use rustc_ast::{Crate, Expr, ExprKind, FormatArgs};
@ -9,13 +9,20 @@ use rustc_session::impl_lint_pass;
use rustc_span::{hygiene, Span};
use std::iter::once;
use std::mem;
use std::rc::Rc;
/// Collects [`rustc_ast::FormatArgs`] so that future late passes can call
/// [`clippy_utils::macros::find_format_args`]
#[derive(Default)]
/// Populates [`FormatArgsStorage`] with AST [`FormatArgs`] nodes
pub struct FormatArgsCollector {
format_args: FxHashMap<Span, Rc<FormatArgs>>,
format_args: FxHashMap<Span, FormatArgs>,
storage: FormatArgsStorage,
}
impl FormatArgsCollector {
pub fn new(storage: FormatArgsStorage) -> Self {
Self {
format_args: FxHashMap::default(),
storage,
}
}
}
impl_lint_pass!(FormatArgsCollector => []);
@ -27,16 +34,12 @@ impl EarlyLintPass for FormatArgsCollector {
return;
}
self.format_args
.insert(expr.span.with_parent(None), Rc::new((**args).clone()));
self.format_args.insert(expr.span.with_parent(None), (**args).clone());
}
}
fn check_crate_post(&mut self, _: &EarlyContext<'_>, _: &Crate) {
AST_FORMAT_ARGS.with(|ast_format_args| {
let result = ast_format_args.set(mem::take(&mut self.format_args));
debug_assert!(result.is_ok());
});
self.storage.set(mem::take(&mut self.format_args));
}
}

View file

@ -1,5 +1,5 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::macros::{find_format_args, format_arg_removal_span, root_macro_call_first_node, MacroCall};
use clippy_utils::macros::{format_arg_removal_span, root_macro_call_first_node, FormatArgsStorage, MacroCall};
use clippy_utils::source::{expand_past_previous_comma, snippet_opt};
use clippy_utils::{is_in_cfg_test, is_in_test_function};
use rustc_ast::token::LitKind;
@ -236,13 +236,15 @@ declare_clippy_lint! {
#[derive(Default)]
pub struct Write {
format_args: FormatArgsStorage,
in_debug_impl: bool,
allow_print_in_tests: bool,
}
impl Write {
pub fn new(allow_print_in_tests: bool) -> Self {
pub fn new(format_args: FormatArgsStorage, allow_print_in_tests: bool) -> Self {
Self {
format_args,
allow_print_in_tests,
..Default::default()
}
@ -307,7 +309,7 @@ impl<'tcx> LateLintPass<'tcx> for Write {
_ => return,
}
if let Some(format_args) = find_format_args(cx, expr, macro_call.expn) {
if let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) {
// ignore `writeln!(w)` and `write!(v, some_macro!())`
if format_args.span.from_expansion() {
return;
@ -315,15 +317,15 @@ impl<'tcx> LateLintPass<'tcx> for Write {
match diag_name {
sym::print_macro | sym::eprint_macro | sym::write_macro => {
check_newline(cx, &format_args, &macro_call, name);
check_newline(cx, format_args, &macro_call, name);
},
sym::println_macro | sym::eprintln_macro | sym::writeln_macro => {
check_empty_string(cx, &format_args, &macro_call, name);
check_empty_string(cx, format_args, &macro_call, name);
},
_ => {},
}
check_literal(cx, &format_args, name);
check_literal(cx, format_args, name);
if !self.in_debug_impl {
for piece in &format_args.template {

View file

@ -5,15 +5,13 @@ use crate::visitors::{for_each_expr, Descend};
use arrayvec::ArrayVec;
use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder};
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::sync::{Lrc, OnceLock};
use rustc_hir::{self as hir, Expr, ExprKind, HirId, Node, QPath};
use rustc_lint::LateContext;
use rustc_span::def_id::DefId;
use rustc_span::hygiene::{self, MacroKind, SyntaxContext};
use rustc_span::{sym, BytePos, ExpnData, ExpnId, ExpnKind, Span, SpanData, Symbol};
use std::cell::OnceCell;
use std::ops::ControlFlow;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
const FORMAT_MACRO_DIAG_ITEMS: &[Symbol] = &[
sym::assert_eq_macro,
@ -388,50 +386,44 @@ fn is_assert_arg(cx: &LateContext<'_>, expr: &Expr<'_>, assert_expn: ExpnId) ->
}
}
thread_local! {
/// We preserve the [`FormatArgs`] structs from the early pass for use in the late pass to be
/// able to access the many features of a [`LateContext`].
/// Stores AST [`FormatArgs`] nodes for use in late lint passes, as they are in a desugared form in
/// the HIR
#[derive(Default, Clone)]
pub struct FormatArgsStorage(Lrc<OnceLock<FxHashMap<Span, FormatArgs>>>);
impl FormatArgsStorage {
/// Returns an AST [`FormatArgs`] node if a `format_args` expansion is found as a descendant of
/// `expn_id`
///
/// A thread local is used because [`FormatArgs`] is `!Send` and `!Sync`, we are making an
/// assumption that the early pass that populates the map and the later late passes will all be
/// running on the same thread.
#[doc(hidden)]
pub static AST_FORMAT_ARGS: OnceCell<FxHashMap<Span, Rc<FormatArgs>>> = {
static CALLED: AtomicBool = AtomicBool::new(false);
debug_assert!(
!CALLED.swap(true, Ordering::SeqCst),
"incorrect assumption: `AST_FORMAT_ARGS` should only be accessed by a single thread",
);
OnceCell::new()
};
}
/// Returns an AST [`FormatArgs`] node if a `format_args` expansion is found as a descendant of
/// `expn_id`
pub fn find_format_args(cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId) -> Option<Rc<FormatArgs>> {
let format_args_expr = for_each_expr(start, |expr| {
let ctxt = expr.span.ctxt();
if ctxt.outer_expn().is_descendant_of(expn_id) {
if macro_backtrace(expr.span)
.map(|macro_call| cx.tcx.item_name(macro_call.def_id))
.any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))
{
ControlFlow::Break(expr)
/// See also [`find_format_arg_expr`]
pub fn get(&self, cx: &LateContext<'_>, start: &Expr<'_>, expn_id: ExpnId) -> Option<&FormatArgs> {
let format_args_expr = for_each_expr(start, |expr| {
let ctxt = expr.span.ctxt();
if ctxt.outer_expn().is_descendant_of(expn_id) {
if macro_backtrace(expr.span)
.map(|macro_call| cx.tcx.item_name(macro_call.def_id))
.any(|name| matches!(name, sym::const_format_args | sym::format_args | sym::format_args_nl))
{
ControlFlow::Break(expr)
} else {
ControlFlow::Continue(Descend::Yes)
}
} else {
ControlFlow::Continue(Descend::Yes)
ControlFlow::Continue(Descend::No)
}
} else {
ControlFlow::Continue(Descend::No)
}
})?;
})?;
AST_FORMAT_ARGS.with(|ast_format_args| {
ast_format_args
.get()?
.get(&format_args_expr.span.with_parent(None))
.cloned()
})
debug_assert!(self.0.get().is_some(), "`FormatArgsStorage` not yet populated");
self.0.get()?.get(&format_args_expr.span.with_parent(None))
}
/// Should only be called by `FormatArgsCollector`
pub fn set(&self, format_args: FxHashMap<Span, FormatArgs>) {
self.0
.set(format_args)
.expect("`FormatArgsStorage::set` should only be called once");
}
}
/// Attempt to find the [`rustc_hir::Expr`] that corresponds to the [`FormatArgument`]'s value, if