Skip to main content

gdscript_syntax/
ast.rs

1//! WS4 — the typed AST.
2//!
3//! A thin, zero-cost typed *view* over the lossless CST (the rust-analyzer model). An
4//! AST node just wraps a red [`GdNode`] of the matching [`SyntaxKind`]; accessors are
5//! filtered child lookups. No data is copied. Because [`GdNode`] is a *resolved* node
6//! (it carries the interner), text accessors are clean — `Name::text()` needs no extra
7//! resolver argument.
8
9use cstree::util::NodeOrToken;
10
11use crate::SyntaxKind;
12use crate::syntax_kind::{GdNode, GdToken};
13
14/// A typed node: a checked view over a red node of one [`SyntaxKind`].
15pub trait AstNode: Sized {
16    /// Whether a node of `kind` can be viewed as `Self`.
17    fn can_cast(kind: SyntaxKind) -> bool;
18    /// View `node` as `Self`, if its kind matches.
19    fn cast(node: GdNode) -> Option<Self>;
20    /// The underlying red node.
21    fn syntax(&self) -> &GdNode;
22}
23
24/// Generate a single-kind AST wrapper struct + its [`AstNode`] impl.
25macro_rules! ast_node {
26    ($(#[$meta:meta])* $name:ident) => {
27        $(#[$meta])*
28        #[derive(Debug, Clone)]
29        pub struct $name(GdNode);
30
31        impl AstNode for $name {
32            fn can_cast(kind: SyntaxKind) -> bool {
33                kind == SyntaxKind::$name
34            }
35            fn cast(node: GdNode) -> Option<Self> {
36                if node.kind() == SyntaxKind::$name {
37                    Some(Self(node))
38                } else {
39                    None
40                }
41            }
42            fn syntax(&self) -> &GdNode {
43                &self.0
44            }
45        }
46    };
47}
48
49ast_node!(
50    /// The whole file.
51    SourceFile
52);
53ast_node!(ClassNameDecl);
54ast_node!(ExtendsClause);
55ast_node!(Annotation);
56ast_node!(FuncDecl);
57ast_node!(VarDecl);
58ast_node!(ConstDecl);
59ast_node!(EnumDecl);
60ast_node!(EnumVariant);
61ast_node!(SignalDecl);
62ast_node!(InnerClassDecl);
63ast_node!(ClassBody);
64ast_node!(ParamList);
65ast_node!(Param);
66ast_node!(Block);
67ast_node!(TypeRef);
68ast_node!(
69    /// A declaration's name (wraps the declared identifier).
70    Name
71);
72
73// ---- generic navigation helpers -------------------------------------------------
74
75/// The first child node castable to `N`.
76fn child<N: AstNode>(node: &GdNode) -> Option<N> {
77    node.children().find_map(|c| N::cast(c.clone()))
78}
79
80/// All child nodes castable to `N`.
81fn children<N: AstNode>(node: &GdNode) -> impl Iterator<Item = N> + '_ {
82    node.children().filter_map(|c| N::cast(c.clone()))
83}
84
85/// Whether `node` has a direct child token of `kind`.
86fn has_token(node: &GdNode, kind: SyntaxKind) -> bool {
87    node.children_with_tokens()
88        .filter_map(NodeOrToken::into_token)
89        .any(|t| t.kind() == kind)
90}
91
92/// The text of the first direct child token of `kind`.
93fn token_text(node: &GdNode, kind: SyntaxKind) -> Option<String> {
94    node.children_with_tokens()
95        .filter_map(NodeOrToken::into_token)
96        .find(|t| t.kind() == kind)
97        .map(|t| t.text().to_owned())
98}
99
100/// The text of the first direct child token usable as a *name* — the grammar's `at_name`
101/// whitelist: an `Ident`, or one of the soft keywords `match`/`when` (Godot's `is_identifier()`
102/// permits both as identifiers). Without this, a symbol named `match`/`when` is silently dropped at
103/// the AST layer because its token is `MatchKw`/`WhenKw`, not `Ident`. See `TECH_DEBT.md`.
104fn name_token_text(node: &GdNode) -> Option<String> {
105    node.children_with_tokens()
106        .filter_map(NodeOrToken::into_token)
107        .find(|t| {
108            matches!(
109                t.kind(),
110                SyntaxKind::Ident | SyntaxKind::MatchKw | SyntaxKind::WhenKw
111            )
112        })
113        .map(|t| t.text().to_owned())
114}
115
116// ---- accessors ------------------------------------------------------------------
117
118impl Name {
119    /// The identifier text (incl. the soft-keyword names `match`/`when`).
120    #[must_use]
121    pub fn text(&self) -> Option<String> {
122        name_token_text(&self.0)
123    }
124}
125
126impl SourceFile {
127    /// The top-level declarations, in source order.
128    pub fn decls(&self) -> impl Iterator<Item = Decl> + '_ {
129        self.0.children().filter_map(|c| Decl::cast(c.clone()))
130    }
131}
132
133impl FuncDecl {
134    /// The function name.
135    #[must_use]
136    pub fn name(&self) -> Option<Name> {
137        child(&self.0)
138    }
139    /// The parameter list.
140    #[must_use]
141    pub fn param_list(&self) -> Option<ParamList> {
142        child(&self.0)
143    }
144    /// The body block.
145    #[must_use]
146    pub fn body(&self) -> Option<Block> {
147        child(&self.0)
148    }
149    /// The declared return type, if any (the `TypeRef` after `->`).
150    #[must_use]
151    pub fn return_type(&self) -> Option<TypeRef> {
152        child(&self.0)
153    }
154    /// Whether this is a `static func`.
155    #[must_use]
156    pub fn is_static(&self) -> bool {
157        has_token(&self.0, SyntaxKind::StaticKw)
158    }
159}
160
161impl ParamList {
162    /// The parameters (excludes vararg rest params).
163    pub fn params(&self) -> impl Iterator<Item = Param> + '_ {
164        children(&self.0)
165    }
166}
167
168impl Param {
169    /// The parameter name.
170    #[must_use]
171    pub fn name(&self) -> Option<Name> {
172        child(&self.0)
173    }
174    /// The declared type, if any.
175    #[must_use]
176    pub fn type_ref(&self) -> Option<TypeRef> {
177        child(&self.0)
178    }
179}
180
181impl VarDecl {
182    /// The variable name.
183    #[must_use]
184    pub fn name(&self) -> Option<Name> {
185        child(&self.0)
186    }
187    /// The declared type, if any.
188    #[must_use]
189    pub fn type_ref(&self) -> Option<TypeRef> {
190        child(&self.0)
191    }
192    /// Whether this is a `static var`.
193    #[must_use]
194    pub fn is_static(&self) -> bool {
195        has_token(&self.0, SyntaxKind::StaticKw)
196    }
197}
198
199impl ConstDecl {
200    /// The constant name.
201    #[must_use]
202    pub fn name(&self) -> Option<Name> {
203        child(&self.0)
204    }
205}
206
207impl EnumDecl {
208    /// The enum's name, if it is a named enum.
209    #[must_use]
210    pub fn name(&self) -> Option<Name> {
211        child(&self.0)
212    }
213    /// The enum variants.
214    pub fn variants(&self) -> impl Iterator<Item = EnumVariant> + '_ {
215        children(&self.0)
216    }
217}
218
219impl EnumVariant {
220    /// The variant name (incl. the soft-keyword names `match`/`when`).
221    #[must_use]
222    pub fn text(&self) -> Option<String> {
223        name_token_text(&self.0)
224    }
225}
226
227impl SignalDecl {
228    /// The signal name.
229    #[must_use]
230    pub fn name(&self) -> Option<Name> {
231        child(&self.0)
232    }
233    /// The typed parameter list, if any.
234    #[must_use]
235    pub fn param_list(&self) -> Option<ParamList> {
236        child(&self.0)
237    }
238}
239
240impl ClassNameDecl {
241    /// The registered global class name.
242    #[must_use]
243    pub fn name(&self) -> Option<Name> {
244        child(&self.0)
245    }
246}
247
248impl InnerClassDecl {
249    /// The inner class name.
250    #[must_use]
251    pub fn name(&self) -> Option<Name> {
252        child(&self.0)
253    }
254    /// The class body (its members), if present.
255    #[must_use]
256    pub fn body(&self) -> Option<ClassBody> {
257        child(&self.0)
258    }
259}
260
261impl ClassBody {
262    /// The member declarations.
263    pub fn decls(&self) -> impl Iterator<Item = Decl> + '_ {
264        self.0.children().filter_map(|c| Decl::cast(c.clone()))
265    }
266}
267
268impl Annotation {
269    /// The annotation name (the identifier after `@`).
270    #[must_use]
271    pub fn name(&self) -> Option<String> {
272        token_text(&self.0, SyntaxKind::Ident)
273    }
274}
275
276impl TypeRef {
277    /// The leading type identifier (e.g. `int`, `Array`).
278    #[must_use]
279    pub fn text(&self) -> Option<String> {
280        self.0
281            .children_with_tokens()
282            .filter_map(NodeOrToken::into_token)
283            .find(|t| matches!(t.kind(), SyntaxKind::Ident | SyntaxKind::VoidKw))
284            .map(|t| t.text().to_owned())
285    }
286}
287
288/// Any top-level or class-body declaration — the unit `document_symbols` iterates.
289#[derive(Debug, Clone)]
290pub enum Decl {
291    /// `class_name X`
292    ClassName(ClassNameDecl),
293    /// `func f(...)`
294    Func(FuncDecl),
295    /// `var x`
296    Var(VarDecl),
297    /// `const X`
298    Const(ConstDecl),
299    /// `enum E { ... }`
300    Enum(EnumDecl),
301    /// `signal s`
302    Signal(SignalDecl),
303    /// `class Inner: ...`
304    Class(InnerClassDecl),
305}
306
307impl Decl {
308    /// View a node as a declaration, if it is one.
309    #[must_use]
310    pub fn cast(node: GdNode) -> Option<Self> {
311        match node.kind() {
312            SyntaxKind::ClassNameDecl => ClassNameDecl::cast(node).map(Self::ClassName),
313            SyntaxKind::FuncDecl => FuncDecl::cast(node).map(Self::Func),
314            SyntaxKind::VarDecl => VarDecl::cast(node).map(Self::Var),
315            SyntaxKind::ConstDecl => ConstDecl::cast(node).map(Self::Const),
316            SyntaxKind::EnumDecl => EnumDecl::cast(node).map(Self::Enum),
317            SyntaxKind::SignalDecl => SignalDecl::cast(node).map(Self::Signal),
318            SyntaxKind::InnerClassDecl => InnerClassDecl::cast(node).map(Self::Class),
319            _ => None,
320        }
321    }
322
323    /// The declaration's underlying node.
324    #[must_use]
325    pub fn syntax(&self) -> &GdNode {
326        match self {
327            Self::ClassName(d) => d.syntax(),
328            Self::Func(d) => d.syntax(),
329            Self::Var(d) => d.syntax(),
330            Self::Const(d) => d.syntax(),
331            Self::Enum(d) => d.syntax(),
332            Self::Signal(d) => d.syntax(),
333            Self::Class(d) => d.syntax(),
334        }
335    }
336
337    /// The declaration's name, if it has one.
338    #[must_use]
339    pub fn name(&self) -> Option<String> {
340        let name = match self {
341            Self::ClassName(d) => d.name(),
342            Self::Func(d) => d.name(),
343            Self::Var(d) => d.name(),
344            Self::Const(d) => d.name(),
345            Self::Enum(d) => d.name(),
346            Self::Signal(d) => d.name(),
347            Self::Class(d) => d.name(),
348        };
349        name.and_then(|n| n.text())
350    }
351}
352
353/// A pre-order walk over every node in the tree (depth-first), for visitors that need
354/// to inspect all declarations/blocks (e.g. folding ranges).
355#[must_use]
356pub fn descendants(root: &GdNode) -> Vec<GdNode> {
357    let mut out = vec![root.clone()];
358    for child in root.children() {
359        out.extend(descendants(child));
360    }
361    out
362}
363
364/// The token (if any) at `offset`, right-biased — the completion-context probe.
365#[must_use]
366pub fn token_at(root: &GdNode, offset: text_size::TextSize) -> Option<GdToken> {
367    root.token_at_offset(offset).right_biased()
368}
369
370#[cfg(test)]
371mod tests {
372    use super::*;
373    use crate::parse;
374
375    #[test]
376    fn func_accessors() {
377        let parse = parse("static func add(a: int, b := 1) -> int:\n\treturn a + b\n");
378        let file = SourceFile::cast(parse.syntax_node()).unwrap();
379        let func = file
380            .decls()
381            .find_map(|d| match d {
382                Decl::Func(f) => Some(f),
383                _ => None,
384            })
385            .unwrap();
386        assert!(func.is_static());
387        assert_eq!(func.name().and_then(|n| n.text()).as_deref(), Some("add"));
388        assert_eq!(
389            func.return_type().and_then(|t| t.text()).as_deref(),
390            Some("int")
391        );
392        let params: Vec<_> = func
393            .param_list()
394            .unwrap()
395            .params()
396            .filter_map(|p| p.name().and_then(|n| n.text()))
397            .collect();
398        assert_eq!(params, vec!["a", "b"]);
399        assert!(func.body().is_some());
400    }
401
402    #[test]
403    fn declarations_are_enumerated() {
404        let parse = parse(
405            "class_name Foo\nconst K = 1\nvar x: int\nsignal s\nenum E { A, B }\nfunc f():\n\tpass\nclass Inner:\n\tvar y = 2\n",
406        );
407        let file = SourceFile::cast(parse.syntax_node()).unwrap();
408        let names: Vec<_> = file.decls().map(|d| d.name().unwrap_or_default()).collect();
409        assert_eq!(names, vec!["Foo", "K", "x", "s", "E", "f", "Inner"]);
410    }
411
412    #[test]
413    fn enum_variants_and_inner_class_members() {
414        let parse =
415            parse("enum E { A, B = 5, C }\nclass Inner:\n\tvar a = 1\n\tfunc m():\n\t\tpass\n");
416        let file = SourceFile::cast(parse.syntax_node()).unwrap();
417
418        let en = file
419            .decls()
420            .find_map(|d| match d {
421                Decl::Enum(e) => Some(e),
422                _ => None,
423            })
424            .unwrap();
425        let variants: Vec<_> = en.variants().filter_map(|v| v.text()).collect();
426        assert_eq!(variants, vec!["A", "B", "C"]);
427
428        let inner = file
429            .decls()
430            .find_map(|d| match d {
431                Decl::Class(c) => Some(c),
432                _ => None,
433            })
434            .unwrap();
435        let member_names: Vec<_> = inner
436            .body()
437            .unwrap()
438            .decls()
439            .map(|d| d.name().unwrap_or_default())
440            .collect();
441        assert_eq!(member_names, vec!["a", "m"]);
442    }
443
444    #[test]
445    fn token_at_offset_finds_identifier() {
446        let src = "var hello = 1\n";
447        let parse = parse(src);
448        let node = parse.syntax_node();
449        // offset 5 is inside "hello" (chars 4..9).
450        let tok = node
451            .token_at_offset(text_size::TextSize::new(5))
452            .right_biased()
453            .unwrap();
454        assert_eq!(tok.kind(), SyntaxKind::Ident);
455        assert_eq!(tok.text(), "hello");
456    }
457}