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, FxHashSet};
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::flow::{self, FlowAnalysis, NarrowedTy, Place};
25use crate::item_tree::{ItemTree, Member, item_tree};
26use crate::resolve::{self, ClassItem, ClassScope, GlobalDef};
27use crate::ty::{self, Assign, EnumRef, ScriptRefId, Ty};
28use crate::warnings::{RawWarning, WarningCode};
29
30pub const INFERENCE_ON_VARIANT: &str = "INFERENCE_ON_VARIANT";
34pub const TYPE_MISMATCH: &str = "TYPE_MISMATCH";
36pub const NARROWING_CONVERSION: &str = "NARROWING_CONVERSION";
38pub const INTEGER_DIVISION: &str = "INTEGER_DIVISION";
40pub const UNSAFE_PROPERTY_ACCESS: &str = "UNSAFE_PROPERTY_ACCESS";
42pub const UNSAFE_METHOD_ACCESS: &str = "UNSAFE_METHOD_ACCESS";
44pub const UNSAFE_CALL_ARGUMENT: &str = "UNSAFE_CALL_ARGUMENT";
47pub const INVALID_NODE_PATH: &str = "INVALID_NODE_PATH";
51pub const SHADOWED_GLOBAL_IDENTIFIER: &str = "SHADOWED_GLOBAL_IDENTIFIER";
55pub const CYCLIC_INHERITANCE: &str = "CYCLIC_INHERITANCE";
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum BindingKind {
63 Var,
65 Param,
67 ForVar,
69 MatchBind,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct Binding {
76 pub name: SmolStr,
78 pub name_range: TextRange,
80 pub ty: Ty,
83 pub init: Option<ExprId>,
85 pub annotated: bool,
87 pub inferred_colon_eq: bool,
89 pub is_const: bool,
92 pub kind: BindingKind,
94}
95
96#[derive(Debug, Clone, Default, PartialEq, Eq)]
98pub struct InferenceResult {
99 pub expr_ty: FxHashMap<ExprId, Ty>,
101 pub bindings: Vec<Binding>,
103 pub diagnostics: Vec<Diagnostic>,
106 pub raw_warnings: Vec<RawWarning>,
109}
110
111impl InferenceResult {
112 #[must_use]
114 pub fn type_of(&self, id: ExprId) -> Option<&Ty> {
115 self.expr_ty.get(&id)
116 }
117
118 #[must_use]
120 pub fn binding_at(&self, offset: u32) -> Option<&Binding> {
121 self.bindings
122 .iter()
123 .find(|b| b.name_range.start <= offset && offset < b.name_range.end)
124 }
125}
126
127#[must_use]
131pub fn infer(
132 db: &dyn Db,
133 api: &EngineApi,
134 root: &GdNode,
135 class: &ClassScope,
136 body: &Body,
137 return_ty: Ty,
138 is_func_body: bool,
139) -> InferenceResult {
140 let self_ty = class.self_ty.clone();
141 let mut cx = Cx {
142 db,
143 api,
144 root,
145 body,
146 class,
147 self_ty,
148 return_ty,
149 expr_ty: FxHashMap::default(),
150 bindings: Vec::new(),
151 diagnostics: Vec::new(),
152 raw_warnings: Vec::new(),
153 locals: FxHashMap::default(),
154 used_locals: FxHashSet::default(),
155 narrowing: FxHashMap::default(),
156 flow: flow::analyze(body),
157 is_func_body,
158 };
159 let params = body.params.clone();
161 for p in ¶ms {
162 let ty = cx.param_ty(p);
163 cx.bindings.push(Binding {
164 name: p.name.clone(),
165 name_range: p.name_range,
166 ty: ty.clone(),
167 init: None,
168 annotated: p.type_ref.is_some(),
169 inferred_colon_eq: false,
170 is_const: false,
171 kind: BindingKind::Param,
172 });
173 cx.locals.insert(p.name.clone(), ty);
174 }
175 if let Some(tail) = body.tail {
176 cx.infer_expr(tail, &Expectation::None);
177 }
178 let block = body.block.clone();
179 cx.infer_block(&block);
180
181 if is_func_body {
185 let unused: Vec<(TextRange, WarningCode, String)> = cx
186 .bindings
187 .iter()
188 .filter_map(|b| {
189 if b.name.starts_with('_') || cx.used_locals.contains(&b.name) {
190 return None;
191 }
192 let (code, what) = match b.kind {
193 BindingKind::Param => (WarningCode::UnusedParameter, "parameter"),
194 BindingKind::Var if b.is_const => {
195 (WarningCode::UnusedLocalConstant, "local constant")
196 }
197 BindingKind::Var => (WarningCode::UnusedVariable, "local variable"),
198 BindingKind::ForVar | BindingKind::MatchBind => return None,
199 };
200 Some((
201 b.name_range,
202 code,
203 format!("The {what} \"{}\" is declared but never used.", b.name),
204 ))
205 })
206 .collect();
207 for (range, code, msg) in unused {
208 cx.warn(range, code, msg);
209 }
210 }
211
212 let unreachable = cx.flow.unreachable_ranges(body);
214 for range in unreachable {
215 cx.warn(
216 range,
217 WarningCode::UnreachableCode,
218 "Unreachable code (statement after a return, break, continue, or an exhaustive match)."
219 .to_owned(),
220 );
221 }
222
223 InferenceResult {
224 expr_ty: cx.expr_ty,
225 bindings: cx.bindings,
226 diagnostics: cx.diagnostics,
227 raw_warnings: cx.raw_warnings,
228 }
229}
230
231#[must_use]
234pub fn infer_func(
235 db: &dyn Db,
236 api: &EngineApi,
237 root: &GdNode,
238 class: &ClassScope,
239 ptr: AstPtr,
240) -> InferenceResult {
241 let Some(node) = ptr.to_node(root) else {
242 return InferenceResult::default();
243 };
244 let body = body::body_of_func(&node);
245 let return_ty = cst::first_child(&node, |k| k == gdscript_syntax::SyntaxKind::TypeRef)
248 .map_or(Ty::Variant, |t| resolve::resolve_type_ref(db, api, &t));
249 infer(db, api, root, class, &body, return_ty, true)
250}
251
252#[derive(Debug, Clone, PartialEq, Eq)]
256pub struct Unit {
257 pub range: TextRange,
259 pub body: Body,
261 pub result: InferenceResult,
263}
264
265#[derive(Debug, Clone, PartialEq, Eq, Default)]
268pub struct FileInference {
269 pub tree: Arc<ItemTree>,
271 pub units: Vec<Unit>,
273 pub diagnostics: Vec<Diagnostic>,
276 pub raw_warnings: Vec<RawWarning>,
279}
280
281impl FileInference {
282 #[must_use]
284 pub fn unit_at(&self, offset: u32) -> Option<&Unit> {
285 self.units
286 .iter()
287 .filter(|u| u.range.start <= offset && offset < u.range.end)
288 .min_by_key(|u| u.range.end - u.range.start)
289 }
290}
291
292#[must_use]
296#[allow(clippy::too_many_lines)] pub fn analyze_file(db: &dyn Db, api: &EngineApi, root: &GdNode, file_id: FileId) -> FileInference {
298 let tree = item_tree(root);
299 let mut units = Vec::new();
300 let mut diagnostics = Vec::new();
301 let mut raw_warnings: Vec<RawWarning> = Vec::new();
302
303 if tree.members.is_empty() && tree.class_name.is_none() && tree.extends.is_none() {
305 raw_warnings.push(RawWarning {
306 range: TextRange::new(0, 0),
307 code: WarningCode::EmptyFile,
308 message: "Empty script file.".to_owned(),
309 });
310 }
311 let mut member_types: FxHashMap<SmolStr, Ty> = FxHashMap::default();
312 let self_ref = Ty::ScriptRef(ScriptRefId(file_id.0));
315 let res_path = db.file_text(file_id).and_then(|ft| ft.res_path(db));
317
318 if let Some(name) = tree.class_name.clone() {
323 let collides = collisions_contains(db, &name)
324 || resolve::resolve_global(api, &name).is_some()
325 || is_autoload_singleton(db, &name);
326 if collides && let Some(range) = class_name_decl_range(root) {
327 diagnostics.push(Diagnostic {
328 range,
329 severity: Severity::Warning,
330 code: SHADOWED_GLOBAL_IDENTIFIER.to_owned(),
331 message: format!(
332 "The global class \"{name}\" hides a built-in/native/global/autoload."
333 ),
334 source: DiagnosticSource::Type,
335 fixes: Vec::new(),
336 });
337 }
338 }
339
340 if extends_chain_is_cyclic(db, file_id)
348 && let Some(range) = extends_decl_range(root)
349 {
350 diagnostics.push(Diagnostic {
351 range,
352 severity: Severity::Warning,
353 code: CYCLIC_INHERITANCE.to_owned(),
354 message: "Cyclic class hierarchy: this class's `extends` chain returns to itself."
355 .to_owned(),
356 source: DiagnosticSource::Type,
357 fixes: Vec::new(),
358 });
359 }
360
361 {
371 const MAX_ROUNDS: usize = 4;
375 let mut final_units: Vec<Unit> = Vec::new();
376 let mut final_diagnostics: Vec<Diagnostic> = Vec::new();
377 let mut final_raw_warnings: Vec<RawWarning> = Vec::new();
378 for _ in 0..MAX_ROUNDS {
379 let mut class = ClassScope::new(db, api, &tree, res_path.as_deref());
380 class.self_ty = self_ref.clone();
381 class.member_types.clone_from(&member_types);
382 let mut next_member_types: FxHashMap<SmolStr, Ty> = FxHashMap::default();
383 final_units = Vec::new();
384 final_diagnostics = Vec::new();
385 final_raw_warnings = Vec::new();
386 for m in &tree.members {
387 let (ptr, range) = match m {
388 Member::Var(v) => (v.ptr, v.range),
389 Member::Const(c) => (c.ptr, c.range),
390 _ => continue,
391 };
392 if let Some(unit) = unit_from_decl(db, api, root, &class, ptr, range) {
393 if let (Some(name), Some(b)) = (m.name(), unit.result.bindings.first()) {
394 next_member_types.insert(SmolStr::new(name), b.ty.clone());
395 }
396 final_diagnostics.extend(unit.result.diagnostics.iter().cloned());
397 final_raw_warnings.extend(unit.result.raw_warnings.iter().cloned());
398 final_units.push(unit);
399 }
400 }
401 if next_member_types == member_types {
402 break;
403 }
404 member_types = next_member_types;
405 }
406 diagnostics.extend(final_diagnostics);
407 raw_warnings.extend(final_raw_warnings);
408 units.extend(final_units);
409 }
410
411 {
413 let mut class = ClassScope::new(db, api, &tree, res_path.as_deref());
414 class.member_types = member_types;
415 class.self_ty = self_ref.clone();
416 for m in &tree.members {
417 let Member::Func(f) = m else { continue };
418 let Some(node) = f.ptr.to_node(root) else {
419 continue;
420 };
421 let body = body::body_of_func(&node);
422 let return_ty = cst::first_child(&node, |k| k == gdscript_syntax::SyntaxKind::TypeRef)
423 .map_or(Ty::Variant, |t| resolve::resolve_type_ref(db, api, &t));
424 let result = infer(db, api, root, &class, &body, return_ty, true);
425 diagnostics.extend(result.diagnostics.iter().cloned());
426 raw_warnings.extend(result.raw_warnings.iter().cloned());
427 units.push(Unit {
428 range: f.range,
429 body,
430 result,
431 });
432 }
433 }
434
435 FileInference {
436 tree,
437 units,
438 diagnostics,
439 raw_warnings,
440 }
441}
442
443fn collisions_contains(db: &dyn Db, name: &SmolStr) -> bool {
447 db.source_root()
448 .is_some_and(|root| crate::queries::class_name_collisions(db, root).contains(name))
449}
450
451fn is_autoload_singleton(db: &dyn Db, name: &str) -> bool {
454 db.project_config().is_some_and(|config| {
455 crate::queries::autoload_registry(db, config)
456 .resolve_path(name)
457 .is_some()
458 })
459}
460
461fn class_name_decl_range(root: &GdNode) -> Option<TextRange> {
466 use gdscript_syntax::SyntaxKind;
467 let decl = gdscript_syntax::ast::descendants(root)
468 .into_iter()
469 .find(|n| n.kind() == SyntaxKind::ClassNameDecl)?;
470 let name_node = decl.children().find(|c| c.kind() == SyntaxKind::Name)?;
471 let r = cst::text_range_of(name_node);
472 let text = name_node.text().to_string();
473 let lead = u32::try_from(text.len() - text.trim_start().len()).unwrap_or(0);
474 let len = u32::try_from(text.trim().len()).unwrap_or(0);
475 Some(TextRange::new(r.start + lead, r.start + lead + len))
476}
477
478fn extends_decl_range(root: &GdNode) -> Option<TextRange> {
485 use gdscript_syntax::SyntaxKind;
486 for child in root.children() {
487 match child.kind() {
488 SyntaxKind::ExtendsClause => return Some(cst::text_range_of(child)),
490 SyntaxKind::ClassNameDecl => {
492 if let Some(kw) = child.children().find(|c| c.kind() == SyntaxKind::ExtendsKw) {
493 let start = cst::text_range_of(kw).start;
494 let end = cst::text_range_of(child).end;
495 return Some(TextRange::new(start, end));
496 }
497 }
498 _ => {}
499 }
500 }
501 None
502}
503
504fn extends_chain_is_cyclic(db: &dyn Db, start: FileId) -> bool {
511 use std::collections::HashSet;
512 let mut visited: HashSet<FileId> = HashSet::new();
513 visited.insert(start);
514 let mut current = start;
515 for _ in 0..=64 {
516 let Some(file) = db.file_text(current) else {
517 return false;
518 };
519 let base = crate::queries::script_class(db, file).base().clone();
520 let Ty::ScriptRef(next) = base else {
521 return false; };
523 let next_id = FileId(next.0);
524 if !visited.insert(next_id) {
525 return true;
528 }
529 current = next_id;
530 }
531 false
532}
533
534fn unit_from_decl(
536 db: &dyn Db,
537 api: &EngineApi,
538 root: &GdNode,
539 class: &ClassScope,
540 ptr: AstPtr,
541 range: TextRange,
542) -> Option<Unit> {
543 let node = ptr.to_node(root)?;
544 let body = body::body_of_decl_stmt(&node);
545 let result = infer(db, api, root, class, &body, Ty::Variant, false);
546 Some(Unit {
547 range,
548 body,
549 result,
550 })
551}
552
553enum Expectation {
555 None,
557 Has(Ty),
559}
560
561struct Cx<'a> {
562 db: &'a dyn Db,
563 api: &'a EngineApi,
564 root: &'a GdNode,
565 body: &'a Body,
566 class: &'a ClassScope<'a>,
567 self_ty: Ty,
568 return_ty: Ty,
569 expr_ty: FxHashMap<ExprId, Ty>,
570 bindings: Vec<Binding>,
571 diagnostics: Vec<Diagnostic>,
572 raw_warnings: Vec<RawWarning>,
574 locals: FxHashMap<SmolStr, Ty>,
576 used_locals: FxHashSet<SmolStr>,
580 narrowing: FxHashMap<String, Ty>,
583 flow: FlowAnalysis,
586 is_func_body: bool,
590}
591
592impl Cx<'_> {
593 fn builtin(&self, name: &str) -> Ty {
596 self.api
597 .builtin_by_name(name)
598 .map_or(Ty::Variant, Ty::Builtin)
599 }
600 fn int_ty(&self) -> Ty {
601 self.builtin("int")
602 }
603 fn float_ty(&self) -> Ty {
604 self.builtin("float")
605 }
606 fn bool_ty(&self) -> Ty {
607 self.builtin("bool")
608 }
609 fn is_int(&self, ty: &Ty) -> bool {
610 matches!(ty, Ty::Builtin(b) if self.api.builtin(*b).name == "int")
611 }
612 fn is_float(&self, ty: &Ty) -> bool {
613 matches!(ty, Ty::Builtin(b) if self.api.builtin(*b).name == "float")
614 }
615 fn is_numeric(&self, ty: &Ty) -> bool {
616 self.is_int(ty) || self.is_float(ty)
617 }
618
619 fn emit(&mut self, range: TextRange, severity: Severity, code: &str, message: String) {
622 self.diagnostics.push(Diagnostic {
623 range,
624 severity,
625 code: code.to_owned(),
626 message,
627 source: DiagnosticSource::Type,
628 fixes: Vec::new(),
629 });
630 }
631
632 fn warn(&mut self, range: TextRange, code: WarningCode, message: String) {
636 self.raw_warnings.push(RawWarning {
637 range,
638 code,
639 message,
640 });
641 }
642
643 fn range_of(&self, id: ExprId) -> TextRange {
644 self.body.source_map.expr_range(id)
645 }
646
647 fn check_assign(&mut self, from: &Ty, to: &Ty, range: TextRange) {
650 match ty::is_assignable(self.api, from, to) {
651 Assign::Narrowing => self.warn(
652 range,
653 WarningCode::NarrowingConversion,
654 "Narrowing conversion (float is converted to int and loses precision).".to_owned(),
655 ),
656 Assign::No => {
657 let to_label = to.label(self.api).unwrap_or_else(|| "?".to_owned());
658 let from_label = from.label(self.api).unwrap_or_else(|| "?".to_owned());
659 self.emit(
660 range,
661 Severity::Error,
662 TYPE_MISMATCH,
663 format!(
664 "Cannot assign a value of type \"{from_label}\" to a target of type \"{to_label}\"."
665 ),
666 );
667 }
668 Assign::IntAsEnum => self.warn(
670 range,
671 WarningCode::IntAsEnumWithoutCast,
672 "Integer used when an enum value is expected. Cast the value to the enum type."
673 .to_owned(),
674 ),
675 Assign::Ok | Assign::OkUnsafe => {}
676 }
677 }
678
679 fn check_standalone(&mut self, e: ExprId) {
683 if self.expr_has_side_effect(e) {
684 return;
685 }
686 match self.body.expr(e) {
687 Expr::Ternary { .. } => self.warn(
688 self.range_of(e),
689 WarningCode::StandaloneTernary,
690 "Standalone ternary conditional: the return value is discarded.".to_owned(),
691 ),
692 Expr::Missing | Expr::Lambda { .. } | Expr::GetNode { .. } | Expr::Preload { .. } => {}
694 _ => self.warn(
695 self.range_of(e),
696 WarningCode::StandaloneExpression,
697 "Standalone expression (the line has no effect).".to_owned(),
698 ),
699 }
700 }
701
702 fn expr_has_side_effect(&self, e: ExprId) -> bool {
705 match self.body.expr(e) {
706 Expr::Call { .. }
707 | Expr::Await(_)
708 | Expr::Preload { .. }
709 | Expr::Bin {
710 op: BinOp::Assign, ..
711 } => true,
712 Expr::Bin { lhs, rhs, .. }
713 | Expr::In { lhs, rhs, .. }
714 | Expr::Index {
715 base: lhs,
716 index: rhs,
717 } => self.expr_has_side_effect(*lhs) || self.expr_has_side_effect(*rhs),
718 Expr::Unary { operand, .. }
719 | Expr::Paren(operand)
720 | Expr::Cast { operand, .. }
721 | Expr::Is { operand, .. } => self.expr_has_side_effect(*operand),
722 Expr::Field { receiver, .. } => self.expr_has_side_effect(*receiver),
723 Expr::Ternary {
724 cond,
725 then_branch,
726 else_branch,
727 } => {
728 self.expr_has_side_effect(*cond)
729 || self.expr_has_side_effect(*then_branch)
730 || self.expr_has_side_effect(*else_branch)
731 }
732 Expr::Array(items) => items.iter().any(|&i| self.expr_has_side_effect(i)),
733 Expr::Dict(entries) => entries.iter().any(|(k, v)| {
734 self.expr_has_side_effect(*k) || v.is_some_and(|e| self.expr_has_side_effect(e))
735 }),
736 _ => false,
737 }
738 }
739
740 fn infer_block(&mut self, block: &[body::StmtId]) {
743 for &stmt in block {
744 self.infer_stmt(stmt);
745 }
746 }
747
748 fn infer_stmt(&mut self, id: body::StmtId) {
749 self.narrowing = self.facts_to_narrowing(id);
752 match self.body.stmt(id).clone() {
753 Stmt::Expr(e) => {
754 self.infer_expr(e, &Expectation::None);
755 self.check_standalone(e);
756 }
757 Stmt::Var(v) => self.infer_local_var(&v),
758 Stmt::Return(e) => {
759 if let Some(e) = e {
760 let expected = if self.return_ty.is_uninformative() {
761 Expectation::None
762 } else {
763 Expectation::Has(self.return_ty.clone())
764 };
765 let t = self.infer_expr(e, &expected);
766 if let Expectation::Has(ret) = expected {
767 self.check_assign(&t, &ret, self.range_of(e));
768 }
769 }
770 }
771 Stmt::If {
772 cond,
773 then_branch,
774 elifs,
775 else_branch,
776 } => {
777 let at_if = self.narrowing.clone();
781 self.infer_expr(cond, &Expectation::None);
782 self.infer_block(&then_branch);
783 for (econd, eblock) in elifs {
784 self.narrowing.clone_from(&at_if);
785 self.infer_expr(econd, &Expectation::None);
786 self.infer_block(&eblock);
787 }
788 if let Some(eb) = else_branch {
789 self.infer_block(&eb);
790 }
791 }
792 Stmt::While { cond, body } => {
793 self.infer_expr(cond, &Expectation::None);
794 self.infer_block(&body);
795 }
796 Stmt::For(f) => {
797 let iter_ty = self.infer_expr(f.iter, &Expectation::None);
798 let var_ty = f.var_type.as_ref().map_or_else(
799 || self.loop_var_ty(&iter_ty),
800 |ptr| self.resolve_ptr_ty(*ptr),
801 );
802 self.bindings.push(Binding {
803 name: f.var.clone(),
804 name_range: f.var_range,
805 ty: var_ty.clone(),
806 init: None,
807 annotated: f.var_type.is_some(),
808 inferred_colon_eq: false,
809 is_const: false,
810 kind: BindingKind::ForVar,
811 });
812 self.locals.insert(f.var.clone(), var_ty);
813 self.infer_block(&f.body);
814 }
815 Stmt::Match { scrutinee, arms } => {
816 let at_match = self.narrowing.clone();
817 self.infer_expr(scrutinee, &Expectation::None);
818 for arm in arms {
819 self.narrowing.clone_from(&at_match);
822 for b in &arm.binds {
823 self.bindings.push(Binding {
827 name: b.name.clone(),
828 name_range: b.range,
829 ty: Ty::Variant,
830 init: None,
831 annotated: false,
832 inferred_colon_eq: false,
833 is_const: false,
834 kind: BindingKind::MatchBind,
835 });
836 self.locals.insert(b.name.clone(), Ty::Variant);
837 }
838 if let Some(g) = arm.guard {
839 self.infer_expr(g, &Expectation::None);
840 }
841 self.infer_block(&arm.body);
842 }
843 }
844 Stmt::Break | Stmt::Continue | Stmt::Pass => {}
845 Stmt::Assert(cond) => {
846 if let Some(cond) = cond {
847 self.infer_expr(cond, &Expectation::None);
848 }
849 }
850 }
851 }
852
853 fn infer_local_var(&mut self, v: &body::LocalVar) {
854 let annotated = v.type_ref.map(|p| self.resolve_ptr_ty(p));
855 let init_ty = v.init.map(|e| {
856 let expected = annotated
857 .as_ref()
858 .map_or(Expectation::None, |t| Expectation::Has(t.clone()));
859 self.infer_expr(e, &expected)
860 });
861 let range = v.init.map_or(v.name_range, |e| self.range_of(e));
862
863 let binding_ty = match (&annotated, &init_ty) {
864 (Some(t), Some(init)) => {
866 self.check_assign(init, t, range);
867 t.clone()
868 }
869 (Some(t), None) => t.clone(),
871 (None, Some(init)) if v.is_inferred => {
873 if init.is_variant() {
874 self.warn(
875 range,
876 WarningCode::InferenceOnVariant,
877 inference_on_variant_msg(if v.is_const { "constant" } else { "variable" }),
878 );
879 Ty::Variant
880 } else {
881 init.clone()
883 }
884 }
885 (None, Some(init)) => {
887 if v.is_const {
888 init.clone()
889 } else {
890 Ty::Variant
891 }
892 }
893 (None, None) => Ty::Variant,
894 };
895 let shadows_param = self
900 .bindings
901 .iter()
902 .any(|b| b.kind == BindingKind::Param && b.name == v.name);
903 let shadows_member = match self.class.lookup(&v.name) {
906 Some(ClassItem::EnumVariant) => true,
907 Some(item) => matches!(
908 self.class.member(item),
909 Some(Member::Var(_) | Member::Const(_) | Member::Signal(_))
910 ),
911 None => false,
912 };
913 if self.is_func_body && (shadows_param || shadows_member) {
914 let what = if v.is_const { "constant" } else { "variable" };
915 let outer = if shadows_param {
916 "parameter"
917 } else {
918 "class member"
919 };
920 self.warn(
921 v.name_range,
922 WarningCode::ShadowedVariable,
923 format!(
924 "The local {what} \"{}\" shadows a {outer} of the same name.",
925 v.name
926 ),
927 );
928 }
929 self.bindings.push(Binding {
930 name: v.name.clone(),
931 name_range: v.name_range,
932 ty: binding_ty.clone(),
933 init: v.init,
934 annotated: v.type_ref.is_some(),
935 inferred_colon_eq: v.is_inferred,
936 is_const: v.is_const,
937 kind: BindingKind::Var,
938 });
939 self.locals.insert(v.name.clone(), binding_ty);
941 }
942
943 fn infer_expr(&mut self, id: ExprId, expected: &Expectation) -> Ty {
946 let ty = self.synth_expr(id, expected);
947 self.expr_ty.insert(id, ty.clone());
948 ty
949 }
950
951 #[allow(clippy::too_many_lines)]
952 fn synth_expr(&mut self, id: ExprId, expected: &Expectation) -> Ty {
953 match self.body.expr(id).clone() {
954 Expr::Missing => Ty::Error,
955 Expr::Literal(lit) => self.literal_ty(lit),
956 Expr::Name(name) => self.resolve_name(id, &name),
957 Expr::SelfExpr => self.self_ty.clone(),
958 Expr::Super => self.class.base.clone(),
959 Expr::Paren(inner) => self.infer_expr(inner, expected),
960 Expr::Bin { op, lhs, rhs } => self.infer_bin(id, op, lhs, rhs),
961 Expr::Unary { op, operand } => {
962 let t = self.infer_expr(operand, &Expectation::None);
963 match op {
964 UnOp::Not => self.bool_ty(),
965 UnOp::BitNot => self.int_ty(),
966 UnOp::Neg | UnOp::Pos => {
967 if t.is_uninformative() || self.is_numeric(&t) {
968 t
969 } else {
970 Ty::Variant
971 }
972 }
973 }
974 }
975 Expr::Ternary {
976 cond,
977 then_branch,
978 else_branch,
979 } => {
980 self.infer_expr(cond, &Expectation::None);
981 let a = self.infer_expr(then_branch, expected);
982 let b = self.infer_expr(else_branch, expected);
983 if self.is_null(else_branch) {
985 a
986 } else if self.is_null(then_branch) {
987 b
988 } else {
989 let r = self.join(&a, &b);
990 if r.is_variant() && !a.is_uninformative() && !b.is_uninformative() {
993 self.warn(
994 self.range_of(id),
995 WarningCode::IncompatibleTernary,
996 "The values of the ternary conditional are not mutually compatible."
997 .to_owned(),
998 );
999 }
1000 r
1001 }
1002 }
1003 Expr::Call { callee, args } => self.infer_call(callee, &args),
1004 Expr::Field {
1005 receiver,
1006 name,
1007 name_range,
1008 } => {
1009 self.infer_field(receiver, &name, name_range, false)
1010 }
1011 Expr::Index { base, index } => {
1012 let base_ty = self.infer_expr(base, &Expectation::None);
1013 self.infer_expr(index, &Expectation::None);
1014 self.index_ty(&base_ty)
1015 }
1016 Expr::Is { operand, .. } => {
1017 self.infer_expr(operand, &Expectation::None);
1018 self.bool_ty()
1019 }
1020 Expr::Cast { operand, ty } => {
1021 self.infer_expr(operand, &Expectation::None);
1022 ty.map_or(Ty::Variant, |p| self.resolve_ptr_ty(p))
1023 }
1024 Expr::In { lhs, rhs, .. } => {
1025 self.infer_expr(lhs, &Expectation::None);
1026 self.infer_expr(rhs, &Expectation::None);
1027 self.bool_ty()
1028 }
1029 Expr::Await(operand) => {
1030 let operand_ty = self.infer_expr(operand, &Expectation::None);
1031 if matches!(operand_ty, Ty::Signal(_)) {
1036 Ty::Unknown
1037 } else {
1038 operand_ty
1039 }
1040 }
1041 Expr::Array(elems) => {
1042 let pushed = match expected {
1046 Expectation::Has(Ty::Array(e)) => Some((**e).clone()),
1047 _ => None,
1048 };
1049 let elem_exp = pushed.clone().map_or(Expectation::None, Expectation::Has);
1050 for e in elems {
1051 self.infer_expr(e, &elem_exp);
1052 }
1053 pushed.map_or_else(Ty::array_of_variant, |e| Ty::Array(Box::new(e)))
1054 }
1055 Expr::Dict(entries) => {
1056 let pushed = match expected {
1057 Expectation::Has(Ty::Dict(k, v)) => Some(((**k).clone(), (**v).clone())),
1058 _ => None,
1059 };
1060 let (kx, vx) = pushed
1061 .clone()
1062 .map_or((Expectation::None, Expectation::None), |(k, v)| {
1063 (Expectation::Has(k), Expectation::Has(v))
1064 });
1065 for (k, v) in entries {
1066 self.infer_expr(k, &kx);
1067 if let Some(v) = v {
1068 self.infer_expr(v, &vx);
1069 }
1070 }
1071 pushed.map_or_else(Ty::dict_of_variant, |(k, v)| {
1072 Ty::Dict(Box::new(k), Box::new(v))
1073 })
1074 }
1075 Expr::Lambda { params, body } => {
1076 self.infer_lambda(¶ms, &body);
1077 Ty::Callable
1078 }
1079 Expr::Preload { arg, path } => {
1080 if let Some(arg) = arg {
1081 self.infer_expr(arg, &Expectation::None);
1082 }
1083 match path {
1088 Some(p) => {
1092 match resolve::anchor_res_path(self.self_res_path().as_deref(), &p) {
1093 Some(abs) => resolve::resolve_external(
1094 self.db,
1095 &resolve::ExternalRef::Preload(abs),
1096 ),
1097 None => Ty::Unknown,
1098 }
1099 }
1100 None => Ty::Unknown,
1101 }
1102 }
1103 Expr::GetNode { path, unique } => self.resolve_node_path(id, path.as_deref(), unique),
1106 }
1107 }
1108
1109 fn is_null(&self, id: ExprId) -> bool {
1111 matches!(self.body.expr(id), Expr::Literal(Literal::Null))
1112 }
1113
1114 fn literal_ty(&self, lit: Literal) -> Ty {
1115 match lit {
1116 Literal::Int => self.int_ty(),
1117 Literal::Float | Literal::MathConst => self.float_ty(),
1118 Literal::Bool => self.bool_ty(),
1119 Literal::Str => self.builtin("String"),
1120 Literal::StringName => self.builtin("StringName"),
1121 Literal::NodePath => self.builtin("NodePath"),
1122 Literal::Null => Ty::Variant,
1124 }
1125 }
1126
1127 fn node_ty(&self) -> Ty {
1128 self.api
1129 .class_by_name("Node")
1130 .map_or(Ty::Unknown, Ty::Object)
1131 }
1132
1133 fn resolve_node_path(&mut self, id: ExprId, path: Option<&str>, unique: bool) -> Ty {
1141 use gdscript_scene::NodePathResolution as R;
1142 let fallback = self.node_ty();
1143 let Some(path) = path else {
1144 return fallback; };
1146 let Some(ctx) = self.owning_scene() else {
1147 return fallback; };
1149 let resolution = if unique {
1150 ctx.model.classify_unique(path)
1151 } else {
1152 ctx.model.classify_path_from(ctx.attach, path)
1153 };
1154 match resolution {
1155 R::Resolved(idx) => ctx
1156 .model
1157 .node(idx)
1158 .and_then(|n| self.scene_node_ty(&ctx.model, n, 0))
1159 .unwrap_or(fallback),
1160 R::Missing if !ctx.ambiguous => {
1161 let what = if unique { "unique name" } else { "node path" };
1162 let sigil = if unique { "%" } else { "$" };
1163 self.emit(
1164 self.range_of(id),
1165 Severity::Warning,
1166 INVALID_NODE_PATH,
1167 format!("no {what} `{sigil}{path}` in the owning scene"),
1168 );
1169 fallback
1170 }
1171 R::IntoInstance => {
1174 let walked = if unique {
1175 ctx.model.resolve_unique_into_instance(path)
1176 } else {
1177 ctx.model.resolve_into_instance(ctx.attach, path)
1178 };
1179 walked
1180 .and_then(|(inst, tail)| {
1181 let inst_node = ctx.model.node(inst)?;
1182 self.resolve_into_instance_ty(&ctx.model, inst_node, &tail, 0)
1183 })
1184 .unwrap_or(fallback)
1185 }
1186 _ => fallback,
1188 }
1189 }
1190
1191 fn owning_scene(&self) -> Option<crate::queries::SceneContext> {
1195 let Ty::ScriptRef(sref) = &self.self_ty else {
1196 return None;
1197 };
1198 let ft = self.db.file_text(FileId(sref.0))?;
1199 crate::queries::scene_context(self.db, ft)
1200 }
1201
1202 fn self_res_path(&self) -> Option<SmolStr> {
1205 let Ty::ScriptRef(sref) = &self.self_ty else {
1206 return None;
1207 };
1208 self.db.file_text(FileId(sref.0))?.res_path(self.db)
1209 }
1210
1211 fn scene_node_ty(&self, scene: &SceneModel, node: &SceneNode, depth: u32) -> Option<Ty> {
1216 if let Some(script_ty) = self.node_script_ref(scene, node) {
1217 return Some(script_ty);
1218 }
1219 if let Some(decl) = node.decl_type.as_ref() {
1220 let ty = resolve::resolve_type_name(self.db, self.api, decl);
1221 if !ty.is_uninformative() {
1222 return Some(ty);
1223 }
1224 }
1225 self.instance_root_ty(scene, node, depth)
1226 }
1227
1228 fn instance_root_ty(&self, scene: &SceneModel, node: &SceneNode, depth: u32) -> Option<Ty> {
1233 if depth >= 16 {
1234 return None;
1235 }
1236 let (sub, sub_root) = self.instance_subscene(scene, node)?;
1237 let root_node = sub.node(sub_root)?;
1238 self.scene_node_ty(&sub, root_node, depth + 1)
1239 }
1240
1241 fn instance_subscene(
1246 &self,
1247 scene: &SceneModel,
1248 node: &SceneNode,
1249 ) -> Option<(Arc<SceneModel>, gdscript_scene::NodeIdx)> {
1250 let inst = node.instance.as_ref()?;
1251 let path = scene.ext_resources.get(inst)?.path.as_ref()?;
1252 let root = self.db.source_root()?;
1253 let file = crate::queries::res_path_registry(self.db, root)
1254 .get(path.as_str())
1255 .copied()?;
1256 let ft = self.db.file_text(file)?;
1257 let sub = crate::queries::scene_model(self.db, ft);
1258 let sub_root = sub.root?;
1259 Some((sub, sub_root))
1260 }
1261
1262 fn resolve_into_instance_ty(
1267 &self,
1268 scene: &SceneModel,
1269 instance_node: &SceneNode,
1270 tail: &str,
1271 depth: u32,
1272 ) -> Option<Ty> {
1273 if depth >= 16 {
1274 return None;
1275 }
1276 let (sub, sub_root) = self.instance_subscene(scene, instance_node)?;
1277 if let Some(idx) = sub.resolve_path_from(sub_root, tail) {
1278 let n = sub.node(idx)?;
1279 return self.scene_node_ty(&sub, n, depth + 1);
1280 }
1281 let (inner, inner_tail) = sub.resolve_into_instance(sub_root, tail)?;
1283 let inner_node = sub.node(inner)?;
1284 self.resolve_into_instance_ty(&sub, inner_node, &inner_tail, depth + 1)
1285 }
1286
1287 fn node_script_ref(&self, scene: &SceneModel, node: &SceneNode) -> Option<Ty> {
1290 let path = scene
1291 .ext_resources
1292 .get(node.script.as_ref()?)?
1293 .path
1294 .as_ref()?;
1295 let root = self.db.source_root()?;
1296 let file = crate::queries::res_path_registry(self.db, root)
1297 .get(path.as_str())
1298 .copied()?;
1299 Some(Ty::ScriptRef(ScriptRefId(file.0)))
1300 }
1301
1302 fn infer_bin(&mut self, id: ExprId, op: BinOp, lhs: ExprId, rhs: ExprId) -> Ty {
1303 if op == BinOp::Assign {
1304 return self.infer_assign(lhs, rhs);
1305 }
1306 if matches!(op, BinOp::And | BinOp::Or) {
1309 self.infer_expr(lhs, &Expectation::None);
1310 let saved = self.narrowing.clone();
1311 self.apply_condition_facts(lhs, op == BinOp::And);
1312 self.infer_expr(rhs, &Expectation::None);
1313 self.narrowing = saved;
1314 return self.bool_ty();
1315 }
1316 let lt = self.infer_expr(lhs, &Expectation::None);
1317 let rt = self.infer_expr(rhs, &Expectation::None);
1318 if op.is_boolean() {
1319 return self.bool_ty();
1320 }
1321 if op == BinOp::Div && self.is_int(<) && self.is_int(&rt) {
1323 self.warn(
1324 self.range_of(id),
1325 WarningCode::IntegerDivision,
1326 "Integer division. Decimal part will be discarded.".to_owned(),
1327 );
1328 return self.int_ty();
1329 }
1330 self.bin_result(op, <, &rt)
1331 }
1332
1333 fn infer_assign(&mut self, lhs: ExprId, rhs: ExprId) -> Ty {
1334 let slot = self.infer_expr(lhs, &Expectation::None);
1335 let expected = if slot.is_uninformative() {
1336 Expectation::None
1337 } else {
1338 Expectation::Has(slot.clone())
1339 };
1340 let value = self.infer_expr(rhs, &expected);
1341 if !slot.is_uninformative() {
1342 self.check_assign(&value, &slot, self.range_of(rhs));
1343 }
1344 slot
1347 }
1348
1349 fn bin_result(&self, op: BinOp, lt: &Ty, rt: &Ty) -> Ty {
1352 if let (Ty::Builtin(b), Some(sym)) = (lt, op_symbol(op)) {
1353 for o in self.api.builtin_operators(*b) {
1354 if o.op == sym
1355 && let Some(right) = &o.right
1356 && self.tyref_matches(right, rt)
1357 {
1358 return ty::resolve_tyref(self.api, &o.result);
1359 }
1360 }
1361 }
1362 if self.is_numeric(lt) && self.is_numeric(rt) {
1363 return if self.is_float(lt) || self.is_float(rt) {
1364 self.float_ty()
1365 } else {
1366 self.int_ty()
1367 };
1368 }
1369 if lt.is_unknown() || rt.is_unknown() || lt.is_error() || rt.is_error() {
1372 return Ty::Unknown;
1373 }
1374 Ty::Variant
1375 }
1376
1377 fn tyref_matches(&self, tyref: &TyRef, ty: &Ty) -> bool {
1378 let resolved = ty::resolve_tyref(self.api, tyref);
1379 resolved.is_variant() || &resolved == ty
1380 }
1381
1382 fn infer_call(&mut self, callee: ExprId, args: &[ExprId]) -> Ty {
1383 for &a in args {
1385 self.infer_expr(a, &Expectation::None);
1386 }
1387 let ret = match self.body.expr(callee).clone() {
1388 Expr::Field {
1389 receiver,
1390 name,
1391 name_range,
1392 } => {
1393 self.infer_field(receiver, &name, name_range, true)
1394 }
1395 Expr::Name(name) => {
1396 let ret = self.resolve_call_name(&name);
1397 self.expr_ty.insert(callee, Ty::Callable);
1398 ret
1399 }
1400 _ => {
1404 self.infer_expr(callee, &Expectation::None);
1405 Ty::Unknown
1406 }
1407 };
1408 self.check_call_args(callee, args);
1411 ret
1412 }
1413
1414 fn check_call_args(&mut self, callee: ExprId, args: &[ExprId]) {
1420 let Some(params) = self.call_param_tys(callee) else {
1421 return;
1422 };
1423 for (i, &arg) in args.iter().enumerate() {
1424 let Some(param_ty) = params.get(i) else {
1425 break; };
1427 if param_ty.is_uninformative() || param_ty.is_variant() {
1428 continue; }
1430 let arg_ty = self.expr_ty.get(&arg).cloned().unwrap_or(Ty::Unknown);
1432 if ty::is_assignable(self.api, &arg_ty, param_ty) == Assign::OkUnsafe {
1433 let pl = param_ty.label(self.api).unwrap_or_else(|| "?".to_owned());
1434 let al = arg_ty.label(self.api).unwrap_or_else(|| "?".to_owned());
1435 self.warn(
1436 self.range_of(arg),
1437 WarningCode::UnsafeCallArgument,
1438 format!(
1439 "The argument {} requires a value of type \"{pl}\" but is passed \"{al}\", which is unsafe.",
1440 i + 1
1441 ),
1442 );
1443 }
1444 }
1445 }
1446
1447 fn call_param_tys(&self, callee: ExprId) -> Option<Vec<Ty>> {
1451 match self.body.expr(callee) {
1452 Expr::Name(name) => self.name_call_param_tys(name),
1453 Expr::Field { receiver, name, .. } => match self.expr_ty.get(receiver)? {
1454 Ty::Object(class) => match self.api.lookup_member(*class, name)? {
1455 MemberRef::Method(sig) => Some(
1456 sig.params
1457 .iter()
1458 .map(|p| ty::resolve_tyref(self.api, &p.ty))
1459 .collect(),
1460 ),
1461 _ => None,
1462 },
1463 _ => None,
1465 },
1466 _ => None,
1467 }
1468 }
1469
1470 fn name_call_param_tys(&self, name: &str) -> Option<Vec<Ty>> {
1474 if let Some(item) = self.class.lookup(name)
1475 && let Some(Member::Func(f)) = self.class.member(item)
1476 {
1477 return Some(
1478 f.params
1479 .iter()
1480 .map(|p| {
1481 p.type_ref.as_deref().map_or(Ty::Variant, |t| {
1482 resolve::resolve_type_name(self.db, self.api, t)
1483 })
1484 })
1485 .collect(),
1486 );
1487 }
1488 if let Ty::Object(base) = self.class.base
1489 && let Some(MemberRef::Method(sig)) = self.api.lookup_member(base, name)
1490 {
1491 return Some(
1492 sig.params
1493 .iter()
1494 .map(|p| ty::resolve_tyref(self.api, &p.ty))
1495 .collect(),
1496 );
1497 }
1498 None
1499 }
1500
1501 fn resolve_call_name(&self, name: &str) -> Ty {
1503 if let Some(item) = self.class.lookup(name)
1504 && let Some(Member::Func(f)) = self.class.member(item)
1505 {
1506 return self.func_return_ty(f.return_type.as_deref());
1507 }
1508 if let Ty::Object(base) = self.class.base
1510 && let Some(MemberRef::Method(sig)) = self.api.lookup_member(base, name)
1511 {
1512 return ty::resolve_tyref(self.api, &sig.return_ty);
1513 }
1514 if let Some(u) = self.api.utility(name) {
1515 return ty::resolve_tyref(self.api, &u.return_ty);
1516 }
1517 if let Some(f) = self.api.gdscript_builtin(name) {
1518 return resolve::layer_to_ty(self.api, f.ret);
1519 }
1520 if let Some(b) = self.api.builtin_by_name(name) {
1524 return ty::resolve_tyref(self.api, &TyRef::Builtin(b));
1525 }
1526 Ty::Unknown
1529 }
1530
1531 fn func_return_ty(&self, annotation: Option<&str>) -> Ty {
1532 annotation.map_or(Ty::Variant, |t| {
1533 resolve::resolve_type_name(self.db, self.api, t)
1534 })
1535 }
1536
1537 fn infer_field(
1541 &mut self,
1542 receiver: ExprId,
1543 name: &str,
1544 name_range: TextRange,
1545 as_method: bool,
1546 ) -> Ty {
1547 let is_self = matches!(self.body.expr(receiver), Expr::SelfExpr);
1548 let recv_ty = self.infer_expr(receiver, &Expectation::None);
1549
1550 if is_self && let Some(item) = self.class.lookup(name) {
1552 return self.own_member_ty(item, as_method);
1553 }
1554
1555 match &recv_ty {
1556 t if t.is_uninformative() => recv_ty.clone(),
1561 Ty::Object(class) => {
1562 if name == "new" {
1563 recv_ty.clone()
1566 } else if let Some(m) = self.api.lookup_member(*class, name) {
1567 self.member_ref_ty(&m, as_method)
1568 } else if let Some(t) = self.class_enum_value(*class, name) {
1569 t
1571 } else {
1572 self.emit_unsafe(name, &recv_ty, name_range, as_method);
1574 Ty::Variant
1575 }
1576 }
1577 Ty::Builtin(_) | Ty::Array(_) | Ty::Dict(..) | Ty::Callable | Ty::Signal(_) => {
1578 self.builtin_member_ty(&recv_ty, name, name_range, as_method)
1579 }
1580 Ty::Enum(_) => self.int_ty(),
1582 Ty::ScriptRef(sref) => self.script_member_ty(*sref, name, as_method),
1584 _ => Ty::Variant,
1585 }
1586 }
1587
1588 fn script_member_ty(&self, sref: ScriptRefId, name: &str, as_method: bool) -> Ty {
1593 if name == "new" {
1594 return Ty::ScriptRef(sref);
1595 }
1596 self.script_member_walk(sref, name, as_method, 0)
1597 .unwrap_or(Ty::Unknown)
1598 }
1599
1600 fn script_member_walk(
1604 &self,
1605 sref: ScriptRefId,
1606 name: &str,
1607 as_method: bool,
1608 depth: u32,
1609 ) -> Option<Ty> {
1610 if depth > 32 {
1611 return None;
1612 }
1613 let file = self.db.file_text(FileId(sref.0))?;
1614 let sc = crate::queries::script_class(self.db, file);
1615 if let Some(m) = sc.member(name) {
1616 return Some(match m {
1617 crate::queries::MemberSig::Method(ret) => {
1618 if as_method {
1619 ret.clone()
1620 } else {
1621 Ty::Callable
1622 }
1623 }
1624 crate::queries::MemberSig::Field(t) => t.clone(),
1625 crate::queries::MemberSig::Signal => Ty::Signal(None),
1626 });
1627 }
1628 match sc.base() {
1630 Ty::ScriptRef(base) => self.script_member_walk(*base, name, as_method, depth + 1),
1631 Ty::Object(class) => self
1632 .api
1633 .lookup_member(*class, name)
1634 .map(|m| self.member_ref_ty(&m, as_method)),
1635 _ => None,
1636 }
1637 }
1638
1639 fn is_subtype(&self, sub: &Ty, sup: &Ty) -> bool {
1644 match (sub, sup) {
1645 (Ty::Object(a), Ty::Object(b)) => self.api.is_subclass(*a, *b),
1646 (Ty::ScriptRef(a), Ty::ScriptRef(b)) => self.script_is_subtype(*a, *b, 0),
1647 (Ty::ScriptRef(a), Ty::Object(b)) => self.script_extends_engine(*a, *b, 0),
1648 _ => false,
1649 }
1650 }
1651
1652 fn script_is_subtype(&self, sub: ScriptRefId, sup: ScriptRefId, depth: u32) -> bool {
1655 if depth > 32 {
1656 return false;
1657 }
1658 if sub == sup {
1659 return true;
1660 }
1661 let Some(file) = self.db.file_text(FileId(sub.0)) else {
1662 return false;
1663 };
1664 match crate::queries::script_class(self.db, file).base() {
1665 Ty::ScriptRef(base) => self.script_is_subtype(*base, sup, depth + 1),
1666 _ => false,
1667 }
1668 }
1669
1670 fn script_extends_engine(
1672 &self,
1673 sub: ScriptRefId,
1674 sup_native: gdscript_api::ClassId,
1675 depth: u32,
1676 ) -> bool {
1677 if depth > 32 {
1678 return false;
1679 }
1680 let Some(file) = self.db.file_text(FileId(sub.0)) else {
1681 return false;
1682 };
1683 match crate::queries::script_class(self.db, file).base() {
1684 Ty::ScriptRef(base) => self.script_extends_engine(*base, sup_native, depth + 1),
1685 Ty::Object(native) => self.api.is_subclass(*native, sup_native),
1686 _ => false,
1687 }
1688 }
1689
1690 fn emit_unsafe(&mut self, name: &str, recv: &Ty, range: TextRange, as_method: bool) {
1691 let recv_label = recv.label(self.api).unwrap_or_else(|| "?".to_owned());
1692 let (code, message) = if as_method {
1693 (
1694 WarningCode::UnsafeMethodAccess,
1695 format!(
1696 "The method \"{name}()\" is not present on the inferred type \"{recv_label}\" (but may be present on a subtype)."
1697 ),
1698 )
1699 } else {
1700 (
1701 WarningCode::UnsafePropertyAccess,
1702 format!(
1703 "The property \"{name}\" is not present on the inferred type \"{recv_label}\" (but may be present on a subtype)."
1704 ),
1705 )
1706 };
1707 self.warn(range, code, message);
1708 }
1709
1710 fn member_ref_ty(&self, m: &MemberRef, as_method: bool) -> Ty {
1711 match m {
1712 MemberRef::Method(sig) => {
1713 if as_method {
1714 ty::resolve_tyref(self.api, &sig.return_ty)
1715 } else {
1716 Ty::Callable
1717 }
1718 }
1719 MemberRef::Property(p) => p.enum_of.as_ref().map_or_else(
1720 || ty::resolve_tyref(self.api, &p.ty),
1721 |q| {
1722 Ty::Enum(EnumRef {
1723 qualified: SmolStr::new(q),
1724 bitfield: false,
1725 })
1726 },
1727 ),
1728 MemberRef::Const(c) => ty::resolve_tyref(self.api, &c.ty),
1729 MemberRef::Signal(_) => Ty::Signal(None),
1730 MemberRef::Enum(_) => Ty::Variant,
1731 }
1732 }
1733
1734 fn builtin_member_ty(
1735 &mut self,
1736 recv: &Ty,
1737 name: &str,
1738 range: TextRange,
1739 as_method: bool,
1740 ) -> Ty {
1741 let Some(bid) = self.builtin_id_of(recv) else {
1742 return Ty::Variant;
1743 };
1744 if as_method {
1745 return if let Some(sig) = self.api.builtin_method(bid, name) {
1746 ty::resolve_tyref(self.api, &sig.return_ty)
1747 } else {
1748 self.emit_unsafe(name, recv, range, true);
1749 Ty::Variant
1750 };
1751 }
1752 if let Some(member) = self.api.builtin_member(bid, name) {
1753 return ty::resolve_tyref(self.api, &member.ty);
1754 }
1755 let data = self.api.builtin(bid);
1757 if let Some(c) = data.constants.iter().find(|c| c.name == name) {
1758 return ty::resolve_tyref(self.api, &c.ty);
1759 }
1760 if data
1761 .enums
1762 .iter()
1763 .any(|e| e.values.iter().any(|v| v.name == name))
1764 {
1765 return self.int_ty();
1766 }
1767 if self.api.builtin_method(bid, name).is_some() {
1768 return Ty::Callable;
1769 }
1770 self.emit_unsafe(name, recv, range, false);
1771 Ty::Variant
1772 }
1773
1774 fn class_enum_value(&self, class: gdscript_api::ClassId, name: &str) -> Option<Ty> {
1781 let mut cur = Some(class);
1782 while let Some(cid) = cur {
1783 let c = self.api.class(cid);
1784 if let Some(e) = c
1785 .enums
1786 .iter()
1787 .find(|e| e.values.iter().any(|v| v.name == name))
1788 {
1789 return Some(Ty::Enum(EnumRef {
1790 qualified: SmolStr::new(format!("{}.{}", c.name, e.name)),
1791 bitfield: e.is_bitfield,
1792 }));
1793 }
1794 cur = c.base;
1795 }
1796 None
1797 }
1798
1799 fn builtin_id_of(&self, ty: &Ty) -> Option<gdscript_api::BuiltinId> {
1801 match ty {
1802 Ty::Builtin(b) => Some(*b),
1803 Ty::Array(_) => self.api.builtin_by_name("Array"),
1804 Ty::Dict(..) => self.api.builtin_by_name("Dictionary"),
1805 Ty::Callable => self.api.builtin_by_name("Callable"),
1806 Ty::Signal(_) => self.api.builtin_by_name("Signal"),
1807 _ => None,
1808 }
1809 }
1810
1811 fn index_ty(&self, base: &Ty) -> Ty {
1813 match base {
1814 Ty::Array(elem) => (**elem).clone(),
1815 Ty::Builtin(b) => self
1816 .api
1817 .builtin(*b)
1818 .indexing_return
1819 .as_ref()
1820 .map_or(Ty::Variant, |r| ty::resolve_tyref(self.api, r)),
1821 Ty::Unknown => Ty::Unknown,
1823 Ty::Error => Ty::Error,
1824 _ => Ty::Variant,
1825 }
1826 }
1827
1828 fn loop_var_ty(&self, iter: &Ty) -> Ty {
1830 match iter {
1831 Ty::Array(elem) => (**elem).clone(),
1832 Ty::Builtin(b) => {
1833 let data = self.api.builtin(*b);
1834 if data.name == "int" {
1835 self.int_ty()
1837 } else if let Some(r) = &data.indexing_return {
1838 ty::resolve_tyref(self.api, r)
1840 } else {
1841 Ty::Variant
1842 }
1843 }
1844 Ty::Unknown => Ty::Unknown,
1846 Ty::Error => Ty::Error,
1847 _ => Ty::Variant,
1848 }
1849 }
1850
1851 fn infer_lambda(&mut self, params: &[ParamBinding], body: &[body::StmtId]) {
1852 let saved_locals = self.locals.clone();
1856 let saved_ret = std::mem::replace(&mut self.return_ty, Ty::Variant);
1857 for p in params {
1858 let ty = self.param_ty(p);
1859 self.bindings.push(Binding {
1860 name: p.name.clone(),
1861 name_range: p.name_range,
1862 ty: ty.clone(),
1863 init: None,
1864 annotated: p.type_ref.is_some(),
1865 inferred_colon_eq: false,
1866 is_const: false,
1867 kind: BindingKind::Param,
1868 });
1869 self.locals.insert(p.name.clone(), ty);
1870 }
1871 self.infer_block(body);
1872 self.return_ty = saved_ret;
1873 self.locals = saved_locals;
1874 }
1875
1876 fn param_ty(&mut self, p: &ParamBinding) -> Ty {
1877 if let Some(ptr) = p.type_ref {
1878 return self.resolve_ptr_ty(ptr);
1879 }
1880 p.default
1882 .map_or(Ty::Variant, |e| self.infer_expr(e, &Expectation::None))
1883 }
1884
1885 fn resolve_name(&mut self, id: ExprId, name: &str) -> Ty {
1888 if self.locals.contains_key(name) {
1891 self.used_locals.insert(SmolStr::new(name));
1892 }
1893 if let Some(key) = self.narrow_key(id)
1895 && let Some(t) = self.narrowing.get(&key)
1896 {
1897 return t.clone();
1898 }
1899 if let Some(t) = self.locals.get(name) {
1900 return t.clone();
1901 }
1902 if let Some(item) = self.class.lookup(name) {
1903 return self.own_member_ty(item, false);
1904 }
1905 match self.class.base.clone() {
1909 Ty::Object(base) => {
1910 if let Some(m) = self.api.lookup_member(base, name) {
1911 return self.member_ref_ty(&m, false);
1912 }
1913 }
1914 Ty::ScriptRef(base) => {
1915 if let Some(t) = self.script_member_walk(base, name, false, 0) {
1916 return t;
1917 }
1918 }
1919 _ => {}
1920 }
1921 if let Some(g) = resolve::resolve_global(self.api, name) {
1922 return global_ty(&g);
1923 }
1924 let by_class = resolve::resolve_external(
1929 self.db,
1930 &resolve::ExternalRef::ClassName(SmolStr::new(name)),
1931 );
1932 if !by_class.is_unknown() {
1933 return by_class;
1934 }
1935 resolve::resolve_external(self.db, &resolve::ExternalRef::Autoload(SmolStr::new(name)))
1936 }
1937
1938 fn own_member_ty(&self, item: ClassItem, as_method: bool) -> Ty {
1939 match item {
1940 ClassItem::EnumVariant => self.int_ty(),
1941 ClassItem::Member(_) => match self.class.member(item) {
1942 Some(Member::Var(v)) => self.field_ty(&v.name, v.ptr),
1943 Some(Member::Const(c)) => self.field_ty(&c.name, c.ptr),
1944 Some(Member::Func(f)) => {
1945 if as_method {
1946 self.func_return_ty(f.return_type.as_deref())
1947 } else {
1948 Ty::Callable
1949 }
1950 }
1951 Some(Member::Signal(_)) => Ty::Signal(None),
1952 Some(Member::Class(_)) => Ty::Unknown,
1953 Some(Member::Enum(_)) | None => Ty::Variant,
1954 },
1955 }
1956 }
1957
1958 fn field_ty(&self, name: &str, ptr: AstPtr) -> Ty {
1961 if let Some(t) = self.class.member_types.get(name) {
1962 return t.clone();
1963 }
1964 self.resolve_decl_annotation(ptr)
1965 }
1966
1967 fn resolve_decl_annotation(&self, ptr: AstPtr) -> Ty {
1969 let Some(node) = ptr.to_node(self.root) else {
1970 return Ty::Variant;
1971 };
1972 cst::first_child(&node, |k| k == gdscript_syntax::SyntaxKind::TypeRef)
1973 .map_or(Ty::Variant, |t| {
1974 resolve::resolve_type_ref(self.db, self.api, &t)
1975 })
1976 }
1977
1978 fn facts_to_narrowing(&self, id: body::StmtId) -> FxHashMap<String, Ty> {
1990 let mut out = FxHashMap::default();
1991 if let Some(facts) = self.flow.facts_before(id) {
1992 for (place, nt) in facts.iter() {
1993 if let Some((key, ty)) = self.narrowing_entry(place, nt) {
1994 out.insert(key, ty);
1995 }
1996 }
1997 }
1998 out
1999 }
2000
2001 fn narrowing_entry(&self, place: &Place, nt: &NarrowedTy) -> Option<(String, Ty)> {
2005 let NarrowedTy::Is(ptr) = nt else {
2006 return None;
2007 };
2008 let narrowed = self.resolve_ptr_ty(*ptr);
2009 if narrowed.is_uninformative() {
2010 return None;
2011 }
2012 if let Place::Local(n) = place
2015 && let Some(cur) = self.locals.get(n)
2016 && !cur.is_uninformative()
2017 && !self.is_subtype(&narrowed, cur)
2018 {
2019 return None;
2020 }
2021 Some((place.dotted_key(), narrowed))
2022 }
2023
2024 fn apply_condition_facts(&mut self, cond: ExprId, truthy: bool) {
2027 for (place, nt) in flow::condition_facts(self.body, cond, truthy) {
2028 if let Some((key, ty)) = self.narrowing_entry(&place, &nt) {
2029 self.narrowing.insert(key, ty);
2030 }
2031 }
2032 }
2033
2034 fn narrow_key(&self, id: ExprId) -> Option<String> {
2037 match self.body.expr(id) {
2038 Expr::Name(n) => Some(n.to_string()),
2039 Expr::SelfExpr => Some("self".to_owned()),
2040 Expr::Paren(inner) => self.narrow_key(*inner),
2041 Expr::Field { receiver, name, .. } => {
2042 Some(format!("{}.{name}", self.narrow_key(*receiver)?))
2043 }
2044 _ => None,
2045 }
2046 }
2047
2048 fn resolve_ptr_ty(&self, ptr: AstPtr) -> Ty {
2049 ptr.to_node(self.root).map_or(Ty::Variant, |n| {
2050 resolve::resolve_type_ref(self.db, self.api, &n)
2051 })
2052 }
2053
2054 fn join(&self, a: &Ty, b: &Ty) -> Ty {
2065 if a == b {
2066 return a.clone();
2067 }
2068 if a.is_error() || b.is_error() {
2069 return Ty::Error;
2070 }
2071 if a.is_unknown() || b.is_unknown() {
2072 return Ty::Unknown;
2073 }
2074 if a.is_variant() || b.is_variant() {
2075 return Ty::Variant;
2076 }
2077 if ty::is_assignable(self.api, a, b) == Assign::Ok {
2078 return b.clone();
2079 }
2080 if ty::is_assignable(self.api, b, a) == Assign::Ok {
2081 return a.clone();
2082 }
2083 Ty::Variant
2084 }
2085}
2086
2087fn global_ty(g: &GlobalDef) -> Ty {
2089 match g {
2090 GlobalDef::Const(t) => t.clone(),
2091 GlobalDef::Singleton(c) | GlobalDef::ClassType(c) => Ty::Object(*c),
2092 GlobalDef::BuiltinType(b) => Ty::Builtin(*b),
2093 GlobalDef::Builtin | GlobalDef::Utility => Ty::Callable,
2095 GlobalDef::GlobalEnum => Ty::Variant,
2096 }
2097}
2098
2099fn inference_on_variant_msg(kind: &str) -> String {
2100 format!(
2101 "The {kind} type is being inferred from a Variant value, so it will be typed as Variant."
2102 )
2103}
2104
2105fn op_symbol(op: BinOp) -> Option<&'static str> {
2107 Some(match op {
2108 BinOp::Add => "+",
2109 BinOp::Sub => "-",
2110 BinOp::Mul => "*",
2111 BinOp::Div => "/",
2112 BinOp::Mod => "%",
2113 BinOp::Pow => "**",
2114 BinOp::BitAnd => "&",
2115 BinOp::BitOr => "|",
2116 BinOp::BitXor => "^",
2117 BinOp::Shl => "<<",
2118 BinOp::Shr => ">>",
2119 _ => return None,
2120 })
2121}
2122
2123#[cfg(test)]
2124mod tests {
2125 use super::*;
2126 use crate::item_tree::item_tree;
2127 use gdscript_syntax::{SyntaxKind, parse};
2128
2129 struct Harness {
2130 result: InferenceResult,
2131 body: Body,
2132 }
2133
2134 fn infer_first_func(src: &str) -> Harness {
2136 let api = gdscript_api::bundled();
2137 let db = gdscript_db::RootDatabase::default();
2138 let root = parse(src).syntax_node();
2139 let tree = item_tree(&root);
2140 let class = ClassScope::new(&db, api, &tree, None);
2141 let func = gdscript_syntax::ast::descendants(&root)
2142 .into_iter()
2143 .find(|n| n.kind() == SyntaxKind::FuncDecl)
2144 .expect("a function");
2145 let body = body::body_of_func(&func);
2146 let return_ty = cst::first_child(&func, |k| k == SyntaxKind::TypeRef)
2147 .map_or(Ty::Variant, |t| resolve::resolve_type_ref(&db, api, &t));
2148 let result = infer(&db, api, &root, &class, &body, return_ty, true);
2149 Harness { result, body }
2150 }
2151
2152 fn codes(h: &Harness) -> Vec<&str> {
2156 h.result
2157 .diagnostics
2158 .iter()
2159 .map(|d| d.code.as_str())
2160 .chain(h.result.raw_warnings.iter().map(|w| w.code.as_str()))
2161 .collect()
2162 }
2163
2164 fn file_codes(src: &str) -> Vec<String> {
2168 let api = gdscript_api::bundled();
2169 let db = gdscript_db::RootDatabase::default();
2170 let root = parse(src).syntax_node();
2171 let fi = analyze_file(&db, api, &root, FileId(0));
2172 fi.diagnostics
2173 .iter()
2174 .map(|d| d.code.clone())
2175 .chain(fi.raw_warnings.iter().map(|w| w.code.as_str().to_owned()))
2176 .collect()
2177 }
2178
2179 #[test]
2180 fn integer_division_warns() {
2181 let h = infer_first_func("func f():\n\tvar x = 5 / 2\n");
2182 assert!(codes(&h).contains(&INTEGER_DIVISION));
2183 }
2184
2185 #[test]
2186 fn float_div_does_not_warn() {
2187 let h = infer_first_func("func f():\n\tvar x = 5.0 / 2\n");
2188 assert!(!codes(&h).contains(&INTEGER_DIVISION));
2189 }
2190
2191 #[test]
2192 fn type_mismatch_on_hard_annotation() {
2193 let h = infer_first_func("func f():\n\tvar s: String = 5\n");
2194 assert!(codes(&h).contains(&TYPE_MISMATCH));
2195 }
2196
2197 #[test]
2198 fn narrowing_conversion_float_to_int() {
2199 let h = infer_first_func("func f():\n\tvar n: int = 1.5\n");
2200 assert!(codes(&h).contains(&NARROWING_CONVERSION));
2201 }
2202
2203 #[test]
2204 fn int_to_float_is_silent() {
2205 let h = infer_first_func("func f():\n\tvar x: float = 3\n\treturn x\n");
2206 assert!(codes(&h).is_empty(), "{:?}", codes(&h));
2207 }
2208
2209 #[test]
2210 fn local_shadowing_a_param_warns_shadowed_variable() {
2211 let h = infer_first_func("func f(x):\n\tvar x = 1\n\treturn x\n");
2212 assert!(codes(&h).contains(&"SHADOWED_VARIABLE"), "{:?}", codes(&h));
2213 }
2214
2215 #[test]
2216 fn local_shadowing_a_class_member_warns_shadowed_variable() {
2217 let h =
2219 infer_first_func("var health = 100\nfunc f():\n\tvar health = 1\n\treturn health\n");
2220 assert!(codes(&h).contains(&"SHADOWED_VARIABLE"), "{:?}", codes(&h));
2221 }
2222
2223 #[test]
2224 fn non_shadowing_local_does_not_warn_shadowed_variable() {
2225 let h = infer_first_func("func f(x):\n\tvar y = 1\n\treturn x + y\n");
2226 assert!(!codes(&h).contains(&"SHADOWED_VARIABLE"), "{:?}", codes(&h));
2227 }
2228
2229 #[test]
2230 fn enum_member_into_its_own_enum_slot_is_not_int_as_enum() {
2231 let h = infer_first_func(
2235 "func f():\n\tvar m: Tween.TweenProcessMode = Tween.TWEEN_PROCESS_IDLE\n\treturn m\n",
2236 );
2237 assert!(
2238 !codes(&h).contains(&"INT_AS_ENUM_WITHOUT_CAST"),
2239 "{:?}",
2240 codes(&h)
2241 );
2242 }
2243
2244 #[test]
2245 fn bare_int_into_enum_slot_still_warns() {
2246 let h = infer_first_func("func f():\n\tvar m: Tween.TweenProcessMode = 0\n\treturn m\n");
2248 assert!(
2249 codes(&h).contains(&"INT_AS_ENUM_WITHOUT_CAST"),
2250 "{:?}",
2251 codes(&h)
2252 );
2253 }
2254
2255 #[test]
2256 fn member_access_resolves_engine_property() {
2257 let h = infer_first_func(
2260 "extends Node\nfunc f():\n\tvar n := get_node(\"x\")\n\tn.get_parent()\n",
2261 );
2262 assert!(
2263 codes(&h).iter().all(|c| !c.starts_with("UNSAFE")),
2264 "{:?}",
2265 h.result.diagnostics
2266 );
2267 }
2268
2269 #[test]
2270 fn unsafe_method_on_known_type() {
2271 let h = infer_first_func(
2272 "extends Node\nfunc f():\n\tvar n := get_node(\"x\")\n\tn.totally_bogus_method()\n",
2273 );
2274 assert!(
2275 codes(&h).contains(&UNSAFE_METHOD_ACCESS),
2276 "{:?}",
2277 h.result.diagnostics
2278 );
2279 }
2280
2281 #[test]
2282 fn is_narrowing_suppresses_unsafe() {
2283 let h = infer_first_func("func f(x):\n\tif x is Node:\n\t\tx.queue_free()\n");
2286 assert!(
2287 codes(&h).iter().all(|c| !c.starts_with("UNSAFE")),
2288 "{:?}",
2289 h.result.diagnostics
2290 );
2291 }
2292
2293 #[test]
2294 fn is_narrowing_flags_real_missing_member() {
2295 let h = infer_first_func("func f(x):\n\tif x is Node:\n\t\tx.bogus_method()\n");
2297 assert!(codes(&h).contains(&UNSAFE_METHOD_ACCESS));
2298 }
2299
2300 #[test]
2301 fn early_return_is_guard_narrows_past_the_guard() {
2302 let safe =
2305 infer_first_func("func f(x):\n\tif not (x is Node):\n\t\treturn\n\tx.get_parent()\n");
2306 assert!(
2307 codes(&safe).iter().all(|c| !c.starts_with("UNSAFE")),
2308 "real Node method must not warn after the guard: {:?}",
2309 codes(&safe)
2310 );
2311 let bogus =
2312 infer_first_func("func f(x):\n\tif not (x is Node):\n\t\treturn\n\tx.bogus_method()\n");
2313 assert!(
2314 codes(&bogus).contains(&UNSAFE_METHOD_ACCESS),
2315 "missing method must warn after the guard: {:?}",
2316 codes(&bogus)
2317 );
2318 }
2319
2320 #[test]
2321 fn and_short_circuit_narrows_the_rhs() {
2322 let safe = infer_first_func("func f(x):\n\tif x is Node and x.get_parent():\n\t\tpass\n");
2325 assert!(
2326 codes(&safe).iter().all(|c| !c.starts_with("UNSAFE")),
2327 "real Node method in the and-rhs must not warn: {:?}",
2328 codes(&safe)
2329 );
2330 let bogus =
2331 infer_first_func("func f(x):\n\tif x is Node and x.bogus_method():\n\t\tpass\n");
2332 assert!(
2333 codes(&bogus).contains(&UNSAFE_METHOD_ACCESS),
2334 "missing method in the and-rhs must warn: {:?}",
2335 codes(&bogus)
2336 );
2337 }
2338
2339 #[test]
2342 fn empty_file_warns() {
2343 assert!(file_codes("").iter().any(|c| c == "EMPTY_FILE"));
2344 assert!(
2345 file_codes("# just a comment\n")
2346 .iter()
2347 .any(|c| c == "EMPTY_FILE")
2348 );
2349 assert!(
2350 file_codes("extends Node\n")
2351 .iter()
2352 .all(|c| c != "EMPTY_FILE")
2353 );
2354 }
2355
2356 #[test]
2357 fn unused_variable_and_parameter() {
2358 let h = infer_first_func("func f(unused_p):\n\tvar unused_v = 1\n");
2359 assert!(codes(&h).contains(&"UNUSED_PARAMETER"), "{:?}", codes(&h));
2360 assert!(codes(&h).contains(&"UNUSED_VARIABLE"), "{:?}", codes(&h));
2361 let used = infer_first_func("func f(p):\n\tvar v = p\n\treturn v\n");
2363 assert!(codes(&used).iter().all(|c| !c.starts_with("UNUSED")));
2364 let underscored = infer_first_func("func f(_ignored):\n\tpass\n");
2365 assert!(!codes(&underscored).contains(&"UNUSED_PARAMETER"));
2366 }
2367
2368 #[test]
2369 fn standalone_expression_and_ternary() {
2370 let expr = infer_first_func("func f(a, b):\n\ta + b\n");
2371 assert!(
2372 codes(&expr).contains(&"STANDALONE_EXPRESSION"),
2373 "{:?}",
2374 codes(&expr)
2375 );
2376 let tern = infer_first_func("func f(c):\n\t1 if c else 2\n");
2377 assert!(
2378 codes(&tern).contains(&"STANDALONE_TERNARY"),
2379 "{:?}",
2380 codes(&tern)
2381 );
2382 let call = infer_first_func("func f(n):\n\tn.queue_free()\n");
2384 assert!(codes(&call).iter().all(|c| !c.starts_with("STANDALONE")));
2385 }
2386
2387 #[test]
2388 fn unreachable_code_after_return() {
2389 let h = infer_first_func("func f():\n\treturn\n\tprint(\"dead\")\n");
2390 assert!(codes(&h).contains(&"UNREACHABLE_CODE"), "{:?}", codes(&h));
2391 }
2392
2393 #[test]
2394 fn incompatible_ternary_warns() {
2395 let h = infer_first_func("func f(c):\n\tvar x = \"s\" if c else 1\n\treturn x\n");
2397 assert!(
2398 codes(&h).contains(&"INCOMPATIBLE_TERNARY"),
2399 "{:?}",
2400 codes(&h)
2401 );
2402 }
2403
2404 #[test]
2405 fn variant_receiver_never_unsafe() {
2406 let h = infer_first_func("func f(x):\n\tx.anything_at_all()\n");
2408 assert!(codes(&h).is_empty(), "{:?}", codes(&h));
2409 }
2410
2411 #[test]
2412 fn unsafe_call_argument_on_variant_into_typed_param() {
2413 let h = infer_first_func("func f(p):\n\ttake(p)\nfunc take(n: Node2D):\n\tpass\n");
2415 assert!(
2416 codes(&h).contains(&UNSAFE_CALL_ARGUMENT),
2417 "{:?}",
2418 h.result.diagnostics
2419 );
2420 }
2421
2422 #[test]
2423 fn unsafe_call_argument_silent_on_safe_and_untyped() {
2424 let upcast =
2426 infer_first_func("func f(n: Node2D):\n\ttake(n)\nfunc take(n: Node):\n\tpass\n");
2427 assert!(
2428 !codes(&upcast).contains(&UNSAFE_CALL_ARGUMENT),
2429 "upcast is safe: {:?}",
2430 upcast.result.diagnostics
2431 );
2432 let untyped = infer_first_func("func f(p):\n\ttake(p)\nfunc take(n):\n\tpass\n");
2433 assert!(
2434 !codes(&untyped).contains(&UNSAFE_CALL_ARGUMENT),
2435 "untyped param accepts anything: {:?}",
2436 untyped.result.diagnostics
2437 );
2438 }
2439
2440 #[test]
2441 fn inference_on_variant() {
2442 let h = infer_first_func("func f(x):\n\tvar y := x\n");
2444 assert!(codes(&h).contains(&INFERENCE_ON_VARIANT));
2445 }
2446
2447 #[test]
2448 fn field_inferred_from_earlier_field_is_typed() {
2449 let codes = file_codes("var a := 1\nvar b := a + 1\n");
2453 assert!(
2454 !codes.iter().any(|c| c == INFERENCE_ON_VARIANT),
2455 "field `b` from earlier field `a` should type as int, not Variant: {codes:?}"
2456 );
2457 }
2458
2459 #[test]
2460 fn field_forward_reference_is_seamed_not_warned() {
2461 let codes = file_codes("var b := a\nvar a := 1\n");
2465 assert!(
2466 !codes.iter().any(|c| c == INFERENCE_ON_VARIANT),
2467 "forward field reference must not false-warn: {codes:?}"
2468 );
2469 }
2470
2471 #[test]
2472 fn standalone_inferred_field_unchanged() {
2473 let codes = file_codes("var n := 0\n");
2475 assert!(
2476 codes.is_empty(),
2477 "a literal-initialised field should produce no diagnostics: {codes:?}"
2478 );
2479 }
2480
2481 #[test]
2482 fn lambda_var_is_callable_not_variant() {
2483 let h = infer_first_func("func f():\n\tvar cb := func():\n\t\tpass\n");
2484 assert!(
2485 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2486 "{:?}",
2487 h.result.diagnostics
2488 );
2489 }
2490
2491 #[test]
2492 fn multiline_lambda_then_paren_line_no_false_warning() {
2493 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";
2497 let h = infer_first_func(src);
2498 assert!(
2499 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2500 "{:?}",
2501 h.result.diagnostics
2502 );
2503 }
2504
2505 #[test]
2506 fn calling_a_callable_value_is_seam_not_variant() {
2507 let src = "func f(cb: Callable):\n\tvar x := (cb)()\n\treturn x\n";
2511 let h = infer_first_func(src);
2512 assert!(
2513 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2514 "{:?}",
2515 h.result.diagnostics
2516 );
2517 }
2518
2519 #[test]
2520 fn ternary_with_seam_branch_does_not_collapse_to_variant() {
2521 let src =
2525 "func f(c: bool):\n\tvar x := 5 if c else await get_tree().process_frame\n\treturn x\n";
2526 let h = infer_first_func(src);
2527 assert!(
2528 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2529 "seam branch should keep the ternary on the seam: {:?}",
2530 h.result.diagnostics
2531 );
2532 }
2533
2534 #[test]
2535 fn await_a_coroutine_call_recovers_its_return_type() {
2536 let src = "func g() -> int:\n\tvar x := await make()\n\treturn x\nfunc make() -> int:\n\treturn 5\n";
2539 let h = infer_first_func(src);
2540 assert!(
2541 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2542 "no false variant warning: {:?}",
2543 h.result.diagnostics
2544 );
2545 let api = gdscript_api::bundled();
2546 let x = &h.result.bindings[0];
2547 assert!(
2548 matches!(&x.ty, Ty::Builtin(b) if api.builtin(*b).name == "int"),
2549 "await make() should recover int, got {:?}",
2550 x.ty
2551 );
2552 }
2553
2554 #[test]
2555 fn await_a_signal_stays_the_seam() {
2556 let src = "func f():\n\tvar x := await get_tree().process_frame\n\treturn x\n";
2559 let h = infer_first_func(src);
2560 assert!(
2561 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2562 "awaiting a signal must not warn: {:?}",
2563 h.result.diagnostics
2564 );
2565 assert!(
2566 matches!(&h.result.bindings[0].ty, Ty::Unknown),
2567 "awaiting a signal stays the seam, got {:?}",
2568 h.result.bindings[0].ty
2569 );
2570 }
2571
2572 #[test]
2573 fn for_var_over_packed_string_array_is_string() {
2574 let h = infer_first_func("func f():\n\tfor s in \"a,b\".split(\",\"):\n\t\tvar x := s\n");
2577 assert!(
2578 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2579 "{:?}",
2580 h.result.diagnostics
2581 );
2582 }
2583
2584 #[test]
2585 fn class_new_is_object_not_variant() {
2586 let h = infer_first_func("func f():\n\tvar s := GDScript.new()\n");
2587 assert!(
2588 !codes(&h).contains(&INFERENCE_ON_VARIANT),
2589 "{:?}",
2590 h.result.diagnostics
2591 );
2592 }
2593
2594 #[test]
2595 fn unknown_seam_never_warns() {
2596 let h = infer_first_func("func f():\n\tvar s := preload(\"res://x.gd\")\n\ts.whatever()\n");
2598 assert!(codes(&h).is_empty(), "{:?}", codes(&h));
2599 }
2600
2601 #[test]
2602 fn expr_types_are_memoized_for_hover() {
2603 let h = infer_first_func("func f():\n\tvar n := 42\n");
2604 let has_int = h
2606 .result
2607 .expr_ty
2608 .values()
2609 .any(|t| matches!(t, Ty::Builtin(_)));
2610 assert!(has_int);
2611 assert!(!h.body.exprs.is_empty());
2613 }
2614}