diff options
Diffstat (limited to 'crates/ra_syntax/src/ast.rs')
-rw-r--r-- | crates/ra_syntax/src/ast.rs | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs new file mode 100644 index 000000000..91c67119f --- /dev/null +++ b/crates/ra_syntax/src/ast.rs | |||
@@ -0,0 +1,365 @@ | |||
1 | mod generated; | ||
2 | |||
3 | use std::marker::PhantomData; | ||
4 | use std::string::String as RustString; | ||
5 | |||
6 | use itertools::Itertools; | ||
7 | |||
8 | pub use self::generated::*; | ||
9 | use crate::{ | ||
10 | yellow::{RefRoot, SyntaxNodeChildren}, | ||
11 | SmolStr, | ||
12 | SyntaxKind::*, | ||
13 | SyntaxNodeRef, | ||
14 | }; | ||
15 | |||
16 | /// The main trait to go from untyped `SyntaxNode` to a typed ast. The | ||
17 | /// conversion itself has zero runtime cost: ast and syntax nodes have exactly | ||
18 | /// the same representation: a pointer to the tree root and a pointer to the | ||
19 | /// node itself. | ||
20 | pub trait AstNode<'a>: Clone + Copy + 'a { | ||
21 | fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> | ||
22 | where | ||
23 | Self: Sized; | ||
24 | fn syntax(self) -> SyntaxNodeRef<'a>; | ||
25 | } | ||
26 | |||
27 | pub trait NameOwner<'a>: AstNode<'a> { | ||
28 | fn name(self) -> Option<Name<'a>> { | ||
29 | child_opt(self) | ||
30 | } | ||
31 | } | ||
32 | |||
33 | pub trait LoopBodyOwner<'a>: AstNode<'a> { | ||
34 | fn loop_body(self) -> Option<Block<'a>> { | ||
35 | child_opt(self) | ||
36 | } | ||
37 | } | ||
38 | |||
39 | pub trait ArgListOwner<'a>: AstNode<'a> { | ||
40 | fn arg_list(self) -> Option<ArgList<'a>> { | ||
41 | child_opt(self) | ||
42 | } | ||
43 | } | ||
44 | |||
45 | pub trait FnDefOwner<'a>: AstNode<'a> { | ||
46 | fn functions(self) -> AstChildren<'a, FnDef<'a>> { | ||
47 | children(self) | ||
48 | } | ||
49 | } | ||
50 | |||
51 | pub trait ModuleItemOwner<'a>: AstNode<'a> { | ||
52 | fn items(self) -> AstChildren<'a, ModuleItem<'a>> { | ||
53 | children(self) | ||
54 | } | ||
55 | } | ||
56 | |||
57 | pub trait TypeParamsOwner<'a>: AstNode<'a> { | ||
58 | fn type_param_list(self) -> Option<TypeParamList<'a>> { | ||
59 | child_opt(self) | ||
60 | } | ||
61 | |||
62 | fn where_clause(self) -> Option<WhereClause<'a>> { | ||
63 | child_opt(self) | ||
64 | } | ||
65 | } | ||
66 | |||
67 | pub trait AttrsOwner<'a>: AstNode<'a> { | ||
68 | fn attrs(self) -> AstChildren<'a, Attr<'a>> { | ||
69 | children(self) | ||
70 | } | ||
71 | } | ||
72 | |||
73 | pub trait DocCommentsOwner<'a>: AstNode<'a> { | ||
74 | fn doc_comments(self) -> AstChildren<'a, Comment<'a>> { | ||
75 | children(self) | ||
76 | } | ||
77 | |||
78 | /// Returns the textual content of a doc comment block as a single string. | ||
79 | /// That is, strips leading `///` and joins lines | ||
80 | fn doc_comment_text(self) -> RustString { | ||
81 | self.doc_comments() | ||
82 | .map(|comment| { | ||
83 | let prefix = comment.prefix(); | ||
84 | let trimmed = comment | ||
85 | .text() | ||
86 | .as_str() | ||
87 | .trim() | ||
88 | .trim_start_matches(prefix) | ||
89 | .trim_start(); | ||
90 | trimmed.to_owned() | ||
91 | }) | ||
92 | .join("\n") | ||
93 | } | ||
94 | } | ||
95 | |||
96 | impl<'a> FnDef<'a> { | ||
97 | pub fn has_atom_attr(&self, atom: &str) -> bool { | ||
98 | self.attrs().filter_map(|x| x.as_atom()).any(|x| x == atom) | ||
99 | } | ||
100 | } | ||
101 | |||
102 | impl<'a> Attr<'a> { | ||
103 | pub fn as_atom(&self) -> Option<SmolStr> { | ||
104 | let tt = self.value()?; | ||
105 | let (_bra, attr, _ket) = tt.syntax().children().collect_tuple()?; | ||
106 | if attr.kind() == IDENT { | ||
107 | Some(attr.leaf_text().unwrap().clone()) | ||
108 | } else { | ||
109 | None | ||
110 | } | ||
111 | } | ||
112 | |||
113 | pub fn as_call(&self) -> Option<(SmolStr, TokenTree<'a>)> { | ||
114 | let tt = self.value()?; | ||
115 | let (_bra, attr, args, _ket) = tt.syntax().children().collect_tuple()?; | ||
116 | let args = TokenTree::cast(args)?; | ||
117 | if attr.kind() == IDENT { | ||
118 | Some((attr.leaf_text().unwrap().clone(), args)) | ||
119 | } else { | ||
120 | None | ||
121 | } | ||
122 | } | ||
123 | } | ||
124 | |||
125 | impl<'a> Lifetime<'a> { | ||
126 | pub fn text(&self) -> SmolStr { | ||
127 | self.syntax().leaf_text().unwrap().clone() | ||
128 | } | ||
129 | } | ||
130 | |||
131 | impl<'a> Char<'a> { | ||
132 | pub fn text(&self) -> &SmolStr { | ||
133 | &self.syntax().leaf_text().unwrap() | ||
134 | } | ||
135 | } | ||
136 | |||
137 | impl<'a> Byte<'a> { | ||
138 | pub fn text(&self) -> &SmolStr { | ||
139 | &self.syntax().leaf_text().unwrap() | ||
140 | } | ||
141 | } | ||
142 | |||
143 | impl<'a> ByteString<'a> { | ||
144 | pub fn text(&self) -> &SmolStr { | ||
145 | &self.syntax().leaf_text().unwrap() | ||
146 | } | ||
147 | } | ||
148 | |||
149 | impl<'a> String<'a> { | ||
150 | pub fn text(&self) -> &SmolStr { | ||
151 | &self.syntax().leaf_text().unwrap() | ||
152 | } | ||
153 | } | ||
154 | |||
155 | impl<'a> Comment<'a> { | ||
156 | pub fn text(&self) -> &SmolStr { | ||
157 | self.syntax().leaf_text().unwrap() | ||
158 | } | ||
159 | |||
160 | pub fn flavor(&self) -> CommentFlavor { | ||
161 | let text = self.text(); | ||
162 | if text.starts_with("///") { | ||
163 | CommentFlavor::Doc | ||
164 | } else if text.starts_with("//!") { | ||
165 | CommentFlavor::ModuleDoc | ||
166 | } else if text.starts_with("//") { | ||
167 | CommentFlavor::Line | ||
168 | } else { | ||
169 | CommentFlavor::Multiline | ||
170 | } | ||
171 | } | ||
172 | |||
173 | pub fn prefix(&self) -> &'static str { | ||
174 | self.flavor().prefix() | ||
175 | } | ||
176 | |||
177 | pub fn count_newlines_lazy(&self) -> impl Iterator<Item = &()> { | ||
178 | self.text().chars().filter(|&c| c == '\n').map(|_| &()) | ||
179 | } | ||
180 | |||
181 | pub fn has_newlines(&self) -> bool { | ||
182 | self.count_newlines_lazy().count() > 0 | ||
183 | } | ||
184 | } | ||
185 | |||
186 | #[derive(Debug, PartialEq, Eq)] | ||
187 | pub enum CommentFlavor { | ||
188 | Line, | ||
189 | Doc, | ||
190 | ModuleDoc, | ||
191 | Multiline, | ||
192 | } | ||
193 | |||
194 | impl CommentFlavor { | ||
195 | pub fn prefix(&self) -> &'static str { | ||
196 | use self::CommentFlavor::*; | ||
197 | match *self { | ||
198 | Line => "//", | ||
199 | Doc => "///", | ||
200 | ModuleDoc => "//!", | ||
201 | Multiline => "/*", | ||
202 | } | ||
203 | } | ||
204 | } | ||
205 | |||
206 | impl<'a> Whitespace<'a> { | ||
207 | pub fn text(&self) -> &SmolStr { | ||
208 | &self.syntax().leaf_text().unwrap() | ||
209 | } | ||
210 | |||
211 | pub fn count_newlines_lazy(&self) -> impl Iterator<Item = &()> { | ||
212 | self.text().chars().filter(|&c| c == '\n').map(|_| &()) | ||
213 | } | ||
214 | |||
215 | pub fn has_newlines(&self) -> bool { | ||
216 | self.count_newlines_lazy().count() > 0 | ||
217 | } | ||
218 | } | ||
219 | |||
220 | impl<'a> Name<'a> { | ||
221 | pub fn text(&self) -> SmolStr { | ||
222 | let ident = self.syntax().first_child().unwrap(); | ||
223 | ident.leaf_text().unwrap().clone() | ||
224 | } | ||
225 | } | ||
226 | |||
227 | impl<'a> NameRef<'a> { | ||
228 | pub fn text(&self) -> SmolStr { | ||
229 | let ident = self.syntax().first_child().unwrap(); | ||
230 | ident.leaf_text().unwrap().clone() | ||
231 | } | ||
232 | } | ||
233 | |||
234 | impl<'a> ImplItem<'a> { | ||
235 | pub fn target_type(self) -> Option<TypeRef<'a>> { | ||
236 | match self.target() { | ||
237 | (Some(t), None) | (_, Some(t)) => Some(t), | ||
238 | _ => None, | ||
239 | } | ||
240 | } | ||
241 | |||
242 | pub fn target_trait(self) -> Option<TypeRef<'a>> { | ||
243 | match self.target() { | ||
244 | (Some(t), Some(_)) => Some(t), | ||
245 | _ => None, | ||
246 | } | ||
247 | } | ||
248 | |||
249 | fn target(self) -> (Option<TypeRef<'a>>, Option<TypeRef<'a>>) { | ||
250 | let mut types = children(self); | ||
251 | let first = types.next(); | ||
252 | let second = types.next(); | ||
253 | (first, second) | ||
254 | } | ||
255 | } | ||
256 | |||
257 | impl<'a> Module<'a> { | ||
258 | pub fn has_semi(self) -> bool { | ||
259 | match self.syntax().last_child() { | ||
260 | None => false, | ||
261 | Some(node) => node.kind() == SEMI, | ||
262 | } | ||
263 | } | ||
264 | } | ||
265 | |||
266 | impl<'a> LetStmt<'a> { | ||
267 | pub fn has_semi(self) -> bool { | ||
268 | match self.syntax().last_child() { | ||
269 | None => false, | ||
270 | Some(node) => node.kind() == SEMI, | ||
271 | } | ||
272 | } | ||
273 | } | ||
274 | |||
275 | impl<'a> IfExpr<'a> { | ||
276 | pub fn then_branch(self) -> Option<Block<'a>> { | ||
277 | self.blocks().nth(0) | ||
278 | } | ||
279 | pub fn else_branch(self) -> Option<Block<'a>> { | ||
280 | self.blocks().nth(1) | ||
281 | } | ||
282 | fn blocks(self) -> AstChildren<'a, Block<'a>> { | ||
283 | children(self) | ||
284 | } | ||
285 | } | ||
286 | |||
287 | #[derive(Debug, Clone, Copy)] | ||
288 | pub enum PathSegmentKind<'a> { | ||
289 | Name(NameRef<'a>), | ||
290 | SelfKw, | ||
291 | SuperKw, | ||
292 | CrateKw, | ||
293 | } | ||
294 | |||
295 | impl<'a> PathSegment<'a> { | ||
296 | pub fn parent_path(self) -> Path<'a> { | ||
297 | self.syntax() | ||
298 | .parent() | ||
299 | .and_then(Path::cast) | ||
300 | .expect("segments are always nested in paths") | ||
301 | } | ||
302 | |||
303 | pub fn kind(self) -> Option<PathSegmentKind<'a>> { | ||
304 | let res = if let Some(name_ref) = self.name_ref() { | ||
305 | PathSegmentKind::Name(name_ref) | ||
306 | } else { | ||
307 | match self.syntax().first_child()?.kind() { | ||
308 | SELF_KW => PathSegmentKind::SelfKw, | ||
309 | SUPER_KW => PathSegmentKind::SuperKw, | ||
310 | CRATE_KW => PathSegmentKind::CrateKw, | ||
311 | _ => return None, | ||
312 | } | ||
313 | }; | ||
314 | Some(res) | ||
315 | } | ||
316 | } | ||
317 | |||
318 | impl<'a> UseTree<'a> { | ||
319 | pub fn has_star(self) -> bool { | ||
320 | self.syntax().children().any(|it| it.kind() == STAR) | ||
321 | } | ||
322 | } | ||
323 | |||
324 | impl<'a> UseTreeList<'a> { | ||
325 | pub fn parent_use_tree(self) -> UseTree<'a> { | ||
326 | self.syntax() | ||
327 | .parent() | ||
328 | .and_then(UseTree::cast) | ||
329 | .expect("UseTreeLists are always nested in UseTrees") | ||
330 | } | ||
331 | } | ||
332 | |||
333 | fn child_opt<'a, P: AstNode<'a>, C: AstNode<'a>>(parent: P) -> Option<C> { | ||
334 | children(parent).next() | ||
335 | } | ||
336 | |||
337 | fn children<'a, P: AstNode<'a>, C: AstNode<'a>>(parent: P) -> AstChildren<'a, C> { | ||
338 | AstChildren::new(parent.syntax()) | ||
339 | } | ||
340 | |||
341 | #[derive(Debug)] | ||
342 | pub struct AstChildren<'a, N> { | ||
343 | inner: SyntaxNodeChildren<RefRoot<'a>>, | ||
344 | ph: PhantomData<N>, | ||
345 | } | ||
346 | |||
347 | impl<'a, N> AstChildren<'a, N> { | ||
348 | fn new(parent: SyntaxNodeRef<'a>) -> Self { | ||
349 | AstChildren { | ||
350 | inner: parent.children(), | ||
351 | ph: PhantomData, | ||
352 | } | ||
353 | } | ||
354 | } | ||
355 | |||
356 | impl<'a, N: AstNode<'a>> Iterator for AstChildren<'a, N> { | ||
357 | type Item = N; | ||
358 | fn next(&mut self) -> Option<N> { | ||
359 | loop { | ||
360 | if let Some(n) = N::cast(self.inner.next()?) { | ||
361 | return Some(n); | ||
362 | } | ||
363 | } | ||
364 | } | ||
365 | } | ||