1use gdscript_api::{EngineApi, MemberRef, TyRef};
13use gdscript_base::{Diagnostic, DiagnosticSource, FileId, Severity, TextRange};
14use gdscript_db::Db;
15use gdscript_scene::{SceneModel, SceneNode};
16use gdscript_syntax::GdNode;
17use rustc_hash::FxHashMap;
18use smol_str::SmolStr;
19
20use std::sync::Arc;
21
22use crate::body::{self, BinOp, Body, Expr, ExprId, Literal, ParamBinding, Stmt, UnOp};
23use crate::cst::{self, AstPtr};
24use crate::item_tree::{ItemTree, Member, item_tree};
25use crate::resolve::{self, ClassItem, ClassScope, GlobalDef};
26use crate::ty::{self, Assign, EnumRef, ScriptRefId, Ty};
27
28pub const INFERENCE_ON_VARIANT: &str = "INFERENCE_ON_VARIANT";
32pub const TYPE_MISMATCH: &str = "TYPE_MISMATCH";
34pub const NARROWING_CONVERSION: &str = "NARROWING_CONVERSION";
36pub const INTEGER_DIVISION: &str = "INTEGER_DIVISION";
38pub const UNSAFE_PROPERTY_ACCESS: &str = "UNSAFE_PROPERTY_ACCESS";
40pub const UNSAFE_METHOD_ACCESS: &str = "UNSAFE_METHOD_ACCESS";
42pub const UNSAFE_CALL_ARGUMENT: &str = "UNSAFE_CALL_ARGUMENT";
45pub const INVALID_NODE_PATH: &str = "INVALID_NODE_PATH";
49pub const SHADOWED_GLOBAL_IDENTIFIER: &str = "SHADOWED_GLOBAL_IDENTIFIER";
53pub const CYCLIC_INHERITANCE: &str = "CYCLIC_INHERITANCE";
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum BindingKind {
61 Var,
63 Param,
65 ForVar,
67 MatchBind,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct Binding {
74 pub name_range: TextRange,
76 pub ty: Ty,
79 pub init: Option<ExprId>,
81 pub annotated: bool,
83 pub inferred_colon_eq: bool,
85 pub kind: BindingKind,
87}
88
89#[derive(Debug, Clone, Default, PartialEq, Eq)]
91pub struct InferenceResult {
92 pub expr_ty: FxHashMap<ExprId, Ty>,
94 pub bindings: Vec<Binding>,
96 pub diagnostics: Vec<Diagnostic>,
98}
99
100impl InferenceResult {
101 #[must_use]
103 pub fn type_of(&self, id: ExprId) -> Option<&Ty> {
104 self.expr_ty.get(&id)
105 }
106
107 #[must_use]
109 pub fn binding_at(&self, offset: u32) -> Option<&Binding> {
110 self.bindings
111 .iter()
112 .find(|b| b.name_range.start <= offset && offset < b.name_range.end)
113 }
114}
115
116#[must_use]
120pub fn infer(
121 db: &dyn Db,
122 api: &EngineApi,
123 root: &GdNode,
124 class: &ClassScope,
125 body: &Body,
126 return_ty: Ty,
127) -> InferenceResult {
128 let self_ty = class.self_ty.clone();
129 let mut cx = Cx {
130 db,
131 api,
132 root,
133 body,
134 class,
135 self_ty,
136 return_ty,
137 expr_ty: FxHashMap::default(),
138 bindings: Vec::new(),
139 diagnostics: Vec::new(),
140 locals: FxHashMap::default(),
141 narrowing: FxHashMap::default(),
142 };
143 let params = body.params.clone();
145 for p in ¶ms {
146 let ty = cx.param_ty(p);
147 cx.bindings.push(Binding {
148 name_range: p.name_range,
149 ty: ty.clone(),
150 init: None,
151 annotated: p.type_ref.is_some(),
152 inferred_colon_eq: false,
153 kind: BindingKind::Param,
154 });
155 cx.locals.insert(p.name.clone(), ty);
156 }
157 if let Some(tail) = body.tail {
158 cx.infer_expr(tail, &Expectation::None);
159 }
160 let block = body.block.clone();
161 cx.infer_block(&block);
162 InferenceResult {
163 expr_ty: cx.expr_ty,
164 bindings: cx.bindings,
165 diagnostics: cx.diagnostics,
166 }
167}
168
169#[must_use]
172pub fn infer_func(
173 db: &dyn Db,
174 api: &EngineApi,
175 root: &GdNode,
176 class: &ClassScope,
177 ptr: AstPtr,
178) -> InferenceResult {
179 let Some(node) = ptr.to_node(root) else {
180 return InferenceResult::default();
181 };
182 let body = body::body_of_func(&node);
183 let return_ty = cst::first_child(&node, |k| k == gdscript_syntax::SyntaxKind::TypeRef)
186 .map_or(Ty::Variant, |t| resolve::resolve_type_ref(db, api, &t));
187 infer(db, api, root, class, &body, return_ty)
188}
189
190#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct Unit {
195 pub range: TextRange,
197 pub body: Body,
199 pub result: InferenceResult,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, Default)]
206pub struct FileInference {
207 pub tree: Arc<ItemTree>,
209 pub units: Vec<Unit>,
211 pub diagnostics: Vec<Diagnostic>,
213}
214
215impl FileInference {
216 #[must_use]
218 pub fn unit_at(&self, offset: u32) -> Option<&Unit> {
219 self.units
220 .iter()
221 .filter(|u| u.range.start <= offset && offset < u.range.end)
222 .min_by_key(|u| u.range.end - u.range.start)
223 }
224}
225
226#[must_use]
230pub fn analyze_file(db: &dyn Db, api: &EngineApi, root: &GdNode, file_id: FileId) -> FileInference {
231 let tree = item_tree(root);
232 let mut units = Vec::new();
233 let mut diagnostics = Vec::new();
234 let mut member_types: FxHashMap<SmolStr, Ty> = FxHashMap::default();
235 let self_ref = Ty::ScriptRef(ScriptRefId(file_id.0));
238 let res_path = db.file_text(file_id).and_then(|ft| ft.res_path(db));
240
241 if let Some(name) = tree.class_name.clone() {
246 let collides = collisions_contains(db, &name)
247 || resolve::resolve_global(api, &name).is_some()
248 || is_autoload_singleton(db, &name);
249 if collides && let Some(range) = class_name_decl_range(root) {
250 diagnostics.push(Diagnostic {
251 range,
252 severity: Severity::Warning,
253 code: SHADOWED_GLOBAL_IDENTIFIER.to_owned(),
254 message: format!(
255 "The global class \"{name}\" hides a built-in/native/global/autoload."
256 ),
257 source: DiagnosticSource::Type,
258 fixes: Vec::new(),
259 });
260 }
261 }
262
263 if extends_chain_is_cyclic(db, file_id)
271 && let Some(range) = extends_decl_range(root)
272 {
273 diagnostics.push(Diagnostic {
274 range,
275 severity: Severity::Warning,
276 code: CYCLIC_INHERITANCE.to_owned(),
277 message: "Cyclic class hierarchy: this class's `extends` chain returns to itself."
278 .to_owned(),
279 source: DiagnosticSource::Type,
280 fixes: Vec::new(),
281 });
282 }
283
284 {
294 const MAX_ROUNDS: usize = 4;
298 let mut final_units: Vec<Unit> = Vec::new();
299 let mut final_diagnostics: Vec<Diagnostic> = Vec::new();
300 for _ in 0..MAX_ROUNDS {
301 let mut class = ClassScope::new(db, api, &tree, res_path.as_deref());
302 class.self_ty = self_ref.clone();
303 class.member_types.clone_from(&member_types);
304 let mut next_member_types: FxHashMap<SmolStr, Ty> = FxHashMap::default();
305 final_units = Vec::new();
306 final_diagnostics = Vec::new();
307 for m in &tree.members {
308 let (ptr, range) = match m {
309 Member::Var(v) => (v.ptr, v.range),
310 Member::Const(c) => (c.ptr, c.range),
311 _ => continue,
312 };
313 if let Some(unit) = unit_from_decl(db, api, root, &class, ptr, range) {
314 if let (Some(name), Some(b)) = (m.name(), unit.result.bindings.first()) {
315 next_member_types.insert(SmolStr::new(name), b.ty.clone());
316 }
317 final_diagnostics.extend(unit.result.diagnostics.iter().cloned());
318 final_units.push(unit);
319 }
320 }
321 if next_member_types == member_types {
322 break;
323 }
324 member_types = next_member_types;
325 }
326 diagnostics.extend(final_diagnostics);
327 units.extend(final_units);
328 }
329
330 {
332 let mut class = ClassScope::new(db, api, &tree, res_path.as_deref());
333 class.member_types = member_types;
334 class.self_ty = self_ref.clone();
335 for m in &tree.members {
336 let Member::Func(f) = m else { continue };
337 let Some(node) = f.ptr.to_node(root) else {
338 continue;
339 };
340 let body = body::body_of_func(&node);
341 let return_ty = cst::first_child(&node, |k| k == gdscript_syntax::SyntaxKind::TypeRef)
342 .map_or(Ty::Variant, |t| resolve::resolve_type_ref(db, api, &t));
343 let result = infer(db, api, root, &class, &body, return_ty);
344 diagnostics.extend(result.diagnostics.iter().cloned());
345 units.push(Unit {
346 range: f.range,
347 body,
348 result,
349 });
350 }
351 }
352
353 FileInference {
354 tree,
355 units,
356 diagnostics,
357 }
358}
359
360fn collisions_contains(db: &dyn Db, name: &SmolStr) -> bool {
364 db.source_root()
365 .is_some_and(|root| crate::queries::class_name_collisions(db, root).contains(name))
366}
367
368fn is_autoload_singleton(db: &dyn Db, name: &str) -> bool {
371 db.project_config().is_some_and(|config| {
372 crate::queries::autoload_registry(db, config)
373 .resolve_path(name)
374 .is_some()
375 })
376}
377
378fn class_name_decl_range(root: &GdNode) -> Option<TextRange> {
383 use gdscript_syntax::SyntaxKind;
384 let decl = gdscript_syntax::ast::descendants(root)
385 .into_iter()
386 .find(|n| n.kind() == SyntaxKind::ClassNameDecl)?;
387 let name_node = decl.children().find(|c| c.kind() == SyntaxKind::Name)?;
388 let r = cst::text_range_of(name_node);
389 let text = name_node.text().to_string();
390 let lead = u32::try_from(text.len() - text.trim_start().len()).unwrap_or(0);
391 let len = u32::try_from(text.trim().len()).unwrap_or(0);
392 Some(TextRange::new(r.start + lead, r.start + lead + len))
393}
394
395fn extends_decl_range(root: &GdNode) -> Option<TextRange> {
402 use gdscript_syntax::SyntaxKind;
403 for child in root.children() {
404 match child.kind() {
405 SyntaxKind::ExtendsClause => return Some(cst::text_range_of(child)),
407 SyntaxKind::ClassNameDecl => {
409 if let Some(kw) = child.children().find(|c| c.kind() == SyntaxKind::ExtendsKw) {
410 let start = cst::text_range_of(kw).start;
411 let end = cst::text_range_of(child).end;
412 return Some(TextRange::new(start, end));
413 }
414 }
415 _ => {}
416 }
417 }
418 None
419}
420
421fn extends_chain_is_cyclic(db: &dyn Db, start: FileId) -> bool {
428 use std::collections::HashSet;
429 let mut visited: HashSet<FileId> = HashSet::new();
430 visited.insert(start);
431 let mut current = start;
432 for _ in 0..=64 {
433 let Some(file) = db.file_text(current) else {
434 return false;
435 };
436 let base = crate::queries::script_class(db, file).base().clone();
437 let Ty::ScriptRef(next) = base else {
438 return false; };
440 let next_id = FileId(next.0);
441 if !visited.insert(next_id) {
442 return true;
445 }
446 current = next_id;
447 }
448 false
449}
450
451fn unit_from_decl(
453 db: &dyn Db,
454 api: &EngineApi,
455 root: &GdNode,
456 class: &ClassScope,
457 ptr: AstPtr,
458 range: TextRange,
459) -> Option<Unit> {
460 let node = ptr.to_node(root)?;
461 let body = body::body_of_decl_stmt(&node);
462 let result = infer(db, api, root, class, &body, Ty::Variant);
463 Some(Unit {
464 range,
465 body,
466 result,
467 })
468}
469
470enum Expectation {
472 None,
474 Has(Ty),
476}
477
478struct Cx<'a> {
479 db: &'a dyn Db,
480 api: &'a EngineApi,
481 root: &'a GdNode,
482 body: &'a Body,
483 class: &'a ClassScope<'a>,
484 self_ty: Ty,
485 return_ty: Ty,
486 expr_ty: FxHashMap<ExprId, Ty>,
487 bindings: Vec<Binding>,
488 diagnostics: Vec<Diagnostic>,
489 locals: FxHashMap<SmolStr, Ty>,
491 narrowing: FxHashMap<String, Ty>,
493}
494
495impl Cx<'_> {
496 fn builtin(&self, name: &str) -> Ty {
499 self.api
500 .builtin_by_name(name)
501 .map_or(Ty::Variant, Ty::Builtin)
502 }
503 fn int_ty(&self) -> Ty {
504 self.builtin("int")
505 }
506 fn float_ty(&self) -> Ty {
507 self.builtin("float")
508 }
509 fn bool_ty(&self) -> Ty {
510 self.builtin("bool")
511 }
512 fn is_int(&self, ty: &Ty) -> bool {
513 matches!(ty, Ty::Builtin(b) if self.api.builtin(*b).name == "int")
514 }
515 fn is_float(&self, ty: &Ty) -> bool {
516 matches!(ty, Ty::Builtin(b) if self.api.builtin(*b).name == "float")
517 }
518 fn is_numeric(&self, ty: &Ty) -> bool {
519 self.is_int(ty) || self.is_float(ty)
520 }
521
522 fn emit(&mut self, range: TextRange, severity: Severity, code: &str, message: String) {
525 self.diagnostics.push(Diagnostic {
526 range,
527 severity,
528 code: code.to_owned(),
529 message,
530 source: DiagnosticSource::Type,
531 fixes: Vec::new(),
532 });
533 }
534
535 fn range_of(&self, id: ExprId) -> TextRange {
536 self.body.source_map.expr_range(id)
537 }
538
539 fn check_assign(&mut self, from: &Ty, to: &Ty, range: TextRange) {
542 match ty::is_assignable(self.api, from, to) {
543 Assign::Narrowing => self.emit(
544 range,
545 Severity::Warning,
546 NARROWING_CONVERSION,
547 "Narrowing conversion (float is converted to int and loses precision).".to_owned(),
548 ),
549 Assign::No => {
550 let to_label = to.label(self.api).unwrap_or_else(|| "?".to_owned());
551 let from_label = from.label(self.api).unwrap_or_else(|| "?".to_owned());
552 self.emit(
553 range,
554 Severity::Error,
555 TYPE_MISMATCH,
556 format!(
557 "Cannot assign a value of type \"{from_label}\" to a target of type \"{to_label}\"."
558 ),
559 );
560 }
561 Assign::Ok | Assign::OkUnsafe | Assign::IntAsEnum => {}
562 }
563 }
564
565 fn infer_block(&mut self, block: &[body::StmtId]) {
568 for &stmt in block {
569 self.infer_stmt(stmt);
570 }
571 }
572
573 fn infer_stmt(&mut self, id: body::StmtId) {
574 match self.body.stmt(id).clone() {
575 Stmt::Expr(e) => {
576 self.infer_expr(e, &Expectation::None);
577 }
578 Stmt::Var(v) => self.infer_local_var(&v),
579 Stmt::Return(e) => {
580 if let Some(e) = e {
581 let expected = if self.return_ty.is_uninformative() {
582 Expectation::None
583 } else {
584 Expectation::Has(self.return_ty.clone())
585 };
586 let t = self.infer_expr(e, &expected);
587 if let Expectation::Has(ret) = expected {
588 self.check_assign(&t, &ret, self.range_of(e));
589 }
590 }
591 }
592 Stmt::If {
593 cond,
594 then_branch,
595 elifs,
596 else_branch,
597 } => {
598 self.infer_expr(cond, &Expectation::None);
599 self.in_branch(|cx| {
600 cx.apply_narrowing(cond);
601 cx.infer_block(&then_branch);
602 });
603 for (econd, eblock) in elifs {
604 self.infer_expr(econd, &Expectation::None);
605 self.in_branch(|cx| {
606 cx.apply_narrowing(econd);
607 cx.infer_block(&eblock);
608 });
609 }
610 if let Some(eb) = else_branch {
611 self.in_branch(|cx| cx.infer_block(&eb));
612 }
613 }
614 Stmt::While { cond, body } => {
615 self.infer_expr(cond, &Expectation::None);
616 self.in_branch(|cx| cx.infer_block(&body));
617 }
618 Stmt::For(f) => {
619 let iter_ty = self.infer_expr(f.iter, &Expectation::None);
620 let var_ty = f.var_type.as_ref().map_or_else(
621 || self.loop_var_ty(&iter_ty),
622 |ptr| self.resolve_ptr_ty(*ptr),
623 );
624 self.bindings.push(Binding {
625 name_range: f.var_range,
626 ty: var_ty.clone(),
627 init: None,
628 annotated: f.var_type.is_some(),
629 inferred_colon_eq: false,
630 kind: BindingKind::ForVar,
631 });
632 self.locals.insert(f.var.clone(), var_ty);
633 self.in_branch(|cx| cx.infer_block(&f.body));
634 }
635 Stmt::Match { scrutinee, arms } => {
636 self.infer_expr(scrutinee, &Expectation::None);
637 for arm in arms {
638 self.in_branch(|cx| {
639 for b in &arm.binds {
640 cx.bindings.push(Binding {
644 name_range: b.range,
645 ty: Ty::Variant,
646 init: None,
647 annotated: false,
648 inferred_colon_eq: false,
649 kind: BindingKind::MatchBind,
650 });
651 cx.locals.insert(b.name.clone(), Ty::Variant);
652 }
653 if let Some(g) = arm.guard {
654 cx.infer_expr(g, &Expectation::None);
655 }
656 cx.infer_block(&arm.body);
657 });
658 }
659 }
660 Stmt::Break | Stmt::Continue | Stmt::Pass => {}
661 Stmt::Assert(cond) => {
662 if let Some(cond) = cond {
663 self.infer_expr(cond, &Expectation::None);
664 }
665 }
666 }
667 }
668
669 fn infer_local_var(&mut self, v: &body::LocalVar) {
670 let annotated = v.type_ref.map(|p| self.resolve_ptr_ty(p));
671 let init_ty = v.init.map(|e| {
672 let expected = annotated
673 .as_ref()
674 .map_or(Expectation::None, |t| Expectation::Has(t.clone()));
675 self.infer_expr(e, &expected)
676 });
677 let range = v.init.map_or(v.name_range, |e| self.range_of(e));
678
679 let binding_ty = match (&annotated, &init_ty) {
680 (Some(t), Some(init)) => {
682 self.check_assign(init, t, range);
683 t.clone()
684 }
685 (Some(t), None) => t.clone(),
687 (None, Some(init)) if v.is_inferred => {
689 if init.is_variant() {
690 self.emit(
691 range,
692 Severity::Error,
693 INFERENCE_ON_VARIANT,
694 inference_on_variant_msg(if v.is_const { "constant" } else { "variable" }),
695 );
696 Ty::Variant
697 } else {
698 init.clone()
700 }
701 }
702 (None, Some(init)) => {
704 if v.is_const {
705 init.clone()
706 } else {
707 Ty::Variant
708 }
709 }
710 (None, None) => Ty::Variant,
711 };
712 self.bindings.push(Binding {
713 name_range: v.name_range,
714 ty: binding_ty.clone(),
715 init: v.init,
716 annotated: v.type_ref.is_some(),
717 inferred_colon_eq: v.is_inferred,
718 kind: BindingKind::Var,
719 });
720 self.narrowing.remove(v.name.as_str());
721 self.locals.insert(v.name.clone(), binding_ty);
722 }
723
724 fn infer_expr(&mut self, id: ExprId, expected: &Expectation) -> Ty {
727 let ty = self.synth_expr(id, expected);
728 self.expr_ty.insert(id, ty.clone());
729 ty
730 }
731
732 #[allow(clippy::too_many_lines)]
733 fn synth_expr(&mut self, id: ExprId, expected: &Expectation) -> Ty {
734 match self.body.expr(id).clone() {
735 Expr::Missing => Ty::Error,
736 Expr::Literal(lit) => self.literal_ty(lit),
737 Expr::Name(name) => self.resolve_name(id, &name),
738 Expr::SelfExpr => self.self_ty.clone(),
739 Expr::Super => self.class.base.clone(),
740 Expr::Paren(inner) => self.infer_expr(inner, expected),
741 Expr::Bin { op, lhs, rhs } => self.infer_bin(id, op, lhs, rhs),
742 Expr::Unary { op, operand } => {
743 let t = self.infer_expr(operand, &Expectation::None);
744 match op {
745 UnOp::Not => self.bool_ty(),
746 UnOp::BitNot => self.int_ty(),
747 UnOp::Neg | UnOp::Pos => {
748 if t.is_uninformative() || self.is_numeric(&t) {
749 t
750 } else {
751 Ty::Variant
752 }
753 }
754 }
755 }
756 Expr::Ternary {
757 cond,
758 then_branch,
759 else_branch,
760 } => {
761 self.infer_expr(cond, &Expectation::None);
762 let a = self.infer_expr(then_branch, expected);
763 let b = self.infer_expr(else_branch, expected);
764 if self.is_null(else_branch) {
766 a
767 } else if self.is_null(then_branch) {
768 b
769 } else {
770 self.join(&a, &b)
771 }
772 }
773 Expr::Call { callee, args } => self.infer_call(callee, &args),
774 Expr::Field {
775 receiver,
776 name,
777 name_range,
778 } => {
779 self.infer_field(receiver, &name, name_range, false)
780 }
781 Expr::Index { base, index } => {
782 let base_ty = self.infer_expr(base, &Expectation::None);
783 self.infer_expr(index, &Expectation::None);
784 self.index_ty(&base_ty)
785 }
786 Expr::Is { operand, .. } => {
787 self.infer_expr(operand, &Expectation::None);
788 self.bool_ty()
789 }
790 Expr::Cast { operand, ty } => {
791 self.infer_expr(operand, &Expectation::None);
792 ty.map_or(Ty::Variant, |p| self.resolve_ptr_ty(p))
793 }
794 Expr::In { lhs, rhs, .. } => {
795 self.infer_expr(lhs, &Expectation::None);
796 self.infer_expr(rhs, &Expectation::None);
797 self.bool_ty()
798 }
799 Expr::Await(operand) => {
800 let operand_ty = self.infer_expr(operand, &Expectation::None);
801 if matches!(operand_ty, Ty::Signal(_)) {
806 Ty::Unknown
807 } else {
808 operand_ty
809 }
810 }
811 Expr::Array(elems) => {
812 let pushed = match expected {
816 Expectation::Has(Ty::Array(e)) => Some((**e).clone()),
817 _ => None,
818 };
819 let elem_exp = pushed.clone().map_or(Expectation::None, Expectation::Has);
820 for e in elems {
821 self.infer_expr(e, &elem_exp);
822 }
823 pushed.map_or_else(Ty::array_of_variant, |e| Ty::Array(Box::new(e)))
824 }
825 Expr::Dict(entries) => {
826 let pushed = match expected {
827 Expectation::Has(Ty::Dict(k, v)) => Some(((**k).clone(), (**v).clone())),
828 _ => None,
829 };
830 let (kx, vx) = pushed
831 .clone()
832 .map_or((Expectation::None, Expectation::None), |(k, v)| {
833 (Expectation::Has(k), Expectation::Has(v))
834 });
835 for (k, v) in entries {
836 self.infer_expr(k, &kx);
837 if let Some(v) = v {
838 self.infer_expr(v, &vx);
839 }
840 }
841 pushed.map_or_else(Ty::dict_of_variant, |(k, v)| {
842 Ty::Dict(Box::new(k), Box::new(v))
843 })
844 }
845 Expr::Lambda { params, body } => {
846 self.infer_lambda(¶ms, &body);
847 Ty::Callable
848 }
849 Expr::Preload { arg, path } => {
850 if let Some(arg) = arg {
851 self.infer_expr(arg, &Expectation::None);
852 }
853 match path {
858 Some(p) => {
862 match resolve::anchor_res_path(self.self_res_path().as_deref(), &p) {
863 Some(abs) => resolve::resolve_external(
864 self.db,
865 &resolve::ExternalRef::Preload(abs),
866 ),
867 None => Ty::Unknown,
868 }
869 }
870 None => Ty::Unknown,
871 }
872 }
873 Expr::GetNode { path, unique } => self.resolve_node_path(id, path.as_deref(), unique),
876 }
877 }
878
879 fn is_null(&self, id: ExprId) -> bool {
881 matches!(self.body.expr(id), Expr::Literal(Literal::Null))
882 }
883
884 fn literal_ty(&self, lit: Literal) -> Ty {
885 match lit {
886 Literal::Int => self.int_ty(),
887 Literal::Float | Literal::MathConst => self.float_ty(),
888 Literal::Bool => self.bool_ty(),
889 Literal::Str => self.builtin("String"),
890 Literal::StringName => self.builtin("StringName"),
891 Literal::NodePath => self.builtin("NodePath"),
892 Literal::Null => Ty::Variant,
894 }
895 }
896
897 fn node_ty(&self) -> Ty {
898 self.api
899 .class_by_name("Node")
900 .map_or(Ty::Unknown, Ty::Object)
901 }
902
903 fn resolve_node_path(&mut self, id: ExprId, path: Option<&str>, unique: bool) -> Ty {
911 use gdscript_scene::NodePathResolution as R;
912 let fallback = self.node_ty();
913 let Some(path) = path else {
914 return fallback; };
916 let Some(ctx) = self.owning_scene() else {
917 return fallback; };
919 let resolution = if unique {
920 ctx.model.classify_unique(path)
921 } else {
922 ctx.model.classify_path_from(ctx.attach, path)
923 };
924 match resolution {
925 R::Resolved(idx) => ctx
926 .model
927 .node(idx)
928 .and_then(|n| self.scene_node_ty(&ctx.model, n, 0))
929 .unwrap_or(fallback),
930 R::Missing if !ctx.ambiguous => {
931 let what = if unique { "unique name" } else { "node path" };
932 let sigil = if unique { "%" } else { "$" };
933 self.emit(
934 self.range_of(id),
935 Severity::Warning,
936 INVALID_NODE_PATH,
937 format!("no {what} `{sigil}{path}` in the owning scene"),
938 );
939 fallback
940 }
941 R::IntoInstance => {
944 let walked = if unique {
945 ctx.model.resolve_unique_into_instance(path)
946 } else {
947 ctx.model.resolve_into_instance(ctx.attach, path)
948 };
949 walked
950 .and_then(|(inst, tail)| {
951 let inst_node = ctx.model.node(inst)?;
952 self.resolve_into_instance_ty(&ctx.model, inst_node, &tail, 0)
953 })
954 .unwrap_or(fallback)
955 }
956 _ => fallback,
958 }
959 }
960
961 fn owning_scene(&self) -> Option<crate::queries::SceneContext> {
965 let Ty::ScriptRef(sref) = &self.self_ty else {
966 return None;
967 };
968 let ft = self.db.file_text(FileId(sref.0))?;
969 crate::queries::scene_context(self.db, ft)
970 }
971
972 fn self_res_path(&self) -> Option<SmolStr> {
975 let Ty::ScriptRef(sref) = &self.self_ty else {
976 return None;
977 };
978 self.db.file_text(FileId(sref.0))?.res_path(self.db)
979 }
980
981 fn scene_node_ty(&self, scene: &SceneModel, node: &SceneNode, depth: u32) -> Option<Ty> {
986 if let Some(script_ty) = self.node_script_ref(scene, node) {
987 return Some(script_ty);
988 }
989 if let Some(decl) = node.decl_type.as_ref() {
990 let ty = resolve::resolve_type_name(self.db, self.api, decl);
991 if !ty.is_uninformative() {
992 return Some(ty);
993 }
994 }
995 self.instance_root_ty(scene, node, depth)
996 }
997
998 fn instance_root_ty(&self, scene: &SceneModel, node: &SceneNode, depth: u32) -> Option<Ty> {
1003 if depth >= 16 {
1004 return None;
1005 }
1006 let (sub, sub_root) = self.instance_subscene(scene, node)?;
1007 let root_node = sub.node(sub_root)?;
1008 self.scene_node_ty(&sub, root_node, depth + 1)
1009 }
1010
1011 fn instance_subscene(
1016 &self,
1017 scene: &SceneModel,
1018 node: &SceneNode,
1019 ) -> Option<(Arc<SceneModel>, gdscript_scene::NodeIdx)> {
1020 let inst = node.instance.as_ref()?;
1021 let path = scene.ext_resources.get(inst)?.path.as_ref()?;
1022 let root = self.db.source_root()?;
1023 let file = crate::queries::res_path_registry(self.db, root)
1024 .get(path.as_str())
1025 .copied()?;
1026 let ft = self.db.file_text(file)?;
1027 let sub = crate::queries::scene_model(self.db, ft);
1028 let sub_root = sub.root?;
1029 Some((sub, sub_root))
1030 }
1031
1032 fn resolve_into_instance_ty(
1037 &self,
1038 scene: &SceneModel,
1039 instance_node: &SceneNode,
1040 tail: &str,
1041 depth: u32,
1042 ) -> Option<Ty> {
1043 if depth >= 16 {
1044 return None;
1045 }
1046 let (sub, sub_root) = self.instance_subscene(scene, instance_node)?;
1047 if let Some(idx) = sub.resolve_path_from(sub_root, tail) {
1048 let n = sub.node(idx)?;
1049 return self.scene_node_ty(&sub, n, depth + 1);
1050 }
1051 let (inner, inner_tail) = sub.resolve_into_instance(sub_root, tail)?;
1053 let inner_node = sub.node(inner)?;
1054 self.resolve_into_instance_ty(&sub, inner_node, &inner_tail, depth + 1)
1055 }
1056
1057 fn node_script_ref(&self, scene: &SceneModel, node: &SceneNode) -> Option<Ty> {
1060 let path = scene
1061 .ext_resources
1062 .get(node.script.as_ref()?)?
1063 .path
1064 .as_ref()?;
1065 let root = self.db.source_root()?;
1066 let file = crate::queries::res_path_registry(self.db, root)
1067 .get(path.as_str())
1068 .copied()?;
1069 Some(Ty::ScriptRef(ScriptRefId(file.0)))
1070 }
1071
1072 fn infer_bin(&mut self, id: ExprId, op: BinOp, lhs: ExprId, rhs: ExprId) -> Ty {
1073 if op == BinOp::Assign {
1074 return self.infer_assign(lhs, rhs);
1075 }
1076 let lt = self.infer_expr(lhs, &Expectation::None);
1077 let rt = self.infer_expr(rhs, &Expectation::None);
1078 if op.is_boolean() {
1079 return self.bool_ty();
1080 }
1081 if op == BinOp::Div && self.is_int(<) && self.is_int(&rt) {
1083 self.emit(
1084 self.range_of(id),
1085 Severity::Warning,
1086 INTEGER_DIVISION,
1087 "Integer division. Decimal part will be discarded.".to_owned(),
1088 );
1089 return self.int_ty();
1090 }
1091 self.bin_result(op, <, &rt)
1092 }
1093
1094 fn infer_assign(&mut self, lhs: ExprId, rhs: ExprId) -> Ty {
1095 let slot = self.infer_expr(lhs, &Expectation::None);
1096 let expected = if slot.is_uninformative() {
1097 Expectation::None
1098 } else {
1099 Expectation::Has(slot.clone())
1100 };
1101 let value = self.infer_expr(rhs, &expected);
1102 if !slot.is_uninformative() {
1103 self.check_assign(&value, &slot, self.range_of(rhs));
1104 }
1105 if let Some(key) = self.narrow_key(lhs) {
1107 let narrowed = if slot.is_uninformative() {
1108 value.clone()
1109 } else {
1110 slot.clone()
1111 };
1112 self.narrowing.insert(key, narrowed);
1113 }
1114 slot
1115 }
1116
1117 fn bin_result(&self, op: BinOp, lt: &Ty, rt: &Ty) -> Ty {
1120 if let (Ty::Builtin(b), Some(sym)) = (lt, op_symbol(op)) {
1121 for o in self.api.builtin_operators(*b) {
1122 if o.op == sym
1123 && let Some(right) = &o.right
1124 && self.tyref_matches(right, rt)
1125 {
1126 return ty::resolve_tyref(self.api, &o.result);
1127 }
1128 }
1129 }
1130 if self.is_numeric(lt) && self.is_numeric(rt) {
1131 return if self.is_float(lt) || self.is_float(rt) {
1132 self.float_ty()
1133 } else {
1134 self.int_ty()
1135 };
1136 }
1137 if lt.is_unknown() || rt.is_unknown() || lt.is_error() || rt.is_error() {
1140 return Ty::Unknown;
1141 }
1142 Ty::Variant
1143 }
1144
1145 fn tyref_matches(&self, tyref: &TyRef, ty: &Ty) -> bool {
1146 let resolved = ty::resolve_tyref(self.api, tyref);
1147 resolved.is_variant() || &resolved == ty
1148 }
1149
1150 fn infer_call(&mut self, callee: ExprId, args: &[ExprId]) -> Ty {
1151 for &a in args {
1153 self.infer_expr(a, &Expectation::None);
1154 }
1155 let ret = match self.body.expr(callee).clone() {
1156 Expr::Field {
1157 receiver,
1158 name,
1159 name_range,
1160 } => {
1161 self.infer_field(receiver, &name, name_range, true)
1162 }
1163 Expr::Name(name) => {
1164 let ret = self.resolve_call_name(&name);
1165 self.expr_ty.insert(callee, Ty::Callable);
1166 ret
1167 }
1168 _ => {
1172 self.infer_expr(callee, &Expectation::None);
1173 Ty::Unknown
1174 }
1175 };
1176 self.check_call_args(callee, args);
1179 ret
1180 }
1181
1182 fn check_call_args(&mut self, callee: ExprId, args: &[ExprId]) {
1188 let Some(params) = self.call_param_tys(callee) else {
1189 return;
1190 };
1191 for (i, &arg) in args.iter().enumerate() {
1192 let Some(param_ty) = params.get(i) else {
1193 break; };
1195 if param_ty.is_uninformative() || param_ty.is_variant() {
1196 continue; }
1198 let arg_ty = self.expr_ty.get(&arg).cloned().unwrap_or(Ty::Unknown);
1200 if ty::is_assignable(self.api, &arg_ty, param_ty) == Assign::OkUnsafe {
1201 let pl = param_ty.label(self.api).unwrap_or_else(|| "?".to_owned());
1202 let al = arg_ty.label(self.api).unwrap_or_else(|| "?".to_owned());
1203 self.emit(
1204 self.range_of(arg),
1205 Severity::Warning,
1206 UNSAFE_CALL_ARGUMENT,
1207 format!(
1208 "The argument {} requires a value of type \"{pl}\" but is passed \"{al}\", which is unsafe.",
1209 i + 1
1210 ),
1211 );
1212 }
1213 }
1214 }
1215
1216 fn call_param_tys(&self, callee: ExprId) -> Option<Vec<Ty>> {
1220 match self.body.expr(callee) {
1221 Expr::Name(name) => self.name_call_param_tys(name),
1222 Expr::Field { receiver, name, .. } => match self.expr_ty.get(receiver)? {
1223 Ty::Object(class) => match self.api.lookup_member(*class, name)? {
1224 MemberRef::Method(sig) => Some(
1225 sig.params
1226 .iter()
1227 .map(|p| ty::resolve_tyref(self.api, &p.ty))
1228 .collect(),
1229 ),
1230 _ => None,
1231 },
1232 _ => None,
1234 },
1235 _ => None,
1236 }
1237 }
1238
1239 fn name_call_param_tys(&self, name: &str) -> Option<Vec<Ty>> {
1243 if let Some(item) = self.class.lookup(name)
1244 && let Some(Member::Func(f)) = self.class.member(item)
1245 {
1246 return Some(
1247 f.params
1248 .iter()
1249 .map(|p| {
1250 p.type_ref.as_deref().map_or(Ty::Variant, |t| {
1251 resolve::resolve_type_name(self.db, self.api, t)
1252 })
1253 })
1254 .collect(),
1255 );
1256 }
1257 if let Ty::Object(base) = self.class.base
1258 && let Some(MemberRef::Method(sig)) = self.api.lookup_member(base, name)
1259 {
1260 return Some(
1261 sig.params
1262 .iter()
1263 .map(|p| ty::resolve_tyref(self.api, &p.ty))
1264 .collect(),
1265 );
1266 }
1267 None
1268 }
1269
1270 fn resolve_call_name(&self, name: &str) -> Ty {
1272 if let Some(item) = self.class.lookup(name)
1273 && let Some(Member::Func(f)) = self.class.member(item)
1274 {
1275 return self.func_return_ty(f.return_type.as_deref());
1276 }
1277 if let Ty::Object(base) = self.class.base
1279 && let Some(MemberRef::Method(sig)) = self.api.lookup_member(base, name)
1280 {
1281 return ty::resolve_tyref(self.api, &sig.return_ty);
1282 }
1283 if let Some(u) = self.api.utility(name) {
1284 return ty::resolve_tyref(self.api, &u.return_ty);
1285 }
1286 if let Some(f) = self.api.gdscript_builtin(name) {
1287 return resolve::layer_to_ty(self.api, f.ret);
1288 }
1289 if let Some(b) = self.api.builtin_by_name(name) {
1293 return ty::resolve_tyref(self.api, &TyRef::Builtin(b));
1294 }
1295 Ty::Unknown
1298 }
1299
1300 fn func_return_ty(&self, annotation: Option<&str>) -> Ty {
1301 annotation.map_or(Ty::Variant, |t| {
1302 resolve::resolve_type_name(self.db, self.api, t)
1303 })
1304 }
1305
1306 fn infer_field(
1310 &mut self,
1311 receiver: ExprId,
1312 name: &str,
1313 name_range: TextRange,
1314 as_method: bool,
1315 ) -> Ty {
1316 let is_self = matches!(self.body.expr(receiver), Expr::SelfExpr);
1317 let recv_ty = self.infer_expr(receiver, &Expectation::None);
1318
1319 if is_self && let Some(item) = self.class.lookup(name) {
1321 return self.own_member_ty(item, as_method);
1322 }
1323
1324 match &recv_ty {
1325 t if t.is_uninformative() => recv_ty.clone(),
1330 Ty::Object(class) => {
1331 if name == "new" {
1332 recv_ty.clone()
1335 } else if let Some(m) = self.api.lookup_member(*class, name) {
1336 self.member_ref_ty(&m, as_method)
1337 } else if let Some(t) = self.class_enum_value(*class, name) {
1338 t
1340 } else {
1341 self.emit_unsafe(name, &recv_ty, name_range, as_method);
1343 Ty::Variant
1344 }
1345 }
1346 Ty::Builtin(_) | Ty::Array(_) | Ty::Dict(..) | Ty::Callable | Ty::Signal(_) => {
1347 self.builtin_member_ty(&recv_ty, name, name_range, as_method)
1348 }
1349 Ty::Enum(_) => self.int_ty(),
1351 Ty::ScriptRef(sref) => self.script_member_ty(*sref, name, as_method),
1353 _ => Ty::Variant,
1354 }
1355 }
1356
1357 fn script_member_ty(&self, sref: ScriptRefId, name: &str, as_method: bool) -> Ty {
1362 if name == "new" {
1363 return Ty::ScriptRef(sref);
1364 }
1365 self.script_member_walk(sref, name, as_method, 0)
1366 .unwrap_or(Ty::Unknown)
1367 }
1368
1369 fn script_member_walk(
1373 &self,
1374 sref: ScriptRefId,
1375 name: &str,
1376 as_method: bool,
1377 depth: u32,
1378 ) -> Option<Ty> {
1379 if depth > 32 {
1380 return None;
1381 }
1382 let file = self.db.file_text(FileId(sref.0))?;
1383 let sc = crate::queries::script_class(self.db, file);
1384 if let Some(m) = sc.member(name) {
1385 return Some(match m {
1386 crate::queries::MemberSig::Method(ret) => {
1387 if as_method {
1388 ret.clone()
1389 } else {
1390 Ty::Callable
1391 }
1392 }
1393 crate::queries::MemberSig::Field(t) => t.clone(),
1394 crate::queries::MemberSig::Signal => Ty::Signal(None),
1395 });
1396 }
1397 match sc.base() {
1399 Ty::ScriptRef(base) => self.script_member_walk(*base, name, as_method, depth + 1),
1400 Ty::Object(class) => self
1401 .api
1402 .lookup_member(*class, name)
1403 .map(|m| self.member_ref_ty(&m, as_method)),
1404 _ => None,
1405 }
1406 }
1407
1408 fn is_subtype(&self, sub: &Ty, sup: &Ty) -> bool {
1413 match (sub, sup) {
1414 (Ty::Object(a), Ty::Object(b)) => self.api.is_subclass(*a, *b),
1415 (Ty::ScriptRef(a), Ty::ScriptRef(b)) => self.script_is_subtype(*a, *b, 0),
1416 (Ty::ScriptRef(a), Ty::Object(b)) => self.script_extends_engine(*a, *b, 0),
1417 _ => false,
1418 }
1419 }
1420
1421 fn script_is_subtype(&self, sub: ScriptRefId, sup: ScriptRefId, depth: u32) -> bool {
1424 if depth > 32 {
1425 return false;
1426 }
1427 if sub == sup {
1428 return true;
1429 }
1430 let Some(file) = self.db.file_text(FileId(sub.0)) else {
1431 return false;
1432 };
1433 match crate::queries::script_class(self.db, file).base() {
1434 Ty::ScriptRef(base) => self.script_is_subtype(*base, sup, depth + 1),
1435 _ => false,
1436 }
1437 }
1438
1439 fn script_extends_engine(
1441 &self,
1442 sub: ScriptRefId,
1443 sup_native: gdscript_api::ClassId,
1444 depth: u32,
1445 ) -> bool {
1446 if depth > 32 {
1447 return false;
1448 }
1449 let Some(file) = self.db.file_text(FileId(sub.0)) else {
1450 return false;
1451 };
1452 match crate::queries::script_class(self.db, file).base() {
1453 Ty::ScriptRef(base) => self.script_extends_engine(*base, sup_native, depth + 1),
1454 Ty::Object(native) => self.api.is_subclass(*native, sup_native),
1455 _ => false,
1456 }
1457 }
1458
1459 fn emit_unsafe(&mut self, name: &str, recv: &Ty, range: TextRange, as_method: bool) {
1460 let recv_label = recv.label(self.api).unwrap_or_else(|| "?".to_owned());
1461 let (code, message) = if as_method {
1462 (
1463 UNSAFE_METHOD_ACCESS,
1464 format!(
1465 "The method \"{name}()\" is not present on the inferred type \"{recv_label}\" (but may be present on a subtype)."
1466 ),
1467 )
1468 } else {
1469 (
1470 UNSAFE_PROPERTY_ACCESS,
1471 format!(
1472 "The property \"{name}\" is not present on the inferred type \"{recv_label}\" (but may be present on a subtype)."
1473 ),
1474 )
1475 };
1476 self.emit(range, Severity::Warning, code, message);
1477 }
1478
1479 fn member_ref_ty(&self, m: &MemberRef, as_method: bool) -> Ty {
1480 match m {
1481 MemberRef::Method(sig) => {
1482 if as_method {
1483 ty::resolve_tyref(self.api, &sig.return_ty)
1484 } else {
1485 Ty::Callable
1486 }
1487 }
1488 MemberRef::Property(p) => p.enum_of.as_ref().map_or_else(
1489 || ty::resolve_tyref(self.api, &p.ty),
1490 |q| {
1491 Ty::Enum(EnumRef {
1492 qualified: SmolStr::new(q),
1493 bitfield: false,
1494 })
1495 },
1496 ),
1497 MemberRef::Const(c) => ty::resolve_tyref(self.api, &c.ty),
1498 MemberRef::Signal(_) => Ty::Signal(None),
1499 MemberRef::Enum(_) => Ty::Variant,
1500 }
1501 }
1502
1503 fn builtin_member_ty(
1504 &mut self,
1505 recv: &Ty,
1506 name: &str,
1507 range: TextRange,
1508 as_method: bool,
1509 ) -> Ty {
1510 let Some(bid) = self.builtin_id_of(recv) else {
1511 return Ty::Variant;
1512 };
1513 if as_method {
1514 return if let Some(sig) = self.api.builtin_method(bid, name) {
1515 ty::resolve_tyref(self.api, &sig.return_ty)
1516 } else {
1517 self.emit_unsafe(name, recv, range, true);
1518 Ty::Variant
1519 };
1520 }
1521 if let Some(member) = self.api.builtin_member(bid, name) {
1522 return ty::resolve_tyref(self.api, &member.ty);
1523 }
1524 let data = self.api.builtin(bid);
1526 if let Some(c) = data.constants.iter().find(|c| c.name == name) {
1527 return ty::resolve_tyref(self.api, &c.ty);
1528 }
1529 if data
1530 .enums
1531 .iter()
1532 .any(|e| e.values.iter().any(|v| v.name == name))
1533 {
1534 return self.int_ty();
1535 }
1536 if self.api.builtin_method(bid, name).is_some() {
1537 return Ty::Callable;
1538 }
1539 self.emit_unsafe(name, recv, range, false);
1540 Ty::Variant
1541 }
1542
1543 fn class_enum_value(&self, class: gdscript_api::ClassId, name: &str) -> Option<Ty> {
1547 let mut cur = Some(class);
1548 while let Some(cid) = cur {
1549 let c = self.api.class(cid);
1550 if c.enums
1551 .iter()
1552 .any(|e| e.values.iter().any(|v| v.name == name))
1553 {
1554 return Some(self.int_ty());
1555 }
1556 cur = c.base;
1557 }
1558 None
1559 }
1560
1561 fn builtin_id_of(&self, ty: &Ty) -> Option<gdscript_api::BuiltinId> {
1563 match ty {
1564 Ty::Builtin(b) => Some(*b),
1565 Ty::Array(_) => self.api.builtin_by_name("Array"),
1566 Ty::Dict(..) => self.api.builtin_by_name("Dictionary"),
1567 Ty::Callable => self.api.builtin_by_name("Callable"),
1568 Ty::Signal(_) => self.api.builtin_by_name("Signal"),
1569 _ => None,
1570 }
1571 }
1572
1573 fn index_ty(&self, base: &Ty) -> Ty {
1575 match base {
1576 Ty::Array(elem) => (**elem).clone(),
1577 Ty::Builtin(b) => self
1578 .api
1579 .builtin(*b)
1580 .indexing_return
1581 .as_ref()
1582 .map_or(Ty::Variant, |r| ty::resolve_tyref(self.api, r)),
1583 Ty::Unknown => Ty::Unknown,
1585 Ty::Error => Ty::Error,
1586 _ => Ty::Variant,
1587 }
1588 }
1589
1590 fn loop_var_ty(&self, iter: &Ty) -> Ty {
1592 match iter {
1593 Ty::Array(elem) => (**elem).clone(),
1594 Ty::Builtin(b) => {
1595 let data = self.api.builtin(*b);
1596 if data.name == "int" {
1597 self.int_ty()
1599 } else if let Some(r) = &data.indexing_return {
1600 ty::resolve_tyref(self.api, r)
1602 } else {
1603 Ty::Variant
1604 }
1605 }
1606 Ty::Unknown => Ty::Unknown,
1608 Ty::Error => Ty::Error,
1609 _ => Ty::Variant,
1610 }
1611 }
1612
1613 fn infer_lambda(&mut self, params: &[ParamBinding], body: &[body::StmtId]) {
1614 let saved_locals = self.locals.clone();
1618 let saved_ret = std::mem::replace(&mut self.return_ty, Ty::Variant);
1619 for p in params {
1620 let ty = self.param_ty(p);
1621 self.bindings.push(Binding {
1622 name_range: p.name_range,
1623 ty: ty.clone(),
1624 init: None,
1625 annotated: p.type_ref.is_some(),
1626 inferred_colon_eq: false,
1627 kind: BindingKind::Param,
1628 });
1629 self.locals.insert(p.name.clone(), ty);
1630 }
1631 self.infer_block(body);
1632 self.return_ty = saved_ret;
1633 self.locals = saved_locals;
1634 }
1635
1636 fn param_ty(&mut self, p: &ParamBinding) -> Ty {
1637 if let Some(ptr) = p.type_ref {
1638 return self.resolve_ptr_ty(ptr);
1639 }
1640 p.default
1642 .map_or(Ty::Variant, |e| self.infer_expr(e, &Expectation::None))
1643 }
1644
1645 fn resolve_name(&mut self, id: ExprId, name: &str) -> Ty {
1648 if let Some(key) = self.narrow_key(id)
1650 && let Some(t) = self.narrowing.get(&key)
1651 {
1652 return t.clone();
1653 }
1654 if let Some(t) = self.locals.get(name) {
1655 return t.clone();
1656 }
1657 if let Some(item) = self.class.lookup(name) {
1658 return self.own_member_ty(item, false);
1659 }
1660 match self.class.base.clone() {
1664 Ty::Object(base) => {
1665 if let Some(m) = self.api.lookup_member(base, name) {
1666 return self.member_ref_ty(&m, false);
1667 }
1668 }
1669 Ty::ScriptRef(base) => {
1670 if let Some(t) = self.script_member_walk(base, name, false, 0) {
1671 return t;
1672 }
1673 }
1674 _ => {}
1675 }
1676 if let Some(g) = resolve::resolve_global(self.api, name) {
1677 return global_ty(&g);
1678 }
1679 let by_class = resolve::resolve_external(
1684 self.db,
1685 &resolve::ExternalRef::ClassName(SmolStr::new(name)),
1686 );
1687 if !by_class.is_unknown() {
1688 return by_class;
1689 }
1690 resolve::resolve_external(self.db, &resolve::ExternalRef::Autoload(SmolStr::new(name)))
1691 }
1692
1693 fn own_member_ty(&self, item: ClassItem, as_method: bool) -> Ty {
1694 match item {
1695 ClassItem::EnumVariant => self.int_ty(),
1696 ClassItem::Member(_) => match self.class.member(item) {
1697 Some(Member::Var(v)) => self.field_ty(&v.name, v.ptr),
1698 Some(Member::Const(c)) => self.field_ty(&c.name, c.ptr),
1699 Some(Member::Func(f)) => {
1700 if as_method {
1701 self.func_return_ty(f.return_type.as_deref())
1702 } else {
1703 Ty::Callable
1704 }
1705 }
1706 Some(Member::Signal(_)) => Ty::Signal(None),
1707 Some(Member::Class(_)) => Ty::Unknown,
1708 Some(Member::Enum(_)) | None => Ty::Variant,
1709 },
1710 }
1711 }
1712
1713 fn field_ty(&self, name: &str, ptr: AstPtr) -> Ty {
1716 if let Some(t) = self.class.member_types.get(name) {
1717 return t.clone();
1718 }
1719 self.resolve_decl_annotation(ptr)
1720 }
1721
1722 fn resolve_decl_annotation(&self, ptr: AstPtr) -> Ty {
1724 let Some(node) = ptr.to_node(self.root) else {
1725 return Ty::Variant;
1726 };
1727 cst::first_child(&node, |k| k == gdscript_syntax::SyntaxKind::TypeRef)
1728 .map_or(Ty::Variant, |t| {
1729 resolve::resolve_type_ref(self.db, self.api, &t)
1730 })
1731 }
1732
1733 fn apply_narrowing(&mut self, cond: ExprId) {
1744 let Expr::Is {
1745 operand,
1746 ty: Some(ptr),
1747 negated: false,
1748 } = self.body.expr(cond).clone()
1749 else {
1750 return;
1751 };
1752 let Some(key) = self.narrow_key(operand) else {
1753 return;
1754 };
1755 let narrowed = self.resolve_ptr_ty(ptr);
1756 if narrowed.is_uninformative() {
1757 return;
1758 }
1759 let cur = self.expr_ty.get(&operand).cloned().unwrap_or(Ty::Variant);
1760 if cur.is_uninformative() || self.is_subtype(&narrowed, &cur) {
1761 self.narrowing.insert(key, narrowed);
1762 }
1763 }
1764
1765 fn narrow_key(&self, id: ExprId) -> Option<String> {
1768 match self.body.expr(id) {
1769 Expr::Name(n) => Some(n.to_string()),
1770 Expr::SelfExpr => Some("self".to_owned()),
1771 Expr::Paren(inner) => self.narrow_key(*inner),
1772 Expr::Field { receiver, name, .. } => {
1773 Some(format!("{}.{name}", self.narrow_key(*receiver)?))
1774 }
1775 _ => None,
1776 }
1777 }
1778
1779 fn resolve_ptr_ty(&self, ptr: AstPtr) -> Ty {
1780 ptr.to_node(self.root).map_or(Ty::Variant, |n| {
1781 resolve::resolve_type_ref(self.db, self.api, &n)
1782 })
1783 }
1784
1785 fn join(&self, a: &Ty, b: &Ty) -> Ty {
1796 if a == b {
1797 return a.clone();
1798 }
1799 if a.is_error() || b.is_error() {
1800 return Ty::Error;
1801 }
1802 if a.is_unknown() || b.is_unknown() {
1803 return Ty::Unknown;
1804 }
1805 if a.is_variant() || b.is_variant() {
1806 return Ty::Variant;
1807 }
1808 if ty::is_assignable(self.api, a, b) == Assign::Ok {
1809 return b.clone();
1810 }
1811 if ty::is_assignable(self.api, b, a) == Assign::Ok {
1812 return a.clone();
1813 }
1814 Ty::Variant
1815 }
1816
1817 fn in_branch<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
1819 let saved = self.narrowing.clone();
1820 let r = f(self);
1821 self.narrowing = saved;
1822 r
1823 }
1824}
1825
1826fn global_ty(g: &GlobalDef) -> Ty {
1828 match g {
1829 GlobalDef::Const(t) => t.clone(),
1830 GlobalDef::Singleton(c) | GlobalDef::ClassType(c) => Ty::Object(*c),
1831 GlobalDef::BuiltinType(b) => Ty::Builtin(*b),
1832 GlobalDef::Builtin | GlobalDef::Utility => Ty::Callable,
1834 GlobalDef::GlobalEnum => Ty::Variant,
1835 }
1836}
1837
1838fn inference_on_variant_msg(kind: &str) -> String {
1839 format!(
1840 "The {kind} type is being inferred from a Variant value, so it will be typed as Variant."
1841 )
1842}
1843
1844fn op_symbol(op: BinOp) -> Option<&'static str> {
1846 Some(match op {
1847 BinOp::Add => "+",
1848 BinOp::Sub => "-",
1849 BinOp::Mul => "*",
1850 BinOp::Div => "/",
1851 BinOp::Mod => "%",
1852 BinOp::Pow => "**",
1853 BinOp::BitAnd => "&",
1854 BinOp::BitOr => "|",
1855 BinOp::BitXor => "^",
1856 BinOp::Shl => "<<",
1857 BinOp::Shr => ">>",
1858 _ => return None,
1859 })
1860}
1861
1862#[cfg(test)]
1863mod tests {
1864 use super::*;
1865 use crate::item_tree::item_tree;
1866 use gdscript_syntax::{SyntaxKind, parse};
1867
1868 struct Harness {
1869 result: InferenceResult,
1870 body: Body,
1871 }
1872
1873 fn infer_first_func(src: &str) -> Harness {
1875 let api = gdscript_api::bundled();
1876 let db = gdscript_db::RootDatabase::default();
1877 let root = parse(src).syntax_node();
1878 let tree = item_tree(&root);
1879 let class = ClassScope::new(&db, api, &tree, None);
1880 let func = gdscript_syntax::ast::descendants(&root)
1881 .into_iter()
1882 .find(|n| n.kind() == SyntaxKind::FuncDecl)
1883 .expect("a function");
1884 let body = body::body_of_func(&func);
1885 let return_ty = cst::first_child(&func, |k| k == SyntaxKind::TypeRef)
1886 .map_or(Ty::Variant, |t| resolve::resolve_type_ref(&db, api, &t));
1887 let result = infer(&db, api, &root, &class, &body, return_ty);
1888 Harness { result, body }
1889 }
1890
1891 fn codes(h: &Harness) -> Vec<&str> {
1892 h.result
1893 .diagnostics
1894 .iter()
1895 .map(|d| d.code.as_str())
1896 .collect()
1897 }
1898
1899 fn file_codes(src: &str) -> Vec<String> {
1902 let api = gdscript_api::bundled();
1903 let db = gdscript_db::RootDatabase::default();
1904 let root = parse(src).syntax_node();
1905 let fi = analyze_file(&db, api, &root, FileId(0));
1906 fi.diagnostics.iter().map(|d| d.code.clone()).collect()
1907 }
1908
1909 #[test]
1910 fn integer_division_warns() {
1911 let h = infer_first_func("func f():\n\tvar x = 5 / 2\n");
1912 assert!(codes(&h).contains(&INTEGER_DIVISION));
1913 }
1914
1915 #[test]
1916 fn float_div_does_not_warn() {
1917 let h = infer_first_func("func f():\n\tvar x = 5.0 / 2\n");
1918 assert!(!codes(&h).contains(&INTEGER_DIVISION));
1919 }
1920
1921 #[test]
1922 fn type_mismatch_on_hard_annotation() {
1923 let h = infer_first_func("func f():\n\tvar s: String = 5\n");
1924 assert!(codes(&h).contains(&TYPE_MISMATCH));
1925 }
1926
1927 #[test]
1928 fn narrowing_conversion_float_to_int() {
1929 let h = infer_first_func("func f():\n\tvar n: int = 1.5\n");
1930 assert!(codes(&h).contains(&NARROWING_CONVERSION));
1931 }
1932
1933 #[test]
1934 fn int_to_float_is_silent() {
1935 let h = infer_first_func("func f():\n\tvar x: float = 3\n");
1936 assert!(
1937 h.result.diagnostics.is_empty(),
1938 "{:?}",
1939 h.result.diagnostics
1940 );
1941 }
1942
1943 #[test]
1944 fn member_access_resolves_engine_property() {
1945 let h = infer_first_func(
1948 "extends Node\nfunc f():\n\tvar n := get_node(\"x\")\n\tn.get_parent()\n",
1949 );
1950 assert!(
1951 codes(&h).iter().all(|c| !c.starts_with("UNSAFE")),
1952 "{:?}",
1953 h.result.diagnostics
1954 );
1955 }
1956
1957 #[test]
1958 fn unsafe_method_on_known_type() {
1959 let h = infer_first_func(
1960 "extends Node\nfunc f():\n\tvar n := get_node(\"x\")\n\tn.totally_bogus_method()\n",
1961 );
1962 assert!(
1963 codes(&h).contains(&UNSAFE_METHOD_ACCESS),
1964 "{:?}",
1965 h.result.diagnostics
1966 );
1967 }
1968
1969 #[test]
1970 fn is_narrowing_suppresses_unsafe() {
1971 let h = infer_first_func("func f(x):\n\tif x is Node:\n\t\tx.queue_free()\n");
1974 assert!(
1975 codes(&h).iter().all(|c| !c.starts_with("UNSAFE")),
1976 "{:?}",
1977 h.result.diagnostics
1978 );
1979 }
1980
1981 #[test]
1982 fn is_narrowing_flags_real_missing_member() {
1983 let h = infer_first_func("func f(x):\n\tif x is Node:\n\t\tx.bogus_method()\n");
1985 assert!(codes(&h).contains(&UNSAFE_METHOD_ACCESS));
1986 }
1987
1988 #[test]
1989 fn variant_receiver_never_unsafe() {
1990 let h = infer_first_func("func f(x):\n\tx.anything_at_all()\n");
1992 assert!(
1993 h.result.diagnostics.is_empty(),
1994 "{:?}",
1995 h.result.diagnostics
1996 );
1997 }
1998
1999 #[test]
2000 fn unsafe_call_argument_on_variant_into_typed_param() {
2001 let h = infer_first_func("func f(p):\n\ttake(p)\nfunc take(n: Node2D):\n\tpass\n");
2003 assert!(
2004 codes(&h).contains(&UNSAFE_CALL_ARGUMENT),
2005 "{:?}",
2006 h.result.diagnostics
2007 );
2008 }
2009
2010 #[test]
2011 fn unsafe_call_argument_silent_on_safe_and_untyped() {
2012 let upcast =
2014 infer_first_func("func f(n: Node2D):\n\ttake(n)\nfunc take(n: Node):\n\tpass\n");
2015 assert!(
2016 !codes(&upcast).contains(&UNSAFE_CALL_ARGUMENT),
2017 "upcast is safe: {:?}",
2018 upcast.result.diagnostics
2019 );
2020 let untyped = infer_first_func("func f(p):\n\ttake(p)\nfunc take(n):\n\tpass\n");
2021 assert!(
2022 !codes(&untyped).contains(&UNSAFE_CALL_ARGUMENT),
2023 "untyped param accepts anything: {:?}",
2024 untyped.result.diagnostics
2025 );
2026 }
2027
2028 #[test]
2029 fn inference_on_variant() {
2030 let h = infer_first_func("func f(x):\n\tvar y := x\n");
2032 assert!(codes(&h).contains(&INFERENCE_ON_VARIANT));
2033 }
2034
2035 #[test]
2036 fn field_inferred_from_earlier_field_is_typed() {
2037 let codes = file_codes("var a := 1\nvar b := a + 1\n");
2041 assert!(
2042 !codes.iter().any(|c| c == INFERENCE_ON_VARIANT),
2043 "field `b` from earlier field `a` should type as int, not Variant: {codes:?}"
2044 );
2045 }
2046
2047 #[test]
2048 fn field_forward_reference_is_seamed_not_warned() {
2049 let codes = file_codes("var b := a\nvar a := 1\n");
2053 assert!(
2054 !codes.iter().any(|c| c == INFERENCE_ON_VARIANT),
2055 "forward field reference must not false-warn: {codes:?}"
2056 );
2057 }
2058
2059 #[test]
2060 fn standalone_inferred_field_unchanged() {
2061 let codes = file_codes("var n := 0\n");
2063 assert!(
2064 codes.is_empty(),
2065 "a literal-initialised field should produce no diagnostics: {codes:?}"
2066 );
2067 }
2068
2069 #[test]
2070 fn lambda_var_is_callable_not_variant() {
2071 let h = infer_first_func("func f():\n\tvar cb := func():\n\t\tpass\n");
2072 assert!(
2073 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2074 "{:?}",
2075 h.result.diagnostics
2076 );
2077 }
2078
2079 #[test]
2080 fn multiline_lambda_then_paren_line_no_false_warning() {
2081 let src = "func f(state, i, loop):\n\tvar cb := func():\n\t\tif i >= state.size():\n\t\t\treturn\n\t(loop as SceneTree).process_frame.connect(cb, CONNECT_ONE_SHOT)\n";
2085 let h = infer_first_func(src);
2086 assert!(
2087 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2088 "{:?}",
2089 h.result.diagnostics
2090 );
2091 }
2092
2093 #[test]
2094 fn calling_a_callable_value_is_seam_not_variant() {
2095 let src = "func f(cb: Callable):\n\tvar x := (cb)()\n\treturn x\n";
2099 let h = infer_first_func(src);
2100 assert!(
2101 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2102 "{:?}",
2103 h.result.diagnostics
2104 );
2105 }
2106
2107 #[test]
2108 fn ternary_with_seam_branch_does_not_collapse_to_variant() {
2109 let src =
2113 "func f(c: bool):\n\tvar x := 5 if c else await get_tree().process_frame\n\treturn x\n";
2114 let h = infer_first_func(src);
2115 assert!(
2116 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2117 "seam branch should keep the ternary on the seam: {:?}",
2118 h.result.diagnostics
2119 );
2120 }
2121
2122 #[test]
2123 fn await_a_coroutine_call_recovers_its_return_type() {
2124 let src = "func g() -> int:\n\tvar x := await make()\n\treturn x\nfunc make() -> int:\n\treturn 5\n";
2127 let h = infer_first_func(src);
2128 assert!(
2129 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2130 "no false variant warning: {:?}",
2131 h.result.diagnostics
2132 );
2133 let api = gdscript_api::bundled();
2134 let x = &h.result.bindings[0];
2135 assert!(
2136 matches!(&x.ty, Ty::Builtin(b) if api.builtin(*b).name == "int"),
2137 "await make() should recover int, got {:?}",
2138 x.ty
2139 );
2140 }
2141
2142 #[test]
2143 fn await_a_signal_stays_the_seam() {
2144 let src = "func f():\n\tvar x := await get_tree().process_frame\n\treturn x\n";
2147 let h = infer_first_func(src);
2148 assert!(
2149 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2150 "awaiting a signal must not warn: {:?}",
2151 h.result.diagnostics
2152 );
2153 assert!(
2154 matches!(&h.result.bindings[0].ty, Ty::Unknown),
2155 "awaiting a signal stays the seam, got {:?}",
2156 h.result.bindings[0].ty
2157 );
2158 }
2159
2160 #[test]
2161 fn for_var_over_packed_string_array_is_string() {
2162 let h = infer_first_func("func f():\n\tfor s in \"a,b\".split(\",\"):\n\t\tvar x := s\n");
2165 assert!(
2166 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2167 "{:?}",
2168 h.result.diagnostics
2169 );
2170 }
2171
2172 #[test]
2173 fn class_new_is_object_not_variant() {
2174 let h = infer_first_func("func f():\n\tvar s := GDScript.new()\n");
2175 assert!(
2176 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2177 "{:?}",
2178 h.result.diagnostics
2179 );
2180 }
2181
2182 #[test]
2183 fn unknown_seam_never_warns() {
2184 let h = infer_first_func("func f():\n\tvar s := preload(\"res://x.gd\")\n\ts.whatever()\n");
2186 assert!(
2187 h.result.diagnostics.is_empty(),
2188 "{:?}",
2189 h.result.diagnostics
2190 );
2191 }
2192
2193 #[test]
2194 fn expr_types_are_memoized_for_hover() {
2195 let h = infer_first_func("func f():\n\tvar n := 42\n");
2196 let has_int = h
2198 .result
2199 .expr_ty
2200 .values()
2201 .any(|t| matches!(t, Ty::Builtin(_)));
2202 assert!(has_int);
2203 assert!(!h.body.exprs.is_empty());
2205 }
2206}