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`] 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}
422
423impl BodySourceMap {
424    /// The source range of an expression.
425    #[must_use]
426    pub fn expr_range(&self, id: ExprId) -> TextRange {
427        self.expr_ranges[id.0 as usize]
428    }
429
430    /// The innermost (tightest) expression whose range contains `offset`.
431    #[must_use]
432    pub fn expr_at_offset(&self, offset: u32) -> Option<ExprId> {
433        self.expr_ranges
434            .iter()
435            .enumerate()
436            .filter(|(_, r)| r.start <= offset && offset < r.end)
437            .min_by_key(|(_, r)| r.end - r.start)
438            .map(|(i, _)| ExprId(u32::try_from(i).unwrap_or(u32::MAX)))
439    }
440
441    /// The expression whose range exactly equals `range` (for mapping a CST node back to its
442    /// `ExprId` — e.g. a member-completion receiver).
443    #[must_use]
444    pub fn expr_for_range(&self, range: TextRange) -> Option<ExprId> {
445        self.expr_ranges
446            .iter()
447            .position(|r| *r == range)
448            .map(|i| ExprId(u32::try_from(i).unwrap_or(u32::MAX)))
449    }
450}
451
452/// A lowered function body, or a single class-level initializer expression.
453#[derive(Debug, Clone, Default, PartialEq, Eq)]
454pub struct Body {
455    /// The expression arena.
456    pub exprs: Vec<Expr>,
457    /// The statement arena.
458    pub stmts: Vec<Stmt>,
459    /// The function parameters (empty for an initializer body).
460    pub params: Vec<ParamBinding>,
461    /// The top-level statements (empty for an initializer body).
462    pub block: Block,
463    /// A bare initializer expression (class-level `var`/`const`); `None` for a function body.
464    pub tail: Option<ExprId>,
465    /// The expr → range map.
466    pub source_map: BodySourceMap,
467}
468
469impl Body {
470    /// The expression behind an id.
471    #[must_use]
472    pub fn expr(&self, id: ExprId) -> &Expr {
473        &self.exprs[id.0 as usize]
474    }
475
476    /// The statement behind an id.
477    #[must_use]
478    pub fn stmt(&self, id: StmtId) -> &Stmt {
479        &self.stmts[id.0 as usize]
480    }
481}
482
483/// Lower a `FuncDecl` node into a [`Body`].
484#[must_use]
485pub fn body_of_func(func: &GdNode) -> Body {
486    let mut low = Lowerer::default();
487    let decl = ast::FuncDecl::cast(func.clone());
488    let params = decl
489        .as_ref()
490        .and_then(ast::FuncDecl::param_list)
491        .map(|pl| low.lower_params(pl.syntax()))
492        .unwrap_or_default();
493    let block = decl
494        .as_ref()
495        .and_then(ast::FuncDecl::body)
496        .map(|b| low.lower_block(b.syntax()))
497        .unwrap_or_default();
498    low.finish(params, block, None)
499}
500
501/// Lower a single expression node into a [`Body`] (a class-level `var`/`const` initializer).
502#[must_use]
503pub fn body_of_expr(expr: &GdNode) -> Body {
504    let mut low = Lowerer::default();
505    let tail = low.lower_expr(expr);
506    low.finish(Vec::new(), Vec::new(), Some(tail))
507}
508
509/// Lower a class-level `VarDecl`/`ConstDecl` node into a [`Body`] holding one local-var
510/// statement — so [`crate::infer`] runs the full annotation/inference checks (and records the
511/// member's binding type) on a class field the same way it does for a local.
512#[must_use]
513pub fn body_of_decl_stmt(decl: &GdNode) -> Body {
514    let mut low = Lowerer::default();
515    let block = low.lower_stmt(decl).into_iter().collect();
516    low.finish(Vec::new(), block, None)
517}
518
519/// Recover the function node for `ptr` from `root` and lower its body.
520#[must_use]
521pub fn body(root: &GdNode, ptr: AstPtr) -> Option<Body> {
522    let node = ptr.to_node(root)?;
523    Some(body_of_func(&node))
524}
525
526#[derive(Default)]
527struct Lowerer {
528    exprs: Vec<Expr>,
529    stmts: Vec<Stmt>,
530    expr_ranges: Vec<TextRange>,
531}
532
533impl Lowerer {
534    fn finish(self, params: Vec<ParamBinding>, block: Block, tail: Option<ExprId>) -> Body {
535        Body {
536            exprs: self.exprs,
537            stmts: self.stmts,
538            params,
539            block,
540            tail,
541            source_map: BodySourceMap {
542                expr_ranges: self.expr_ranges,
543            },
544        }
545    }
546
547    fn alloc_expr(&mut self, expr: Expr, range: TextRange) -> ExprId {
548        let id = ExprId(u32::try_from(self.exprs.len()).unwrap_or(u32::MAX));
549        self.exprs.push(expr);
550        self.expr_ranges.push(range);
551        id
552    }
553
554    fn alloc_stmt(&mut self, stmt: Stmt) -> StmtId {
555        let id = StmtId(u32::try_from(self.stmts.len()).unwrap_or(u32::MAX));
556        self.stmts.push(stmt);
557        id
558    }
559
560    fn missing(&mut self, range: TextRange) -> ExprId {
561        self.alloc_expr(Expr::Missing, range)
562    }
563
564    /// Lower the first child expression, or a `Missing` placeholder spanning `node`.
565    fn lower_first_expr(&mut self, node: &GdNode) -> ExprId {
566        match cst::first_child_expr(node) {
567            Some(c) => self.lower_expr(&c),
568            None => self.missing(cst::text_range_of(node)),
569        }
570    }
571
572    #[allow(clippy::too_many_lines)]
573    fn lower_expr(&mut self, node: &GdNode) -> ExprId {
574        use SyntaxKind as K;
575        let range = cst::text_range_of(node);
576        let expr = match node.kind() {
577            K::Literal => Expr::Literal(literal_kind(node)),
578            K::NameRef => return self.lower_name_ref(node),
579            K::ParenExpr => Expr::Paren(self.lower_first_expr(node)),
580            K::BinExpr => {
581                let exprs = cst::child_exprs(node);
582                let op = bin_op(node).unwrap_or(BinOp::Add);
583                let lhs = self.lower_or_missing(exprs.first(), range);
584                let rhs = self.lower_or_missing(exprs.get(1), range);
585                Expr::Bin { op, lhs, rhs }
586            }
587            K::UnaryExpr => {
588                let op = un_op(node).unwrap_or(UnOp::Pos);
589                let operand = self.lower_first_expr(node);
590                Expr::Unary { op, operand }
591            }
592            K::AwaitExpr => Expr::Await(self.lower_first_expr(node)),
593            K::TernaryExpr => {
594                let exprs = cst::child_exprs(node);
595                let then_branch = self.lower_or_missing(exprs.first(), range);
596                let cond = self.lower_or_missing(exprs.get(1), range);
597                let else_branch = self.lower_or_missing(exprs.get(2), range);
598                Expr::Ternary {
599                    cond,
600                    then_branch,
601                    else_branch,
602                }
603            }
604            K::CallExpr => {
605                // `get_node("literal")` / `get_node_or_null("literal")` types like `$literal`.
606                if let Some(path) = get_node_call_path(node) {
607                    Expr::GetNode {
608                        path: Some(path),
609                        unique: false,
610                    }
611                } else {
612                    let callee = self.lower_first_expr(node);
613                    let args = cst::first_child(node, |k| k == K::ArgList)
614                        .map(|al| self.lower_exprs(&al))
615                        .unwrap_or_default();
616                    Expr::Call { callee, args }
617                }
618            }
619            K::IndexExpr => {
620                let exprs = cst::child_exprs(node);
621                let base = self.lower_or_missing(exprs.first(), range);
622                let index = self.lower_or_missing(exprs.get(1), range);
623                Expr::Index { base, index }
624            }
625            K::FieldExpr => {
626                let receiver = self.lower_first_expr(node);
627                let (name, name_range) = field_member(node).unwrap_or((SmolStr::default(), range));
628                Expr::Field {
629                    receiver,
630                    name,
631                    name_range,
632                }
633            }
634            K::IsExpr => {
635                let operand = self.lower_first_expr(node);
636                Expr::Is {
637                    operand,
638                    ty: type_ref_ptr(node),
639                    negated: cst::has_token(node, K::NotKw),
640                }
641            }
642            K::CastExpr => {
643                let operand = self.lower_first_expr(node);
644                Expr::Cast {
645                    operand,
646                    ty: type_ref_ptr(node),
647                }
648            }
649            K::InExpr => {
650                let exprs = cst::child_exprs(node);
651                let lhs = self.lower_or_missing(exprs.first(), range);
652                let rhs = self.lower_or_missing(exprs.get(1), range);
653                Expr::In {
654                    lhs,
655                    rhs,
656                    negated: cst::has_token(node, K::NotKw),
657                }
658            }
659            K::ArrayLit => Expr::Array(self.lower_exprs(node)),
660            K::DictLit => {
661                let entries = cst::children_of(node, K::DictEntry)
662                    .iter()
663                    .map(|e| {
664                        let kv = cst::child_exprs(e);
665                        let key = self.lower_or_missing(kv.first(), cst::text_range_of(e));
666                        let value = kv.get(1).map(|v| self.lower_expr(v));
667                        (key, value)
668                    })
669                    .collect();
670                Expr::Dict(entries)
671            }
672            K::LambdaExpr => {
673                let params = cst::first_child(node, |k| k == K::ParamList)
674                    .map(|pl| self.lower_params(&pl))
675                    .unwrap_or_default();
676                let body = cst::first_child(node, |k| k == K::Block)
677                    .map(|b| self.lower_block(&b))
678                    .unwrap_or_default();
679                Expr::Lambda { params, body }
680            }
681            K::PreloadExpr => {
682                let arg_node = cst::first_child(node, |k| k == K::ArgList)
683                    .and_then(|al| cst::first_child_expr(&al));
684                // Constant-fold a string-literal path (`preload("res://x.gd")`) so inference can
685                // resolve it. Trim matching quotes, as the `extends "…"` path lowering does.
686                let path = arg_node
687                    .as_ref()
688                    .filter(|n| n.kind() == K::Literal)
689                    .and_then(|n| cst::child_token_text(n, K::String))
690                    .map(|s| SmolStr::new(s.trim_matches(['"', '\''])));
691                let arg = arg_node.map(|e| self.lower_expr(&e));
692                Expr::Preload { arg, path }
693            }
694            K::GetNodeExpr | K::UniqueNodeExpr => Expr::GetNode {
695                path: node_path_text(node),
696                unique: node.kind() == K::UniqueNodeExpr,
697            },
698            _ => Expr::Missing,
699        };
700        self.alloc_expr(expr, range)
701    }
702
703    fn lower_name_ref(&mut self, node: &GdNode) -> ExprId {
704        let range = cst::text_range_of(node);
705        let expr = match cst::first_token(node) {
706            Some(t) if t.kind() == SyntaxKind::SelfKw => Expr::SelfExpr,
707            Some(t) if t.kind() == SyntaxKind::SuperKw => Expr::Super,
708            Some(t) => Expr::Name(SmolStr::new(t.text())),
709            None => Expr::Missing,
710        };
711        self.alloc_expr(expr, range)
712    }
713
714    fn lower_or_missing(&mut self, node: Option<&GdNode>, fallback: TextRange) -> ExprId {
715        match node {
716            Some(n) => self.lower_expr(n),
717            None => self.missing(fallback),
718        }
719    }
720
721    fn lower_exprs(&mut self, node: &GdNode) -> Vec<ExprId> {
722        cst::child_exprs(node)
723            .iter()
724            .map(|c| self.lower_expr(c))
725            .collect()
726    }
727
728    fn lower_params(&mut self, param_list: &GdNode) -> Vec<ParamBinding> {
729        cst::children_of(param_list, SyntaxKind::Param)
730            .iter()
731            .filter_map(|p| {
732                let name_tok = ast::Param::cast(p.clone())?.name()?;
733                let name_node = name_tok.syntax();
734                Some(ParamBinding {
735                    name: SmolStr::new(name_tok.text()?),
736                    type_ref: type_ref_ptr(p),
737                    default: cst::first_child_expr(p).map(|e| self.lower_expr(&e)),
738                    name_range: cst::text_range_of(name_node),
739                })
740            })
741            .collect()
742    }
743
744    fn lower_block(&mut self, block: &GdNode) -> Block {
745        block
746            .children()
747            .filter_map(|c| self.lower_stmt(c))
748            .collect()
749    }
750
751    fn lower_stmt(&mut self, node: &GdNode) -> Option<StmtId> {
752        use SyntaxKind as K;
753        let stmt = match node.kind() {
754            K::ExprStmt => Stmt::Expr(self.lower_first_expr(node)),
755            K::VarDecl | K::ConstDecl => Stmt::Var(self.lower_local_var(node)),
756            K::ReturnStmt => Stmt::Return(cst::first_child_expr(node).map(|e| self.lower_expr(&e))),
757            K::IfStmt => self.lower_if(node),
758            K::WhileStmt => Stmt::While {
759                cond: self.lower_first_expr(node),
760                body: self.lower_child_block(node),
761            },
762            K::ForStmt => Stmt::For(self.lower_for(node)),
763            K::MatchStmt => self.lower_match(node),
764            K::BreakStmt => Stmt::Break,
765            K::ContinueStmt => Stmt::Continue,
766            K::PassStmt | K::BreakpointStmt => Stmt::Pass,
767            K::AssertStmt => Stmt::Assert(
768                cst::first_child(node, |k| k == K::ArgList)
769                    .and_then(|al| cst::first_child_expr(&al))
770                    .map(|e| self.lower_expr(&e)),
771            ),
772            // A nested local `func` is a declaration, not a statement we type in Phase 2.
773            _ => return None,
774        };
775        Some(self.alloc_stmt(stmt))
776    }
777
778    fn lower_local_var(&mut self, node: &GdNode) -> LocalVar {
779        let name_node = cst::first_child(node, |k| k == SyntaxKind::Name);
780        let name = name_node
781            .as_ref()
782            .and_then(|n| ast::Name::cast(n.clone()))
783            .and_then(|n| n.text())
784            .map(SmolStr::new)
785            .unwrap_or_default();
786        LocalVar {
787            name,
788            type_ref: type_ref_ptr(node),
789            init: cst::first_child_expr(node).map(|e| self.lower_expr(&e)),
790            is_inferred: cst::has_token(node, SyntaxKind::ColonEq),
791            is_const: node.kind() == SyntaxKind::ConstDecl,
792            name_range: name_node
793                .as_ref()
794                .map_or_else(|| cst::text_range_of(node), cst::text_range_of),
795        }
796    }
797
798    fn lower_if(&mut self, node: &GdNode) -> Stmt {
799        let cond = self.lower_first_expr(node);
800        let then_branch = self.lower_child_block(node);
801        let elifs = cst::children_of(node, SyntaxKind::ElifClause)
802            .iter()
803            .map(|c| (self.lower_first_expr(c), self.lower_child_block(c)))
804            .collect();
805        let else_branch = cst::first_child(node, |k| k == SyntaxKind::ElseClause)
806            .map(|c| self.lower_child_block(&c));
807        Stmt::If {
808            cond,
809            then_branch,
810            elifs,
811            else_branch,
812        }
813    }
814
815    fn lower_for(&mut self, node: &GdNode) -> ForLoop {
816        let name = cst::first_child(node, |k| k == SyntaxKind::Name);
817        let var = name
818            .as_ref()
819            .and_then(|n| ast::Name::cast(n.clone()))
820            .and_then(|n| n.text())
821            .map(SmolStr::new)
822            .unwrap_or_default();
823        ForLoop {
824            var,
825            var_type: type_ref_ptr(node),
826            var_range: name
827                .as_ref()
828                .map_or_else(|| cst::text_range_of(node), cst::text_range_of),
829            iter: self.lower_first_expr(node),
830            body: self.lower_child_block(node),
831        }
832    }
833
834    fn lower_match(&mut self, node: &GdNode) -> Stmt {
835        let scrutinee = self.lower_first_expr(node);
836        let arms = cst::children_of(node, SyntaxKind::MatchArm)
837            .iter()
838            .map(|arm| {
839                let binds = cst::children_of(arm, SyntaxKind::PatternBind)
840                    .iter()
841                    .filter_map(|b| {
842                        let name_node = cst::first_child(b, |k| k == SyntaxKind::Name)?;
843                        let name = ast::Name::cast(name_node.clone())?
844                            .text()
845                            .map(SmolStr::new)?;
846                        Some(MatchBind {
847                            name,
848                            range: cst::text_range_of(&name_node),
849                        })
850                    })
851                    .collect();
852                let guard = cst::first_child(arm, |k| k == SyntaxKind::PatternGuard)
853                    .and_then(|g| cst::first_child_expr(&g))
854                    .map(|e| self.lower_expr(&e));
855                let body = self.lower_child_block(arm);
856                MatchArm { binds, guard, body }
857            })
858            .collect();
859        Stmt::Match { scrutinee, arms }
860    }
861
862    /// The (first) `Block` child of `node`, lowered.
863    fn lower_child_block(&mut self, node: &GdNode) -> Block {
864        cst::first_child(node, |k| k == SyntaxKind::Block)
865            .map(|b| self.lower_block(&b))
866            .unwrap_or_default()
867    }
868}
869
870/// The `AstPtr` of a node's (first) direct `TypeRef` child.
871fn type_ref_ptr(node: &GdNode) -> Option<AstPtr> {
872    cst::first_child(node, |k| k == SyntaxKind::TypeRef).map(|t| AstPtr::of(&t))
873}
874
875/// Classify a `Literal` node by its token.
876fn literal_kind(node: &GdNode) -> Literal {
877    use SyntaxKind as K;
878    match cst::first_token(node).map(|t| t.kind()) {
879        Some(K::Int) => Literal::Int,
880        Some(K::Float) => Literal::Float,
881        Some(K::String) => Literal::Str,
882        Some(K::StringName) => Literal::StringName,
883        Some(K::NodePath) => Literal::NodePath,
884        Some(K::True | K::False) => Literal::Bool,
885        Some(K::ConstPi | K::ConstTau | K::ConstInf | K::ConstNan) => Literal::MathConst,
886        _ => Literal::Null,
887    }
888}
889
890/// The binary operator token of a `BinExpr`.
891fn bin_op(node: &GdNode) -> Option<BinOp> {
892    node.children_with_tokens()
893        .filter_map(cstree::util::NodeOrToken::into_token)
894        .find_map(|t| BinOp::from_token(t.kind()))
895}
896
897/// The prefix operator token of a `UnaryExpr`.
898fn un_op(node: &GdNode) -> Option<UnOp> {
899    node.children_with_tokens()
900        .filter_map(cstree::util::NodeOrToken::into_token)
901        .find_map(|t| UnOp::from_token(t.kind()))
902}
903
904/// The member name + its range from a `FieldExpr` (the `NameRef` after the `.`).
905fn field_member(node: &GdNode) -> Option<(SmolStr, TextRange)> {
906    let nameref = cst::children_of(node, SyntaxKind::NameRef).pop()?;
907    let tok = cst::first_token(&nameref)?;
908    Some((SmolStr::new(tok.text()), cst::token_range(&tok)))
909}
910
911/// The literal path of a `get_node("…")` / `get_node_or_null("…")` call (a **bare** call = implicit
912/// `self.get_node`), or `None` if it isn't such a call or the argument is computed (the latter stays
913/// a normal call → `Node`). Lets the call lower to a [`Expr::GetNode`] so it types like `$path`.
914fn get_node_call_path(node: &GdNode) -> Option<SmolStr> {
915    let callee = cst::first_child_expr(node)?;
916    // The callee must be `get_node`/`get_node_or_null`, either **bare** (implicit `self`) or
917    // **`self.<m>`** (explicit self = the same attach node). A *foreign* receiver
918    // (`obj.get_node(...)`) is left as a normal call — its path is relative to another node we can't
919    // resolve here.
920    let is_get_node = match callee.kind() {
921        SyntaxKind::NameRef => {
922            cst::first_token(&callee).is_some_and(|t| is_get_node_name(t.text()))
923        }
924        SyntaxKind::FieldExpr => {
925            is_self_receiver(&callee)
926                && field_member(&callee).is_some_and(|(name, _)| is_get_node_name(&name))
927        }
928        _ => false,
929    };
930    if !is_get_node {
931        return None;
932    }
933    let arg = cst::first_child(node, |k| k == SyntaxKind::ArgList)
934        .and_then(|al| cst::first_child_expr(&al))?;
935    if arg.kind() != SyntaxKind::Literal {
936        return None; // computed `get_node(var)` — stays a normal call (→ Node)
937    }
938    let s = cst::child_token_text(&arg, SyntaxKind::String)?;
939    Some(SmolStr::new(s.trim_matches(['"', '\''])))
940}
941
942fn is_get_node_name(name: &str) -> bool {
943    matches!(name, "get_node" | "get_node_or_null")
944}
945
946/// Whether a `FieldExpr`'s receiver is `self` (a `NameRef` carrying a `SelfKw` token).
947fn is_self_receiver(field_expr: &GdNode) -> bool {
948    cst::first_child_expr(field_expr).is_some_and(|recv| {
949        recv.kind() == SyntaxKind::NameRef
950            && recv
951                .children_with_tokens()
952                .filter_map(cstree::util::NodeOrToken::into_token)
953                .any(|t| t.kind() == SyntaxKind::SelfKw)
954    })
955}
956
957/// The literal node path from a `$Path`/`%Unique` (`GetNodeExpr`/`UniqueNodeExpr`) node: a dequoted
958/// `$"a/b"` string, or the `/`-joined `Ident` segments of `$a/b`. `None` if it carries no path.
959fn node_path_text(node: &GdNode) -> Option<SmolStr> {
960    if let Some(s) = cst::child_token_text(node, SyntaxKind::String) {
961        return Some(SmolStr::new(s.trim_matches(['"', '\''])));
962    }
963    let segs: Vec<String> = node
964        .children_with_tokens()
965        .filter_map(cstree::util::NodeOrToken::into_token)
966        .filter(|t| t.kind() == SyntaxKind::Ident)
967        .map(|t| t.text().to_owned())
968        .collect();
969    (!segs.is_empty()).then(|| SmolStr::new(segs.join("/")))
970}
971
972#[cfg(test)]
973mod tests {
974    use super::*;
975    use gdscript_syntax::parse;
976
977    fn func_body(src: &str) -> Body {
978        let root = parse(src).syntax_node();
979        let func = gdscript_syntax::ast::descendants(&root)
980            .into_iter()
981            .find(|n| n.kind() == SyntaxKind::FuncDecl)
982            .expect("a FuncDecl");
983        body_of_func(&func)
984    }
985
986    #[test]
987    fn lowers_params_and_return() {
988        let body = func_body("func add(a: int, b := 1) -> int:\n\treturn a + b\n");
989        assert_eq!(body.params.len(), 2);
990        assert_eq!(body.params[0].name, "a");
991        assert!(body.params[0].type_ref.is_some());
992        assert!(body.params[1].default.is_some());
993        assert_eq!(body.block.len(), 1);
994        let Stmt::Return(Some(ret)) = body.stmt(body.block[0]) else {
995            panic!("expected return")
996        };
997        assert!(matches!(body.expr(*ret), Expr::Bin { op: BinOp::Add, .. }));
998    }
999
1000    #[test]
1001    fn lowers_local_var_and_field_and_call() {
1002        let body = func_body("func f():\n\tvar n := get_node(\"x\")\n\tn.show()\n");
1003        // local var
1004        let Stmt::Var(v) = body.stmt(body.block[0]) else {
1005            panic!("expected var")
1006        };
1007        assert_eq!(v.name, "n");
1008        assert!(v.is_inferred && v.init.is_some());
1009        // n.show() — a call on a field
1010        let Stmt::Expr(e) = body.stmt(body.block[1]) else {
1011            panic!("expected expr stmt")
1012        };
1013        let Expr::Call { callee, .. } = body.expr(*e) else {
1014            panic!("expected call")
1015        };
1016        assert!(matches!(body.expr(*callee), Expr::Field { name, .. } if name == "show"));
1017    }
1018
1019    #[test]
1020    fn lowers_if_with_is_narrowing() {
1021        let body = func_body("func f(x):\n\tif x is Node:\n\t\tx.free()\n\telse:\n\t\tpass\n");
1022        let Stmt::If {
1023            cond,
1024            then_branch,
1025            else_branch,
1026            ..
1027        } = body.stmt(body.block[0])
1028        else {
1029            panic!("expected if")
1030        };
1031        assert!(matches!(body.expr(*cond), Expr::Is { negated: false, .. }));
1032        assert_eq!(then_branch.len(), 1);
1033        assert!(else_branch.is_some());
1034    }
1035
1036    #[test]
1037    fn source_map_finds_tightest_expr() {
1038        // `a + b` — offset on `b` should resolve to the Name(b) expr, not the whole BinExpr.
1039        let body = func_body("func f(a, b):\n\treturn a + b\n");
1040        let b_offset = u32::try_from("func f(a, b):\n\treturn a + ".len()).unwrap();
1041        let id = body
1042            .source_map
1043            .expr_at_offset(b_offset)
1044            .expect("an expr at b");
1045        assert!(matches!(body.expr(id), Expr::Name(n) if n == "b"));
1046    }
1047
1048    #[test]
1049    fn initializer_body_has_tail() {
1050        let root = parse("var x = 1 + 2\n").syntax_node();
1051        let var = gdscript_syntax::ast::descendants(&root)
1052            .into_iter()
1053            .find(|n| n.kind() == SyntaxKind::VarDecl)
1054            .unwrap();
1055        let init = crate::cst::first_child_expr(&var).unwrap();
1056        let body = body_of_expr(&init);
1057        assert!(body.tail.is_some());
1058        assert!(matches!(
1059            body.expr(body.tail.unwrap()),
1060            Expr::Bin { op: BinOp::Add, .. }
1061        ));
1062    }
1063}