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 INVALID_NODE_PATH: &str = "INVALID_NODE_PATH";
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum BindingKind {
50 Var,
52 Param,
54 ForVar,
56 MatchBind,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct Binding {
63 pub name_range: TextRange,
65 pub ty: Ty,
68 pub init: Option<ExprId>,
70 pub annotated: bool,
72 pub inferred_colon_eq: bool,
74 pub kind: BindingKind,
76}
77
78#[derive(Debug, Clone, Default, PartialEq, Eq)]
80pub struct InferenceResult {
81 pub expr_ty: FxHashMap<ExprId, Ty>,
83 pub bindings: Vec<Binding>,
85 pub diagnostics: Vec<Diagnostic>,
87}
88
89impl InferenceResult {
90 #[must_use]
92 pub fn type_of(&self, id: ExprId) -> Option<&Ty> {
93 self.expr_ty.get(&id)
94 }
95
96 #[must_use]
98 pub fn binding_at(&self, offset: u32) -> Option<&Binding> {
99 self.bindings
100 .iter()
101 .find(|b| b.name_range.start <= offset && offset < b.name_range.end)
102 }
103}
104
105#[must_use]
109pub fn infer(
110 db: &dyn Db,
111 api: &EngineApi,
112 root: &GdNode,
113 class: &ClassScope,
114 body: &Body,
115 return_ty: Ty,
116) -> InferenceResult {
117 let self_ty = class.self_ty.clone();
118 let mut cx = Cx {
119 db,
120 api,
121 root,
122 body,
123 class,
124 self_ty,
125 return_ty,
126 expr_ty: FxHashMap::default(),
127 bindings: Vec::new(),
128 diagnostics: Vec::new(),
129 locals: FxHashMap::default(),
130 narrowing: FxHashMap::default(),
131 };
132 let params = body.params.clone();
134 for p in ¶ms {
135 let ty = cx.param_ty(p);
136 cx.bindings.push(Binding {
137 name_range: p.name_range,
138 ty: ty.clone(),
139 init: None,
140 annotated: p.type_ref.is_some(),
141 inferred_colon_eq: false,
142 kind: BindingKind::Param,
143 });
144 cx.locals.insert(p.name.clone(), ty);
145 }
146 if let Some(tail) = body.tail {
147 cx.infer_expr(tail, &Expectation::None);
148 }
149 let block = body.block.clone();
150 cx.infer_block(&block);
151 InferenceResult {
152 expr_ty: cx.expr_ty,
153 bindings: cx.bindings,
154 diagnostics: cx.diagnostics,
155 }
156}
157
158#[must_use]
161pub fn infer_func(
162 db: &dyn Db,
163 api: &EngineApi,
164 root: &GdNode,
165 class: &ClassScope,
166 ptr: AstPtr,
167) -> InferenceResult {
168 let Some(node) = ptr.to_node(root) else {
169 return InferenceResult::default();
170 };
171 let body = body::body_of_func(&node);
172 let return_ty = cst::first_child(&node, |k| k == gdscript_syntax::SyntaxKind::TypeRef)
175 .map_or(Ty::Variant, |t| resolve::resolve_type_ref(db, api, &t));
176 infer(db, api, root, class, &body, return_ty)
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
183pub struct Unit {
184 pub range: TextRange,
186 pub body: Body,
188 pub result: InferenceResult,
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, Default)]
195pub struct FileInference {
196 pub tree: Arc<ItemTree>,
198 pub units: Vec<Unit>,
200 pub diagnostics: Vec<Diagnostic>,
202}
203
204impl FileInference {
205 #[must_use]
207 pub fn unit_at(&self, offset: u32) -> Option<&Unit> {
208 self.units
209 .iter()
210 .filter(|u| u.range.start <= offset && offset < u.range.end)
211 .min_by_key(|u| u.range.end - u.range.start)
212 }
213}
214
215#[must_use]
219pub fn analyze_file(db: &dyn Db, api: &EngineApi, root: &GdNode, file_id: FileId) -> FileInference {
220 let tree = item_tree(root);
221 let mut units = Vec::new();
222 let mut diagnostics = Vec::new();
223 let mut member_types: FxHashMap<SmolStr, Ty> = FxHashMap::default();
224 let self_ref = Ty::ScriptRef(ScriptRefId(file_id.0));
227
228 {
231 let mut class = ClassScope::new(db, api, &tree);
232 class.self_ty = self_ref.clone();
233 for m in &tree.members {
234 let (ptr, range) = match m {
235 Member::Var(v) => (v.ptr, v.range),
236 Member::Const(c) => (c.ptr, c.range),
237 _ => continue,
238 };
239 if let Some(unit) = unit_from_decl(db, api, root, &class, ptr, range) {
240 if let (Some(name), Some(b)) = (m.name(), unit.result.bindings.first()) {
241 member_types.insert(SmolStr::new(name), b.ty.clone());
242 }
243 diagnostics.extend(unit.result.diagnostics.iter().cloned());
244 units.push(unit);
245 }
246 }
247 }
248
249 {
251 let mut class = ClassScope::new(db, api, &tree);
252 class.member_types = member_types;
253 class.self_ty = self_ref.clone();
254 for m in &tree.members {
255 let Member::Func(f) = m else { continue };
256 let Some(node) = f.ptr.to_node(root) else {
257 continue;
258 };
259 let body = body::body_of_func(&node);
260 let return_ty = cst::first_child(&node, |k| k == gdscript_syntax::SyntaxKind::TypeRef)
261 .map_or(Ty::Variant, |t| resolve::resolve_type_ref(db, api, &t));
262 let result = infer(db, api, root, &class, &body, return_ty);
263 diagnostics.extend(result.diagnostics.iter().cloned());
264 units.push(Unit {
265 range: f.range,
266 body,
267 result,
268 });
269 }
270 }
271
272 FileInference {
273 tree,
274 units,
275 diagnostics,
276 }
277}
278
279fn unit_from_decl(
281 db: &dyn Db,
282 api: &EngineApi,
283 root: &GdNode,
284 class: &ClassScope,
285 ptr: AstPtr,
286 range: TextRange,
287) -> Option<Unit> {
288 let node = ptr.to_node(root)?;
289 let body = body::body_of_decl_stmt(&node);
290 let result = infer(db, api, root, class, &body, Ty::Variant);
291 Some(Unit {
292 range,
293 body,
294 result,
295 })
296}
297
298enum Expectation {
300 None,
302 Has(Ty),
304}
305
306struct Cx<'a> {
307 db: &'a dyn Db,
308 api: &'a EngineApi,
309 root: &'a GdNode,
310 body: &'a Body,
311 class: &'a ClassScope<'a>,
312 self_ty: Ty,
313 return_ty: Ty,
314 expr_ty: FxHashMap<ExprId, Ty>,
315 bindings: Vec<Binding>,
316 diagnostics: Vec<Diagnostic>,
317 locals: FxHashMap<SmolStr, Ty>,
319 narrowing: FxHashMap<String, Ty>,
321}
322
323impl Cx<'_> {
324 fn builtin(&self, name: &str) -> Ty {
327 self.api
328 .builtin_by_name(name)
329 .map_or(Ty::Variant, Ty::Builtin)
330 }
331 fn int_ty(&self) -> Ty {
332 self.builtin("int")
333 }
334 fn float_ty(&self) -> Ty {
335 self.builtin("float")
336 }
337 fn bool_ty(&self) -> Ty {
338 self.builtin("bool")
339 }
340 fn is_int(&self, ty: &Ty) -> bool {
341 matches!(ty, Ty::Builtin(b) if self.api.builtin(*b).name == "int")
342 }
343 fn is_float(&self, ty: &Ty) -> bool {
344 matches!(ty, Ty::Builtin(b) if self.api.builtin(*b).name == "float")
345 }
346 fn is_numeric(&self, ty: &Ty) -> bool {
347 self.is_int(ty) || self.is_float(ty)
348 }
349
350 fn emit(&mut self, range: TextRange, severity: Severity, code: &str, message: String) {
353 self.diagnostics.push(Diagnostic {
354 range,
355 severity,
356 code: code.to_owned(),
357 message,
358 source: DiagnosticSource::Type,
359 fixes: Vec::new(),
360 });
361 }
362
363 fn range_of(&self, id: ExprId) -> TextRange {
364 self.body.source_map.expr_range(id)
365 }
366
367 fn check_assign(&mut self, from: &Ty, to: &Ty, range: TextRange) {
370 match ty::is_assignable(self.api, from, to) {
371 Assign::Narrowing => self.emit(
372 range,
373 Severity::Warning,
374 NARROWING_CONVERSION,
375 "Narrowing conversion (float is converted to int and loses precision).".to_owned(),
376 ),
377 Assign::No => {
378 let to_label = to.label(self.api).unwrap_or_else(|| "?".to_owned());
379 let from_label = from.label(self.api).unwrap_or_else(|| "?".to_owned());
380 self.emit(
381 range,
382 Severity::Error,
383 TYPE_MISMATCH,
384 format!(
385 "Cannot assign a value of type \"{from_label}\" to a target of type \"{to_label}\"."
386 ),
387 );
388 }
389 Assign::Ok | Assign::OkUnsafe | Assign::IntAsEnum => {}
390 }
391 }
392
393 fn infer_block(&mut self, block: &[body::StmtId]) {
396 for &stmt in block {
397 self.infer_stmt(stmt);
398 }
399 }
400
401 fn infer_stmt(&mut self, id: body::StmtId) {
402 match self.body.stmt(id).clone() {
403 Stmt::Expr(e) => {
404 self.infer_expr(e, &Expectation::None);
405 }
406 Stmt::Var(v) => self.infer_local_var(&v),
407 Stmt::Return(e) => {
408 if let Some(e) = e {
409 let expected = if self.return_ty.is_uninformative() {
410 Expectation::None
411 } else {
412 Expectation::Has(self.return_ty.clone())
413 };
414 let t = self.infer_expr(e, &expected);
415 if let Expectation::Has(ret) = expected {
416 self.check_assign(&t, &ret, self.range_of(e));
417 }
418 }
419 }
420 Stmt::If {
421 cond,
422 then_branch,
423 elifs,
424 else_branch,
425 } => {
426 self.infer_expr(cond, &Expectation::None);
427 self.in_branch(|cx| {
428 cx.apply_narrowing(cond);
429 cx.infer_block(&then_branch);
430 });
431 for (econd, eblock) in elifs {
432 self.infer_expr(econd, &Expectation::None);
433 self.in_branch(|cx| {
434 cx.apply_narrowing(econd);
435 cx.infer_block(&eblock);
436 });
437 }
438 if let Some(eb) = else_branch {
439 self.in_branch(|cx| cx.infer_block(&eb));
440 }
441 }
442 Stmt::While { cond, body } => {
443 self.infer_expr(cond, &Expectation::None);
444 self.in_branch(|cx| cx.infer_block(&body));
445 }
446 Stmt::For(f) => {
447 let iter_ty = self.infer_expr(f.iter, &Expectation::None);
448 let var_ty = f.var_type.as_ref().map_or_else(
449 || self.loop_var_ty(&iter_ty),
450 |ptr| self.resolve_ptr_ty(*ptr),
451 );
452 self.bindings.push(Binding {
453 name_range: f.var_range,
454 ty: var_ty.clone(),
455 init: None,
456 annotated: f.var_type.is_some(),
457 inferred_colon_eq: false,
458 kind: BindingKind::ForVar,
459 });
460 self.locals.insert(f.var.clone(), var_ty);
461 self.in_branch(|cx| cx.infer_block(&f.body));
462 }
463 Stmt::Match { scrutinee, arms } => {
464 self.infer_expr(scrutinee, &Expectation::None);
465 for arm in arms {
466 self.in_branch(|cx| {
467 for b in &arm.binds {
468 cx.bindings.push(Binding {
472 name_range: b.range,
473 ty: Ty::Variant,
474 init: None,
475 annotated: false,
476 inferred_colon_eq: false,
477 kind: BindingKind::MatchBind,
478 });
479 cx.locals.insert(b.name.clone(), Ty::Variant);
480 }
481 if let Some(g) = arm.guard {
482 cx.infer_expr(g, &Expectation::None);
483 }
484 cx.infer_block(&arm.body);
485 });
486 }
487 }
488 Stmt::Break | Stmt::Continue | Stmt::Pass => {}
489 Stmt::Assert(cond) => {
490 if let Some(cond) = cond {
491 self.infer_expr(cond, &Expectation::None);
492 }
493 }
494 }
495 }
496
497 fn infer_local_var(&mut self, v: &body::LocalVar) {
498 let annotated = v.type_ref.map(|p| self.resolve_ptr_ty(p));
499 let init_ty = v.init.map(|e| {
500 let expected = annotated
501 .as_ref()
502 .map_or(Expectation::None, |t| Expectation::Has(t.clone()));
503 self.infer_expr(e, &expected)
504 });
505 let range = v.init.map_or(v.name_range, |e| self.range_of(e));
506
507 let binding_ty = match (&annotated, &init_ty) {
508 (Some(t), Some(init)) => {
510 self.check_assign(init, t, range);
511 t.clone()
512 }
513 (Some(t), None) => t.clone(),
515 (None, Some(init)) if v.is_inferred => {
517 if init.is_variant() {
518 self.emit(
519 range,
520 Severity::Error,
521 INFERENCE_ON_VARIANT,
522 inference_on_variant_msg(if v.is_const { "constant" } else { "variable" }),
523 );
524 Ty::Variant
525 } else {
526 init.clone()
528 }
529 }
530 (None, Some(init)) => {
532 if v.is_const {
533 init.clone()
534 } else {
535 Ty::Variant
536 }
537 }
538 (None, None) => Ty::Variant,
539 };
540 self.bindings.push(Binding {
541 name_range: v.name_range,
542 ty: binding_ty.clone(),
543 init: v.init,
544 annotated: v.type_ref.is_some(),
545 inferred_colon_eq: v.is_inferred,
546 kind: BindingKind::Var,
547 });
548 self.narrowing.remove(v.name.as_str());
549 self.locals.insert(v.name.clone(), binding_ty);
550 }
551
552 fn infer_expr(&mut self, id: ExprId, expected: &Expectation) -> Ty {
555 let ty = self.synth_expr(id, expected);
556 self.expr_ty.insert(id, ty.clone());
557 ty
558 }
559
560 #[allow(clippy::too_many_lines)]
561 fn synth_expr(&mut self, id: ExprId, expected: &Expectation) -> Ty {
562 match self.body.expr(id).clone() {
563 Expr::Missing => Ty::Error,
564 Expr::Literal(lit) => self.literal_ty(lit),
565 Expr::Name(name) => self.resolve_name(id, &name),
566 Expr::SelfExpr => self.self_ty.clone(),
567 Expr::Super => self.class.base.clone(),
568 Expr::Paren(inner) => self.infer_expr(inner, expected),
569 Expr::Bin { op, lhs, rhs } => self.infer_bin(id, op, lhs, rhs),
570 Expr::Unary { op, operand } => {
571 let t = self.infer_expr(operand, &Expectation::None);
572 match op {
573 UnOp::Not => self.bool_ty(),
574 UnOp::BitNot => self.int_ty(),
575 UnOp::Neg | UnOp::Pos => {
576 if t.is_uninformative() || self.is_numeric(&t) {
577 t
578 } else {
579 Ty::Variant
580 }
581 }
582 }
583 }
584 Expr::Ternary {
585 cond,
586 then_branch,
587 else_branch,
588 } => {
589 self.infer_expr(cond, &Expectation::None);
590 let a = self.infer_expr(then_branch, expected);
591 let b = self.infer_expr(else_branch, expected);
592 if self.is_null(else_branch) {
594 a
595 } else if self.is_null(then_branch) {
596 b
597 } else {
598 self.join(&a, &b)
599 }
600 }
601 Expr::Call { callee, args } => self.infer_call(callee, &args),
602 Expr::Field {
603 receiver,
604 name,
605 name_range,
606 } => {
607 self.infer_field(receiver, &name, name_range, false)
608 }
609 Expr::Index { base, index } => {
610 let base_ty = self.infer_expr(base, &Expectation::None);
611 self.infer_expr(index, &Expectation::None);
612 self.index_ty(&base_ty)
613 }
614 Expr::Is { operand, .. } => {
615 self.infer_expr(operand, &Expectation::None);
616 self.bool_ty()
617 }
618 Expr::Cast { operand, ty } => {
619 self.infer_expr(operand, &Expectation::None);
620 ty.map_or(Ty::Variant, |p| self.resolve_ptr_ty(p))
621 }
622 Expr::In { lhs, rhs, .. } => {
623 self.infer_expr(lhs, &Expectation::None);
624 self.infer_expr(rhs, &Expectation::None);
625 self.bool_ty()
626 }
627 Expr::Await(operand) => {
628 self.infer_expr(operand, &Expectation::None);
629 Ty::Unknown
632 }
633 Expr::Array(elems) => {
634 let pushed = match expected {
638 Expectation::Has(Ty::Array(e)) => Some((**e).clone()),
639 _ => None,
640 };
641 let elem_exp = pushed.clone().map_or(Expectation::None, Expectation::Has);
642 for e in elems {
643 self.infer_expr(e, &elem_exp);
644 }
645 pushed.map_or_else(Ty::array_of_variant, |e| Ty::Array(Box::new(e)))
646 }
647 Expr::Dict(entries) => {
648 let pushed = match expected {
649 Expectation::Has(Ty::Dict(k, v)) => Some(((**k).clone(), (**v).clone())),
650 _ => None,
651 };
652 let (kx, vx) = pushed
653 .clone()
654 .map_or((Expectation::None, Expectation::None), |(k, v)| {
655 (Expectation::Has(k), Expectation::Has(v))
656 });
657 for (k, v) in entries {
658 self.infer_expr(k, &kx);
659 if let Some(v) = v {
660 self.infer_expr(v, &vx);
661 }
662 }
663 pushed.map_or_else(Ty::dict_of_variant, |(k, v)| {
664 Ty::Dict(Box::new(k), Box::new(v))
665 })
666 }
667 Expr::Lambda { params, body } => {
668 self.infer_lambda(¶ms, &body);
669 Ty::Callable
670 }
671 Expr::Preload { arg, path } => {
672 if let Some(arg) = arg {
673 self.infer_expr(arg, &Expectation::None);
674 }
675 match path {
680 Some(p) => {
681 resolve::resolve_external(self.db, &resolve::ExternalRef::Preload(p))
682 }
683 None => Ty::Unknown,
684 }
685 }
686 Expr::GetNode { path, unique } => self.resolve_node_path(id, path.as_deref(), unique),
689 }
690 }
691
692 fn is_null(&self, id: ExprId) -> bool {
694 matches!(self.body.expr(id), Expr::Literal(Literal::Null))
695 }
696
697 fn literal_ty(&self, lit: Literal) -> Ty {
698 match lit {
699 Literal::Int => self.int_ty(),
700 Literal::Float | Literal::MathConst => self.float_ty(),
701 Literal::Bool => self.bool_ty(),
702 Literal::Str => self.builtin("String"),
703 Literal::StringName => self.builtin("StringName"),
704 Literal::NodePath => self.builtin("NodePath"),
705 Literal::Null => Ty::Variant,
707 }
708 }
709
710 fn node_ty(&self) -> Ty {
711 self.api
712 .class_by_name("Node")
713 .map_or(Ty::Unknown, Ty::Object)
714 }
715
716 fn resolve_node_path(&mut self, id: ExprId, path: Option<&str>, unique: bool) -> Ty {
724 use gdscript_scene::NodePathResolution as R;
725 let fallback = self.node_ty();
726 let Some(path) = path else {
727 return fallback; };
729 let Some(ctx) = self.owning_scene() else {
730 return fallback; };
732 let resolution = if unique {
733 ctx.model.classify_unique(path)
734 } else {
735 ctx.model.classify_path_from(ctx.attach, path)
736 };
737 match resolution {
738 R::Resolved(idx) => ctx
739 .model
740 .node(idx)
741 .and_then(|n| self.scene_node_ty(&ctx.model, n, 0))
742 .unwrap_or(fallback),
743 R::Missing if !ctx.ambiguous => {
744 let what = if unique { "unique name" } else { "node path" };
745 let sigil = if unique { "%" } else { "$" };
746 self.emit(
747 self.range_of(id),
748 Severity::Warning,
749 INVALID_NODE_PATH,
750 format!("no {what} `{sigil}{path}` in the owning scene"),
751 );
752 fallback
753 }
754 _ => fallback,
756 }
757 }
758
759 fn owning_scene(&self) -> Option<crate::queries::SceneContext> {
763 let Ty::ScriptRef(sref) = &self.self_ty else {
764 return None;
765 };
766 let ft = self.db.file_text(FileId(sref.0))?;
767 crate::queries::scene_context(self.db, ft)
768 }
769
770 fn scene_node_ty(&self, scene: &SceneModel, node: &SceneNode, depth: u32) -> Option<Ty> {
775 if let Some(script_ty) = self.node_script_ref(scene, node) {
776 return Some(script_ty);
777 }
778 if let Some(decl) = node.decl_type.as_ref() {
779 let ty = resolve::resolve_type_name(self.db, self.api, decl);
780 if !ty.is_uninformative() {
781 return Some(ty);
782 }
783 }
784 self.instance_root_ty(scene, node, depth)
785 }
786
787 fn instance_root_ty(&self, scene: &SceneModel, node: &SceneNode, depth: u32) -> Option<Ty> {
792 if depth >= 16 {
793 return None;
794 }
795 let inst = node.instance.as_ref()?;
796 let path = scene.ext_resources.get(inst)?.path.as_ref()?;
797 let root = self.db.source_root()?;
798 let file = crate::queries::res_path_registry(self.db, root)
799 .get(path.as_str())
800 .copied()?;
801 let ft = self.db.file_text(file)?;
802 let sub = crate::queries::scene_model(self.db, ft);
803 let root_node = sub.node(sub.root?)?;
804 self.scene_node_ty(&sub, root_node, depth + 1)
805 }
806
807 fn node_script_ref(&self, scene: &SceneModel, node: &SceneNode) -> Option<Ty> {
810 let path = scene
811 .ext_resources
812 .get(node.script.as_ref()?)?
813 .path
814 .as_ref()?;
815 let root = self.db.source_root()?;
816 let file = crate::queries::res_path_registry(self.db, root)
817 .get(path.as_str())
818 .copied()?;
819 Some(Ty::ScriptRef(ScriptRefId(file.0)))
820 }
821
822 fn infer_bin(&mut self, id: ExprId, op: BinOp, lhs: ExprId, rhs: ExprId) -> Ty {
823 if op == BinOp::Assign {
824 return self.infer_assign(lhs, rhs);
825 }
826 let lt = self.infer_expr(lhs, &Expectation::None);
827 let rt = self.infer_expr(rhs, &Expectation::None);
828 if op.is_boolean() {
829 return self.bool_ty();
830 }
831 if op == BinOp::Div && self.is_int(<) && self.is_int(&rt) {
833 self.emit(
834 self.range_of(id),
835 Severity::Warning,
836 INTEGER_DIVISION,
837 "Integer division. Decimal part will be discarded.".to_owned(),
838 );
839 return self.int_ty();
840 }
841 self.bin_result(op, <, &rt)
842 }
843
844 fn infer_assign(&mut self, lhs: ExprId, rhs: ExprId) -> Ty {
845 let slot = self.infer_expr(lhs, &Expectation::None);
846 let expected = if slot.is_uninformative() {
847 Expectation::None
848 } else {
849 Expectation::Has(slot.clone())
850 };
851 let value = self.infer_expr(rhs, &expected);
852 if !slot.is_uninformative() {
853 self.check_assign(&value, &slot, self.range_of(rhs));
854 }
855 if let Some(key) = self.narrow_key(lhs) {
857 let narrowed = if slot.is_uninformative() {
858 value.clone()
859 } else {
860 slot.clone()
861 };
862 self.narrowing.insert(key, narrowed);
863 }
864 slot
865 }
866
867 fn bin_result(&self, op: BinOp, lt: &Ty, rt: &Ty) -> Ty {
870 if let (Ty::Builtin(b), Some(sym)) = (lt, op_symbol(op)) {
871 for o in self.api.builtin_operators(*b) {
872 if o.op == sym
873 && let Some(right) = &o.right
874 && self.tyref_matches(right, rt)
875 {
876 return ty::resolve_tyref(self.api, &o.result);
877 }
878 }
879 }
880 if self.is_numeric(lt) && self.is_numeric(rt) {
881 return if self.is_float(lt) || self.is_float(rt) {
882 self.float_ty()
883 } else {
884 self.int_ty()
885 };
886 }
887 if lt.is_unknown() || rt.is_unknown() || lt.is_error() || rt.is_error() {
890 return Ty::Unknown;
891 }
892 Ty::Variant
893 }
894
895 fn tyref_matches(&self, tyref: &TyRef, ty: &Ty) -> bool {
896 let resolved = ty::resolve_tyref(self.api, tyref);
897 resolved.is_variant() || &resolved == ty
898 }
899
900 fn infer_call(&mut self, callee: ExprId, args: &[ExprId]) -> Ty {
901 for &a in args {
903 self.infer_expr(a, &Expectation::None);
904 }
905 match self.body.expr(callee).clone() {
906 Expr::Field {
907 receiver,
908 name,
909 name_range,
910 } => {
911 self.infer_field(receiver, &name, name_range, true)
912 }
913 Expr::Name(name) => {
914 let ret = self.resolve_call_name(&name);
915 self.expr_ty.insert(callee, Ty::Callable);
916 ret
917 }
918 _ => {
922 self.infer_expr(callee, &Expectation::None);
923 Ty::Unknown
924 }
925 }
926 }
927
928 fn resolve_call_name(&self, name: &str) -> Ty {
930 if let Some(item) = self.class.lookup(name)
931 && let Some(Member::Func(f)) = self.class.member(item)
932 {
933 return self.func_return_ty(f.return_type.as_deref());
934 }
935 if let Ty::Object(base) = self.class.base
937 && let Some(MemberRef::Method(sig)) = self.api.lookup_member(base, name)
938 {
939 return ty::resolve_tyref(self.api, &sig.return_ty);
940 }
941 if let Some(u) = self.api.utility(name) {
942 return ty::resolve_tyref(self.api, &u.return_ty);
943 }
944 if let Some(f) = self.api.gdscript_builtin(name) {
945 return resolve::layer_to_ty(self.api, f.ret);
946 }
947 if let Some(b) = self.api.builtin_by_name(name) {
951 return ty::resolve_tyref(self.api, &TyRef::Builtin(b));
952 }
953 Ty::Unknown
956 }
957
958 fn func_return_ty(&self, annotation: Option<&str>) -> Ty {
959 annotation.map_or(Ty::Variant, |t| {
960 resolve::resolve_type_name(self.db, self.api, t)
961 })
962 }
963
964 fn infer_field(
968 &mut self,
969 receiver: ExprId,
970 name: &str,
971 name_range: TextRange,
972 as_method: bool,
973 ) -> Ty {
974 let is_self = matches!(self.body.expr(receiver), Expr::SelfExpr);
975 let recv_ty = self.infer_expr(receiver, &Expectation::None);
976
977 if is_self && let Some(item) = self.class.lookup(name) {
979 return self.own_member_ty(item, as_method);
980 }
981
982 match &recv_ty {
983 t if t.is_uninformative() => recv_ty.clone(),
988 Ty::Object(class) => {
989 if name == "new" {
990 recv_ty.clone()
993 } else if let Some(m) = self.api.lookup_member(*class, name) {
994 self.member_ref_ty(&m, as_method)
995 } else if let Some(t) = self.class_enum_value(*class, name) {
996 t
998 } else {
999 self.emit_unsafe(name, &recv_ty, name_range, as_method);
1001 Ty::Variant
1002 }
1003 }
1004 Ty::Builtin(_) | Ty::Array(_) | Ty::Dict(..) | Ty::Callable | Ty::Signal(_) => {
1005 self.builtin_member_ty(&recv_ty, name, name_range, as_method)
1006 }
1007 Ty::Enum(_) => self.int_ty(),
1009 Ty::ScriptRef(sref) => self.script_member_ty(*sref, name, as_method),
1011 _ => Ty::Variant,
1012 }
1013 }
1014
1015 fn script_member_ty(&self, sref: ScriptRefId, name: &str, as_method: bool) -> Ty {
1020 if name == "new" {
1021 return Ty::ScriptRef(sref);
1022 }
1023 self.script_member_walk(sref, name, as_method, 0)
1024 .unwrap_or(Ty::Unknown)
1025 }
1026
1027 fn script_member_walk(
1031 &self,
1032 sref: ScriptRefId,
1033 name: &str,
1034 as_method: bool,
1035 depth: u32,
1036 ) -> Option<Ty> {
1037 if depth > 32 {
1038 return None;
1039 }
1040 let file = self.db.file_text(FileId(sref.0))?;
1041 let sc = crate::queries::script_class(self.db, file);
1042 if let Some(m) = sc.member(name) {
1043 return Some(match m {
1044 crate::queries::MemberSig::Method(ret) => {
1045 if as_method {
1046 ret.clone()
1047 } else {
1048 Ty::Callable
1049 }
1050 }
1051 crate::queries::MemberSig::Field(t) => t.clone(),
1052 crate::queries::MemberSig::Signal => Ty::Signal(None),
1053 });
1054 }
1055 match sc.base() {
1057 Ty::ScriptRef(base) => self.script_member_walk(*base, name, as_method, depth + 1),
1058 Ty::Object(class) => self
1059 .api
1060 .lookup_member(*class, name)
1061 .map(|m| self.member_ref_ty(&m, as_method)),
1062 _ => None,
1063 }
1064 }
1065
1066 fn is_subtype(&self, sub: &Ty, sup: &Ty) -> bool {
1071 match (sub, sup) {
1072 (Ty::Object(a), Ty::Object(b)) => self.api.is_subclass(*a, *b),
1073 (Ty::ScriptRef(a), Ty::ScriptRef(b)) => self.script_is_subtype(*a, *b, 0),
1074 (Ty::ScriptRef(a), Ty::Object(b)) => self.script_extends_engine(*a, *b, 0),
1075 _ => false,
1076 }
1077 }
1078
1079 fn script_is_subtype(&self, sub: ScriptRefId, sup: ScriptRefId, depth: u32) -> bool {
1082 if depth > 32 {
1083 return false;
1084 }
1085 if sub == sup {
1086 return true;
1087 }
1088 let Some(file) = self.db.file_text(FileId(sub.0)) else {
1089 return false;
1090 };
1091 match crate::queries::script_class(self.db, file).base() {
1092 Ty::ScriptRef(base) => self.script_is_subtype(*base, sup, depth + 1),
1093 _ => false,
1094 }
1095 }
1096
1097 fn script_extends_engine(
1099 &self,
1100 sub: ScriptRefId,
1101 sup_native: gdscript_api::ClassId,
1102 depth: u32,
1103 ) -> bool {
1104 if depth > 32 {
1105 return false;
1106 }
1107 let Some(file) = self.db.file_text(FileId(sub.0)) else {
1108 return false;
1109 };
1110 match crate::queries::script_class(self.db, file).base() {
1111 Ty::ScriptRef(base) => self.script_extends_engine(*base, sup_native, depth + 1),
1112 Ty::Object(native) => self.api.is_subclass(*native, sup_native),
1113 _ => false,
1114 }
1115 }
1116
1117 fn emit_unsafe(&mut self, name: &str, recv: &Ty, range: TextRange, as_method: bool) {
1118 let recv_label = recv.label(self.api).unwrap_or_else(|| "?".to_owned());
1119 let (code, message) = if as_method {
1120 (
1121 UNSAFE_METHOD_ACCESS,
1122 format!(
1123 "The method \"{name}()\" is not present on the inferred type \"{recv_label}\" (but may be present on a subtype)."
1124 ),
1125 )
1126 } else {
1127 (
1128 UNSAFE_PROPERTY_ACCESS,
1129 format!(
1130 "The property \"{name}\" is not present on the inferred type \"{recv_label}\" (but may be present on a subtype)."
1131 ),
1132 )
1133 };
1134 self.emit(range, Severity::Warning, code, message);
1135 }
1136
1137 fn member_ref_ty(&self, m: &MemberRef, as_method: bool) -> Ty {
1138 match m {
1139 MemberRef::Method(sig) => {
1140 if as_method {
1141 ty::resolve_tyref(self.api, &sig.return_ty)
1142 } else {
1143 Ty::Callable
1144 }
1145 }
1146 MemberRef::Property(p) => p.enum_of.as_ref().map_or_else(
1147 || ty::resolve_tyref(self.api, &p.ty),
1148 |q| {
1149 Ty::Enum(EnumRef {
1150 qualified: SmolStr::new(q),
1151 bitfield: false,
1152 })
1153 },
1154 ),
1155 MemberRef::Const(c) => ty::resolve_tyref(self.api, &c.ty),
1156 MemberRef::Signal(_) => Ty::Signal(None),
1157 MemberRef::Enum(_) => Ty::Variant,
1158 }
1159 }
1160
1161 fn builtin_member_ty(
1162 &mut self,
1163 recv: &Ty,
1164 name: &str,
1165 range: TextRange,
1166 as_method: bool,
1167 ) -> Ty {
1168 let Some(bid) = self.builtin_id_of(recv) else {
1169 return Ty::Variant;
1170 };
1171 if as_method {
1172 return if let Some(sig) = self.api.builtin_method(bid, name) {
1173 ty::resolve_tyref(self.api, &sig.return_ty)
1174 } else {
1175 self.emit_unsafe(name, recv, range, true);
1176 Ty::Variant
1177 };
1178 }
1179 if let Some(member) = self.api.builtin_member(bid, name) {
1180 return ty::resolve_tyref(self.api, &member.ty);
1181 }
1182 let data = self.api.builtin(bid);
1184 if let Some(c) = data.constants.iter().find(|c| c.name == name) {
1185 return ty::resolve_tyref(self.api, &c.ty);
1186 }
1187 if data
1188 .enums
1189 .iter()
1190 .any(|e| e.values.iter().any(|v| v.name == name))
1191 {
1192 return self.int_ty();
1193 }
1194 if self.api.builtin_method(bid, name).is_some() {
1195 return Ty::Callable;
1196 }
1197 self.emit_unsafe(name, recv, range, false);
1198 Ty::Variant
1199 }
1200
1201 fn class_enum_value(&self, class: gdscript_api::ClassId, name: &str) -> Option<Ty> {
1205 let mut cur = Some(class);
1206 while let Some(cid) = cur {
1207 let c = self.api.class(cid);
1208 if c.enums
1209 .iter()
1210 .any(|e| e.values.iter().any(|v| v.name == name))
1211 {
1212 return Some(self.int_ty());
1213 }
1214 cur = c.base;
1215 }
1216 None
1217 }
1218
1219 fn builtin_id_of(&self, ty: &Ty) -> Option<gdscript_api::BuiltinId> {
1221 match ty {
1222 Ty::Builtin(b) => Some(*b),
1223 Ty::Array(_) => self.api.builtin_by_name("Array"),
1224 Ty::Dict(..) => self.api.builtin_by_name("Dictionary"),
1225 Ty::Callable => self.api.builtin_by_name("Callable"),
1226 Ty::Signal(_) => self.api.builtin_by_name("Signal"),
1227 _ => None,
1228 }
1229 }
1230
1231 fn index_ty(&self, base: &Ty) -> Ty {
1233 match base {
1234 Ty::Array(elem) => (**elem).clone(),
1235 Ty::Builtin(b) => self
1236 .api
1237 .builtin(*b)
1238 .indexing_return
1239 .as_ref()
1240 .map_or(Ty::Variant, |r| ty::resolve_tyref(self.api, r)),
1241 Ty::Unknown => Ty::Unknown,
1243 Ty::Error => Ty::Error,
1244 _ => Ty::Variant,
1245 }
1246 }
1247
1248 fn loop_var_ty(&self, iter: &Ty) -> Ty {
1250 match iter {
1251 Ty::Array(elem) => (**elem).clone(),
1252 Ty::Builtin(b) => {
1253 let data = self.api.builtin(*b);
1254 if data.name == "int" {
1255 self.int_ty()
1257 } else if let Some(r) = &data.indexing_return {
1258 ty::resolve_tyref(self.api, r)
1260 } else {
1261 Ty::Variant
1262 }
1263 }
1264 Ty::Unknown => Ty::Unknown,
1266 Ty::Error => Ty::Error,
1267 _ => Ty::Variant,
1268 }
1269 }
1270
1271 fn infer_lambda(&mut self, params: &[ParamBinding], body: &[body::StmtId]) {
1272 let saved_locals = self.locals.clone();
1276 let saved_ret = std::mem::replace(&mut self.return_ty, Ty::Variant);
1277 for p in params {
1278 let ty = self.param_ty(p);
1279 self.bindings.push(Binding {
1280 name_range: p.name_range,
1281 ty: ty.clone(),
1282 init: None,
1283 annotated: p.type_ref.is_some(),
1284 inferred_colon_eq: false,
1285 kind: BindingKind::Param,
1286 });
1287 self.locals.insert(p.name.clone(), ty);
1288 }
1289 self.infer_block(body);
1290 self.return_ty = saved_ret;
1291 self.locals = saved_locals;
1292 }
1293
1294 fn param_ty(&mut self, p: &ParamBinding) -> Ty {
1295 if let Some(ptr) = p.type_ref {
1296 return self.resolve_ptr_ty(ptr);
1297 }
1298 p.default
1300 .map_or(Ty::Variant, |e| self.infer_expr(e, &Expectation::None))
1301 }
1302
1303 fn resolve_name(&mut self, id: ExprId, name: &str) -> Ty {
1306 if let Some(key) = self.narrow_key(id)
1308 && let Some(t) = self.narrowing.get(&key)
1309 {
1310 return t.clone();
1311 }
1312 if let Some(t) = self.locals.get(name) {
1313 return t.clone();
1314 }
1315 if let Some(item) = self.class.lookup(name) {
1316 return self.own_member_ty(item, false);
1317 }
1318 match self.class.base.clone() {
1322 Ty::Object(base) => {
1323 if let Some(m) = self.api.lookup_member(base, name) {
1324 return self.member_ref_ty(&m, false);
1325 }
1326 }
1327 Ty::ScriptRef(base) => {
1328 if let Some(t) = self.script_member_walk(base, name, false, 0) {
1329 return t;
1330 }
1331 }
1332 _ => {}
1333 }
1334 if let Some(g) = resolve::resolve_global(self.api, name) {
1335 return global_ty(&g);
1336 }
1337 let by_class = resolve::resolve_external(
1342 self.db,
1343 &resolve::ExternalRef::ClassName(SmolStr::new(name)),
1344 );
1345 if !by_class.is_unknown() {
1346 return by_class;
1347 }
1348 resolve::resolve_external(self.db, &resolve::ExternalRef::Autoload(SmolStr::new(name)))
1349 }
1350
1351 fn own_member_ty(&self, item: ClassItem, as_method: bool) -> Ty {
1352 match item {
1353 ClassItem::EnumVariant => self.int_ty(),
1354 ClassItem::Member(_) => match self.class.member(item) {
1355 Some(Member::Var(v)) => self.field_ty(&v.name, v.ptr),
1356 Some(Member::Const(c)) => self.field_ty(&c.name, c.ptr),
1357 Some(Member::Func(f)) => {
1358 if as_method {
1359 self.func_return_ty(f.return_type.as_deref())
1360 } else {
1361 Ty::Callable
1362 }
1363 }
1364 Some(Member::Signal(_)) => Ty::Signal(None),
1365 Some(Member::Class(_)) => Ty::Unknown,
1366 Some(Member::Enum(_)) | None => Ty::Variant,
1367 },
1368 }
1369 }
1370
1371 fn field_ty(&self, name: &str, ptr: AstPtr) -> Ty {
1374 if let Some(t) = self.class.member_types.get(name) {
1375 return t.clone();
1376 }
1377 self.resolve_decl_annotation(ptr)
1378 }
1379
1380 fn resolve_decl_annotation(&self, ptr: AstPtr) -> Ty {
1382 let Some(node) = ptr.to_node(self.root) else {
1383 return Ty::Variant;
1384 };
1385 cst::first_child(&node, |k| k == gdscript_syntax::SyntaxKind::TypeRef)
1386 .map_or(Ty::Variant, |t| {
1387 resolve::resolve_type_ref(self.db, self.api, &t)
1388 })
1389 }
1390
1391 fn apply_narrowing(&mut self, cond: ExprId) {
1402 let Expr::Is {
1403 operand,
1404 ty: Some(ptr),
1405 negated: false,
1406 } = self.body.expr(cond).clone()
1407 else {
1408 return;
1409 };
1410 let Some(key) = self.narrow_key(operand) else {
1411 return;
1412 };
1413 let narrowed = self.resolve_ptr_ty(ptr);
1414 if narrowed.is_uninformative() {
1415 return;
1416 }
1417 let cur = self.expr_ty.get(&operand).cloned().unwrap_or(Ty::Variant);
1418 if cur.is_uninformative() || self.is_subtype(&narrowed, &cur) {
1419 self.narrowing.insert(key, narrowed);
1420 }
1421 }
1422
1423 fn narrow_key(&self, id: ExprId) -> Option<String> {
1426 match self.body.expr(id) {
1427 Expr::Name(n) => Some(n.to_string()),
1428 Expr::SelfExpr => Some("self".to_owned()),
1429 Expr::Paren(inner) => self.narrow_key(*inner),
1430 Expr::Field { receiver, name, .. } => {
1431 Some(format!("{}.{name}", self.narrow_key(*receiver)?))
1432 }
1433 _ => None,
1434 }
1435 }
1436
1437 fn resolve_ptr_ty(&self, ptr: AstPtr) -> Ty {
1438 ptr.to_node(self.root).map_or(Ty::Variant, |n| {
1439 resolve::resolve_type_ref(self.db, self.api, &n)
1440 })
1441 }
1442
1443 fn join(&self, a: &Ty, b: &Ty) -> Ty {
1454 if a == b {
1455 return a.clone();
1456 }
1457 if a.is_error() || b.is_error() {
1458 return Ty::Error;
1459 }
1460 if a.is_unknown() || b.is_unknown() {
1461 return Ty::Unknown;
1462 }
1463 if a.is_variant() || b.is_variant() {
1464 return Ty::Variant;
1465 }
1466 if ty::is_assignable(self.api, a, b) == Assign::Ok {
1467 return b.clone();
1468 }
1469 if ty::is_assignable(self.api, b, a) == Assign::Ok {
1470 return a.clone();
1471 }
1472 Ty::Variant
1473 }
1474
1475 fn in_branch<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> R {
1477 let saved = self.narrowing.clone();
1478 let r = f(self);
1479 self.narrowing = saved;
1480 r
1481 }
1482}
1483
1484fn global_ty(g: &GlobalDef) -> Ty {
1486 match g {
1487 GlobalDef::Const(t) => t.clone(),
1488 GlobalDef::Singleton(c) | GlobalDef::ClassType(c) => Ty::Object(*c),
1489 GlobalDef::BuiltinType(b) => Ty::Builtin(*b),
1490 GlobalDef::Builtin | GlobalDef::Utility => Ty::Callable,
1492 GlobalDef::GlobalEnum => Ty::Variant,
1493 }
1494}
1495
1496fn inference_on_variant_msg(kind: &str) -> String {
1497 format!(
1498 "The {kind} type is being inferred from a Variant value, so it will be typed as Variant."
1499 )
1500}
1501
1502fn op_symbol(op: BinOp) -> Option<&'static str> {
1504 Some(match op {
1505 BinOp::Add => "+",
1506 BinOp::Sub => "-",
1507 BinOp::Mul => "*",
1508 BinOp::Div => "/",
1509 BinOp::Mod => "%",
1510 BinOp::Pow => "**",
1511 BinOp::BitAnd => "&",
1512 BinOp::BitOr => "|",
1513 BinOp::BitXor => "^",
1514 BinOp::Shl => "<<",
1515 BinOp::Shr => ">>",
1516 _ => return None,
1517 })
1518}
1519
1520#[cfg(test)]
1521mod tests {
1522 use super::*;
1523 use crate::item_tree::item_tree;
1524 use gdscript_syntax::{SyntaxKind, parse};
1525
1526 struct Harness {
1527 result: InferenceResult,
1528 body: Body,
1529 }
1530
1531 fn infer_first_func(src: &str) -> Harness {
1533 let api = gdscript_api::bundled();
1534 let db = gdscript_db::RootDatabase::default();
1535 let root = parse(src).syntax_node();
1536 let tree = item_tree(&root);
1537 let class = ClassScope::new(&db, api, &tree);
1538 let func = gdscript_syntax::ast::descendants(&root)
1539 .into_iter()
1540 .find(|n| n.kind() == SyntaxKind::FuncDecl)
1541 .expect("a function");
1542 let body = body::body_of_func(&func);
1543 let return_ty = cst::first_child(&func, |k| k == SyntaxKind::TypeRef)
1544 .map_or(Ty::Variant, |t| resolve::resolve_type_ref(&db, api, &t));
1545 let result = infer(&db, api, &root, &class, &body, return_ty);
1546 Harness { result, body }
1547 }
1548
1549 fn codes(h: &Harness) -> Vec<&str> {
1550 h.result
1551 .diagnostics
1552 .iter()
1553 .map(|d| d.code.as_str())
1554 .collect()
1555 }
1556
1557 #[test]
1558 fn integer_division_warns() {
1559 let h = infer_first_func("func f():\n\tvar x = 5 / 2\n");
1560 assert!(codes(&h).contains(&INTEGER_DIVISION));
1561 }
1562
1563 #[test]
1564 fn float_div_does_not_warn() {
1565 let h = infer_first_func("func f():\n\tvar x = 5.0 / 2\n");
1566 assert!(!codes(&h).contains(&INTEGER_DIVISION));
1567 }
1568
1569 #[test]
1570 fn type_mismatch_on_hard_annotation() {
1571 let h = infer_first_func("func f():\n\tvar s: String = 5\n");
1572 assert!(codes(&h).contains(&TYPE_MISMATCH));
1573 }
1574
1575 #[test]
1576 fn narrowing_conversion_float_to_int() {
1577 let h = infer_first_func("func f():\n\tvar n: int = 1.5\n");
1578 assert!(codes(&h).contains(&NARROWING_CONVERSION));
1579 }
1580
1581 #[test]
1582 fn int_to_float_is_silent() {
1583 let h = infer_first_func("func f():\n\tvar x: float = 3\n");
1584 assert!(
1585 h.result.diagnostics.is_empty(),
1586 "{:?}",
1587 h.result.diagnostics
1588 );
1589 }
1590
1591 #[test]
1592 fn member_access_resolves_engine_property() {
1593 let h = infer_first_func(
1596 "extends Node\nfunc f():\n\tvar n := get_node(\"x\")\n\tn.get_parent()\n",
1597 );
1598 assert!(
1599 codes(&h).iter().all(|c| !c.starts_with("UNSAFE")),
1600 "{:?}",
1601 h.result.diagnostics
1602 );
1603 }
1604
1605 #[test]
1606 fn unsafe_method_on_known_type() {
1607 let h = infer_first_func(
1608 "extends Node\nfunc f():\n\tvar n := get_node(\"x\")\n\tn.totally_bogus_method()\n",
1609 );
1610 assert!(
1611 codes(&h).contains(&UNSAFE_METHOD_ACCESS),
1612 "{:?}",
1613 h.result.diagnostics
1614 );
1615 }
1616
1617 #[test]
1618 fn is_narrowing_suppresses_unsafe() {
1619 let h = infer_first_func("func f(x):\n\tif x is Node:\n\t\tx.queue_free()\n");
1622 assert!(
1623 codes(&h).iter().all(|c| !c.starts_with("UNSAFE")),
1624 "{:?}",
1625 h.result.diagnostics
1626 );
1627 }
1628
1629 #[test]
1630 fn is_narrowing_flags_real_missing_member() {
1631 let h = infer_first_func("func f(x):\n\tif x is Node:\n\t\tx.bogus_method()\n");
1633 assert!(codes(&h).contains(&UNSAFE_METHOD_ACCESS));
1634 }
1635
1636 #[test]
1637 fn variant_receiver_never_unsafe() {
1638 let h = infer_first_func("func f(x):\n\tx.anything_at_all()\n");
1640 assert!(
1641 h.result.diagnostics.is_empty(),
1642 "{:?}",
1643 h.result.diagnostics
1644 );
1645 }
1646
1647 #[test]
1648 fn inference_on_variant() {
1649 let h = infer_first_func("func f(x):\n\tvar y := x\n");
1651 assert!(codes(&h).contains(&INFERENCE_ON_VARIANT));
1652 }
1653
1654 #[test]
1655 fn lambda_var_is_callable_not_variant() {
1656 let h = infer_first_func("func f():\n\tvar cb := func():\n\t\tpass\n");
1657 assert!(
1658 !codes(&h).contains(&INFERENCE_ON_VARIANT),
1659 "{:?}",
1660 h.result.diagnostics
1661 );
1662 }
1663
1664 #[test]
1665 fn multiline_lambda_then_paren_line_no_false_warning() {
1666 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";
1670 let h = infer_first_func(src);
1671 assert!(
1672 !codes(&h).contains(&INFERENCE_ON_VARIANT),
1673 "{:?}",
1674 h.result.diagnostics
1675 );
1676 }
1677
1678 #[test]
1679 fn calling_a_callable_value_is_seam_not_variant() {
1680 let src = "func f(cb: Callable):\n\tvar x := (cb)()\n\treturn x\n";
1684 let h = infer_first_func(src);
1685 assert!(
1686 !codes(&h).contains(&INFERENCE_ON_VARIANT),
1687 "{:?}",
1688 h.result.diagnostics
1689 );
1690 }
1691
1692 #[test]
1693 fn ternary_with_seam_branch_does_not_collapse_to_variant() {
1694 let src =
1698 "func f(c: bool):\n\tvar x := 5 if c else await get_tree().process_frame\n\treturn x\n";
1699 let h = infer_first_func(src);
1700 assert!(
1701 !codes(&h).contains(&INFERENCE_ON_VARIANT),
1702 "seam branch should keep the ternary on the seam: {:?}",
1703 h.result.diagnostics
1704 );
1705 }
1706
1707 #[test]
1708 fn for_var_over_packed_string_array_is_string() {
1709 let h = infer_first_func("func f():\n\tfor s in \"a,b\".split(\",\"):\n\t\tvar x := s\n");
1712 assert!(
1713 !codes(&h).contains(&INFERENCE_ON_VARIANT),
1714 "{:?}",
1715 h.result.diagnostics
1716 );
1717 }
1718
1719 #[test]
1720 fn class_new_is_object_not_variant() {
1721 let h = infer_first_func("func f():\n\tvar s := GDScript.new()\n");
1722 assert!(
1723 !codes(&h).contains(&INFERENCE_ON_VARIANT),
1724 "{:?}",
1725 h.result.diagnostics
1726 );
1727 }
1728
1729 #[test]
1730 fn unknown_seam_never_warns() {
1731 let h = infer_first_func("func f():\n\tvar s := preload(\"res://x.gd\")\n\ts.whatever()\n");
1733 assert!(
1734 h.result.diagnostics.is_empty(),
1735 "{:?}",
1736 h.result.diagnostics
1737 );
1738 }
1739
1740 #[test]
1741 fn expr_types_are_memoized_for_hover() {
1742 let h = infer_first_func("func f():\n\tvar n := 42\n");
1743 let has_int = h
1745 .result
1746 .expr_ty
1747 .values()
1748 .any(|t| matches!(t, Ty::Builtin(_)));
1749 assert!(has_int);
1750 assert!(!h.body.exprs.is_empty());
1752 }
1753}