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