1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub struct ExprId(pub u32);
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub struct StmtId(pub u32);
25
26pub type Block = Vec<StmtId>;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum Literal {
32 Int,
34 Float,
36 Bool,
38 Str,
40 StringName,
42 NodePath,
44 Null,
46 MathConst,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub enum BinOp {
53 Add,
55 Sub,
57 Mul,
59 Div,
61 Mod,
63 Pow,
65 Eq,
67 Ne,
69 Lt,
71 Gt,
73 Le,
75 Ge,
77 And,
79 Or,
81 BitAnd,
83 BitOr,
85 BitXor,
87 Shl,
89 Shr,
91 Assign,
93}
94
95impl BinOp {
96 #[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 #[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 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum UnOp {
159 Neg,
161 Pos,
163 Not,
165 BitNot,
167}
168
169impl UnOp {
170 #[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#[derive(Debug, Clone, PartialEq, Eq)]
185pub enum Expr {
186 Missing,
188 Literal(Literal),
190 Name(SmolStr),
192 SelfExpr,
194 Super,
196 Bin {
198 op: BinOp,
200 lhs: ExprId,
202 rhs: ExprId,
204 },
205 Unary {
207 op: UnOp,
209 operand: ExprId,
211 },
212 Ternary {
214 cond: ExprId,
216 then_branch: ExprId,
218 else_branch: ExprId,
220 },
221 Call {
223 callee: ExprId,
225 args: Vec<ExprId>,
227 },
228 Field {
230 receiver: ExprId,
232 name: SmolStr,
234 name_range: TextRange,
236 },
237 Index {
239 base: ExprId,
241 index: ExprId,
243 },
244 Is {
246 operand: ExprId,
248 ty: Option<AstPtr>,
250 negated: bool,
252 },
253 Cast {
255 operand: ExprId,
257 ty: Option<AstPtr>,
259 },
260 In {
262 lhs: ExprId,
264 rhs: ExprId,
266 negated: bool,
268 },
269 Await(ExprId),
271 Array(Vec<ExprId>),
273 Dict(Vec<(ExprId, Option<ExprId>)>),
275 Lambda {
277 params: Vec<ParamBinding>,
279 body: Block,
281 },
282 Preload {
287 arg: Option<ExprId>,
289 path: Option<SmolStr>,
291 },
292 GetNode {
296 path: Option<SmolStr>,
298 unique: bool,
300 },
301 Paren(ExprId),
303}
304
305#[derive(Debug, Clone, PartialEq, Eq)]
307pub struct ParamBinding {
308 pub name: SmolStr,
310 pub type_ref: Option<AstPtr>,
312 pub default: Option<ExprId>,
314 pub name_range: TextRange,
316}
317
318#[derive(Debug, Clone, PartialEq, Eq)]
320pub struct LocalVar {
321 pub name: SmolStr,
323 pub type_ref: Option<AstPtr>,
325 pub init: Option<ExprId>,
327 pub is_inferred: bool,
329 pub is_const: bool,
331 pub name_range: TextRange,
333}
334
335#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct ForLoop {
338 pub var: SmolStr,
340 pub var_type: Option<AstPtr>,
342 pub var_range: TextRange,
344 pub iter: ExprId,
346 pub body: Block,
348}
349
350#[derive(Debug, Clone, PartialEq, Eq)]
352pub struct MatchBind {
353 pub name: SmolStr,
355 pub range: TextRange,
357}
358
359#[derive(Debug, Clone, PartialEq, Eq)]
361pub struct MatchArm {
362 pub binds: Vec<MatchBind>,
364 pub guard: Option<ExprId>,
366 pub body: Block,
368}
369
370#[derive(Debug, Clone, PartialEq, Eq)]
372pub enum Stmt {
373 Expr(ExprId),
375 Var(LocalVar),
377 Return(Option<ExprId>),
379 If {
381 cond: ExprId,
383 then_branch: Block,
385 elifs: Vec<(ExprId, Block)>,
387 else_branch: Option<Block>,
389 },
390 While {
392 cond: ExprId,
394 body: Block,
396 },
397 For(ForLoop),
399 Match {
401 scrutinee: ExprId,
403 arms: Vec<MatchArm>,
405 },
406 Break,
408 Continue,
410 Pass,
412 Assert(Option<ExprId>),
414}
415
416#[derive(Debug, Clone, Default, PartialEq, Eq)]
419pub struct BodySourceMap {
420 expr_ranges: Vec<TextRange>,
421}
422
423impl BodySourceMap {
424 #[must_use]
426 pub fn expr_range(&self, id: ExprId) -> TextRange {
427 self.expr_ranges[id.0 as usize]
428 }
429
430 #[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 #[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#[derive(Debug, Clone, Default, PartialEq, Eq)]
454pub struct Body {
455 pub exprs: Vec<Expr>,
457 pub stmts: Vec<Stmt>,
459 pub params: Vec<ParamBinding>,
461 pub block: Block,
463 pub tail: Option<ExprId>,
465 pub source_map: BodySourceMap,
467}
468
469impl Body {
470 #[must_use]
472 pub fn expr(&self, id: ExprId) -> &Expr {
473 &self.exprs[id.0 as usize]
474 }
475
476 #[must_use]
478 pub fn stmt(&self, id: StmtId) -> &Stmt {
479 &self.stmts[id.0 as usize]
480 }
481}
482
483#[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#[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#[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#[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 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 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 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 _ => 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 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
870fn type_ref_ptr(node: &GdNode) -> Option<AstPtr> {
872 cst::first_child(node, |k| k == SyntaxKind::TypeRef).map(|t| AstPtr::of(&t))
873}
874
875fn 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
890fn 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
897fn 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
904fn 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
911fn get_node_call_path(node: &GdNode) -> Option<SmolStr> {
915 let callee = cst::first_child_expr(node)?;
916 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; }
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
946fn 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
957fn 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 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 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 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}