fix: respect rustc's lint attribute application order

Reverse the order of returned lint attributes for a `SyntaxNode` to
match rustc's behavior.

When multiple lint attributes are present, rustc overrides earlier ones
with the last defined attribute. The previous iteration order was
incorrect, causing earlier attributes to override the later ones.
This commit is contained in:
Jesung Yang 2025-12-14 03:25:08 +00:00
parent b3e6363a47
commit 8bd369063e
4 changed files with 60 additions and 10 deletions

View file

@ -267,7 +267,7 @@ impl<DB: HirDatabase + ?Sized> Semantics<'_, DB> {
&self,
krate: Crate,
item: ast::AnyHasAttrs,
) -> impl Iterator<Item = (LintAttr, SmolStr)> {
) -> impl DoubleEndedIterator<Item = (LintAttr, SmolStr)> {
let mut cfg_options = None;
let cfg_options = || *cfg_options.get_or_insert_with(|| krate.id.cfg_options(self.db));
let mut result = Vec::new();

View file

@ -1000,7 +1000,8 @@ mod OtherBadCase;
// ^^^^^^^^^^^^ 💡 error: Module `OtherBadCase` should have snake_case name, e.g. `other_bad_case`
//- /BAD_CASE/OtherBadCase.rs
#![deny(non_snake_case)]
#![allow(non_snake_case)]
#![deny(non_snake_case)] // The lint level has been overridden.
fn FOO() {}
// ^^^ 💡 error: Function `FOO` should have snake_case name, e.g. `foo`

View file

@ -182,6 +182,61 @@ fn main2() {
);
}
#[test]
fn apply_last_lint_attribute_when_multiple_are_present() {
check_diagnostics(
r#"
#![allow(unused_variables)]
#![warn(unused_variables)]
#![deny(unused_variables)]
fn main() {
let x = 2;
//^ 💡 error: unused variable
#[deny(unused_variables)]
#[warn(unused_variables)]
#[allow(unused_variables)]
let y = 0;
}
"#,
);
}
#[test]
fn prefer_closest_ancestor_lint_attribute() {
check_diagnostics(
r#"
#![allow(unused_variables)]
fn main() {
#![warn(unused_variables)]
#[deny(unused_variables)]
let x = 2;
//^ 💡 error: unused variable
}
#[warn(unused_variables)]
fn main2() {
#[deny(unused_variables)]
let x = 2;
//^ 💡 error: unused variable
}
#[warn(unused_variables)]
fn main3() {
let x = 2;
//^ 💡 warn: unused variable
}
fn main4() {
let x = 2;
}
"#,
);
}
#[test]
fn fix_unused_variable() {
check_fix(

View file

@ -643,19 +643,13 @@ fn find_outline_mod_lint_severity(
let mod_def = sema.to_module_def(&mod_node)?;
let module_source_file = sema.module_definition_node(mod_def);
let mut result = None;
let lint_groups = lint_groups(&diag.code, edition);
lint_attrs(
sema,
krate,
ast::AnyHasAttrs::cast(module_source_file.value).expect("SourceFile always has attrs"),
)
.for_each(|(lint, severity)| {
if lint_groups.contains(&lint) {
result = Some(severity);
}
});
result
.find_map(|(lint, severity)| lint_groups.contains(&lint).then_some(severity))
}
fn lint_severity_at(
@ -682,7 +676,7 @@ fn lint_attrs(
krate: hir::Crate,
ancestor: ast::AnyHasAttrs,
) -> impl Iterator<Item = (SmolStr, Severity)> {
sema.lint_attrs(krate, ancestor).map(|(lint_attr, lint)| {
sema.lint_attrs(krate, ancestor).rev().map(|(lint_attr, lint)| {
let severity = match lint_attr {
hir::LintAttr::Allow | hir::LintAttr::Expect => Severity::Allow,
hir::LintAttr::Warn => Severity::Warning,