rust-analyzer/crates/syntax/src/ast/traits.rs
Chayim Refael Friedman 4eb19df5e9 Use more correct handling of lint attributes
The previous analysis was top-down, and worked on a single file (expanding macros). The new analysis is bottom-up, starting from the diagnostics and climbing up the syntax and module tree.

While this is more efficient (and in fact, efficiency was the motivating reason to work on this), unfortunately the code was already fast enough. But luckily, it also fixes a correctness problem: outline parent modules' attributes were not respected for the previous analysis. Case lints specifically did their own analysis to accommodate that, but it was limited to only them. The new analysis works on all kinds of lints, present and future.

It was basically impossible to fix the old analysis without rewriting it because navigating the module hierarchy must come bottom-up, and if we already have a bottom-up analysis (including syntax analysis because modules can be nested in other syntax elements, including macros), it makes sense to use only this kind of analysis.

Few other bugs (not fundamental ti the previous analysis) are also fixed, e.g. overwriting of lint levels (i.e. `#[allow(lint)] mod foo { #[warn(lint)] mod bar; }`.
2024-09-12 15:24:38 +03:00

167 lines
4.7 KiB
Rust

//! Various traits that are implemented by ast nodes.
//!
//! The implementations are usually trivial, and live in generated.rs
use either::Either;
use crate::{
ast::{self, support, AstChildren, AstNode, AstToken},
syntax_node::SyntaxElementChildren,
SyntaxElement, SyntaxToken, T,
};
pub trait HasName: AstNode {
fn name(&self) -> Option<ast::Name> {
support::child(self.syntax())
}
}
pub trait HasVisibility: AstNode {
fn visibility(&self) -> Option<ast::Visibility> {
support::child(self.syntax())
}
}
pub trait HasLoopBody: AstNode {
fn loop_body(&self) -> Option<ast::BlockExpr> {
support::child(self.syntax())
}
fn label(&self) -> Option<ast::Label> {
support::child(self.syntax())
}
}
pub trait HasArgList: AstNode {
fn arg_list(&self) -> Option<ast::ArgList> {
support::child(self.syntax())
}
}
pub trait HasModuleItem: AstNode {
fn items(&self) -> AstChildren<ast::Item> {
support::children(self.syntax())
}
}
pub trait HasGenericParams: AstNode {
fn generic_param_list(&self) -> Option<ast::GenericParamList> {
support::child(self.syntax())
}
fn where_clause(&self) -> Option<ast::WhereClause> {
support::child(self.syntax())
}
}
pub trait HasGenericArgs: AstNode {
fn generic_arg_list(&self) -> Option<ast::GenericArgList> {
support::child(self.syntax())
}
}
pub trait HasTypeBounds: AstNode {
fn type_bound_list(&self) -> Option<ast::TypeBoundList> {
support::child(self.syntax())
}
fn colon_token(&self) -> Option<SyntaxToken> {
support::token(self.syntax(), T![:])
}
}
pub trait HasAttrs: AstNode {
fn attrs(&self) -> AstChildren<ast::Attr> {
support::children(self.syntax())
}
fn has_atom_attr(&self, atom: &str) -> bool {
self.attrs().filter_map(|x| x.as_simple_atom()).any(|x| x == atom)
}
/// Returns all attributes of this node, including inner attributes that may not be directly under this node
/// but under a child.
fn attrs_including_inner(self) -> impl Iterator<Item = ast::Attr>
where
Self: Sized,
{
let inner_attrs_node = if let Some(it) =
support::child::<ast::BlockExpr>(self.syntax()).and_then(|it| it.stmt_list())
{
Some(it.syntax)
} else if let Some(it) = support::child::<ast::MatchArmList>(self.syntax()) {
Some(it.syntax)
} else if let Some(it) = support::child::<ast::AssocItemList>(self.syntax()) {
Some(it.syntax)
} else if let Some(it) = support::child::<ast::ItemList>(self.syntax()) {
Some(it.syntax)
} else if let Some(it) = support::child::<ast::ExternItemList>(self.syntax()) {
Some(it.syntax)
} else if let Some(it) = support::child::<ast::MacroItems>(self.syntax()) {
Some(it.syntax)
} else {
None
};
self.attrs().chain(inner_attrs_node.into_iter().flat_map(|it| support::children(&it)))
}
}
pub trait HasDocComments: HasAttrs {
fn doc_comments(&self) -> DocCommentIter {
DocCommentIter { iter: self.syntax().children_with_tokens() }
}
}
impl DocCommentIter {
pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> DocCommentIter {
DocCommentIter { iter: syntax_node.children_with_tokens() }
}
#[cfg(test)]
pub fn doc_comment_text(self) -> Option<String> {
let docs = itertools::Itertools::join(
&mut self.filter_map(|comment| comment.doc_comment().map(ToOwned::to_owned)),
"\n",
);
if docs.is_empty() {
None
} else {
Some(docs)
}
}
}
pub struct DocCommentIter {
iter: SyntaxElementChildren,
}
impl Iterator for DocCommentIter {
type Item = ast::Comment;
fn next(&mut self) -> Option<ast::Comment> {
self.iter.by_ref().find_map(|el| {
el.into_token().and_then(ast::Comment::cast).filter(ast::Comment::is_doc)
})
}
}
pub struct AttrDocCommentIter {
iter: SyntaxElementChildren,
}
impl AttrDocCommentIter {
pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> AttrDocCommentIter {
AttrDocCommentIter { iter: syntax_node.children_with_tokens() }
}
}
impl Iterator for AttrDocCommentIter {
type Item = Either<ast::Attr, ast::Comment>;
fn next(&mut self) -> Option<Self::Item> {
self.iter.by_ref().find_map(|el| match el {
SyntaxElement::Node(node) => ast::Attr::cast(node).map(Either::Left),
SyntaxElement::Token(tok) => {
ast::Comment::cast(tok).filter(ast::Comment::is_doc).map(Either::Right)
}
})
}
}
impl<A: HasName, B: HasName> HasName for Either<A, B> {}