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 _ => fallback,
943 }
944 }
945
946 fn owning_scene(&self) -> Option<crate::queries::SceneContext> {
950 let Ty::ScriptRef(sref) = &self.self_ty else {
951 return None;
952 };
953 let ft = self.db.file_text(FileId(sref.0))?;
954 crate::queries::scene_context(self.db, ft)
955 }
956
957 fn self_res_path(&self) -> Option<SmolStr> {
960 let Ty::ScriptRef(sref) = &self.self_ty else {
961 return None;
962 };
963 self.db.file_text(FileId(sref.0))?.res_path(self.db)
964 }
965
966 fn scene_node_ty(&self, scene: &SceneModel, node: &SceneNode, depth: u32) -> Option<Ty> {
971 if let Some(script_ty) = self.node_script_ref(scene, node) {
972 return Some(script_ty);
973 }
974 if let Some(decl) = node.decl_type.as_ref() {
975 let ty = resolve::resolve_type_name(self.db, self.api, decl);
976 if !ty.is_uninformative() {
977 return Some(ty);
978 }
979 }
980 self.instance_root_ty(scene, node, depth)
981 }
982
983 fn instance_root_ty(&self, scene: &SceneModel, node: &SceneNode, depth: u32) -> Option<Ty> {
988 if depth >= 16 {
989 return None;
990 }
991 let inst = node.instance.as_ref()?;
992 let path = scene.ext_resources.get(inst)?.path.as_ref()?;
993 let root = self.db.source_root()?;
994 let file = crate::queries::res_path_registry(self.db, root)
995 .get(path.as_str())
996 .copied()?;
997 let ft = self.db.file_text(file)?;
998 let sub = crate::queries::scene_model(self.db, ft);
999 let root_node = sub.node(sub.root?)?;
1000 self.scene_node_ty(&sub, root_node, depth + 1)
1001 }
1002
1003 fn node_script_ref(&self, scene: &SceneModel, node: &SceneNode) -> Option<Ty> {
1006 let path = scene
1007 .ext_resources
1008 .get(node.script.as_ref()?)?
1009 .path
1010 .as_ref()?;
1011 let root = self.db.source_root()?;
1012 let file = crate::queries::res_path_registry(self.db, root)
1013 .get(path.as_str())
1014 .copied()?;
1015 Some(Ty::ScriptRef(ScriptRefId(file.0)))
1016 }
1017
1018 fn infer_bin(&mut self, id: ExprId, op: BinOp, lhs: ExprId, rhs: ExprId) -> Ty {
1019 if op == BinOp::Assign {
1020 return self.infer_assign(lhs, rhs);
1021 }
1022 let lt = self.infer_expr(lhs, &Expectation::None);
1023 let rt = self.infer_expr(rhs, &Expectation::None);
1024 if op.is_boolean() {
1025 return self.bool_ty();
1026 }
1027 if op == BinOp::Div && self.is_int(<) && self.is_int(&rt) {
1029 self.emit(
1030 self.range_of(id),
1031 Severity::Warning,
1032 INTEGER_DIVISION,
1033 "Integer division. Decimal part will be discarded.".to_owned(),
1034 );
1035 return self.int_ty();
1036 }
1037 self.bin_result(op, <, &rt)
1038 }
1039
1040 fn infer_assign(&mut self, lhs: ExprId, rhs: ExprId) -> Ty {
1041 let slot = self.infer_expr(lhs, &Expectation::None);
1042 let expected = if slot.is_uninformative() {
1043 Expectation::None
1044 } else {
1045 Expectation::Has(slot.clone())
1046 };
1047 let value = self.infer_expr(rhs, &expected);
1048 if !slot.is_uninformative() {
1049 self.check_assign(&value, &slot, self.range_of(rhs));
1050 }
1051 if let Some(key) = self.narrow_key(lhs) {
1053 let narrowed = if slot.is_uninformative() {
1054 value.clone()
1055 } else {
1056 slot.clone()
1057 };
1058 self.narrowing.insert(key, narrowed);
1059 }
1060 slot
1061 }
1062
1063 fn bin_result(&self, op: BinOp, lt: &Ty, rt: &Ty) -> Ty {
1066 if let (Ty::Builtin(b), Some(sym)) = (lt, op_symbol(op)) {
1067 for o in self.api.builtin_operators(*b) {
1068 if o.op == sym
1069 && let Some(right) = &o.right
1070 && self.tyref_matches(right, rt)
1071 {
1072 return ty::resolve_tyref(self.api, &o.result);
1073 }
1074 }
1075 }
1076 if self.is_numeric(lt) && self.is_numeric(rt) {
1077 return if self.is_float(lt) || self.is_float(rt) {
1078 self.float_ty()
1079 } else {
1080 self.int_ty()
1081 };
1082 }
1083 if lt.is_unknown() || rt.is_unknown() || lt.is_error() || rt.is_error() {
1086 return Ty::Unknown;
1087 }
1088 Ty::Variant
1089 }
1090
1091 fn tyref_matches(&self, tyref: &TyRef, ty: &Ty) -> bool {
1092 let resolved = ty::resolve_tyref(self.api, tyref);
1093 resolved.is_variant() || &resolved == ty
1094 }
1095
1096 fn infer_call(&mut self, callee: ExprId, args: &[ExprId]) -> Ty {
1097 for &a in args {
1099 self.infer_expr(a, &Expectation::None);
1100 }
1101 let ret = match self.body.expr(callee).clone() {
1102 Expr::Field {
1103 receiver,
1104 name,
1105 name_range,
1106 } => {
1107 self.infer_field(receiver, &name, name_range, true)
1108 }
1109 Expr::Name(name) => {
1110 let ret = self.resolve_call_name(&name);
1111 self.expr_ty.insert(callee, Ty::Callable);
1112 ret
1113 }
1114 _ => {
1118 self.infer_expr(callee, &Expectation::None);
1119 Ty::Unknown
1120 }
1121 };
1122 self.check_call_args(callee, args);
1125 ret
1126 }
1127
1128 fn check_call_args(&mut self, callee: ExprId, args: &[ExprId]) {
1134 let Some(params) = self.call_param_tys(callee) else {
1135 return;
1136 };
1137 for (i, &arg) in args.iter().enumerate() {
1138 let Some(param_ty) = params.get(i) else {
1139 break; };
1141 if param_ty.is_uninformative() || param_ty.is_variant() {
1142 continue; }
1144 let arg_ty = self.expr_ty.get(&arg).cloned().unwrap_or(Ty::Unknown);
1146 if ty::is_assignable(self.api, &arg_ty, param_ty) == Assign::OkUnsafe {
1147 let pl = param_ty.label(self.api).unwrap_or_else(|| "?".to_owned());
1148 let al = arg_ty.label(self.api).unwrap_or_else(|| "?".to_owned());
1149 self.emit(
1150 self.range_of(arg),
1151 Severity::Warning,
1152 UNSAFE_CALL_ARGUMENT,
1153 format!(
1154 "The argument {} requires a value of type \"{pl}\" but is passed \"{al}\", which is unsafe.",
1155 i + 1
1156 ),
1157 );
1158 }
1159 }
1160 }
1161
1162 fn call_param_tys(&self, callee: ExprId) -> Option<Vec<Ty>> {
1166 match self.body.expr(callee) {
1167 Expr::Name(name) => self.name_call_param_tys(name),
1168 Expr::Field { receiver, name, .. } => match self.expr_ty.get(receiver)? {
1169 Ty::Object(class) => match self.api.lookup_member(*class, name)? {
1170 MemberRef::Method(sig) => Some(
1171 sig.params
1172 .iter()
1173 .map(|p| ty::resolve_tyref(self.api, &p.ty))
1174 .collect(),
1175 ),
1176 _ => None,
1177 },
1178 _ => None,
1180 },
1181 _ => None,
1182 }
1183 }
1184
1185 fn name_call_param_tys(&self, name: &str) -> Option<Vec<Ty>> {
1189 if let Some(item) = self.class.lookup(name)
1190 && let Some(Member::Func(f)) = self.class.member(item)
1191 {
1192 return Some(
1193 f.params
1194 .iter()
1195 .map(|p| {
1196 p.type_ref.as_deref().map_or(Ty::Variant, |t| {
1197 resolve::resolve_type_name(self.db, self.api, t)
1198 })
1199 })
1200 .collect(),
1201 );
1202 }
1203 if let Ty::Object(base) = self.class.base
1204 && let Some(MemberRef::Method(sig)) = self.api.lookup_member(base, name)
1205 {
1206 return Some(
1207 sig.params
1208 .iter()
1209 .map(|p| ty::resolve_tyref(self.api, &p.ty))
1210 .collect(),
1211 );
1212 }
1213 None
1214 }
1215
1216 fn resolve_call_name(&self, name: &str) -> Ty {
1218 if let Some(item) = self.class.lookup(name)
1219 && let Some(Member::Func(f)) = self.class.member(item)
1220 {
1221 return self.func_return_ty(f.return_type.as_deref());
1222 }
1223 if let Ty::Object(base) = self.class.base
1225 && let Some(MemberRef::Method(sig)) = self.api.lookup_member(base, name)
1226 {
1227 return ty::resolve_tyref(self.api, &sig.return_ty);
1228 }
1229 if let Some(u) = self.api.utility(name) {
1230 return ty::resolve_tyref(self.api, &u.return_ty);
1231 }
1232 if let Some(f) = self.api.gdscript_builtin(name) {
1233 return resolve::layer_to_ty(self.api, f.ret);
1234 }
1235 if let Some(b) = self.api.builtin_by_name(name) {
1239 return ty::resolve_tyref(self.api, &TyRef::Builtin(b));
1240 }
1241 Ty::Unknown
1244 }
1245
1246 fn func_return_ty(&self, annotation: Option<&str>) -> Ty {
1247 annotation.map_or(Ty::Variant, |t| {
1248 resolve::resolve_type_name(self.db, self.api, t)
1249 })
1250 }
1251
1252 fn infer_field(
1256 &mut self,
1257 receiver: ExprId,
1258 name: &str,
1259 name_range: TextRange,
1260 as_method: bool,
1261 ) -> Ty {
1262 let is_self = matches!(self.body.expr(receiver), Expr::SelfExpr);
1263 let recv_ty = self.infer_expr(receiver, &Expectation::None);
1264
1265 if is_self && let Some(item) = self.class.lookup(name) {
1267 return self.own_member_ty(item, as_method);
1268 }
1269
1270 match &recv_ty {
1271 t if t.is_uninformative() => recv_ty.clone(),
1276 Ty::Object(class) => {
1277 if name == "new" {
1278 recv_ty.clone()
1281 } else if let Some(m) = self.api.lookup_member(*class, name) {
1282 self.member_ref_ty(&m, as_method)
1283 } else if let Some(t) = self.class_enum_value(*class, name) {
1284 t
1286 } else {
1287 self.emit_unsafe(name, &recv_ty, name_range, as_method);
1289 Ty::Variant
1290 }
1291 }
1292 Ty::Builtin(_) | Ty::Array(_) | Ty::Dict(..) | Ty::Callable | Ty::Signal(_) => {
1293 self.builtin_member_ty(&recv_ty, name, name_range, as_method)
1294 }
1295 Ty::Enum(_) => self.int_ty(),
1297 Ty::ScriptRef(sref) => self.script_member_ty(*sref, name, as_method),
1299 _ => Ty::Variant,
1300 }
1301 }
1302
1303 fn script_member_ty(&self, sref: ScriptRefId, name: &str, as_method: bool) -> Ty {
1308 if name == "new" {
1309 return Ty::ScriptRef(sref);
1310 }
1311 self.script_member_walk(sref, name, as_method, 0)
1312 .unwrap_or(Ty::Unknown)
1313 }
1314
1315 fn script_member_walk(
1319 &self,
1320 sref: ScriptRefId,
1321 name: &str,
1322 as_method: bool,
1323 depth: u32,
1324 ) -> Option<Ty> {
1325 if depth > 32 {
1326 return None;
1327 }
1328 let file = self.db.file_text(FileId(sref.0))?;
1329 let sc = crate::queries::script_class(self.db, file);
1330 if let Some(m) = sc.member(name) {
1331 return Some(match m {
1332 crate::queries::MemberSig::Method(ret) => {
1333 if as_method {
1334 ret.clone()
1335 } else {
1336 Ty::Callable
1337 }
1338 }
1339 crate::queries::MemberSig::Field(t) => t.clone(),
1340 crate::queries::MemberSig::Signal => Ty::Signal(None),
1341 });
1342 }
1343 match sc.base() {
1345 Ty::ScriptRef(base) => self.script_member_walk(*base, name, as_method, depth + 1),
1346 Ty::Object(class) => self
1347 .api
1348 .lookup_member(*class, name)
1349 .map(|m| self.member_ref_ty(&m, as_method)),
1350 _ => None,
1351 }
1352 }
1353
1354 fn is_subtype(&self, sub: &Ty, sup: &Ty) -> bool {
1359 match (sub, sup) {
1360 (Ty::Object(a), Ty::Object(b)) => self.api.is_subclass(*a, *b),
1361 (Ty::ScriptRef(a), Ty::ScriptRef(b)) => self.script_is_subtype(*a, *b, 0),
1362 (Ty::ScriptRef(a), Ty::Object(b)) => self.script_extends_engine(*a, *b, 0),
1363 _ => false,
1364 }
1365 }
1366
1367 fn script_is_subtype(&self, sub: ScriptRefId, sup: ScriptRefId, depth: u32) -> bool {
1370 if depth > 32 {
1371 return false;
1372 }
1373 if sub == sup {
1374 return true;
1375 }
1376 let Some(file) = self.db.file_text(FileId(sub.0)) else {
1377 return false;
1378 };
1379 match crate::queries::script_class(self.db, file).base() {
1380 Ty::ScriptRef(base) => self.script_is_subtype(*base, sup, depth + 1),
1381 _ => false,
1382 }
1383 }
1384
1385 fn script_extends_engine(
1387 &self,
1388 sub: ScriptRefId,
1389 sup_native: gdscript_api::ClassId,
1390 depth: u32,
1391 ) -> bool {
1392 if depth > 32 {
1393 return false;
1394 }
1395 let Some(file) = self.db.file_text(FileId(sub.0)) else {
1396 return false;
1397 };
1398 match crate::queries::script_class(self.db, file).base() {
1399 Ty::ScriptRef(base) => self.script_extends_engine(*base, sup_native, depth + 1),
1400 Ty::Object(native) => self.api.is_subclass(*native, sup_native),
1401 _ => false,
1402 }
1403 }
1404
1405 fn emit_unsafe(&mut self, name: &str, recv: &Ty, range: TextRange, as_method: bool) {
1406 let recv_label = recv.label(self.api).unwrap_or_else(|| "?".to_owned());
1407 let (code, message) = if as_method {
1408 (
1409 UNSAFE_METHOD_ACCESS,
1410 format!(
1411 "The method \"{name}()\" is not present on the inferred type \"{recv_label}\" (but may be present on a subtype)."
1412 ),
1413 )
1414 } else {
1415 (
1416 UNSAFE_PROPERTY_ACCESS,
1417 format!(
1418 "The property \"{name}\" is not present on the inferred type \"{recv_label}\" (but may be present on a subtype)."
1419 ),
1420 )
1421 };
1422 self.emit(range, Severity::Warning, code, message);
1423 }
1424
1425 fn member_ref_ty(&self, m: &MemberRef, as_method: bool) -> Ty {
1426 match m {
1427 MemberRef::Method(sig) => {
1428 if as_method {
1429 ty::resolve_tyref(self.api, &sig.return_ty)
1430 } else {
1431 Ty::Callable
1432 }
1433 }
1434 MemberRef::Property(p) => p.enum_of.as_ref().map_or_else(
1435 || ty::resolve_tyref(self.api, &p.ty),
1436 |q| {
1437 Ty::Enum(EnumRef {
1438 qualified: SmolStr::new(q),
1439 bitfield: false,
1440 })
1441 },
1442 ),
1443 MemberRef::Const(c) => ty::resolve_tyref(self.api, &c.ty),
1444 MemberRef::Signal(_) => Ty::Signal(None),
1445 MemberRef::Enum(_) => Ty::Variant,
1446 }
1447 }
1448
1449 fn builtin_member_ty(
1450 &mut self,
1451 recv: &Ty,
1452 name: &str,
1453 range: TextRange,
1454 as_method: bool,
1455 ) -> Ty {
1456 let Some(bid) = self.builtin_id_of(recv) else {
1457 return Ty::Variant;
1458 };
1459 if as_method {
1460 return if let Some(sig) = self.api.builtin_method(bid, name) {
1461 ty::resolve_tyref(self.api, &sig.return_ty)
1462 } else {
1463 self.emit_unsafe(name, recv, range, true);
1464 Ty::Variant
1465 };
1466 }
1467 if let Some(member) = self.api.builtin_member(bid, name) {
1468 return ty::resolve_tyref(self.api, &member.ty);
1469 }
1470 let data = self.api.builtin(bid);
1472 if let Some(c) = data.constants.iter().find(|c| c.name == name) {
1473 return ty::resolve_tyref(self.api, &c.ty);
1474 }
1475 if data
1476 .enums
1477 .iter()
1478 .any(|e| e.values.iter().any(|v| v.name == name))
1479 {
1480 return self.int_ty();
1481 }
1482 if self.api.builtin_method(bid, name).is_some() {
1483 return Ty::Callable;
1484 }
1485 self.emit_unsafe(name, recv, range, false);
1486 Ty::Variant
1487 }
1488
1489 fn class_enum_value(&self, class: gdscript_api::ClassId, name: &str) -> Option<Ty> {
1493 let mut cur = Some(class);
1494 while let Some(cid) = cur {
1495 let c = self.api.class(cid);
1496 if c.enums
1497 .iter()
1498 .any(|e| e.values.iter().any(|v| v.name == name))
1499 {
1500 return Some(self.int_ty());
1501 }
1502 cur = c.base;
1503 }
1504 None
1505 }
1506
1507 fn builtin_id_of(&self, ty: &Ty) -> Option<gdscript_api::BuiltinId> {
1509 match ty {
1510 Ty::Builtin(b) => Some(*b),
1511 Ty::Array(_) => self.api.builtin_by_name("Array"),
1512 Ty::Dict(..) => self.api.builtin_by_name("Dictionary"),
1513 Ty::Callable => self.api.builtin_by_name("Callable"),
1514 Ty::Signal(_) => self.api.builtin_by_name("Signal"),
1515 _ => None,
1516 }
1517 }
1518
1519 fn index_ty(&self, base: &Ty) -> Ty {
1521 match base {
1522 Ty::Array(elem) => (**elem).clone(),
1523 Ty::Builtin(b) => self
1524 .api
1525 .builtin(*b)
1526 .indexing_return
1527 .as_ref()
1528 .map_or(Ty::Variant, |r| ty::resolve_tyref(self.api, r)),
1529 Ty::Unknown => Ty::Unknown,
1531 Ty::Error => Ty::Error,
1532 _ => Ty::Variant,
1533 }
1534 }
1535
1536 fn loop_var_ty(&self, iter: &Ty) -> Ty {
1538 match iter {
1539 Ty::Array(elem) => (**elem).clone(),
1540 Ty::Builtin(b) => {
1541 let data = self.api.builtin(*b);
1542 if data.name == "int" {
1543 self.int_ty()
1545 } else if let Some(r) = &data.indexing_return {
1546 ty::resolve_tyref(self.api, r)
1548 } else {
1549 Ty::Variant
1550 }
1551 }
1552 Ty::Unknown => Ty::Unknown,
1554 Ty::Error => Ty::Error,
1555 _ => Ty::Variant,
1556 }
1557 }
1558
1559 fn infer_lambda(&mut self, params: &[ParamBinding], body: &[body::StmtId]) {
1560 let saved_locals = self.locals.clone();
1564 let saved_ret = std::mem::replace(&mut self.return_ty, Ty::Variant);
1565 for p in params {
1566 let ty = self.param_ty(p);
1567 self.bindings.push(Binding {
1568 name_range: p.name_range,
1569 ty: ty.clone(),
1570 init: None,
1571 annotated: p.type_ref.is_some(),
1572 inferred_colon_eq: false,
1573 kind: BindingKind::Param,
1574 });
1575 self.locals.insert(p.name.clone(), ty);
1576 }
1577 self.infer_block(body);
1578 self.return_ty = saved_ret;
1579 self.locals = saved_locals;
1580 }
1581
1582 fn param_ty(&mut self, p: &ParamBinding) -> Ty {
1583 if let Some(ptr) = p.type_ref {
1584 return self.resolve_ptr_ty(ptr);
1585 }
1586 p.default
1588 .map_or(Ty::Variant, |e| self.infer_expr(e, &Expectation::None))
1589 }
1590
1591 fn resolve_name(&mut self, id: ExprId, name: &str) -> Ty {
1594 if let Some(key) = self.narrow_key(id)
1596 && let Some(t) = self.narrowing.get(&key)
1597 {
1598 return t.clone();
1599 }
1600 if let Some(t) = self.locals.get(name) {
1601 return t.clone();
1602 }
1603 if let Some(item) = self.class.lookup(name) {
1604 return self.own_member_ty(item, false);
1605 }
1606 match self.class.base.clone() {
1610 Ty::Object(base) => {
1611 if let Some(m) = self.api.lookup_member(base, name) {
1612 return self.member_ref_ty(&m, false);
1613 }
1614 }
1615 Ty::ScriptRef(base) => {
1616 if let Some(t) = self.script_member_walk(base, name, false, 0) {
1617 return t;
1618 }
1619 }
1620 _ => {}
1621 }
1622 if let Some(g) = resolve::resolve_global(self.api, name) {
1623 return global_ty(&g);
1624 }
1625 let by_class = resolve::resolve_external(
1630 self.db,
1631 &resolve::ExternalRef::ClassName(SmolStr::new(name)),
1632 );
1633 if !by_class.is_unknown() {
1634 return by_class;
1635 }
1636 resolve::resolve_external(self.db, &resolve::ExternalRef::Autoload(SmolStr::new(name)))
1637 }
1638
1639 fn own_member_ty(&self, item: ClassItem, as_method: bool) -> Ty {
1640 match item {
1641 ClassItem::EnumVariant => self.int_ty(),
1642 ClassItem::Member(_) => match self.class.member(item) {
1643 Some(Member::Var(v)) => self.field_ty(&v.name, v.ptr),
1644 Some(Member::Const(c)) => self.field_ty(&c.name, c.ptr),
1645 Some(Member::Func(f)) => {
1646 if as_method {
1647 self.func_return_ty(f.return_type.as_deref())
1648 } else {
1649 Ty::Callable
1650 }
1651 }
1652 Some(Member::Signal(_)) => Ty::Signal(None),
1653 Some(Member::Class(_)) => Ty::Unknown,
1654 Some(Member::Enum(_)) | None => Ty::Variant,
1655 },
1656 }
1657 }
1658
1659 fn field_ty(&self, name: &str, ptr: AstPtr) -> Ty {
1662 if let Some(t) = self.class.member_types.get(name) {
1663 return t.clone();
1664 }
1665 self.resolve_decl_annotation(ptr)
1666 }
1667
1668 fn resolve_decl_annotation(&self, ptr: AstPtr) -> Ty {
1670 let Some(node) = ptr.to_node(self.root) else {
1671 return Ty::Variant;
1672 };
1673 cst::first_child(&node, |k| k == gdscript_syntax::SyntaxKind::TypeRef)
1674 .map_or(Ty::Variant, |t| {
1675 resolve::resolve_type_ref(self.db, self.api, &t)
1676 })
1677 }
1678
1679 fn apply_narrowing(&mut self, cond: ExprId) {
1690 let Expr::Is {
1691 operand,
1692 ty: Some(ptr),
1693 negated: false,
1694 } = self.body.expr(cond).clone()
1695 else {
1696 return;
1697 };
1698 let Some(key) = self.narrow_key(operand) else {
1699 return;
1700 };
1701 let narrowed = self.resolve_ptr_ty(ptr);
1702 if narrowed.is_uninformative() {
1703 return;
1704 }
1705 let cur = self.expr_ty.get(&operand).cloned().unwrap_or(Ty::Variant);
1706 if cur.is_uninformative() || self.is_subtype(&narrowed, &cur) {
1707 self.narrowing.insert(key, narrowed);
1708 }
1709 }
1710
1711 fn narrow_key(&self, id: ExprId) -> Option<String> {
1714 match self.body.expr(id) {
1715 Expr::Name(n) => Some(n.to_string()),
1716 Expr::SelfExpr => Some("self".to_owned()),
1717 Expr::Paren(inner) => self.narrow_key(*inner),
1718 Expr::Field { receiver, name, .. } => {
1719 Some(format!("{}.{name}", self.narrow_key(*receiver)?))
1720 }
1721 _ => None,
1722 }
1723 }
1724
1725 fn resolve_ptr_ty(&self, ptr: AstPtr) -> Ty {
1726 ptr.to_node(self.root).map_or(Ty::Variant, |n| {
1727 resolve::resolve_type_ref(self.db, self.api, &n)
1728 })
1729 }
1730
1731 fn join(&self, a: &Ty, b: &Ty) -> Ty {
1742 if a == b {
1743 return a.clone();
1744 }
1745 if a.is_error() || b.is_error() {
1746 return Ty::Error;
1747 }
1748 if a.is_unknown() || b.is_unknown() {
1749 return Ty::Unknown;
1750 }
1751 if a.is_variant() || b.is_variant() {
1752 return Ty::Variant;
1753 }
1754 if ty::is_assignable(self.api, a, b) == Assign::Ok {
1755 return b.clone();
1756 }
1757 if ty::is_assignable(self.api, b, a) == Assign::Ok {
1758 return a.clone();
1759 }
1760 Ty::Variant
1761 }
1762
1763 fn in_branch<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
1765 let saved = self.narrowing.clone();
1766 let r = f(self);
1767 self.narrowing = saved;
1768 r
1769 }
1770}
1771
1772fn global_ty(g: &GlobalDef) -> Ty {
1774 match g {
1775 GlobalDef::Const(t) => t.clone(),
1776 GlobalDef::Singleton(c) | GlobalDef::ClassType(c) => Ty::Object(*c),
1777 GlobalDef::BuiltinType(b) => Ty::Builtin(*b),
1778 GlobalDef::Builtin | GlobalDef::Utility => Ty::Callable,
1780 GlobalDef::GlobalEnum => Ty::Variant,
1781 }
1782}
1783
1784fn inference_on_variant_msg(kind: &str) -> String {
1785 format!(
1786 "The {kind} type is being inferred from a Variant value, so it will be typed as Variant."
1787 )
1788}
1789
1790fn op_symbol(op: BinOp) -> Option<&'static str> {
1792 Some(match op {
1793 BinOp::Add => "+",
1794 BinOp::Sub => "-",
1795 BinOp::Mul => "*",
1796 BinOp::Div => "/",
1797 BinOp::Mod => "%",
1798 BinOp::Pow => "**",
1799 BinOp::BitAnd => "&",
1800 BinOp::BitOr => "|",
1801 BinOp::BitXor => "^",
1802 BinOp::Shl => "<<",
1803 BinOp::Shr => ">>",
1804 _ => return None,
1805 })
1806}
1807
1808#[cfg(test)]
1809mod tests {
1810 use super::*;
1811 use crate::item_tree::item_tree;
1812 use gdscript_syntax::{SyntaxKind, parse};
1813
1814 struct Harness {
1815 result: InferenceResult,
1816 body: Body,
1817 }
1818
1819 fn infer_first_func(src: &str) -> Harness {
1821 let api = gdscript_api::bundled();
1822 let db = gdscript_db::RootDatabase::default();
1823 let root = parse(src).syntax_node();
1824 let tree = item_tree(&root);
1825 let class = ClassScope::new(&db, api, &tree, None);
1826 let func = gdscript_syntax::ast::descendants(&root)
1827 .into_iter()
1828 .find(|n| n.kind() == SyntaxKind::FuncDecl)
1829 .expect("a function");
1830 let body = body::body_of_func(&func);
1831 let return_ty = cst::first_child(&func, |k| k == SyntaxKind::TypeRef)
1832 .map_or(Ty::Variant, |t| resolve::resolve_type_ref(&db, api, &t));
1833 let result = infer(&db, api, &root, &class, &body, return_ty);
1834 Harness { result, body }
1835 }
1836
1837 fn codes(h: &Harness) -> Vec<&str> {
1838 h.result
1839 .diagnostics
1840 .iter()
1841 .map(|d| d.code.as_str())
1842 .collect()
1843 }
1844
1845 fn file_codes(src: &str) -> Vec<String> {
1848 let api = gdscript_api::bundled();
1849 let db = gdscript_db::RootDatabase::default();
1850 let root = parse(src).syntax_node();
1851 let fi = analyze_file(&db, api, &root, FileId(0));
1852 fi.diagnostics.iter().map(|d| d.code.clone()).collect()
1853 }
1854
1855 #[test]
1856 fn integer_division_warns() {
1857 let h = infer_first_func("func f():\n\tvar x = 5 / 2\n");
1858 assert!(codes(&h).contains(&INTEGER_DIVISION));
1859 }
1860
1861 #[test]
1862 fn float_div_does_not_warn() {
1863 let h = infer_first_func("func f():\n\tvar x = 5.0 / 2\n");
1864 assert!(!codes(&h).contains(&INTEGER_DIVISION));
1865 }
1866
1867 #[test]
1868 fn type_mismatch_on_hard_annotation() {
1869 let h = infer_first_func("func f():\n\tvar s: String = 5\n");
1870 assert!(codes(&h).contains(&TYPE_MISMATCH));
1871 }
1872
1873 #[test]
1874 fn narrowing_conversion_float_to_int() {
1875 let h = infer_first_func("func f():\n\tvar n: int = 1.5\n");
1876 assert!(codes(&h).contains(&NARROWING_CONVERSION));
1877 }
1878
1879 #[test]
1880 fn int_to_float_is_silent() {
1881 let h = infer_first_func("func f():\n\tvar x: float = 3\n");
1882 assert!(
1883 h.result.diagnostics.is_empty(),
1884 "{:?}",
1885 h.result.diagnostics
1886 );
1887 }
1888
1889 #[test]
1890 fn member_access_resolves_engine_property() {
1891 let h = infer_first_func(
1894 "extends Node\nfunc f():\n\tvar n := get_node(\"x\")\n\tn.get_parent()\n",
1895 );
1896 assert!(
1897 codes(&h).iter().all(|c| !c.starts_with("UNSAFE")),
1898 "{:?}",
1899 h.result.diagnostics
1900 );
1901 }
1902
1903 #[test]
1904 fn unsafe_method_on_known_type() {
1905 let h = infer_first_func(
1906 "extends Node\nfunc f():\n\tvar n := get_node(\"x\")\n\tn.totally_bogus_method()\n",
1907 );
1908 assert!(
1909 codes(&h).contains(&UNSAFE_METHOD_ACCESS),
1910 "{:?}",
1911 h.result.diagnostics
1912 );
1913 }
1914
1915 #[test]
1916 fn is_narrowing_suppresses_unsafe() {
1917 let h = infer_first_func("func f(x):\n\tif x is Node:\n\t\tx.queue_free()\n");
1920 assert!(
1921 codes(&h).iter().all(|c| !c.starts_with("UNSAFE")),
1922 "{:?}",
1923 h.result.diagnostics
1924 );
1925 }
1926
1927 #[test]
1928 fn is_narrowing_flags_real_missing_member() {
1929 let h = infer_first_func("func f(x):\n\tif x is Node:\n\t\tx.bogus_method()\n");
1931 assert!(codes(&h).contains(&UNSAFE_METHOD_ACCESS));
1932 }
1933
1934 #[test]
1935 fn variant_receiver_never_unsafe() {
1936 let h = infer_first_func("func f(x):\n\tx.anything_at_all()\n");
1938 assert!(
1939 h.result.diagnostics.is_empty(),
1940 "{:?}",
1941 h.result.diagnostics
1942 );
1943 }
1944
1945 #[test]
1946 fn unsafe_call_argument_on_variant_into_typed_param() {
1947 let h = infer_first_func("func f(p):\n\ttake(p)\nfunc take(n: Node2D):\n\tpass\n");
1949 assert!(
1950 codes(&h).contains(&UNSAFE_CALL_ARGUMENT),
1951 "{:?}",
1952 h.result.diagnostics
1953 );
1954 }
1955
1956 #[test]
1957 fn unsafe_call_argument_silent_on_safe_and_untyped() {
1958 let upcast =
1960 infer_first_func("func f(n: Node2D):\n\ttake(n)\nfunc take(n: Node):\n\tpass\n");
1961 assert!(
1962 !codes(&upcast).contains(&UNSAFE_CALL_ARGUMENT),
1963 "upcast is safe: {:?}",
1964 upcast.result.diagnostics
1965 );
1966 let untyped = infer_first_func("func f(p):\n\ttake(p)\nfunc take(n):\n\tpass\n");
1967 assert!(
1968 !codes(&untyped).contains(&UNSAFE_CALL_ARGUMENT),
1969 "untyped param accepts anything: {:?}",
1970 untyped.result.diagnostics
1971 );
1972 }
1973
1974 #[test]
1975 fn inference_on_variant() {
1976 let h = infer_first_func("func f(x):\n\tvar y := x\n");
1978 assert!(codes(&h).contains(&INFERENCE_ON_VARIANT));
1979 }
1980
1981 #[test]
1982 fn field_inferred_from_earlier_field_is_typed() {
1983 let codes = file_codes("var a := 1\nvar b := a + 1\n");
1987 assert!(
1988 !codes.iter().any(|c| c == INFERENCE_ON_VARIANT),
1989 "field `b` from earlier field `a` should type as int, not Variant: {codes:?}"
1990 );
1991 }
1992
1993 #[test]
1994 fn field_forward_reference_is_seamed_not_warned() {
1995 let codes = file_codes("var b := a\nvar a := 1\n");
1999 assert!(
2000 !codes.iter().any(|c| c == INFERENCE_ON_VARIANT),
2001 "forward field reference must not false-warn: {codes:?}"
2002 );
2003 }
2004
2005 #[test]
2006 fn standalone_inferred_field_unchanged() {
2007 let codes = file_codes("var n := 0\n");
2009 assert!(
2010 codes.is_empty(),
2011 "a literal-initialised field should produce no diagnostics: {codes:?}"
2012 );
2013 }
2014
2015 #[test]
2016 fn lambda_var_is_callable_not_variant() {
2017 let h = infer_first_func("func f():\n\tvar cb := func():\n\t\tpass\n");
2018 assert!(
2019 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2020 "{:?}",
2021 h.result.diagnostics
2022 );
2023 }
2024
2025 #[test]
2026 fn multiline_lambda_then_paren_line_no_false_warning() {
2027 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";
2031 let h = infer_first_func(src);
2032 assert!(
2033 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2034 "{:?}",
2035 h.result.diagnostics
2036 );
2037 }
2038
2039 #[test]
2040 fn calling_a_callable_value_is_seam_not_variant() {
2041 let src = "func f(cb: Callable):\n\tvar x := (cb)()\n\treturn x\n";
2045 let h = infer_first_func(src);
2046 assert!(
2047 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2048 "{:?}",
2049 h.result.diagnostics
2050 );
2051 }
2052
2053 #[test]
2054 fn ternary_with_seam_branch_does_not_collapse_to_variant() {
2055 let src =
2059 "func f(c: bool):\n\tvar x := 5 if c else await get_tree().process_frame\n\treturn x\n";
2060 let h = infer_first_func(src);
2061 assert!(
2062 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2063 "seam branch should keep the ternary on the seam: {:?}",
2064 h.result.diagnostics
2065 );
2066 }
2067
2068 #[test]
2069 fn await_a_coroutine_call_recovers_its_return_type() {
2070 let src = "func g() -> int:\n\tvar x := await make()\n\treturn x\nfunc make() -> int:\n\treturn 5\n";
2073 let h = infer_first_func(src);
2074 assert!(
2075 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2076 "no false variant warning: {:?}",
2077 h.result.diagnostics
2078 );
2079 let api = gdscript_api::bundled();
2080 let x = &h.result.bindings[0];
2081 assert!(
2082 matches!(&x.ty, Ty::Builtin(b) if api.builtin(*b).name == "int"),
2083 "await make() should recover int, got {:?}",
2084 x.ty
2085 );
2086 }
2087
2088 #[test]
2089 fn await_a_signal_stays_the_seam() {
2090 let src = "func f():\n\tvar x := await get_tree().process_frame\n\treturn x\n";
2093 let h = infer_first_func(src);
2094 assert!(
2095 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2096 "awaiting a signal must not warn: {:?}",
2097 h.result.diagnostics
2098 );
2099 assert!(
2100 matches!(&h.result.bindings[0].ty, Ty::Unknown),
2101 "awaiting a signal stays the seam, got {:?}",
2102 h.result.bindings[0].ty
2103 );
2104 }
2105
2106 #[test]
2107 fn for_var_over_packed_string_array_is_string() {
2108 let h = infer_first_func("func f():\n\tfor s in \"a,b\".split(\",\"):\n\t\tvar x := s\n");
2111 assert!(
2112 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2113 "{:?}",
2114 h.result.diagnostics
2115 );
2116 }
2117
2118 #[test]
2119 fn class_new_is_object_not_variant() {
2120 let h = infer_first_func("func f():\n\tvar s := GDScript.new()\n");
2121 assert!(
2122 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2123 "{:?}",
2124 h.result.diagnostics
2125 );
2126 }
2127
2128 #[test]
2129 fn unknown_seam_never_warns() {
2130 let h = infer_first_func("func f():\n\tvar s := preload(\"res://x.gd\")\n\ts.whatever()\n");
2132 assert!(
2133 h.result.diagnostics.is_empty(),
2134 "{:?}",
2135 h.result.diagnostics
2136 );
2137 }
2138
2139 #[test]
2140 fn expr_types_are_memoized_for_hover() {
2141 let h = infer_first_func("func f():\n\tvar n := 42\n");
2142 let has_int = h
2144 .result
2145 .expr_ty
2146 .values()
2147 .any(|t| matches!(t, Ty::Builtin(_)));
2148 assert!(has_int);
2149 assert!(!h.body.exprs.is_empty());
2151 }
2152}