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