syntax/ast/
traits.rs

1//! Various traits that are implemented by ast nodes.
2//!
3//! The implementations are usually trivial, and live in generated.rs
4use either::Either;
5
6use crate::{
7    SyntaxElement, SyntaxNode, SyntaxToken, T,
8    ast::{self, AstChildren, AstNode, AstToken, support},
9    match_ast,
10    syntax_node::SyntaxElementChildren,
11};
12
13pub trait HasName: AstNode {
14    fn name(&self) -> Option<ast::Name> {
15        support::child(self.syntax())
16    }
17}
18
19pub trait HasVisibility: AstNode {
20    fn visibility(&self) -> Option<ast::Visibility> {
21        support::child(self.syntax())
22    }
23}
24
25pub trait HasLoopBody: AstNode {
26    fn loop_body(&self) -> Option<ast::BlockExpr> {
27        support::child(self.syntax())
28    }
29
30    fn label(&self) -> Option<ast::Label> {
31        support::child(self.syntax())
32    }
33}
34
35pub trait HasArgList: AstNode {
36    fn arg_list(&self) -> Option<ast::ArgList> {
37        support::child(self.syntax())
38    }
39}
40
41pub trait HasModuleItem: AstNode {
42    fn items(&self) -> AstChildren<ast::Item> {
43        support::children(self.syntax())
44    }
45}
46
47pub trait HasGenericParams: AstNode {
48    fn generic_param_list(&self) -> Option<ast::GenericParamList> {
49        support::child(self.syntax())
50    }
51
52    fn where_clause(&self) -> Option<ast::WhereClause> {
53        support::child(self.syntax())
54    }
55}
56pub trait HasGenericArgs: AstNode {
57    fn generic_arg_list(&self) -> Option<ast::GenericArgList> {
58        support::child(self.syntax())
59    }
60}
61
62pub trait HasTypeBounds: AstNode {
63    fn type_bound_list(&self) -> Option<ast::TypeBoundList> {
64        support::child(self.syntax())
65    }
66
67    fn colon_token(&self) -> Option<SyntaxToken> {
68        support::token(self.syntax(), T![:])
69    }
70}
71
72pub trait HasAttrs: AstNode {
73    fn attrs(&self) -> AstChildren<ast::Attr> {
74        support::children(self.syntax())
75    }
76    fn has_atom_attr(&self, atom: &str) -> bool {
77        self.attrs().filter_map(|x| x.as_simple_atom()).any(|x| x == atom)
78    }
79
80    /// This may return the same node as called with (with `SourceFile`). The caller has the responsibility
81    /// to avoid duplicate attributes.
82    fn inner_attributes_node(&self) -> Option<SyntaxNode> {
83        let syntax = self.syntax();
84        Some(match_ast! {
85            match syntax {
86                // A `SourceFile` contains the inner attributes of itself.
87                ast::SourceFile(_) => syntax.clone(),
88                ast::ExternBlock(it) => it.extern_item_list()?.syntax().clone(),
89                ast::Fn(it) => it.body()?.stmt_list()?.syntax().clone(),
90                ast::MatchExpr(it) => it.match_arm_list()?.syntax().clone(),
91                ast::Impl(it) => it.assoc_item_list()?.syntax().clone(),
92                ast::Trait(it) => it.assoc_item_list()?.syntax().clone(),
93                ast::Module(it) => it.item_list()?.syntax().clone(),
94                ast::BlockExpr(it) => {
95                    if !it.may_carry_attributes() {
96                        return None;
97                    }
98                    syntax.clone()
99                },
100                _ => return None,
101            }
102        })
103    }
104}
105
106/// Returns all attributes of this node, including inner attributes that may not be directly under this node
107/// but under a child.
108pub fn attrs_including_inner(owner: &dyn HasAttrs) -> impl Iterator<Item = ast::Attr> + Clone {
109    owner.attrs().filter(|attr| attr.kind().is_outer()).chain(
110        owner
111            .inner_attributes_node()
112            .into_iter()
113            .flat_map(|node| support::children::<ast::Attr>(&node))
114            .filter(|attr| attr.kind().is_inner()),
115    )
116}
117
118pub trait HasDocComments: HasAttrs {
119    fn doc_comments(&self) -> DocCommentIter {
120        DocCommentIter { iter: self.syntax().children_with_tokens() }
121    }
122}
123
124impl DocCommentIter {
125    pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> DocCommentIter {
126        DocCommentIter { iter: syntax_node.children_with_tokens() }
127    }
128
129    #[cfg(test)]
130    pub fn doc_comment_text(self) -> Option<String> {
131        let docs = itertools::Itertools::join(
132            &mut self.filter_map(|comment| comment.doc_comment().map(|it| it.0.to_owned())),
133            "\n",
134        );
135        if docs.is_empty() { None } else { Some(docs) }
136    }
137}
138
139pub struct DocCommentIter {
140    iter: SyntaxElementChildren,
141}
142
143impl Iterator for DocCommentIter {
144    type Item = ast::Comment;
145    fn next(&mut self) -> Option<ast::Comment> {
146        self.iter.by_ref().find_map(|el| {
147            el.into_token().and_then(ast::Comment::cast).filter(ast::Comment::is_doc)
148        })
149    }
150}
151
152pub struct AttrDocCommentIter {
153    iter: SyntaxElementChildren,
154}
155
156impl AttrDocCommentIter {
157    pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> AttrDocCommentIter {
158        AttrDocCommentIter { iter: syntax_node.children_with_tokens() }
159    }
160}
161
162impl Iterator for AttrDocCommentIter {
163    type Item = Either<ast::Attr, ast::Comment>;
164    fn next(&mut self) -> Option<Self::Item> {
165        self.iter.find_map(|el| match el {
166            SyntaxElement::Node(node) => ast::Attr::cast(node).map(Either::Left),
167            SyntaxElement::Token(tok) => {
168                ast::Comment::cast(tok).filter(ast::Comment::is_doc).map(Either::Right)
169            }
170        })
171    }
172}
173
174impl<A: HasName, B: HasName> HasName for Either<A, B> {}