Skip to main content

gdscript_hir/
body.rs

1//! Body lowering (Playbook §3.1/§3.4): a function body (or a class-level initializer
2//! expression) lowered from the CST into a flat arena of [`Expr`]/[`Stmt`] addressed by
3//! [`ExprId`]/[`StmtId`], plus a [`BodySourceMap`] mapping every [`ExprId`] back to its byte
4//! range. Every IDE feature (hover, inlay, completion) maps a cursor offset → `ExprId` through
5//! this source map, then reads the inferred type from [`crate::infer`].
6//!
7//! Lowering is a pure function of the CST: no engine API, no name resolution, no types. Type
8//! annotations (`is`/`as`/`var`/param/`for`) are kept as [`AstPtr`]s to their `TypeRef` nodes
9//! and resolved later, so this stage never depends on the model.
10
11use gdscript_base::TextRange;
12use gdscript_syntax::ast::{self, AstNode};
13use gdscript_syntax::{GdNode, SyntaxKind};
14use smol_str::SmolStr;
15
16use crate::cst::{self, AstPtr};
17
18/// An index into [`Body::exprs`].
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub struct ExprId(pub u32);
21
22/// An index into [`Body::stmts`].
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub struct StmtId(pub u32);
25
26/// A lowered block: its statements, in order.
27pub type Block = Vec<StmtId>;
28
29/// A literal's kind (the value text lives in the CST; only the kind drives typing).
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum Literal {
32    /// An integer literal.
33    Int,
34    /// A float literal.
35    Float,
36    /// `true` / `false`.
37    Bool,
38    /// A `String` literal.
39    Str,
40    /// A `&"…"` `StringName` literal.
41    StringName,
42    /// A `^"…"` `NodePath` literal.
43    NodePath,
44    /// `null`.
45    Null,
46    /// `PI` / `TAU` / `INF` / `NAN` (a `float`).
47    MathConst,
48}
49
50/// A binary operator.
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum BinOp {
53    /// `+`
54    Add,
55    /// `-`
56    Sub,
57    /// `*`
58    Mul,
59    /// `/`
60    Div,
61    /// `%`
62    Mod,
63    /// `**`
64    Pow,
65    /// `==`
66    Eq,
67    /// `!=`
68    Ne,
69    /// `<`
70    Lt,
71    /// `>`
72    Gt,
73    /// `<=`
74    Le,
75    /// `>=`
76    Ge,
77    /// `and` / `&&`
78    And,
79    /// `or` / `||`
80    Or,
81    /// `&`
82    BitAnd,
83    /// `|`
84    BitOr,
85    /// `^`
86    BitXor,
87    /// `<<`
88    Shl,
89    /// `>>`
90    Shr,
91    /// `=` (assignment) or any compound assignment (`+=`, `<<=`, …).
92    Assign,
93}
94
95impl BinOp {
96    /// Map an operator token kind to a [`BinOp`]. Compound assignments collapse to
97    /// [`BinOp::Assign`] (the typing rule is the same: check the RHS against the LHS slot).
98    #[must_use]
99    pub fn from_token(kind: SyntaxKind) -> Option<Self> {
100        use SyntaxKind as K;
101        Some(match kind {
102            K::Plus => Self::Add,
103            K::Minus => Self::Sub,
104            K::Star => Self::Mul,
105            K::Slash => Self::Div,
106            K::Percent => Self::Mod,
107            K::StarStar => Self::Pow,
108            K::EqEq => Self::Eq,
109            K::Neq => Self::Ne,
110            K::Lt => Self::Lt,
111            K::Gt => Self::Gt,
112            K::Le => Self::Le,
113            K::Ge => Self::Ge,
114            K::AndKw | K::AmpAmp => Self::And,
115            K::OrKw | K::PipePipe => Self::Or,
116            K::Amp => Self::BitAnd,
117            K::Pipe => Self::BitOr,
118            K::Caret => Self::BitXor,
119            K::Shl => Self::Shl,
120            K::Shr => Self::Shr,
121            K::Eq
122            | K::PlusEq
123            | K::MinusEq
124            | K::StarEq
125            | K::SlashEq
126            | K::PercentEq
127            | K::StarStarEq
128            | K::AmpEq
129            | K::PipeEq
130            | K::CaretEq
131            | K::ShlEq
132            | K::ShrEq => Self::Assign,
133            _ => return None,
134        })
135    }
136
137    /// Whether this is an arithmetic operator (`+ - * / % **`).
138    #[must_use]
139    pub fn is_arithmetic(self) -> bool {
140        matches!(
141            self,
142            Self::Add | Self::Sub | Self::Mul | Self::Div | Self::Mod | Self::Pow
143        )
144    }
145
146    /// Whether this is a comparison / logical operator (result is `bool`).
147    #[must_use]
148    pub fn is_boolean(self) -> bool {
149        matches!(
150            self,
151            Self::Eq | Self::Ne | Self::Lt | Self::Gt | Self::Le | Self::Ge | Self::And | Self::Or
152        )
153    }
154}
155
156/// A prefix unary operator.
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum UnOp {
159    /// `-`
160    Neg,
161    /// `+`
162    Pos,
163    /// `not` / `!`
164    Not,
165    /// `~`
166    BitNot,
167}
168
169impl UnOp {
170    /// Map a prefix operator token kind to a [`UnOp`].
171    #[must_use]
172    pub fn from_token(kind: SyntaxKind) -> Option<Self> {
173        Some(match kind {
174            SyntaxKind::Minus => Self::Neg,
175            SyntaxKind::Plus => Self::Pos,
176            SyntaxKind::NotKw | SyntaxKind::Bang => Self::Not,
177            SyntaxKind::Tilde => Self::BitNot,
178            _ => return None,
179        })
180    }
181}
182
183/// A lowered expression. Children are referenced by [`ExprId`].
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub enum Expr {
186    /// An unlowerable / recovered expression (typed `Error`, suppresses cascade).
187    Missing,
188    /// A literal.
189    Literal(Literal),
190    /// A bare identifier reference.
191    Name(SmolStr),
192    /// `self`.
193    SelfExpr,
194    /// `super`.
195    Super,
196    /// A binary expression.
197    Bin {
198        /// The operator.
199        op: BinOp,
200        /// Left operand.
201        lhs: ExprId,
202        /// Right operand.
203        rhs: ExprId,
204    },
205    /// A prefix unary expression.
206    Unary {
207        /// The operator.
208        op: UnOp,
209        /// The operand.
210        operand: ExprId,
211    },
212    /// `a if c else b`.
213    Ternary {
214        /// The condition.
215        cond: ExprId,
216        /// Value when the condition holds.
217        then_branch: ExprId,
218        /// Value otherwise.
219        else_branch: ExprId,
220    },
221    /// `callee(args…)`.
222    Call {
223        /// The callee.
224        callee: ExprId,
225        /// The argument expressions.
226        args: Vec<ExprId>,
227    },
228    /// `receiver.name`.
229    Field {
230        /// The receiver.
231        receiver: ExprId,
232        /// The member name.
233        name: SmolStr,
234        /// The member-name token range (for hover / member-completion context).
235        name_range: TextRange,
236    },
237    /// `base[index]`.
238    Index {
239        /// The indexed value.
240        base: ExprId,
241        /// The index expression.
242        index: ExprId,
243    },
244    /// `operand is [not] T` — always `bool`; narrows on the true branch.
245    Is {
246        /// The tested operand.
247        operand: ExprId,
248        /// The `TypeRef` node tested against.
249        ty: Option<AstPtr>,
250        /// Whether it was `is not`.
251        negated: bool,
252    },
253    /// `operand as T` — optimistic downcast to `T`.
254    Cast {
255        /// The operand.
256        operand: ExprId,
257        /// The target `TypeRef` node.
258        ty: Option<AstPtr>,
259    },
260    /// `lhs [not] in rhs` — always `bool`.
261    In {
262        /// The needle.
263        lhs: ExprId,
264        /// The haystack.
265        rhs: ExprId,
266        /// Whether it was `not in`.
267        negated: bool,
268    },
269    /// `await operand`.
270    Await(ExprId),
271    /// `[a, b, …]`.
272    Array(Vec<ExprId>),
273    /// `{ k: v, … }` (value is `None` only on recovery).
274    Dict(Vec<(ExprId, Option<ExprId>)>),
275    /// `func(...): …` — an anonymous function (typed `Callable`).
276    Lambda {
277        /// The lambda parameters.
278        params: Vec<ParamBinding>,
279        /// The lambda body.
280        body: Block,
281    },
282    /// `preload(path)` — a compile-time resource reference. When `path` is a constant string
283    /// literal (the only form Godot accepts), it is captured here so inference can resolve it to
284    /// the declaring file's `ScriptRef` (M3); a non-literal argument leaves `path` `None` (the
285    /// seam).
286    Preload {
287        /// The lowered path argument expression, if present (kept so it is still type-walked).
288        arg: Option<ExprId>,
289        /// The constant-folded path string (unquoted), when the argument is a string literal.
290        path: Option<SmolStr>,
291    },
292    /// `$Path` / `%Unique` / `get_node("…")` — a node-path access. In Phase 2 this was always
293    /// `Object(Node)`; Phase-4 M1 resolves the literal path against the owning scene to the node's
294    /// concrete type. A computed `get_node(var)` keeps `path: None` (stays `Node`, never warns).
295    GetNode {
296        /// The literal node path (`"Panel/VBox/Button"`, or a `%Unique` name), or `None` if computed.
297        path: Option<SmolStr>,
298        /// `true` for the `%Unique` form (resolve via `unique_name_in_owner`); `false` for `$Path`.
299        unique: bool,
300    },
301    /// `(inner)`.
302    Paren(ExprId),
303}
304
305/// A function / lambda parameter binding.
306#[derive(Debug, Clone, PartialEq, Eq)]
307pub struct ParamBinding {
308    /// The parameter name.
309    pub name: SmolStr,
310    /// The `TypeRef` annotation node, if written.
311    pub type_ref: Option<AstPtr>,
312    /// The default-value expression, if written.
313    pub default: Option<ExprId>,
314    /// The name token range.
315    pub name_range: TextRange,
316}
317
318/// A local `var` / `const` declaration.
319#[derive(Debug, Clone, PartialEq, Eq)]
320pub struct LocalVar {
321    /// The binding name.
322    pub name: SmolStr,
323    /// The `TypeRef` annotation node, if written.
324    pub type_ref: Option<AstPtr>,
325    /// The initializer expression, if written.
326    pub init: Option<ExprId>,
327    /// Whether the type was inferred with `:=`.
328    pub is_inferred: bool,
329    /// Whether this is a `const`.
330    pub is_const: bool,
331    /// The name token range.
332    pub name_range: TextRange,
333}
334
335/// A `for var [: T] in iter:` loop.
336#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct ForLoop {
338    /// The loop variable name.
339    pub var: SmolStr,
340    /// The loop variable's `TypeRef` annotation node (4.2+ `for x: T in …`), if written.
341    pub var_type: Option<AstPtr>,
342    /// The loop variable's name token range.
343    pub var_range: TextRange,
344    /// The iterated expression.
345    pub iter: ExprId,
346    /// The loop body.
347    pub body: Block,
348}
349
350/// One `var x` capture in a `match` pattern — a local binding (so navigation can find/rename it).
351#[derive(Debug, Clone, PartialEq, Eq)]
352pub struct MatchBind {
353    /// The captured name.
354    pub name: SmolStr,
355    /// The capture's name-token range (may carry leading whitespace, like other body bindings).
356    pub range: TextRange,
357}
358
359/// One `match` arm.
360#[derive(Debug, Clone, PartialEq, Eq)]
361pub struct MatchArm {
362    /// Names bound by `var x` patterns in this arm (typed `Variant` in Phase 2).
363    pub binds: Vec<MatchBind>,
364    /// The `when` guard expression, if any.
365    pub guard: Option<ExprId>,
366    /// The arm body.
367    pub body: Block,
368}
369
370/// A lowered statement.
371#[derive(Debug, Clone, PartialEq, Eq)]
372pub enum Stmt {
373    /// An expression statement.
374    Expr(ExprId),
375    /// A local `var` / `const`.
376    Var(LocalVar),
377    /// `return [expr]`.
378    Return(Option<ExprId>),
379    /// `if … elif … else …`.
380    If {
381        /// The `if` condition.
382        cond: ExprId,
383        /// The `if` branch.
384        then_branch: Block,
385        /// The `elif` branches.
386        elifs: Vec<(ExprId, Block)>,
387        /// The `else` branch.
388        else_branch: Option<Block>,
389    },
390    /// `while cond:`.
391    While {
392        /// The loop condition.
393        cond: ExprId,
394        /// The loop body.
395        body: Block,
396    },
397    /// `for …:`.
398    For(ForLoop),
399    /// `match …:`.
400    Match {
401        /// The matched value.
402        scrutinee: ExprId,
403        /// The arms.
404        arms: Vec<MatchArm>,
405    },
406    /// `break`.
407    Break,
408    /// `continue`.
409    Continue,
410    /// `pass` / `breakpoint`.
411    Pass,
412    /// `assert(cond[, msg])`.
413    Assert(Option<ExprId>),
414}
415
416/// Maps every [`ExprId`]/[`StmtId`] back to its source byte range. The reverse direction (offset →
417/// `ExprId`) is the tightest containing expression.
418#[derive(Debug, Clone, Default, PartialEq, Eq)]
419pub struct BodySourceMap {
420    expr_ranges: Vec<TextRange>,
421    stmt_ranges: Vec<TextRange>,
422}
423
424impl BodySourceMap {
425    /// The source range of an expression.
426    #[must_use]
427    pub fn expr_range(&self, id: ExprId) -> TextRange {
428        self.expr_ranges[id.0 as usize]
429    }
430
431    /// The source range of a statement (the whole statement node — the `UNREACHABLE_CODE` anchor).
432    #[must_use]
433    pub fn stmt_range(&self, id: StmtId) -> TextRange {
434        self.stmt_ranges[id.0 as usize]
435    }
436
437    /// The innermost (tightest) expression whose range contains `offset`.
438    #[must_use]
439    pub fn expr_at_offset(&self, offset: u32) -> Option<ExprId> {
440        self.expr_ranges
441            .iter()
442            .enumerate()
443            .filter(|(_, r)| r.start <= offset && offset < r.end)
444            .min_by_key(|(_, r)| r.end - r.start)
445            .map(|(i, _)| ExprId(u32::try_from(i).unwrap_or(u32::MAX)))
446    }
447
448    /// The expression whose range exactly equals `range` (for mapping a CST node back to its
449    /// `ExprId` — e.g. a member-completion receiver).
450    #[must_use]
451    pub fn expr_for_range(&self, range: TextRange) -> Option<ExprId> {
452        self.expr_ranges
453            .iter()
454            .position(|r| *r == range)
455            .map(|i| ExprId(u32::try_from(i).unwrap_or(u32::MAX)))
456    }
457}
458
459/// A lowered function body, or a single class-level initializer expression.
460#[derive(Debug, Clone, Default, PartialEq, Eq)]
461pub struct Body {
462    /// The expression arena.
463    pub exprs: Vec<Expr>,
464    /// The statement arena.
465    pub stmts: Vec<Stmt>,
466    /// The function parameters (empty for an initializer body).
467    pub params: Vec<ParamBinding>,
468    /// The top-level statements (empty for an initializer body).
469    pub block: Block,
470    /// A bare initializer expression (class-level `var`/`const`); `None` for a function body.
471    pub tail: Option<ExprId>,
472    /// The expr → range map.
473    pub source_map: BodySourceMap,
474}
475
476impl Body {
477    /// The expression behind an id.
478    #[must_use]
479    pub fn expr(&self, id: ExprId) -> &Expr {
480        &self.exprs[id.0 as usize]
481    }
482
483    /// The statement behind an id.
484    #[must_use]
485    pub fn stmt(&self, id: StmtId) -> &Stmt {
486        &self.stmts[id.0 as usize]
487    }
488}
489
490/// Lower a `FuncDecl` node into a [`Body`].
491#[must_use]
492pub fn body_of_func(func: &GdNode) -> Body {
493    let mut low = Lowerer::default();
494    let decl = ast::FuncDecl::cast(func.clone());
495    let params = decl
496        .as_ref()
497        .and_then(ast::FuncDecl::param_list)
498        .map(|pl| low.lower_params(pl.syntax()))
499        .unwrap_or_default();
500    let block = decl
501        .as_ref()
502        .and_then(ast::FuncDecl::body)
503        .map(|b| low.lower_block(b.syntax()))
504        .unwrap_or_default();
505    low.finish(params, block, None)
506}
507
508/// Lower a single expression node into a [`Body`] (a class-level `var`/`const` initializer).
509#[must_use]
510pub fn body_of_expr(expr: &GdNode) -> Body {
511    let mut low = Lowerer::default();
512    let tail = low.lower_expr(expr);
513    low.finish(Vec::new(), Vec::new(), Some(tail))
514}
515
516/// Lower a class-level `VarDecl`/`ConstDecl` node into a [`Body`] holding one local-var
517/// statement — so [`crate::infer`] runs the full annotation/inference checks (and records the
518/// member's binding type) on a class field the same way it does for a local.
519#[must_use]
520pub fn body_of_decl_stmt(decl: &GdNode) -> Body {
521    let mut low = Lowerer::default();
522    let block = low.lower_stmt(decl).into_iter().collect();
523    low.finish(Vec::new(), block, None)
524}
525
526/// Recover the function node for `ptr` from `root` and lower its body.
527#[must_use]
528pub fn body(root: &GdNode, ptr: AstPtr) -> Option<Body> {
529    let node = ptr.to_node(root)?;
530    Some(body_of_func(&node))
531}
532
533#[derive(Default)]
534struct Lowerer {
535    exprs: Vec<Expr>,
536    stmts: Vec<Stmt>,
537    expr_ranges: Vec<TextRange>,
538    stmt_ranges: Vec<TextRange>,
539}
540
541impl Lowerer {
542    fn finish(self, params: Vec<ParamBinding>, block: Block, tail: Option<ExprId>) -> Body {
543        Body {
544            exprs: self.exprs,
545            stmts: self.stmts,
546            params,
547            block,
548            tail,
549            source_map: BodySourceMap {
550                expr_ranges: self.expr_ranges,
551                stmt_ranges: self.stmt_ranges,
552            },
553        }
554    }
555
556    fn alloc_expr(&mut self, expr: Expr, range: TextRange) -> ExprId {
557        let id = ExprId(u32::try_from(self.exprs.len()).unwrap_or(u32::MAX));
558        self.exprs.push(expr);
559        self.expr_ranges.push(range);
560        id
561    }
562
563    fn alloc_stmt(&mut self, stmt: Stmt, range: TextRange) -> StmtId {
564        let id = StmtId(u32::try_from(self.stmts.len()).unwrap_or(u32::MAX));
565        self.stmts.push(stmt);
566        self.stmt_ranges.push(range);
567        id
568    }
569
570    fn missing(&mut self, range: TextRange) -> ExprId {
571        self.alloc_expr(Expr::Missing, range)
572    }
573
574    /// Lower the first child expression, or a `Missing` placeholder spanning `node`.
575    fn lower_first_expr(&mut self, node: &GdNode) -> ExprId {
576        match cst::first_child_expr(node) {
577            Some(c) => self.lower_expr(&c),
578            None => self.missing(cst::text_range_of(node)),
579        }
580    }
581
582    #[allow(clippy::too_many_lines)]
583    fn lower_expr(&mut self, node: &GdNode) -> ExprId {
584        use SyntaxKind as K;
585        let range = cst::text_range_of(node);
586        let expr = match node.kind() {
587            K::Literal => Expr::Literal(literal_kind(node)),
588            K::NameRef => return self.lower_name_ref(node),
589            K::ParenExpr => Expr::Paren(self.lower_first_expr(node)),
590            K::BinExpr => {
591                let exprs = cst::child_exprs(node);
592                let op = bin_op(node).unwrap_or(BinOp::Add);
593                let lhs = self.lower_or_missing(exprs.first(), range);
594                let rhs = self.lower_or_missing(exprs.get(1), range);
595                Expr::Bin { op, lhs, rhs }
596            }
597            K::UnaryExpr => {
598                let op = un_op(node).unwrap_or(UnOp::Pos);
599                let operand = self.lower_first_expr(node);
600                Expr::Unary { op, operand }
601            }
602            K::AwaitExpr => Expr::Await(self.lower_first_expr(node)),
603            K::TernaryExpr => {
604                let exprs = cst::child_exprs(node);
605                let then_branch = self.lower_or_missing(exprs.first(), range);
606                let cond = self.lower_or_missing(exprs.get(1), range);
607                let else_branch = self.lower_or_missing(exprs.get(2), range);
608                Expr::Ternary {
609                    cond,
610                    then_branch,
611                    else_branch,
612                }
613            }
614            K::CallExpr => {
615                // `get_node("literal")` / `get_node_or_null("literal")` types like `$literal`.
616                if let Some(path) = get_node_call_path(node) {
617                    Expr::GetNode {
618                        path: Some(path),
619                        unique: false,
620                    }
621                } else {
622                    let callee = self.lower_first_expr(node);
623                    let args = cst::first_child(node, |k| k == K::ArgList)
624                        .map(|al| self.lower_exprs(&al))
625                        .unwrap_or_default();
626                    Expr::Call { callee, args }
627                }
628            }
629            K::IndexExpr => {
630                let exprs = cst::child_exprs(node);
631                let base = self.lower_or_missing(exprs.first(), range);
632                let index = self.lower_or_missing(exprs.get(1), range);
633                Expr::Index { base, index }
634            }
635            K::FieldExpr => {
636                let receiver = self.lower_first_expr(node);
637                let (name, name_range) = field_member(node).unwrap_or((SmolStr::default(), range));
638                Expr::Field {
639                    receiver,
640                    name,
641                    name_range,
642                }
643            }
644            K::IsExpr => {
645                let operand = self.lower_first_expr(node);
646                Expr::Is {
647                    operand,
648                    ty: type_ref_ptr(node),
649                    negated: cst::has_token(node, K::NotKw),
650                }
651            }
652            K::CastExpr => {
653                let operand = self.lower_first_expr(node);
654                Expr::Cast {
655                    operand,
656                    ty: type_ref_ptr(node),
657                }
658            }
659            K::InExpr => {
660                let exprs = cst::child_exprs(node);
661                let lhs = self.lower_or_missing(exprs.first(), range);
662                let rhs = self.lower_or_missing(exprs.get(1), range);
663                Expr::In {
664                    lhs,
665                    rhs,
666                    negated: cst::has_token(node, K::NotKw),
667                }
668            }
669            K::ArrayLit => Expr::Array(self.lower_exprs(node)),
670            K::DictLit => {
671                let entries = cst::children_of(node, K::DictEntry)
672                    .iter()
673                    .map(|e| {
674                        let kv = cst::child_exprs(e);
675                        let key = self.lower_or_missing(kv.first(), cst::text_range_of(e));
676                        let value = kv.get(1).map(|v| self.lower_expr(v));
677                        (key, value)
678                    })
679                    .collect();
680                Expr::Dict(entries)
681            }
682            K::LambdaExpr => {
683                let params = cst::first_child(node, |k| k == K::ParamList)
684                    .map(|pl| self.lower_params(&pl))
685                    .unwrap_or_default();
686                let body = cst::first_child(node, |k| k == K::Block)
687                    .map(|b| self.lower_block(&b))
688                    .unwrap_or_default();
689                Expr::Lambda { params, body }
690            }
691            K::PreloadExpr => {
692                let arg_node = cst::first_child(node, |k| k == K::ArgList)
693                    .and_then(|al| cst::first_child_expr(&al));
694                // Constant-fold a string-literal path (`preload("res://x.gd")`) so inference can
695                // resolve it. Trim matching quotes, as the `extends "…"` path lowering does.
696                let path = arg_node
697                    .as_ref()
698                    .filter(|n| n.kind() == K::Literal)
699                    .and_then(|n| cst::child_token_text(n, K::String))
700                    .map(|s| SmolStr::new(s.trim_matches(['"', '\''])));
701                let arg = arg_node.map(|e| self.lower_expr(&e));
702                Expr::Preload { arg, path }
703            }
704            K::GetNodeExpr | K::UniqueNodeExpr => Expr::GetNode {
705                path: node_path_text(node),
706                unique: node.kind() == K::UniqueNodeExpr,
707            },
708            _ => Expr::Missing,
709        };
710        self.alloc_expr(expr, range)
711    }
712
713    fn lower_name_ref(&mut self, node: &GdNode) -> ExprId {
714        let range = cst::text_range_of(node);
715        let expr = match cst::first_token(node) {
716            Some(t) if t.kind() == SyntaxKind::SelfKw => Expr::SelfExpr,
717            Some(t) if t.kind() == SyntaxKind::SuperKw => Expr::Super,
718            Some(t) => Expr::Name(SmolStr::new(t.text())),
719            None => Expr::Missing,
720        };
721        self.alloc_expr(expr, range)
722    }
723
724    fn lower_or_missing(&mut self, node: Option<&GdNode>, fallback: TextRange) -> ExprId {
725        match node {
726            Some(n) => self.lower_expr(n),
727            None => self.missing(fallback),
728        }
729    }
730
731    fn lower_exprs(&mut self, node: &GdNode) -> Vec<ExprId> {
732        cst::child_exprs(node)
733            .iter()
734            .map(|c| self.lower_expr(c))
735            .collect()
736    }
737
738    fn lower_params(&mut self, param_list: &GdNode) -> Vec<ParamBinding> {
739        cst::children_of(param_list, SyntaxKind::Param)
740            .iter()
741            .filter_map(|p| {
742                let name_tok = ast::Param::cast(p.clone())?.name()?;
743                let name_node = name_tok.syntax();
744                Some(ParamBinding {
745                    name: SmolStr::new(name_tok.text()?),
746                    type_ref: type_ref_ptr(p),
747                    default: cst::first_child_expr(p).map(|e| self.lower_expr(&e)),
748                    name_range: cst::text_range_of(name_node),
749                })
750            })
751            .collect()
752    }
753
754    fn lower_block(&mut self, block: &GdNode) -> Block {
755        block
756            .children()
757            .filter_map(|c| self.lower_stmt(c))
758            .collect()
759    }
760
761    fn lower_stmt(&mut self, node: &GdNode) -> Option<StmtId> {
762        use SyntaxKind as K;
763        let range = cst::text_range_of(node);
764        let stmt = match node.kind() {
765            K::ExprStmt => Stmt::Expr(self.lower_first_expr(node)),
766            K::VarDecl | K::ConstDecl => Stmt::Var(self.lower_local_var(node)),
767            K::ReturnStmt => Stmt::Return(cst::first_child_expr(node).map(|e| self.lower_expr(&e))),
768            K::IfStmt => self.lower_if(node),
769            K::WhileStmt => Stmt::While {
770                cond: self.lower_first_expr(node),
771                body: self.lower_child_block(node),
772            },
773            K::ForStmt => Stmt::For(self.lower_for(node)),
774            K::MatchStmt => self.lower_match(node),
775            K::BreakStmt => Stmt::Break,
776            K::ContinueStmt => Stmt::Continue,
777            K::PassStmt | K::BreakpointStmt => Stmt::Pass,
778            K::AssertStmt => Stmt::Assert(
779                cst::first_child(node, |k| k == K::ArgList)
780                    .and_then(|al| cst::first_child_expr(&al))
781                    .map(|e| self.lower_expr(&e)),
782            ),
783            // A nested local `func` is a declaration, not a statement we type in Phase 2.
784            _ => return None,
785        };
786        Some(self.alloc_stmt(stmt, range))
787    }
788
789    fn lower_local_var(&mut self, node: &GdNode) -> LocalVar {
790        let name_node = cst::first_child(node, |k| k == SyntaxKind::Name);
791        let name = name_node
792            .as_ref()
793            .and_then(|n| ast::Name::cast(n.clone()))
794            .and_then(|n| n.text())
795            .map(SmolStr::new)
796            .unwrap_or_default();
797        LocalVar {
798            name,
799            type_ref: type_ref_ptr(node),
800            init: cst::first_child_expr(node).map(|e| self.lower_expr(&e)),
801            is_inferred: cst::has_token(node, SyntaxKind::ColonEq),
802            is_const: node.kind() == SyntaxKind::ConstDecl,
803            name_range: name_node
804                .as_ref()
805                .map_or_else(|| cst::text_range_of(node), cst::text_range_of),
806        }
807    }
808
809    fn lower_if(&mut self, node: &GdNode) -> Stmt {
810        let cond = self.lower_first_expr(node);
811        let then_branch = self.lower_child_block(node);
812        let elifs = cst::children_of(node, SyntaxKind::ElifClause)
813            .iter()
814            .map(|c| (self.lower_first_expr(c), self.lower_child_block(c)))
815            .collect();
816        let else_branch = cst::first_child(node, |k| k == SyntaxKind::ElseClause)
817            .map(|c| self.lower_child_block(&c));
818        Stmt::If {
819            cond,
820            then_branch,
821            elifs,
822            else_branch,
823        }
824    }
825
826    fn lower_for(&mut self, node: &GdNode) -> ForLoop {
827        let name = cst::first_child(node, |k| k == SyntaxKind::Name);
828        let var = name
829            .as_ref()
830            .and_then(|n| ast::Name::cast(n.clone()))
831            .and_then(|n| n.text())
832            .map(SmolStr::new)
833            .unwrap_or_default();
834        ForLoop {
835            var,
836            var_type: type_ref_ptr(node),
837            var_range: name
838                .as_ref()
839                .map_or_else(|| cst::text_range_of(node), cst::text_range_of),
840            iter: self.lower_first_expr(node),
841            body: self.lower_child_block(node),
842        }
843    }
844
845    fn lower_match(&mut self, node: &GdNode) -> Stmt {
846        let scrutinee = self.lower_first_expr(node);
847        let arms = cst::children_of(node, SyntaxKind::MatchArm)
848            .iter()
849            .map(|arm| {
850                let binds = cst::children_of(arm, SyntaxKind::PatternBind)
851                    .iter()
852                    .filter_map(|b| {
853                        let name_node = cst::first_child(b, |k| k == SyntaxKind::Name)?;
854                        let name = ast::Name::cast(name_node.clone())?
855                            .text()
856                            .map(SmolStr::new)?;
857                        Some(MatchBind {
858                            name,
859                            range: cst::text_range_of(&name_node),
860                        })
861                    })
862                    .collect();
863                let guard = cst::first_child(arm, |k| k == SyntaxKind::PatternGuard)
864                    .and_then(|g| cst::first_child_expr(&g))
865                    .map(|e| self.lower_expr(&e));
866                let body = self.lower_child_block(arm);
867                MatchArm { binds, guard, body }
868            })
869            .collect();
870        Stmt::Match { scrutinee, arms }
871    }
872
873    /// The (first) `Block` child of `node`, lowered.
874    fn lower_child_block(&mut self, node: &GdNode) -> Block {
875        cst::first_child(node, |k| k == SyntaxKind::Block)
876            .map(|b| self.lower_block(&b))
877            .unwrap_or_default()
878    }
879}
880
881/// The `AstPtr` of a node's (first) direct `TypeRef` child.
882fn type_ref_ptr(node: &GdNode) -> Option<AstPtr> {
883    cst::first_child(node, |k| k == SyntaxKind::TypeRef).map(|t| AstPtr::of(&t))
884}
885
886/// Classify a `Literal` node by its token.
887fn literal_kind(node: &GdNode) -> Literal {
888    use SyntaxKind as K;
889    match cst::first_token(node).map(|t| t.kind()) {
890        Some(K::Int) => Literal::Int,
891        Some(K::Float) => Literal::Float,
892        Some(K::String) => Literal::Str,
893        Some(K::StringName) => Literal::StringName,
894        Some(K::NodePath) => Literal::NodePath,
895        Some(K::True | K::False) => Literal::Bool,
896        Some(K::ConstPi | K::ConstTau | K::ConstInf | K::ConstNan) => Literal::MathConst,
897        _ => Literal::Null,
898    }
899}
900
901/// The binary operator token of a `BinExpr`.
902fn bin_op(node: &GdNode) -> Option<BinOp> {
903    node.children_with_tokens()
904        .filter_map(cstree::util::NodeOrToken::into_token)
905        .find_map(|t| BinOp::from_token(t.kind()))
906}
907
908/// The prefix operator token of a `UnaryExpr`.
909fn un_op(node: &GdNode) -> Option<UnOp> {
910    node.children_with_tokens()
911        .filter_map(cstree::util::NodeOrToken::into_token)
912        .find_map(|t| UnOp::from_token(t.kind()))
913}
914
915/// The member name + its range from a `FieldExpr` (the `NameRef` after the `.`).
916fn field_member(node: &GdNode) -> Option<(SmolStr, TextRange)> {
917    let nameref = cst::children_of(node, SyntaxKind::NameRef).pop()?;
918    let tok = cst::first_token(&nameref)?;
919    Some((SmolStr::new(tok.text()), cst::token_range(&tok)))
920}
921
922/// The literal path of a `get_node("…")` / `get_node_or_null("…")` call (a **bare** call = implicit
923/// `self.get_node`), or `None` if it isn't such a call or the argument is computed (the latter stays
924/// a normal call → `Node`). Lets the call lower to a [`Expr::GetNode`] so it types like `$path`.
925fn get_node_call_path(node: &GdNode) -> Option<SmolStr> {
926    let callee = cst::first_child_expr(node)?;
927    // The callee must be `get_node`/`get_node_or_null`, either **bare** (implicit `self`) or
928    // **`self.<m>`** (explicit self = the same attach node). A *foreign* receiver
929    // (`obj.get_node(...)`) is left as a normal call — its path is relative to another node we can't
930    // resolve here.
931    let is_get_node = match callee.kind() {
932        SyntaxKind::NameRef => {
933            cst::first_token(&callee).is_some_and(|t| is_get_node_name(t.text()))
934        }
935        SyntaxKind::FieldExpr => {
936            is_self_receiver(&callee)
937                && field_member(&callee).is_some_and(|(name, _)| is_get_node_name(&name))
938        }
939        _ => false,
940    };
941    if !is_get_node {
942        return None;
943    }
944    let arg = cst::first_child(node, |k| k == SyntaxKind::ArgList)
945        .and_then(|al| cst::first_child_expr(&al))?;
946    if arg.kind() != SyntaxKind::Literal {
947        return None; // computed `get_node(var)` — stays a normal call (→ Node)
948    }
949    let s = cst::child_token_text(&arg, SyntaxKind::String)?;
950    Some(SmolStr::new(s.trim_matches(['"', '\''])))
951}
952
953fn is_get_node_name(name: &str) -> bool {
954    matches!(name, "get_node" | "get_node_or_null")
955}
956
957/// Whether a `FieldExpr`'s receiver is `self` (a `NameRef` carrying a `SelfKw` token).
958fn is_self_receiver(field_expr: &GdNode) -> bool {
959    cst::first_child_expr(field_expr).is_some_and(|recv| {
960        recv.kind() == SyntaxKind::NameRef
961            && recv
962                .children_with_tokens()
963                .filter_map(cstree::util::NodeOrToken::into_token)
964                .any(|t| t.kind() == SyntaxKind::SelfKw)
965    })
966}
967
968/// The literal node path from a `$Path`/`%Unique` (`GetNodeExpr`/`UniqueNodeExpr`) node: a dequoted
969/// `$"a/b"` string, or the `/`-joined `Ident` segments of `$a/b`. `None` if it carries no path.
970fn node_path_text(node: &GdNode) -> Option<SmolStr> {
971    if let Some(s) = cst::child_token_text(node, SyntaxKind::String) {
972        return Some(SmolStr::new(s.trim_matches(['"', '\''])));
973    }
974    let segs: Vec<String> = node
975        .children_with_tokens()
976        .filter_map(cstree::util::NodeOrToken::into_token)
977        .filter(|t| t.kind() == SyntaxKind::Ident)
978        .map(|t| t.text().to_owned())
979        .collect();
980    (!segs.is_empty()).then(|| SmolStr::new(segs.join("/")))
981}
982
983#[cfg(test)]
984mod tests {
985    use super::*;
986    use gdscript_syntax::parse;
987
988    fn func_body(src: &str) -> Body {
989        let root = parse(src).syntax_node();
990        let func = gdscript_syntax::ast::descendants(&root)
991            .into_iter()
992            .find(|n| n.kind() == SyntaxKind::FuncDecl)
993            .expect("a FuncDecl");
994        body_of_func(&func)
995    }
996
997    #[test]
998    fn lowers_params_and_return() {
999        let body = func_body("func add(a: int, b := 1) -> int:\n\treturn a + b\n");
1000        assert_eq!(body.params.len(), 2);
1001        assert_eq!(body.params[0].name, "a");
1002        assert!(body.params[0].type_ref.is_some());
1003        assert!(body.params[1].default.is_some());
1004        assert_eq!(body.block.len(), 1);
1005        let Stmt::Return(Some(ret)) = body.stmt(body.block[0]) else {
1006            panic!("expected return")
1007        };
1008        assert!(matches!(body.expr(*ret), Expr::Bin { op: BinOp::Add, .. }));
1009    }
1010
1011    #[test]
1012    fn lowers_local_var_and_field_and_call() {
1013        let body = func_body("func f():\n\tvar n := get_node(\"x\")\n\tn.show()\n");
1014        // local var
1015        let Stmt::Var(v) = body.stmt(body.block[0]) else {
1016            panic!("expected var")
1017        };
1018        assert_eq!(v.name, "n");
1019        assert!(v.is_inferred && v.init.is_some());
1020        // n.show() — a call on a field
1021        let Stmt::Expr(e) = body.stmt(body.block[1]) else {
1022            panic!("expected expr stmt")
1023        };
1024        let Expr::Call { callee, .. } = body.expr(*e) else {
1025            panic!("expected call")
1026        };
1027        assert!(matches!(body.expr(*callee), Expr::Field { name, .. } if name == "show"));
1028    }
1029
1030    #[test]
1031    fn lowers_if_with_is_narrowing() {
1032        let body = func_body("func f(x):\n\tif x is Node:\n\t\tx.free()\n\telse:\n\t\tpass\n");
1033        let Stmt::If {
1034            cond,
1035            then_branch,
1036            else_branch,
1037            ..
1038        } = body.stmt(body.block[0])
1039        else {
1040            panic!("expected if")
1041        };
1042        assert!(matches!(body.expr(*cond), Expr::Is { negated: false, .. }));
1043        assert_eq!(then_branch.len(), 1);
1044        assert!(else_branch.is_some());
1045    }
1046
1047    #[test]
1048    fn source_map_finds_tightest_expr() {
1049        // `a + b` — offset on `b` should resolve to the Name(b) expr, not the whole BinExpr.
1050        let body = func_body("func f(a, b):\n\treturn a + b\n");
1051        let b_offset = u32::try_from("func f(a, b):\n\treturn a + ".len()).unwrap();
1052        let id = body
1053            .source_map
1054            .expr_at_offset(b_offset)
1055            .expect("an expr at b");
1056        assert!(matches!(body.expr(id), Expr::Name(n) if n == "b"));
1057    }
1058
1059    #[test]
1060    fn initializer_body_has_tail() {
1061        let root = parse("var x = 1 + 2\n").syntax_node();
1062        let var = gdscript_syntax::ast::descendants(&root)
1063            .into_iter()
1064            .find(|n| n.kind() == SyntaxKind::VarDecl)
1065            .unwrap();
1066        let init = crate::cst::first_child_expr(&var).unwrap();
1067        let body = body_of_expr(&init);
1068        assert!(body.tail.is_some());
1069        assert!(matches!(
1070            body.expr(body.tail.unwrap()),
1071            Expr::Bin { op: BinOp::Add, .. }
1072        ));
1073    }
1074}