syntax/
ast.rs

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