Skip to content

Commit 2cbf358

Browse files
authored
Merge pull request #21208 from ChayimFriedman2/lint-attrs-hir
internal: Handle lint attributes via hir-expand attr handling
2 parents d4f45d7 + 753449b commit 2cbf358

File tree

3 files changed

+90
-99
lines changed

3 files changed

+90
-99
lines changed

crates/hir/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ pub use crate::{
117117
diagnostics::*,
118118
has_source::HasSource,
119119
semantics::{
120-
PathResolution, PathResolutionPerNs, Semantics, SemanticsImpl, SemanticsScope, TypeInfo,
121-
VisibleTraits,
120+
LintAttr, PathResolution, PathResolutionPerNs, Semantics, SemanticsImpl, SemanticsScope,
121+
TypeInfo, VisibleTraits,
122122
},
123123
};
124124

crates/hir/src/semantics.rs

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ use smallvec::{SmallVec, smallvec};
3939
use span::{FileId, SyntaxContext};
4040
use stdx::{TupleExt, always};
4141
use syntax::{
42-
AstNode, AstToken, Direction, SyntaxKind, SyntaxNode, SyntaxNodePtr, SyntaxToken, TextRange,
43-
TextSize,
42+
AstNode, AstToken, Direction, SmolStr, SmolStrBuilder, SyntaxElement, SyntaxKind, SyntaxNode,
43+
SyntaxNodePtr, SyntaxToken, T, TextRange, TextSize,
4444
algo::skip_trivia_token,
4545
ast::{self, HasAttrs as _, HasGenericParams},
4646
};
@@ -174,6 +174,15 @@ impl<'db, DB: ?Sized> ops::Deref for Semantics<'db, DB> {
174174
}
175175
}
176176

177+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
178+
pub enum LintAttr {
179+
Allow,
180+
Expect,
181+
Warn,
182+
Deny,
183+
Forbid,
184+
}
185+
177186
// Note: while this variant of `Semantics<'_, _>` might seem unused, as it does not
178187
// find actual use within the rust-analyzer project itself, it exists to enable the use
179188
// within e.g. tracked salsa functions in third-party crates that build upon `ra_ap_hir`.
@@ -254,6 +263,59 @@ impl<DB: HirDatabase + ?Sized> Semantics<'_, DB> {
254263
.filter_map(ast::NameLike::cast)
255264
}
256265

266+
pub fn lint_attrs(
267+
&self,
268+
krate: Crate,
269+
item: ast::AnyHasAttrs,
270+
) -> impl Iterator<Item = (LintAttr, SmolStr)> {
271+
let mut cfg_options = None;
272+
let cfg_options = || *cfg_options.get_or_insert_with(|| krate.id.cfg_options(self.db));
273+
let mut result = Vec::new();
274+
hir_expand::attrs::expand_cfg_attr::<Infallible>(
275+
ast::attrs_including_inner(&item),
276+
cfg_options,
277+
|attr, _, _, _| {
278+
let hir_expand::attrs::Meta::TokenTree { path, tt } = attr else {
279+
return ControlFlow::Continue(());
280+
};
281+
if path.segments.len() != 1 {
282+
return ControlFlow::Continue(());
283+
}
284+
let lint_attr = match path.segments[0].text() {
285+
"allow" => LintAttr::Allow,
286+
"expect" => LintAttr::Expect,
287+
"warn" => LintAttr::Warn,
288+
"deny" => LintAttr::Deny,
289+
"forbid" => LintAttr::Forbid,
290+
_ => return ControlFlow::Continue(()),
291+
};
292+
let mut lint = SmolStrBuilder::new();
293+
for token in
294+
tt.syntax().children_with_tokens().filter_map(SyntaxElement::into_token)
295+
{
296+
match token.kind() {
297+
T![:] | T![::] => lint.push_str(token.text()),
298+
kind if kind.is_any_identifier() => lint.push_str(token.text()),
299+
T![,] => {
300+
let lint = mem::replace(&mut lint, SmolStrBuilder::new()).finish();
301+
if !lint.is_empty() {
302+
result.push((lint_attr, lint));
303+
}
304+
}
305+
_ => {}
306+
}
307+
}
308+
let lint = lint.finish();
309+
if !lint.is_empty() {
310+
result.push((lint_attr, lint));
311+
}
312+
313+
ControlFlow::Continue(())
314+
},
315+
);
316+
result.into_iter()
317+
}
318+
257319
pub fn resolve_range_pat(&self, range_pat: &ast::RangePat) -> Option<Struct> {
258320
self.imp.resolve_range_pat(range_pat).map(Struct::from)
259321
}

crates/ide-diagnostics/src/lib.rs

Lines changed: 24 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,8 @@ mod handlers {
8383
#[cfg(test)]
8484
mod tests;
8585

86-
use std::{iter, sync::LazyLock};
86+
use std::sync::LazyLock;
8787

88-
use either::Either;
8988
use hir::{
9089
Crate, DisplayTarget, InFile, Semantics, db::ExpandDatabase, diagnostics::AnyDiagnostic,
9190
};
@@ -97,11 +96,9 @@ use ide_db::{
9796
imports::insert_use::InsertUseConfig,
9897
label::Label,
9998
source_change::SourceChange,
100-
syntax_helpers::node_ext::parse_tt_as_comma_sep_paths,
10199
};
102-
use itertools::Itertools;
103100
use syntax::{
104-
AstPtr, Edition, NodeOrToken, SmolStr, SyntaxKind, SyntaxNode, SyntaxNodePtr, T, TextRange,
101+
AstPtr, Edition, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange,
105102
ast::{self, AstNode},
106103
};
107104

@@ -483,7 +480,7 @@ pub fn semantic_diagnostics(
483480

484481
// The edition isn't accurate (each diagnostics may have its own edition due to macros),
485482
// but it's okay as it's only being used for error recovery.
486-
handle_lints(&ctx.sema, &mut lints, editioned_file_id.edition(db));
483+
handle_lints(&ctx.sema, krate, &mut lints, editioned_file_id.edition(db));
487484

488485
res.retain(|d| d.severity != Severity::Allow);
489486

@@ -591,6 +588,7 @@ fn build_lints_map(
591588

592589
fn handle_lints(
593590
sema: &Semantics<'_, RootDatabase>,
591+
krate: hir::Crate,
594592
diagnostics: &mut [(InFile<SyntaxNode>, &mut Diagnostic)],
595593
edition: Edition,
596594
) {
@@ -606,10 +604,10 @@ fn handle_lints(
606604
}
607605

608606
let mut diag_severity =
609-
lint_severity_at(sema, node, &lint_groups(&diag.code, edition), edition);
607+
lint_severity_at(sema, krate, node, &lint_groups(&diag.code, edition));
610608

611609
if let outline_diag_severity @ Some(_) =
612-
find_outline_mod_lint_severity(sema, node, diag, edition)
610+
find_outline_mod_lint_severity(sema, krate, node, diag, edition)
613611
{
614612
diag_severity = outline_diag_severity;
615613
}
@@ -632,6 +630,7 @@ fn default_lint_severity(lint: &Lint, edition: Edition) -> Severity {
632630

633631
fn find_outline_mod_lint_severity(
634632
sema: &Semantics<'_, RootDatabase>,
633+
krate: hir::Crate,
635634
node: &InFile<SyntaxNode>,
636635
diag: &Diagnostic,
637636
edition: Edition,
@@ -648,8 +647,8 @@ fn find_outline_mod_lint_severity(
648647
let lint_groups = lint_groups(&diag.code, edition);
649648
lint_attrs(
650649
sema,
651-
&ast::AnyHasAttrs::cast(module_source_file.value).expect("SourceFile always has attrs"),
652-
edition,
650+
krate,
651+
ast::AnyHasAttrs::cast(module_source_file.value).expect("SourceFile always has attrs"),
653652
)
654653
.for_each(|(lint, severity)| {
655654
if lint_groups.contains(&lint) {
@@ -661,106 +660,36 @@ fn find_outline_mod_lint_severity(
661660

662661
fn lint_severity_at(
663662
sema: &Semantics<'_, RootDatabase>,
663+
krate: hir::Crate,
664664
node: &InFile<SyntaxNode>,
665665
lint_groups: &LintGroups,
666-
edition: Edition,
667666
) -> Option<Severity> {
668667
node.value
669668
.ancestors()
670669
.filter_map(ast::AnyHasAttrs::cast)
671670
.find_map(|ancestor| {
672-
lint_attrs(sema, &ancestor, edition)
671+
lint_attrs(sema, krate, ancestor)
673672
.find_map(|(lint, severity)| lint_groups.contains(&lint).then_some(severity))
674673
})
675674
.or_else(|| {
676-
lint_severity_at(sema, &sema.find_parent_file(node.file_id)?, lint_groups, edition)
675+
lint_severity_at(sema, krate, &sema.find_parent_file(node.file_id)?, lint_groups)
677676
})
678677
}
679678

680679
// FIXME: Switch this to analysis' `expand_cfg_attr`.
681-
fn lint_attrs<'a>(
682-
sema: &'a Semantics<'a, RootDatabase>,
683-
ancestor: &'a ast::AnyHasAttrs,
684-
edition: Edition,
685-
) -> impl Iterator<Item = (SmolStr, Severity)> + 'a {
686-
ast::attrs_including_inner(ancestor)
687-
.filter_map(|attr| {
688-
attr.as_simple_call().and_then(|(name, value)| match &*name {
689-
"allow" | "expect" => Some(Either::Left(iter::once((Severity::Allow, value)))),
690-
"warn" => Some(Either::Left(iter::once((Severity::Warning, value)))),
691-
"forbid" | "deny" => Some(Either::Left(iter::once((Severity::Error, value)))),
692-
"cfg_attr" => {
693-
let mut lint_attrs = Vec::new();
694-
cfg_attr_lint_attrs(sema, &value, &mut lint_attrs);
695-
Some(Either::Right(lint_attrs.into_iter()))
696-
}
697-
_ => None,
698-
})
699-
})
700-
.flatten()
701-
.flat_map(move |(severity, lints)| {
702-
parse_tt_as_comma_sep_paths(lints, edition).into_iter().flat_map(move |lints| {
703-
// Rejoin the idents with `::`, so we have no spaces in between.
704-
lints.into_iter().map(move |lint| {
705-
(
706-
lint.segments().filter_map(|segment| segment.name_ref()).join("::").into(),
707-
severity,
708-
)
709-
})
710-
})
711-
})
712-
}
713-
714-
fn cfg_attr_lint_attrs(
680+
fn lint_attrs(
715681
sema: &Semantics<'_, RootDatabase>,
716-
value: &ast::TokenTree,
717-
lint_attrs: &mut Vec<(Severity, ast::TokenTree)>,
718-
) {
719-
let prev_len = lint_attrs.len();
720-
721-
let mut iter = value.token_trees_and_tokens().filter(|it| match it {
722-
NodeOrToken::Node(_) => true,
723-
NodeOrToken::Token(it) => !it.kind().is_trivia(),
724-
});
725-
726-
// Skip the condition.
727-
for value in &mut iter {
728-
if value.as_token().is_some_and(|it| it.kind() == T![,]) {
729-
break;
730-
}
731-
}
732-
733-
while let Some(value) = iter.next() {
734-
if let Some(token) = value.as_token()
735-
&& token.kind() == SyntaxKind::IDENT
736-
{
737-
let severity = match token.text() {
738-
"allow" | "expect" => Some(Severity::Allow),
739-
"warn" => Some(Severity::Warning),
740-
"forbid" | "deny" => Some(Severity::Error),
741-
"cfg_attr" => {
742-
if let Some(NodeOrToken::Node(value)) = iter.next() {
743-
cfg_attr_lint_attrs(sema, &value, lint_attrs);
744-
}
745-
None
746-
}
747-
_ => None,
748-
};
749-
if let Some(severity) = severity {
750-
let lints = iter.next();
751-
if let Some(NodeOrToken::Node(lints)) = lints {
752-
lint_attrs.push((severity, lints));
753-
}
754-
}
755-
}
756-
}
757-
758-
if prev_len != lint_attrs.len()
759-
&& let Some(false) | None = sema.check_cfg_attr(value)
760-
{
761-
// Discard the attributes when the condition is false.
762-
lint_attrs.truncate(prev_len);
763-
}
682+
krate: hir::Crate,
683+
ancestor: ast::AnyHasAttrs,
684+
) -> impl Iterator<Item = (SmolStr, Severity)> {
685+
sema.lint_attrs(krate, ancestor).map(|(lint_attr, lint)| {
686+
let severity = match lint_attr {
687+
hir::LintAttr::Allow | hir::LintAttr::Expect => Severity::Allow,
688+
hir::LintAttr::Warn => Severity::Warning,
689+
hir::LintAttr::Deny | hir::LintAttr::Forbid => Severity::Error,
690+
};
691+
(lint, severity)
692+
})
764693
}
765694

766695
#[derive(Debug)]

0 commit comments

Comments
 (0)