diff options
Diffstat (limited to 'crates/syntax/src/ast.rs')
-rw-r--r-- | crates/syntax/src/ast.rs | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs new file mode 100644 index 000000000..d536bb1e7 --- /dev/null +++ b/crates/syntax/src/ast.rs | |||
@@ -0,0 +1,331 @@ | |||
1 | //! Abstract Syntax Tree, layered on top of untyped `SyntaxNode`s | ||
2 | |||
3 | mod generated; | ||
4 | mod traits; | ||
5 | mod token_ext; | ||
6 | mod node_ext; | ||
7 | mod expr_ext; | ||
8 | pub mod edit; | ||
9 | pub mod make; | ||
10 | |||
11 | use std::marker::PhantomData; | ||
12 | |||
13 | use crate::{ | ||
14 | syntax_node::{SyntaxNode, SyntaxNodeChildren, SyntaxToken}, | ||
15 | SmolStr, SyntaxKind, | ||
16 | }; | ||
17 | |||
18 | pub use self::{ | ||
19 | expr_ext::{ArrayExprKind, BinOp, Effect, ElseBranch, LiteralKind, PrefixOp, RangeOp}, | ||
20 | generated::*, | ||
21 | node_ext::{ | ||
22 | AttrKind, FieldKind, NameOrNameRef, PathSegmentKind, SelfParamKind, SlicePatComponents, | ||
23 | StructKind, TypeBoundKind, VisibilityKind, | ||
24 | }, | ||
25 | token_ext::*, | ||
26 | traits::*, | ||
27 | }; | ||
28 | |||
29 | /// The main trait to go from untyped `SyntaxNode` to a typed ast. The | ||
30 | /// conversion itself has zero runtime cost: ast and syntax nodes have exactly | ||
31 | /// the same representation: a pointer to the tree root and a pointer to the | ||
32 | /// node itself. | ||
33 | pub trait AstNode { | ||
34 | fn can_cast(kind: SyntaxKind) -> bool | ||
35 | where | ||
36 | Self: Sized; | ||
37 | |||
38 | fn cast(syntax: SyntaxNode) -> Option<Self> | ||
39 | where | ||
40 | Self: Sized; | ||
41 | |||
42 | fn syntax(&self) -> &SyntaxNode; | ||
43 | } | ||
44 | |||
45 | /// Like `AstNode`, but wraps tokens rather than interior nodes. | ||
46 | pub trait AstToken { | ||
47 | fn can_cast(token: SyntaxKind) -> bool | ||
48 | where | ||
49 | Self: Sized; | ||
50 | |||
51 | fn cast(syntax: SyntaxToken) -> Option<Self> | ||
52 | where | ||
53 | Self: Sized; | ||
54 | |||
55 | fn syntax(&self) -> &SyntaxToken; | ||
56 | |||
57 | fn text(&self) -> &SmolStr { | ||
58 | self.syntax().text() | ||
59 | } | ||
60 | } | ||
61 | |||
62 | /// An iterator over `SyntaxNode` children of a particular AST type. | ||
63 | #[derive(Debug, Clone)] | ||
64 | pub struct AstChildren<N> { | ||
65 | inner: SyntaxNodeChildren, | ||
66 | ph: PhantomData<N>, | ||
67 | } | ||
68 | |||
69 | impl<N> AstChildren<N> { | ||
70 | fn new(parent: &SyntaxNode) -> Self { | ||
71 | AstChildren { inner: parent.children(), ph: PhantomData } | ||
72 | } | ||
73 | } | ||
74 | |||
75 | impl<N: AstNode> Iterator for AstChildren<N> { | ||
76 | type Item = N; | ||
77 | fn next(&mut self) -> Option<N> { | ||
78 | self.inner.find_map(N::cast) | ||
79 | } | ||
80 | } | ||
81 | |||
82 | mod support { | ||
83 | use super::{AstChildren, AstNode, SyntaxKind, SyntaxNode, SyntaxToken}; | ||
84 | |||
85 | pub(super) fn child<N: AstNode>(parent: &SyntaxNode) -> Option<N> { | ||
86 | parent.children().find_map(N::cast) | ||
87 | } | ||
88 | |||
89 | pub(super) fn children<N: AstNode>(parent: &SyntaxNode) -> AstChildren<N> { | ||
90 | AstChildren::new(parent) | ||
91 | } | ||
92 | |||
93 | pub(super) fn token(parent: &SyntaxNode, kind: SyntaxKind) -> Option<SyntaxToken> { | ||
94 | parent.children_with_tokens().filter_map(|it| it.into_token()).find(|it| it.kind() == kind) | ||
95 | } | ||
96 | } | ||
97 | |||
98 | #[test] | ||
99 | fn assert_ast_is_object_safe() { | ||
100 | fn _f(_: &dyn AstNode, _: &dyn NameOwner) {} | ||
101 | } | ||
102 | |||
103 | #[test] | ||
104 | fn test_doc_comment_none() { | ||
105 | let file = SourceFile::parse( | ||
106 | r#" | ||
107 | // non-doc | ||
108 | mod foo {} | ||
109 | "#, | ||
110 | ) | ||
111 | .ok() | ||
112 | .unwrap(); | ||
113 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | ||
114 | assert!(module.doc_comment_text().is_none()); | ||
115 | } | ||
116 | |||
117 | #[test] | ||
118 | fn test_doc_comment_of_items() { | ||
119 | let file = SourceFile::parse( | ||
120 | r#" | ||
121 | //! doc | ||
122 | // non-doc | ||
123 | mod foo {} | ||
124 | "#, | ||
125 | ) | ||
126 | .ok() | ||
127 | .unwrap(); | ||
128 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | ||
129 | assert_eq!("doc", module.doc_comment_text().unwrap()); | ||
130 | } | ||
131 | |||
132 | #[test] | ||
133 | fn test_doc_comment_of_statics() { | ||
134 | let file = SourceFile::parse( | ||
135 | r#" | ||
136 | /// Number of levels | ||
137 | static LEVELS: i32 = 0; | ||
138 | "#, | ||
139 | ) | ||
140 | .ok() | ||
141 | .unwrap(); | ||
142 | let st = file.syntax().descendants().find_map(Static::cast).unwrap(); | ||
143 | assert_eq!("Number of levels", st.doc_comment_text().unwrap()); | ||
144 | } | ||
145 | |||
146 | #[test] | ||
147 | fn test_doc_comment_preserves_indents() { | ||
148 | let file = SourceFile::parse( | ||
149 | r#" | ||
150 | /// doc1 | ||
151 | /// ``` | ||
152 | /// fn foo() { | ||
153 | /// // ... | ||
154 | /// } | ||
155 | /// ``` | ||
156 | mod foo {} | ||
157 | "#, | ||
158 | ) | ||
159 | .ok() | ||
160 | .unwrap(); | ||
161 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | ||
162 | assert_eq!("doc1\n```\nfn foo() {\n // ...\n}\n```", module.doc_comment_text().unwrap()); | ||
163 | } | ||
164 | |||
165 | #[test] | ||
166 | fn test_doc_comment_preserves_newlines() { | ||
167 | let file = SourceFile::parse( | ||
168 | r#" | ||
169 | /// this | ||
170 | /// is | ||
171 | /// mod | ||
172 | /// foo | ||
173 | mod foo {} | ||
174 | "#, | ||
175 | ) | ||
176 | .ok() | ||
177 | .unwrap(); | ||
178 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | ||
179 | assert_eq!("this\nis\nmod\nfoo", module.doc_comment_text().unwrap()); | ||
180 | } | ||
181 | |||
182 | #[test] | ||
183 | fn test_doc_comment_single_line_block_strips_suffix() { | ||
184 | let file = SourceFile::parse( | ||
185 | r#" | ||
186 | /** this is mod foo*/ | ||
187 | mod foo {} | ||
188 | "#, | ||
189 | ) | ||
190 | .ok() | ||
191 | .unwrap(); | ||
192 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | ||
193 | assert_eq!("this is mod foo", module.doc_comment_text().unwrap()); | ||
194 | } | ||
195 | |||
196 | #[test] | ||
197 | fn test_doc_comment_single_line_block_strips_suffix_whitespace() { | ||
198 | let file = SourceFile::parse( | ||
199 | r#" | ||
200 | /** this is mod foo */ | ||
201 | mod foo {} | ||
202 | "#, | ||
203 | ) | ||
204 | .ok() | ||
205 | .unwrap(); | ||
206 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | ||
207 | assert_eq!("this is mod foo ", module.doc_comment_text().unwrap()); | ||
208 | } | ||
209 | |||
210 | #[test] | ||
211 | fn test_doc_comment_multi_line_block_strips_suffix() { | ||
212 | let file = SourceFile::parse( | ||
213 | r#" | ||
214 | /** | ||
215 | this | ||
216 | is | ||
217 | mod foo | ||
218 | */ | ||
219 | mod foo {} | ||
220 | "#, | ||
221 | ) | ||
222 | .ok() | ||
223 | .unwrap(); | ||
224 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | ||
225 | assert_eq!( | ||
226 | " this\n is\n mod foo\n ", | ||
227 | module.doc_comment_text().unwrap() | ||
228 | ); | ||
229 | } | ||
230 | |||
231 | #[test] | ||
232 | fn test_comments_preserve_trailing_whitespace() { | ||
233 | let file = SourceFile::parse( | ||
234 | "\n/// Representation of a Realm. \n/// In the specification these are called Realm Records.\nstruct Realm {}", | ||
235 | ) | ||
236 | .ok() | ||
237 | .unwrap(); | ||
238 | let def = file.syntax().descendants().find_map(Struct::cast).unwrap(); | ||
239 | assert_eq!( | ||
240 | "Representation of a Realm. \nIn the specification these are called Realm Records.", | ||
241 | def.doc_comment_text().unwrap() | ||
242 | ); | ||
243 | } | ||
244 | |||
245 | #[test] | ||
246 | fn test_four_slash_line_comment() { | ||
247 | let file = SourceFile::parse( | ||
248 | r#" | ||
249 | //// too many slashes to be a doc comment | ||
250 | /// doc comment | ||
251 | mod foo {} | ||
252 | "#, | ||
253 | ) | ||
254 | .ok() | ||
255 | .unwrap(); | ||
256 | let module = file.syntax().descendants().find_map(Module::cast).unwrap(); | ||
257 | assert_eq!("doc comment", module.doc_comment_text().unwrap()); | ||
258 | } | ||
259 | |||
260 | #[test] | ||
261 | fn test_where_predicates() { | ||
262 | fn assert_bound(text: &str, bound: Option<TypeBound>) { | ||
263 | assert_eq!(text, bound.unwrap().syntax().text().to_string()); | ||
264 | } | ||
265 | |||
266 | let file = SourceFile::parse( | ||
267 | r#" | ||
268 | fn foo() | ||
269 | where | ||
270 | T: Clone + Copy + Debug + 'static, | ||
271 | 'a: 'b + 'c, | ||
272 | Iterator::Item: 'a + Debug, | ||
273 | Iterator::Item: Debug + 'a, | ||
274 | <T as Iterator>::Item: Debug + 'a, | ||
275 | for<'a> F: Fn(&'a str) | ||
276 | {} | ||
277 | "#, | ||
278 | ) | ||
279 | .ok() | ||
280 | .unwrap(); | ||
281 | let where_clause = file.syntax().descendants().find_map(WhereClause::cast).unwrap(); | ||
282 | |||
283 | let mut predicates = where_clause.predicates(); | ||
284 | |||
285 | let pred = predicates.next().unwrap(); | ||
286 | let mut bounds = pred.type_bound_list().unwrap().bounds(); | ||
287 | |||
288 | assert!(pred.for_token().is_none()); | ||
289 | assert!(pred.generic_param_list().is_none()); | ||
290 | assert_eq!("T", pred.ty().unwrap().syntax().text().to_string()); | ||
291 | assert_bound("Clone", bounds.next()); | ||
292 | assert_bound("Copy", bounds.next()); | ||
293 | assert_bound("Debug", bounds.next()); | ||
294 | assert_bound("'static", bounds.next()); | ||
295 | |||
296 | let pred = predicates.next().unwrap(); | ||
297 | let mut bounds = pred.type_bound_list().unwrap().bounds(); | ||
298 | |||
299 | assert_eq!("'a", pred.lifetime_token().unwrap().text()); | ||
300 | |||
301 | assert_bound("'b", bounds.next()); | ||
302 | assert_bound("'c", bounds.next()); | ||
303 | |||
304 | let pred = predicates.next().unwrap(); | ||
305 | let mut bounds = pred.type_bound_list().unwrap().bounds(); | ||
306 | |||
307 | assert_eq!("Iterator::Item", pred.ty().unwrap().syntax().text().to_string()); | ||
308 | assert_bound("'a", bounds.next()); | ||
309 | |||
310 | let pred = predicates.next().unwrap(); | ||
311 | let mut bounds = pred.type_bound_list().unwrap().bounds(); | ||
312 | |||
313 | assert_eq!("Iterator::Item", pred.ty().unwrap().syntax().text().to_string()); | ||
314 | assert_bound("Debug", bounds.next()); | ||
315 | assert_bound("'a", bounds.next()); | ||
316 | |||
317 | let pred = predicates.next().unwrap(); | ||
318 | let mut bounds = pred.type_bound_list().unwrap().bounds(); | ||
319 | |||
320 | assert_eq!("<T as Iterator>::Item", pred.ty().unwrap().syntax().text().to_string()); | ||
321 | assert_bound("Debug", bounds.next()); | ||
322 | assert_bound("'a", bounds.next()); | ||
323 | |||
324 | let pred = predicates.next().unwrap(); | ||
325 | let mut bounds = pred.type_bound_list().unwrap().bounds(); | ||
326 | |||
327 | assert!(pred.for_token().is_some()); | ||
328 | assert_eq!("<'a>", pred.generic_param_list().unwrap().syntax().text().to_string()); | ||
329 | assert_eq!("F", pred.ty().unwrap().syntax().text().to_string()); | ||
330 | assert_bound("Fn(&'a str)", bounds.next()); | ||
331 | } | ||