]> git.lizzy.rs Git - rust.git/blob - crates/syntax/src/ast.rs
Merge #7335 #7691
[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 pub mod edit;
9 pub mod make;
10
11 use std::marker::PhantomData;
12
13 use crate::{
14     syntax_node::{SyntaxNode, SyntaxNodeChildren, SyntaxToken},
15     SyntaxKind,
16 };
17
18 pub use self::{
19     expr_ext::{ArrayExprKind, BinOp, Effect, ElseBranch, LiteralKind, PrefixOp, RangeOp},
20     generated::{nodes::*, tokens::*},
21     node_ext::{
22         AttrKind, FieldKind, Macro, NameLike, NameOrNameRef, PathSegmentKind, SelfParamKind,
23         SlicePatComponents, 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) -> &str {
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_outer_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_inner_doc_comment_of_items() {
134     let file = SourceFile::parse(
135         r#"
136         //! doc
137         // non-doc
138         mod foo {}
139         "#,
140     )
141     .ok()
142     .unwrap();
143     let module = file.syntax().descendants().find_map(Module::cast).unwrap();
144     assert!(module.doc_comment_text().is_none());
145 }
146
147 #[test]
148 fn test_doc_comment_of_statics() {
149     let file = SourceFile::parse(
150         r#"
151         /// Number of levels
152         static LEVELS: i32 = 0;
153         "#,
154     )
155     .ok()
156     .unwrap();
157     let st = file.syntax().descendants().find_map(Static::cast).unwrap();
158     assert_eq!("Number of levels", st.doc_comment_text().unwrap());
159 }
160
161 #[test]
162 fn test_doc_comment_preserves_indents() {
163     let file = SourceFile::parse(
164         r#"
165         /// doc1
166         /// ```
167         /// fn foo() {
168         ///     // ...
169         /// }
170         /// ```
171         mod foo {}
172         "#,
173     )
174     .ok()
175     .unwrap();
176     let module = file.syntax().descendants().find_map(Module::cast).unwrap();
177     assert_eq!("doc1\n```\nfn foo() {\n    // ...\n}\n```", module.doc_comment_text().unwrap());
178 }
179
180 #[test]
181 fn test_doc_comment_preserves_newlines() {
182     let file = SourceFile::parse(
183         r#"
184         /// this
185         /// is
186         /// mod
187         /// foo
188         mod foo {}
189         "#,
190     )
191     .ok()
192     .unwrap();
193     let module = file.syntax().descendants().find_map(Module::cast).unwrap();
194     assert_eq!("this\nis\nmod\nfoo", module.doc_comment_text().unwrap());
195 }
196
197 #[test]
198 fn test_doc_comment_single_line_block_strips_suffix() {
199     let file = SourceFile::parse(
200         r#"
201         /** this is mod foo*/
202         mod foo {}
203         "#,
204     )
205     .ok()
206     .unwrap();
207     let module = file.syntax().descendants().find_map(Module::cast).unwrap();
208     assert_eq!("this is mod foo", module.doc_comment_text().unwrap());
209 }
210
211 #[test]
212 fn test_doc_comment_single_line_block_strips_suffix_whitespace() {
213     let file = SourceFile::parse(
214         r#"
215         /** this is mod foo */
216         mod foo {}
217         "#,
218     )
219     .ok()
220     .unwrap();
221     let module = file.syntax().descendants().find_map(Module::cast).unwrap();
222     assert_eq!("this is mod foo ", module.doc_comment_text().unwrap());
223 }
224
225 #[test]
226 fn test_doc_comment_multi_line_block_strips_suffix() {
227     let file = SourceFile::parse(
228         r#"
229         /**
230         this
231         is
232         mod foo
233         */
234         mod foo {}
235         "#,
236     )
237     .ok()
238     .unwrap();
239     let module = file.syntax().descendants().find_map(Module::cast).unwrap();
240     assert_eq!(
241         "        this\n        is\n        mod foo\n        ",
242         module.doc_comment_text().unwrap()
243     );
244 }
245
246 #[test]
247 fn test_comments_preserve_trailing_whitespace() {
248     let file = SourceFile::parse(
249         "\n/// Representation of a Realm.   \n/// In the specification these are called Realm Records.\nstruct Realm {}",
250     )
251     .ok()
252     .unwrap();
253     let def = file.syntax().descendants().find_map(Struct::cast).unwrap();
254     assert_eq!(
255         "Representation of a Realm.   \nIn the specification these are called Realm Records.",
256         def.doc_comment_text().unwrap()
257     );
258 }
259
260 #[test]
261 fn test_four_slash_line_comment() {
262     let file = SourceFile::parse(
263         r#"
264         //// too many slashes to be a doc comment
265         /// doc comment
266         mod foo {}
267         "#,
268     )
269     .ok()
270     .unwrap();
271     let module = file.syntax().descendants().find_map(Module::cast).unwrap();
272     assert_eq!("doc comment", module.doc_comment_text().unwrap());
273 }
274
275 #[test]
276 fn test_where_predicates() {
277     fn assert_bound(text: &str, bound: Option<TypeBound>) {
278         assert_eq!(text, bound.unwrap().syntax().text().to_string());
279     }
280
281     let file = SourceFile::parse(
282         r#"
283 fn foo()
284 where
285    T: Clone + Copy + Debug + 'static,
286    'a: 'b + 'c,
287    Iterator::Item: 'a + Debug,
288    Iterator::Item: Debug + 'a,
289    <T as Iterator>::Item: Debug + 'a,
290    for<'a> F: Fn(&'a str)
291 {}
292         "#,
293     )
294     .ok()
295     .unwrap();
296     let where_clause = file.syntax().descendants().find_map(WhereClause::cast).unwrap();
297
298     let mut predicates = where_clause.predicates();
299
300     let pred = predicates.next().unwrap();
301     let mut bounds = pred.type_bound_list().unwrap().bounds();
302
303     assert!(pred.for_token().is_none());
304     assert!(pred.generic_param_list().is_none());
305     assert_eq!("T", pred.ty().unwrap().syntax().text().to_string());
306     assert_bound("Clone", bounds.next());
307     assert_bound("Copy", bounds.next());
308     assert_bound("Debug", bounds.next());
309     assert_bound("'static", bounds.next());
310
311     let pred = predicates.next().unwrap();
312     let mut bounds = pred.type_bound_list().unwrap().bounds();
313
314     assert_eq!("'a", pred.lifetime().unwrap().lifetime_ident_token().unwrap().text());
315
316     assert_bound("'b", bounds.next());
317     assert_bound("'c", bounds.next());
318
319     let pred = predicates.next().unwrap();
320     let mut bounds = pred.type_bound_list().unwrap().bounds();
321
322     assert_eq!("Iterator::Item", pred.ty().unwrap().syntax().text().to_string());
323     assert_bound("'a", bounds.next());
324
325     let pred = predicates.next().unwrap();
326     let mut bounds = pred.type_bound_list().unwrap().bounds();
327
328     assert_eq!("Iterator::Item", pred.ty().unwrap().syntax().text().to_string());
329     assert_bound("Debug", bounds.next());
330     assert_bound("'a", bounds.next());
331
332     let pred = predicates.next().unwrap();
333     let mut bounds = pred.type_bound_list().unwrap().bounds();
334
335     assert_eq!("<T as Iterator>::Item", pred.ty().unwrap().syntax().text().to_string());
336     assert_bound("Debug", bounds.next());
337     assert_bound("'a", bounds.next());
338
339     let pred = predicates.next().unwrap();
340     let mut bounds = pred.type_bound_list().unwrap().bounds();
341
342     assert!(pred.for_token().is_some());
343     assert_eq!("<'a>", pred.generic_param_list().unwrap().syntax().text().to_string());
344     assert_eq!("F", pred.ty().unwrap().syntax().text().to_string());
345     assert_bound("Fn(&'a str)", bounds.next());
346 }