Skip to content

Commit c6bf954

Browse files
committed
Auto merge of rust-lang#12624 - J-ZhengLi:issue12586, r=xFrednet
fix [`large_stack_arrays`] linting in `vec` macro fixes: rust-lang#12586 this PR also adds a wrapper function `matching_root_macro_call` to `clippy_utils::macros`, considering how often that same pattern appears in the codebase. (I'm always very indecisive towards naming, so, if anyone have better idea of how that function should be named, feel free to suggest it) --- changelog: fix [`large_stack_arrays`] linting in `vec` macro; add `matching_root_macro_call` to clippy_utils
2 parents de4fce8 + 2861729 commit c6bf954

11 files changed

+194
-48
lines changed

clippy_lints/src/format_args.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
44
use clippy_utils::is_diag_trait_item;
55
use clippy_utils::macros::{
66
find_format_arg_expr, find_format_args, format_arg_removal_span, format_placeholder_format_span, is_assert_macro,
7-
is_format_macro, is_panic, root_macro_call, root_macro_call_first_node, FormatParamUsage, MacroCall,
7+
is_format_macro, is_panic, matching_root_macro_call, root_macro_call_first_node, FormatParamUsage, MacroCall,
88
};
99
use clippy_utils::source::snippet_opt;
1010
use clippy_utils::ty::{implements_trait, is_type_lang_item};
@@ -271,9 +271,7 @@ impl<'a, 'tcx> FormatArgsExpr<'a, 'tcx> {
271271
let mut suggest_format = |spec| {
272272
let message = format!("for the {spec} to apply consider using `format!()`");
273273

274-
if let Some(mac_call) = root_macro_call(arg_span)
275-
&& self.cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id)
276-
{
274+
if let Some(mac_call) = matching_root_macro_call(self.cx, arg_span, sym::format_args_macro) {
277275
diag.span_suggestion(
278276
self.cx.sess().source_map().span_until_char(mac_call.span, '!'),
279277
message,

clippy_lints/src/large_stack_arrays.rs

+55-10
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
use clippy_utils::diagnostics::span_lint_and_help;
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::is_from_proc_macro;
3+
use clippy_utils::macros::macro_backtrace;
24
use clippy_utils::source::snippet;
3-
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node};
5+
use rustc_hir::{ArrayLen, Expr, ExprKind, Item, ItemKind, Node};
46
use rustc_lint::{LateContext, LateLintPass};
57
use rustc_middle::ty::layout::LayoutOf;
68
use rustc_middle::ty::{self, ConstKind};
79
use rustc_session::impl_lint_pass;
10+
use rustc_span::{sym, Span};
811

912
declare_clippy_lint! {
1013
/// ### What it does
@@ -25,20 +28,41 @@ declare_clippy_lint! {
2528

2629
pub struct LargeStackArrays {
2730
maximum_allowed_size: u128,
31+
prev_vec_macro_callsite: Option<Span>,
2832
}
2933

3034
impl LargeStackArrays {
3135
#[must_use]
3236
pub fn new(maximum_allowed_size: u128) -> Self {
33-
Self { maximum_allowed_size }
37+
Self {
38+
maximum_allowed_size,
39+
prev_vec_macro_callsite: None,
40+
}
41+
}
42+
43+
/// Check if the given span of an expr is already in a `vec!` call.
44+
fn is_from_vec_macro(&mut self, cx: &LateContext<'_>, span: Span) -> bool {
45+
// First, we check if this is span is within the last encountered `vec!` macro's root callsite.
46+
self.prev_vec_macro_callsite
47+
.is_some_and(|vec_mac| vec_mac.contains(span))
48+
|| {
49+
// Then, we try backtracking the macro expansions, to see if there's a `vec!` macro,
50+
// and update the `prev_vec_macro_callsite`.
51+
let res = macro_backtrace(span).any(|mac| cx.tcx.is_diagnostic_item(sym::vec_macro, mac.def_id));
52+
if res {
53+
self.prev_vec_macro_callsite = Some(span.source_callsite());
54+
}
55+
res
56+
}
3457
}
3558
}
3659

3760
impl_lint_pass!(LargeStackArrays => [LARGE_STACK_ARRAYS]);
3861

3962
impl<'tcx> LateLintPass<'tcx> for LargeStackArrays {
40-
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
63+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) {
4164
if let ExprKind::Repeat(_, _) | ExprKind::Array(_) = expr.kind
65+
&& !self.is_from_vec_macro(cx, expr.span)
4266
&& let ty::Array(element_type, cst) = cx.typeck_results().expr_ty(expr).kind()
4367
&& let ConstKind::Value(ty::ValTree::Leaf(element_count)) = cst.kind()
4468
&& let Ok(element_count) = element_count.try_to_target_usize(cx.tcx)
@@ -54,20 +78,41 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackArrays {
5478
})
5579
&& self.maximum_allowed_size < u128::from(element_count) * u128::from(element_size)
5680
{
57-
span_lint_and_help(
81+
span_lint_and_then(
5882
cx,
5983
LARGE_STACK_ARRAYS,
6084
expr.span,
6185
format!(
6286
"allocating a local array larger than {} bytes",
6387
self.maximum_allowed_size
6488
),
65-
None,
66-
format!(
67-
"consider allocating on the heap with `vec!{}.into_boxed_slice()`",
68-
snippet(cx, expr.span, "[...]")
69-
),
89+
|diag| {
90+
if !might_be_expanded(cx, expr) {
91+
diag.help(format!(
92+
"consider allocating on the heap with `vec!{}.into_boxed_slice()`",
93+
snippet(cx, expr.span, "[...]")
94+
));
95+
}
96+
},
7097
);
7198
}
7299
}
73100
}
101+
102+
/// Only giving help messages if the expr does not contains macro expanded codes.
103+
fn might_be_expanded<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool {
104+
/// Check if the span of `ArrayLen` of a repeat expression is within the expr's span,
105+
/// if not, meaning this repeat expr is definitely from some proc-macro.
106+
///
107+
/// This is a fail-safe to a case where even the `is_from_proc_macro` is unable to determain the
108+
/// correct result.
109+
fn repeat_expr_might_be_expanded<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool {
110+
let ExprKind::Repeat(_, ArrayLen::Body(anon_const)) = expr.kind else {
111+
return false;
112+
};
113+
let len_span = cx.tcx.def_span(anon_const.def_id);
114+
!expr.span.contains(len_span)
115+
}
116+
117+
expr.span.from_expansion() || is_from_proc_macro(cx, expr) || repeat_expr_might_be_expanded(cx, expr)
118+
}

clippy_lints/src/manual_assert.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
use crate::rustc_lint::LintContext;
22
use clippy_utils::diagnostics::span_lint_and_then;
3-
use clippy_utils::macros::root_macro_call;
3+
use clippy_utils::macros::{is_panic, root_macro_call};
44
use clippy_utils::{is_else_clause, is_parent_stmt, peel_blocks_with_stmt, span_extract_comment, sugg};
55
use rustc_errors::Applicability;
66
use rustc_hir::{Expr, ExprKind, UnOp};
77
use rustc_lint::{LateContext, LateLintPass};
88
use rustc_session::declare_lint_pass;
9-
use rustc_span::sym;
109

1110
declare_clippy_lint! {
1211
/// ### What it does
@@ -42,7 +41,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAssert {
4241
&& !expr.span.from_expansion()
4342
&& let then = peel_blocks_with_stmt(then)
4443
&& let Some(macro_call) = root_macro_call(then.span)
45-
&& cx.tcx.item_name(macro_call.def_id) == sym::panic
44+
&& is_panic(cx, macro_call.def_id)
4645
&& !cx.tcx.sess.source_map().is_multiline(cond.span)
4746
&& let Ok(panic_snippet) = cx.sess().source_map().span_to_snippet(macro_call.span)
4847
&& let Some(panic_snippet) = panic_snippet.strip_suffix(')')

clippy_lints/src/manual_is_ascii_check.rs

+2-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use clippy_config::msrvs::{self, Msrv};
22
use clippy_utils::diagnostics::span_lint_and_sugg;
3-
use clippy_utils::macros::root_macro_call;
3+
use clippy_utils::macros::matching_root_macro_call;
44
use clippy_utils::sugg::Sugg;
55
use clippy_utils::{higher, in_constant};
66
use rustc_ast::ast::RangeLimits;
@@ -9,7 +9,6 @@ use rustc_errors::Applicability;
99
use rustc_hir::{BorrowKind, Expr, ExprKind, PatKind, RangeEnd};
1010
use rustc_lint::{LateContext, LateLintPass};
1111
use rustc_session::impl_lint_pass;
12-
use rustc_span::def_id::DefId;
1312
use rustc_span::{sym, Span};
1413

1514
declare_clippy_lint! {
@@ -97,9 +96,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck {
9796
return;
9897
}
9998

100-
if let Some(macro_call) = root_macro_call(expr.span)
101-
&& is_matches_macro(cx, macro_call.def_id)
102-
{
99+
if let Some(macro_call) = matching_root_macro_call(cx, expr.span, sym::matches_macro) {
103100
if let ExprKind::Match(recv, [arm, ..], _) = expr.kind {
104101
let range = check_pat(&arm.pat.kind);
105102
check_is_ascii(cx, macro_call.span, recv, &range);
@@ -187,11 +184,3 @@ fn check_range(start: &Expr<'_>, end: &Expr<'_>) -> CharRange {
187184
CharRange::Otherwise
188185
}
189186
}
190-
191-
fn is_matches_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool {
192-
if let Some(name) = cx.tcx.get_diagnostic_name(macro_def_id) {
193-
return sym::matches_macro == name;
194-
}
195-
196-
false
197-
}

clippy_lints/src/methods/filter_map.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
2-
use clippy_utils::macros::{is_panic, root_macro_call};
2+
use clippy_utils::macros::{is_panic, matching_root_macro_call, root_macro_call};
33
use clippy_utils::source::{indent_of, reindent_multiline, snippet};
44
use clippy_utils::ty::is_type_diagnostic_item;
55
use clippy_utils::{higher, is_trait_method, path_to_local_id, peel_blocks, SpanlessEq};
@@ -247,8 +247,7 @@ impl<'tcx> OffendingFilterExpr<'tcx> {
247247
} else {
248248
None
249249
}
250-
} else if let Some(macro_call) = root_macro_call(expr.span)
251-
&& cx.tcx.get_diagnostic_name(macro_call.def_id) == Some(sym::matches_macro)
250+
} else if matching_root_macro_call(cx, expr.span, sym::matches_macro).is_some()
252251
// we know for a fact that the wildcard pattern is the second arm
253252
&& let ExprKind::Match(scrutinee, [arm, _], _) = expr.kind
254253
&& path_to_local_id(scrutinee, filter_param_id)

clippy_lints/src/repeat_vec_with_capacity.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use clippy_utils::consts::{constant, Constant};
22
use clippy_utils::diagnostics::span_lint_and_then;
33
use clippy_utils::higher::VecArgs;
4-
use clippy_utils::macros::root_macro_call;
4+
use clippy_utils::macros::matching_root_macro_call;
55
use clippy_utils::source::snippet;
66
use clippy_utils::{expr_or_init, fn_def_id, match_def_path, paths};
77
use rustc_errors::Applicability;
@@ -65,8 +65,7 @@ fn emit_lint(cx: &LateContext<'_>, span: Span, kind: &str, note: &'static str, s
6565

6666
/// Checks `vec![Vec::with_capacity(x); n]`
6767
fn check_vec_macro(cx: &LateContext<'_>, expr: &Expr<'_>) {
68-
if let Some(mac_call) = root_macro_call(expr.span)
69-
&& cx.tcx.is_diagnostic_item(sym::vec_macro, mac_call.def_id)
68+
if matching_root_macro_call(cx, expr.span, sym::vec_macro).is_some()
7069
&& let Some(VecArgs::Repeat(repeat_expr, len_expr)) = VecArgs::hir(cx, expr)
7170
&& fn_def_id(cx, repeat_expr).is_some_and(|did| match_def_path(cx, did, &paths::VEC_WITH_CAPACITY))
7271
&& !len_expr.span.from_expansion()

clippy_lints/src/slow_vector_initialization.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2-
use clippy_utils::macros::root_macro_call;
2+
use clippy_utils::macros::matching_root_macro_call;
33
use clippy_utils::sugg::Sugg;
44
use clippy_utils::{
55
get_enclosing_block, is_expr_path_def_path, is_integer_literal, is_path_diagnostic_item, path_to_local,
@@ -145,9 +145,7 @@ impl SlowVectorInit {
145145
// Generally don't warn if the vec initializer comes from an expansion, except for the vec! macro.
146146
// This lets us still warn on `vec![]`, while ignoring other kinds of macros that may output an
147147
// empty vec
148-
if expr.span.from_expansion()
149-
&& root_macro_call(expr.span).map(|m| m.def_id) != cx.tcx.get_diagnostic_item(sym::vec_macro)
150-
{
148+
if expr.span.from_expansion() && matching_root_macro_call(cx, expr.span, sym::vec_macro).is_none() {
151149
return None;
152150
}
153151

clippy_utils/src/macros.rs

+10
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,20 @@ pub fn macro_backtrace(span: Span) -> impl Iterator<Item = MacroCall> {
119119

120120
/// If the macro backtrace of `span` has a macro call at the root expansion
121121
/// (i.e. not a nested macro call), returns `Some` with the `MacroCall`
122+
///
123+
/// If you only want to check whether the root macro has a specific name,
124+
/// consider using [`matching_root_macro_call`] instead.
122125
pub fn root_macro_call(span: Span) -> Option<MacroCall> {
123126
macro_backtrace(span).last()
124127
}
125128

129+
/// A combination of [`root_macro_call`] and
130+
/// [`is_diagnostic_item`](rustc_middle::ty::TyCtxt::is_diagnostic_item) that returns a `MacroCall`
131+
/// at the root expansion if only it matches the given name.
132+
pub fn matching_root_macro_call(cx: &LateContext<'_>, span: Span, name: Symbol) -> Option<MacroCall> {
133+
root_macro_call(span).filter(|mc| cx.tcx.is_diagnostic_item(name, mc.def_id))
134+
}
135+
126136
/// Like [`root_macro_call`], but only returns `Some` if `node` is the "first node"
127137
/// produced by the macro call, as in [`first_node_in_macro`].
128138
pub fn root_macro_call_first_node(cx: &LateContext<'_>, node: &impl HirNode) -> Option<MacroCall> {

tests/ui/auxiliary/proc_macros.rs

+17
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use proc_macro::token_stream::IntoIter;
99
use proc_macro::Delimiter::{self, Brace, Parenthesis};
1010
use proc_macro::Spacing::{self, Alone, Joint};
1111
use proc_macro::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree as TT};
12+
use syn::spanned::Spanned;
1213

1314
type Result<T> = core::result::Result<T, TokenStream>;
1415

@@ -124,6 +125,22 @@ fn write_with_span(s: Span, mut input: IntoIter, out: &mut TokenStream) -> Resul
124125
Ok(())
125126
}
126127

128+
/// Takes an array repeat expression such as `[0_u32; 2]`, and return the tokens with 10 times the
129+
/// original size, which turns to `[0_u32; 20]`.
130+
#[proc_macro]
131+
pub fn make_it_big(input: TokenStream) -> TokenStream {
132+
let mut expr_repeat = syn::parse_macro_input!(input as syn::ExprRepeat);
133+
let len_span = expr_repeat.len.span();
134+
if let syn::Expr::Lit(expr_lit) = &mut *expr_repeat.len {
135+
if let syn::Lit::Int(lit_int) = &expr_lit.lit {
136+
let orig_val = lit_int.base10_parse::<usize>().expect("not a valid length parameter");
137+
let new_val = orig_val.saturating_mul(10);
138+
expr_lit.lit = syn::parse_quote_spanned!( len_span => #new_val);
139+
}
140+
}
141+
quote::quote!(#expr_repeat).into()
142+
}
143+
127144
/// Within the item this attribute is attached to, an `inline!` macro is available which expands the
128145
/// contained tokens as though they came from a macro expansion.
129146
///

tests/ui/large_stack_arrays.rs

+48
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
//@aux-build:proc_macros.rs
12
#![warn(clippy::large_stack_arrays)]
23
#![allow(clippy::large_enum_variant)]
34

5+
extern crate proc_macros;
6+
47
#[derive(Clone, Copy)]
58
struct S {
69
pub data: [u64; 32],
@@ -55,3 +58,48 @@ fn main() {
5558
[(); 20_000_000],
5659
);
5760
}
61+
62+
#[allow(clippy::useless_vec)]
63+
fn issue_12586() {
64+
macro_rules! dummy {
65+
($n:expr) => {
66+
$n
67+
};
68+
// Weird rule to test help messages.
69+
($a:expr => $b:expr) => {
70+
[$a, $b, $a, $b]
71+
//~^ ERROR: allocating a local array larger than 512000 bytes
72+
};
73+
($id:ident; $n:literal) => {
74+
dummy!(::std::vec![$id;$n])
75+
};
76+
($($id:expr),+ $(,)?) => {
77+
::std::vec![$($id),*]
78+
}
79+
}
80+
macro_rules! create_then_move {
81+
($id:ident; $n:literal) => {{
82+
let _x_ = [$id; $n];
83+
//~^ ERROR: allocating a local array larger than 512000 bytes
84+
_x_
85+
}};
86+
}
87+
88+
let x = [0u32; 50_000];
89+
let y = vec![x, x, x, x, x];
90+
let y = vec![dummy![x, x, x, x, x]];
91+
let y = vec![dummy![[x, x, x, x, x]]];
92+
let y = dummy![x, x, x, x, x];
93+
let y = [x, x, dummy!(x), x, x];
94+
//~^ ERROR: allocating a local array larger than 512000 bytes
95+
let y = dummy![x => x];
96+
let y = dummy![x;5];
97+
let y = dummy!(vec![dummy![x, x, x, x, x]]);
98+
let y = dummy![[x, x, x, x, x]];
99+
//~^ ERROR: allocating a local array larger than 512000 bytes
100+
101+
let y = proc_macros::make_it_big!([x; 1]);
102+
//~^ ERROR: allocating a local array larger than 512000 bytes
103+
let y = vec![proc_macros::make_it_big!([x; 10])];
104+
let y = vec![create_then_move![x; 5]; 5];
105+
}

0 commit comments

Comments
 (0)