Skip to main content

ra_ap_syntax/
ast.rs

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