fix(collapsible_span_lint_calls): use snippet_with_context for spans that are likely to contain macro expns (#15881)

Fixes https://github.com/rust-lang/rust-clippy/issues/15880

changelog: none
This commit is contained in:
llogiq 2026-01-18 15:51:15 +00:00 committed by GitHub
commit fe4bc3123f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 162 additions and 55 deletions

View file

@ -1,11 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use clippy_utils::{SpanlessEq, is_lint_allowed, peel_blocks_with_stmt};
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
use clippy_utils::{SpanlessEq, is_lint_allowed, peel_blocks_with_stmt, sym};
use rustc_errors::Applicability;
use rustc_hir::{Closure, Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
use rustc_span::{Span, SyntaxContext};
use std::borrow::{Borrow, Cow};
@ -88,33 +88,42 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
&& let ExprKind::MethodCall(ps, recv, span_call_args, _) = &only_expr.kind
&& let ExprKind::Path(..) = recv.kind
{
let and_then_snippets =
get_and_then_snippets(cx, call_cx.span, call_lint.span, call_sp.span, call_msg.span);
let mut app = Applicability::MachineApplicable;
let expr_ctxt = expr.span.ctxt();
let and_then_snippets = get_and_then_snippets(
cx,
expr_ctxt,
call_cx.span,
call_lint.span,
call_sp.span,
call_msg.span,
&mut app,
);
let mut sle = SpanlessEq::new(cx).deny_side_effects();
match ps.ident.as_str() {
"span_suggestion" if sle.eq_expr(call_sp, &span_call_args[0]) => {
suggest_suggestion(
cx,
expr,
&and_then_snippets,
&span_suggestion_snippets(cx, span_call_args),
);
match ps.ident.name {
sym::span_suggestion if sle.eq_expr(call_sp, &span_call_args[0]) => {
let snippets = span_suggestion_snippets(cx, expr_ctxt, span_call_args, &mut app);
suggest_suggestion(cx, expr, &and_then_snippets, &snippets, app);
},
"span_help" if sle.eq_expr(call_sp, &span_call_args[0]) => {
let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true);
sym::span_help if sle.eq_expr(call_sp, &span_call_args[0]) => {
let help_snippet =
snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, &mut app).0;
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), true, app);
},
"span_note" if sle.eq_expr(call_sp, &span_call_args[0]) => {
let note_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true);
sym::span_note if sle.eq_expr(call_sp, &span_call_args[0]) => {
let note_snippet =
snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, &mut app).0;
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), true, app);
},
"help" => {
let help_snippet = snippet(cx, span_call_args[0].span, r#""...""#);
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false);
sym::help => {
let help_snippet =
snippet_with_context(cx, span_call_args[0].span, expr_ctxt, r#""...""#, &mut app).0;
suggest_help(cx, expr, &and_then_snippets, help_snippet.borrow(), false, app);
},
"note" => {
let note_snippet = snippet(cx, span_call_args[0].span, r#""...""#);
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false);
sym::note => {
let note_snippet =
snippet_with_context(cx, span_call_args[0].span, expr_ctxt, r#""...""#, &mut app).0;
suggest_note(cx, expr, &and_then_snippets, note_snippet.borrow(), false, app);
},
_ => (),
}
@ -122,24 +131,26 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls {
}
}
struct AndThenSnippets<'a> {
cx: Cow<'a, str>,
lint: Cow<'a, str>,
span: Cow<'a, str>,
msg: Cow<'a, str>,
struct AndThenSnippets {
cx: Cow<'static, str>,
lint: Cow<'static, str>,
span: Cow<'static, str>,
msg: Cow<'static, str>,
}
fn get_and_then_snippets(
cx: &LateContext<'_>,
expr_ctxt: SyntaxContext,
cx_span: Span,
lint_span: Span,
span_span: Span,
msg_span: Span,
) -> AndThenSnippets<'static> {
let cx_snippet = snippet(cx, cx_span, "cx");
let lint_snippet = snippet(cx, lint_span, "..");
let span_snippet = snippet(cx, span_span, "span");
let msg_snippet = snippet(cx, msg_span, r#""...""#);
app: &mut Applicability,
) -> AndThenSnippets {
let cx_snippet = snippet_with_applicability(cx, cx_span, "cx", app);
let lint_snippet = snippet_with_applicability(cx, lint_span, "..", app);
let span_snippet = snippet_with_applicability(cx, span_span, "span", app);
let msg_snippet = snippet_with_context(cx, msg_span, expr_ctxt, r#""...""#, app).0;
AndThenSnippets {
cx: cx_snippet,
@ -149,19 +160,22 @@ fn get_and_then_snippets(
}
}
struct SpanSuggestionSnippets<'a> {
help: Cow<'a, str>,
sugg: Cow<'a, str>,
applicability: Cow<'a, str>,
struct SpanSuggestionSnippets {
help: Cow<'static, str>,
sugg: Cow<'static, str>,
applicability: Cow<'static, str>,
}
fn span_suggestion_snippets<'a, 'hir>(
fn span_suggestion_snippets<'hir>(
cx: &LateContext<'_>,
expr_ctxt: SyntaxContext,
span_call_args: &'hir [Expr<'hir>],
) -> SpanSuggestionSnippets<'a> {
let help_snippet = snippet(cx, span_call_args[1].span, r#""...""#);
let sugg_snippet = snippet(cx, span_call_args[2].span, "..");
let applicability_snippet = snippet(cx, span_call_args[3].span, "Applicability::MachineApplicable");
app: &mut Applicability,
) -> SpanSuggestionSnippets {
let help_snippet = snippet_with_context(cx, span_call_args[1].span, expr_ctxt, r#""...""#, app).0;
let sugg_snippet = snippet_with_context(cx, span_call_args[2].span, expr_ctxt, "..", app).0;
let applicability_snippet =
snippet_with_applicability(cx, span_call_args[3].span, "Applicability::MachineApplicable", app);
SpanSuggestionSnippets {
help: help_snippet,
@ -173,8 +187,9 @@ fn span_suggestion_snippets<'a, 'hir>(
fn suggest_suggestion(
cx: &LateContext<'_>,
expr: &Expr<'_>,
and_then_snippets: &AndThenSnippets<'_>,
span_suggestion_snippets: &SpanSuggestionSnippets<'_>,
and_then_snippets: &AndThenSnippets,
span_suggestion_snippets: &SpanSuggestionSnippets,
app: Applicability,
) {
span_lint_and_sugg(
cx,
@ -192,16 +207,17 @@ fn suggest_suggestion(
span_suggestion_snippets.sugg,
span_suggestion_snippets.applicability
),
Applicability::MachineApplicable,
app,
);
}
fn suggest_help(
cx: &LateContext<'_>,
expr: &Expr<'_>,
and_then_snippets: &AndThenSnippets<'_>,
and_then_snippets: &AndThenSnippets,
help: &str,
with_span: bool,
app: Applicability,
) {
let option_span = if with_span {
format!("Some({})", and_then_snippets.span)
@ -219,16 +235,17 @@ fn suggest_help(
"span_lint_and_help({}, {}, {}, {}, {}, {help})",
and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg, &option_span,
),
Applicability::MachineApplicable,
app,
);
}
fn suggest_note(
cx: &LateContext<'_>,
expr: &Expr<'_>,
and_then_snippets: &AndThenSnippets<'_>,
and_then_snippets: &AndThenSnippets,
note: &str,
with_span: bool,
app: Applicability,
) {
let note_span = if with_span {
format!("Some({})", and_then_snippets.span)
@ -246,6 +263,6 @@ fn suggest_note(
"span_lint_and_note({}, {}, {}, {}, {note_span}, {note})",
and_then_snippets.cx, and_then_snippets.lint, and_then_snippets.span, and_then_snippets.msg,
),
Applicability::MachineApplicable,
app,
);
}

View file

@ -31,7 +31,7 @@ extern crate rustc_session;
extern crate rustc_span;
mod almost_standard_lint_formulation;
mod collapsible_calls;
mod collapsible_span_lint_calls;
mod derive_deserialize_allowing_unknown;
mod internal_paths;
mod lint_without_lint_pass;
@ -48,7 +48,7 @@ use rustc_lint::{Lint, LintStore};
static LINTS: &[&Lint] = &[
almost_standard_lint_formulation::ALMOST_STANDARD_LINT_FORMULATION,
collapsible_calls::COLLAPSIBLE_SPAN_LINT_CALLS,
collapsible_span_lint_calls::COLLAPSIBLE_SPAN_LINT_CALLS,
derive_deserialize_allowing_unknown::DERIVE_DESERIALIZE_ALLOWING_UNKNOWN,
lint_without_lint_pass::DEFAULT_LINT,
lint_without_lint_pass::INVALID_CLIPPY_VERSION_ATTRIBUTE,
@ -69,7 +69,7 @@ pub fn register_lints(store: &mut LintStore) {
store.register_early_pass(|| Box::new(unsorted_clippy_utils_paths::UnsortedClippyUtilsPaths));
store.register_early_pass(|| Box::new(produce_ice::ProduceIce));
store.register_late_pass(|_| Box::new(collapsible_calls::CollapsibleCalls));
store.register_late_pass(|_| Box::new(collapsible_span_lint_calls::CollapsibleCalls));
store.register_late_pass(|_| Box::new(derive_deserialize_allowing_unknown::DeriveDeserializeAllowingUnknown));
store.register_late_pass(|_| Box::<symbols::Symbols>::default());
store.register_late_pass(|_| Box::<lint_without_lint_pass::LintWithoutLintPass>::default());

View file

@ -189,6 +189,7 @@ generate! {
get_unchecked,
get_unchecked_mut,
has_significant_drop,
help,
hidden_glob_reexports,
hygiene,
ilog,
@ -337,7 +338,10 @@ generate! {
sort,
sort_by,
sort_unstable_by,
span_help,
span_lint_and_then,
span_note,
span_suggestion,
split,
split_at,
split_at_checked,

View file

@ -50,6 +50,15 @@ impl EarlyLintPass for Pass {
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
db.help(help_msg).help(help_msg);
});
// Issue #15880
#[expect(clippy::disallowed_names)]
let foo = "foo";
span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, format!("try using {foo}"), format!("{foo}.use"), Applicability::MachineApplicable);
span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("try using {foo}"));
span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, format!("try using {foo}"));
span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("required because of {foo}"));
span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, format!("required because of {foo}"));
}
}

View file

@ -65,6 +65,35 @@ impl EarlyLintPass for Pass {
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
db.help(help_msg).help(help_msg);
});
// Issue #15880
#[expect(clippy::disallowed_names)]
let foo = "foo";
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.span_suggestion(
expr.span,
format!("try using {foo}"),
format!("{foo}.use"),
Applicability::MachineApplicable,
);
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.span_help(expr.span, format!("try using {foo}"));
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.help(format!("try using {foo}"));
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.span_note(expr.span, format!("required because of {foo}"));
});
span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
//~^ collapsible_span_lint_calls
db.note(format!("required because of {foo}"));
});
}
}

View file

@ -49,5 +49,53 @@ LL | | db.note(note_msg);
LL | | });
| |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, note_msg)`
error: aborting due to 5 previous errors
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:72:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.span_suggestion(
LL | | expr.span,
... |
LL | | );
LL | | });
| |__________^ help: collapse into: `span_lint_and_sugg(cx, TEST_LINT, expr.span, lint_msg, format!("try using {foo}"), format!("{foo}.use"), Applicability::MachineApplicable)`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:81:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.span_help(expr.span, format!("try using {foo}"));
LL | | });
| |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("try using {foo}"))`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:85:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.help(format!("try using {foo}"));
LL | | });
| |__________^ help: collapse into: `span_lint_and_help(cx, TEST_LINT, expr.span, lint_msg, None, format!("try using {foo}"))`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:89:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.span_note(expr.span, format!("required because of {foo}"));
LL | | });
| |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, Some(expr.span), format!("required because of {foo}"))`
error: this call is collapsible
--> tests/ui-internal/collapsible_span_lint_calls.rs:93:9
|
LL | / span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |db| {
LL | |
LL | | db.note(format!("required because of {foo}"));
LL | | });
| |__________^ help: collapse into: `span_lint_and_note(cx, TEST_LINT, expr.span, lint_msg, None, format!("required because of {foo}"))`
error: aborting due to 10 previous errors