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