gruel_air/inference/generate.rs
1//! Constraint generation for Hindley-Milner type inference.
2//!
3//! This module provides the constraint generation phase (Phase 1 of HM inference):
4//! - [`ConstraintContext`] - Scoped variable tracking during generation
5//! - [`ExprInfo`] - Result of constraint generation for an expression
6//! - [`ConstraintGenerator`] - Walks RIR and generates type constraints
7//! - Function/method signature types for type checking
8
9use super::constraint::Constraint;
10use super::types::{InferType, TypeVarAllocator, TypeVarId};
11use crate::Type;
12use crate::intern_pool::TypeInternPool;
13use crate::scope::ScopedContext;
14use crate::sema::InferenceContext;
15use crate::types::{
16 EnumId, PtrMutability, StructId, TypeKind, parse_array_type_syntax, parse_pointer_type_syntax,
17 parse_type_call_syntax,
18};
19use gruel_builtins::BuiltinTypeConstructorKind;
20use gruel_intrinsics::{IntrinsicId, lookup_by_name};
21use gruel_rir::{InstData, InstRef, Rir};
22use gruel_util::Span;
23use gruel_util::{BinOp, UnaryOp};
24use lasso::{Spur, ThreadedRodeo};
25use rustc_hash::FxHashMap as HashMap;
26
27/// Information about a local variable during constraint generation.
28#[derive(Debug, Clone)]
29pub struct LocalVarInfo {
30 /// The inferred type of this variable.
31 pub ty: InferType,
32 /// Whether the variable is mutable.
33 pub is_mut: bool,
34 /// Span of the variable declaration.
35 pub span: Span,
36}
37
38/// Information about a function parameter during constraint generation.
39#[derive(Debug, Clone)]
40pub struct ParamVarInfo {
41 /// The type of this parameter, as InferType for uniform handling.
42 pub ty: InferType,
43 /// Internal-mode after Phase-2/`analyze_function` normalization. A
44 /// `MutRef(T)` parameter has been lowered to `(T, Inout)`; a `Ref(T)`
45 /// parameter to `(T, Borrow)`. The mode is needed at HM constraint
46 /// generation so that ADR-0076's implicit forwarding can relax the
47 /// constraint when a `Ref(T)` / `MutRef(T)`-shaped binding is passed
48 /// onward to a `Ref(T)` / `MutRef(T)` callee param.
49 pub mode: gruel_rir::RirParamMode,
50}
51
52/// Information about a function during constraint generation.
53///
54/// Uses `InferType` rather than `Type` so that array types are represented
55/// structurally (as `InferType::Array { element, length }`) rather than by
56/// opaque IDs. This allows uniform handling during inference.
57#[derive(Debug, Clone)]
58pub struct FunctionSig {
59 /// Parameter types (in order), as InferTypes for uniform handling.
60 pub param_types: Vec<InferType>,
61 /// Return type, as InferType for uniform handling.
62 pub return_type: InferType,
63 /// Whether this is a generic function (has comptime type parameters).
64 /// Generic functions skip type checking during constraint generation -
65 /// they'll be checked during specialization.
66 pub is_generic: bool,
67 /// Parameter modes (Normal, Inout, Borrow, Comptime).
68 pub param_modes: Vec<gruel_rir::RirParamMode>,
69 /// Which parameters are comptime (declared with `comptime` keyword).
70 /// This is separate from param_modes because `comptime T: type` sets
71 /// is_comptime=true but mode=Normal.
72 pub param_comptime: Vec<bool>,
73 /// Parameter names, needed for type substitution in generic returns.
74 pub param_names: Vec<lasso::Spur>,
75 /// The return type as a symbol (used for substitution lookup).
76 pub return_type_sym: lasso::Spur,
77}
78
79/// Information about a method during constraint generation.
80///
81/// Used for method calls (receiver.method()) and associated function calls (Type::function()).
82#[derive(Debug, Clone)]
83pub struct MethodSig {
84 /// The struct type this method belongs to (as concrete Type::Struct)
85 pub struct_type: Type,
86 /// Whether this is a method (has self) or associated function (no self)
87 pub has_self: bool,
88 /// Parameter types (excluding self), as InferTypes for uniform handling.
89 pub param_types: Vec<InferType>,
90 /// Return type, as InferType for uniform handling.
91 pub return_type: InferType,
92}
93
94/// Context for constraint generation within a single function.
95pub struct ConstraintContext<'a> {
96 /// Local variables in scope.
97 pub locals: HashMap<Spur, LocalVarInfo>,
98 /// Function parameters.
99 pub params: &'a HashMap<Spur, ParamVarInfo>,
100 /// Return type of the current function.
101 pub return_type: Type,
102 /// How many loops we're nested inside (for break/continue validation).
103 pub loop_depth: u32,
104 /// Scope stack for efficient scope management.
105 scope_stack: Vec<Vec<(Spur, Option<LocalVarInfo>)>>,
106}
107
108impl<'a> ConstraintContext<'a> {
109 /// Create a new context for a function.
110 pub fn new(params: &'a HashMap<Spur, ParamVarInfo>, return_type: Type) -> Self {
111 Self {
112 locals: HashMap::default(),
113 params,
114 return_type,
115 loop_depth: 0,
116 scope_stack: Vec::new(),
117 }
118 }
119}
120
121impl ScopedContext for ConstraintContext<'_> {
122 type VarInfo = LocalVarInfo;
123
124 fn locals_mut(&mut self) -> &mut HashMap<Spur, Self::VarInfo> {
125 &mut self.locals
126 }
127
128 fn scope_stack_mut(&mut self) -> &mut Vec<Vec<(Spur, Option<Self::VarInfo>)>> {
129 &mut self.scope_stack
130 }
131}
132
133/// Result of constraint generation for an expression.
134#[derive(Debug, Clone)]
135pub struct ExprInfo {
136 /// The inferred type of this expression.
137 pub ty: InferType,
138 /// The span of this expression (for error reporting).
139 pub span: Span,
140}
141
142impl ExprInfo {
143 /// Create a new expression info.
144 pub fn new(ty: InferType, span: Span) -> Self {
145 Self { ty, span }
146 }
147}
148
149/// Constraint generator that walks RIR and generates type constraints.
150/// Return type of [`ConstraintGenerator::into_parts`].
151pub type ConstraintGeneratorParts = (
152 Vec<Constraint>,
153 Vec<TypeVarId>,
154 Vec<TypeVarId>,
155 HashMap<InstRef, InferType>,
156 u32,
157);
158
159///
160/// This is Phase 1 of HM inference: constraint generation. The constraints
161/// are later solved by the `Unifier` to determine concrete types.
162pub struct ConstraintGenerator<'a> {
163 /// The RIR being analyzed.
164 rir: &'a Rir,
165 /// String interner for resolving symbols.
166 interner: &'a ThreadedRodeo,
167 /// Type variable allocator.
168 type_vars: TypeVarAllocator,
169 /// Collected constraints.
170 constraints: Vec<Constraint>,
171 /// Mapping from RIR instruction to its inferred type.
172 expr_types: HashMap<InstRef, InferType>,
173 /// Function signatures (for call type checking).
174 functions: &'a HashMap<Spur, FunctionSig>,
175 /// Struct types (name -> Type::new_struct(id)).
176 structs: &'a HashMap<Spur, Type>,
177 /// Enum types (name -> Type::new_enum(id)).
178 enums: &'a HashMap<Spur, Type>,
179 /// Method signatures: (struct_id, method_name) -> MethodSig
180 methods: &'a HashMap<(StructId, Spur), MethodSig>,
181 /// Enum method signatures: (enum_id, method_name) -> MethodSig
182 enum_methods: &'a HashMap<(EnumId, Spur), MethodSig>,
183 /// Type variables allocated for integer literals.
184 /// These start as unbound and need to be defaulted to i32 if unconstrained.
185 int_literal_vars: Vec<TypeVarId>,
186 /// Type variables allocated for float literals.
187 /// These start as unbound and need to be defaulted to f64 if unconstrained.
188 float_literal_vars: Vec<TypeVarId>,
189 /// Type substitutions for Self and type parameters (used in method bodies).
190 /// Maps type names (like "Self") to their concrete types.
191 type_subst: Option<&'a HashMap<Spur, Type>>,
192 /// Type intern pool for creating pointer and array types during constraint generation.
193 type_pool: &'a TypeInternPool,
194}
195
196impl<'a> ConstraintGenerator<'a> {
197 /// Create a new constraint generator.
198 pub fn new(
199 rir: &'a Rir,
200 interner: &'a ThreadedRodeo,
201 infer_ctx: &'a InferenceContext,
202 type_pool: &'a TypeInternPool,
203 ) -> Self {
204 Self {
205 rir,
206 interner,
207 type_vars: TypeVarAllocator::new(),
208 constraints: Vec::new(),
209 expr_types: HashMap::default(),
210 functions: &infer_ctx.func_sigs,
211 structs: &infer_ctx.struct_types,
212 enums: &infer_ctx.enum_types,
213 methods: &infer_ctx.method_sigs,
214 enum_methods: &infer_ctx.enum_method_sigs,
215 int_literal_vars: Vec::new(),
216 float_literal_vars: Vec::new(),
217 type_subst: None,
218 type_pool,
219 }
220 }
221
222 /// Set type substitutions for `Self` and type parameters (builder pattern).
223 ///
224 /// The `type_subst` map provides type substitutions for names like "Self"
225 /// that should be resolved to concrete types during constraint generation.
226 /// This is used for method bodies where `Self { ... }` struct literals
227 /// need to know the concrete struct type.
228 pub fn with_type_subst(mut self, type_subst: Option<&'a HashMap<Spur, Type>>) -> Self {
229 self.type_subst = type_subst;
230 self
231 }
232
233 /// Get the type variables allocated for integer literals.
234 pub fn int_literal_vars(&self) -> &[TypeVarId] {
235 &self.int_literal_vars
236 }
237
238 /// Allocate a fresh type variable.
239 pub fn fresh_var(&mut self) -> TypeVarId {
240 self.type_vars.fresh()
241 }
242
243 /// Add a constraint.
244 pub fn add_constraint(&mut self, constraint: Constraint) {
245 self.constraints.push(constraint);
246 }
247
248 /// ADR-0076: when a call argument is a bare `Var(name)` referencing a
249 /// `Borrow`/`Inout`-mode parameter, and the callee expects `Ref(T)` /
250 /// `MutRef(T)` of the param's inner type, the arg's inferred type is
251 /// the inner `T` (the parameter has been normalized post-Phase-2). The
252 /// caller would have written `&name` / `&mut name` explicitly under
253 /// the old surface; per ADR-0076 the reference is implicit. Relax
254 /// the constraint target to `T` so the call type-checks.
255 fn relax_ref_param_for_implicit_forwarding(
256 &self,
257 arg_ref: InstRef,
258 param_ty: &InferType,
259 ctx: &ConstraintContext<'_>,
260 ) -> Option<InferType> {
261 let InferType::Concrete(param_concrete) = param_ty else {
262 return None;
263 };
264 let inner_ty = match param_concrete.kind() {
265 crate::types::TypeKind::Ref(id) => self.type_pool.ref_def(id),
266 crate::types::TypeKind::MutRef(id) => self.type_pool.mut_ref_def(id),
267 _ => return None,
268 };
269 let InstData::VarRef { name } = &self.rir.get(arg_ref).data else {
270 return None;
271 };
272 let p = ctx.params.get(name)?;
273 // The arg binding's normalized inner type must match the callee's
274 // referent. The mode must be `Ref` or `MutRef` (i.e. the binding
275 // came from an original `Ref(T)` / `MutRef(T)` declaration that
276 // routes through the legacy by-pointer mode for interface-typed
277 // params per ADR-0076).
278 if !matches!(
279 p.mode,
280 gruel_rir::RirParamMode::Ref | gruel_rir::RirParamMode::MutRef
281 ) {
282 return None;
283 }
284 // Convert the referent Type to InferType for the comparison —
285 // arrays are stored structurally as `InferType::Array { ... }`
286 // rather than `InferType::Concrete(Type::Array(...))`.
287 let inner_infer = self.type_to_infer(inner_ty);
288 if p.ty != inner_infer {
289 return None;
290 }
291 Some(inner_infer)
292 }
293
294 /// ADR-0076: auto-deref a `Ref(T)` / `MutRef(T)` value type to its
295 /// referent `T` for constraint purposes. Used at every site that
296 /// expects a "value" type (arithmetic, comparison, equality with a
297 /// callee param). Non-ref types pass through unchanged.
298 fn auto_deref(&self, ty: InferType) -> InferType {
299 match &ty {
300 InferType::Concrete(t) => match t.kind() {
301 crate::types::TypeKind::Ref(id) => InferType::Concrete(self.type_pool.ref_def(id)),
302 crate::types::TypeKind::MutRef(id) => {
303 InferType::Concrete(self.type_pool.mut_ref_def(id))
304 }
305 _ => ty,
306 },
307 _ => ty,
308 }
309 }
310
311 /// Convert a concrete `Type` to `InferType`, preserving the structural
312 /// form for arrays (mirrors `Sema::type_to_infer_type`).
313 fn type_to_infer(&self, ty: Type) -> InferType {
314 match ty.kind() {
315 crate::types::TypeKind::Array(array_id) => {
316 let (element_type, length) = self.type_pool.array_def(array_id);
317 let element_infer = self.type_to_infer(element_type);
318 InferType::Array {
319 element: Box::new(element_infer),
320 length,
321 }
322 }
323 crate::types::TypeKind::ComptimeInt => InferType::IntLiteral,
324 _ => InferType::Concrete(ty),
325 }
326 }
327
328 /// Record the type of an expression.
329 pub fn record_type(&mut self, inst_ref: InstRef, ty: InferType) {
330 self.expr_types.insert(inst_ref, ty);
331 }
332
333 /// Get the recorded type of an expression.
334 pub fn get_type(&self, inst_ref: InstRef) -> Option<&InferType> {
335 self.expr_types.get(&inst_ref)
336 }
337
338 /// Get all collected constraints.
339 pub fn constraints(&self) -> &[Constraint] {
340 &self.constraints
341 }
342
343 /// Take ownership of the collected constraints.
344 pub fn take_constraints(self) -> Vec<Constraint> {
345 self.constraints
346 }
347
348 /// Get the expression type mapping.
349 pub fn expr_types(&self) -> &HashMap<InstRef, InferType> {
350 &self.expr_types
351 }
352
353 /// Consume the constraint generator and return (constraints, int_literal_vars, float_literal_vars, expr_types, type_var_count).
354 ///
355 /// This is useful when you need ownership of the expression types map.
356 /// The `type_var_count` can be used to pre-size the unifier's substitution for better performance.
357 pub fn into_parts(self) -> ConstraintGeneratorParts {
358 (
359 self.constraints,
360 self.int_literal_vars,
361 self.float_literal_vars,
362 self.expr_types,
363 self.type_vars.count(),
364 )
365 }
366
367 /// Generate constraints for an expression.
368 ///
369 /// Returns the inferred type of the expression. Records the type in
370 /// `expr_types` and adds constraints to `constraints`.
371 pub fn generate(&mut self, inst_ref: InstRef, ctx: &mut ConstraintContext) -> ExprInfo {
372 let inst = self.rir.get(inst_ref);
373 let span = inst.span;
374
375 let ty = match &inst.data {
376 InstData::IntConst(_) => {
377 // Integer literals get a fresh type variable that we immediately
378 // bind to IntLiteral. This allows unification to track when the
379 // literal is constrained to a specific integer type.
380 //
381 // Example: `let x: i64 = 42` generates:
382 // - type_var(?0) for the literal 42
383 // - substitution: ?0 -> IntLiteral
384 // - constraint: Equal(Var(?0), Concrete(i64))
385 //
386 // During unification, Equal(IntLiteral, Concrete(i64)) succeeds
387 // and rebinds ?0 -> Concrete(i64) via rebind_int_literal_to_concrete.
388 let var = self.fresh_var();
389 self.int_literal_vars.push(var);
390 InferType::Var(var)
391 }
392
393 InstData::FloatConst(_) => {
394 // Float literals work like int literals but default to f64.
395 let var = self.fresh_var();
396 self.float_literal_vars.push(var);
397 InferType::Var(var)
398 }
399
400 InstData::BoolConst(_) => InferType::Concrete(Type::BOOL),
401
402 // ADR-0071: char literal — Unicode scalar value, type is char.
403 InstData::CharConst(_) => InferType::Concrete(Type::CHAR),
404
405 // String constants use the builtin String struct type.
406 InstData::StringConst(_) => {
407 // Look up the String type from the structs map
408 if let Some(string_spur) = self.interner.get("String") {
409 if let Some(&string_ty) = self.structs.get(&string_spur) {
410 InferType::Concrete(string_ty)
411 } else {
412 // Fallback if String struct not found (shouldn't happen after builtin injection)
413 InferType::Concrete(Type::ERROR)
414 }
415 } else {
416 InferType::Concrete(Type::ERROR)
417 }
418 }
419
420 InstData::UnitConst => InferType::Concrete(Type::UNIT),
421
422 InstData::Bin { op, lhs, rhs } => match op {
423 BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod => {
424 self.generate_binary_arith(*lhs, *rhs, ctx)
425 }
426 BinOp::BitAnd | BinOp::BitOr | BinOp::BitXor | BinOp::Shl | BinOp::Shr => {
427 self.generate_binary_bitwise(*lhs, *rhs, ctx)
428 }
429 BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge => {
430 let lhs_info = self.generate(*lhs, ctx);
431 let rhs_info = self.generate(*rhs, ctx);
432 self.add_constraint(Constraint::equal(lhs_info.ty, rhs_info.ty, span));
433 InferType::Concrete(Type::BOOL)
434 }
435 BinOp::And | BinOp::Or => {
436 let lhs_info = self.generate(*lhs, ctx);
437 let rhs_info = self.generate(*rhs, ctx);
438 self.add_constraint(Constraint::equal(
439 lhs_info.ty,
440 InferType::Concrete(Type::BOOL),
441 lhs_info.span,
442 ));
443 self.add_constraint(Constraint::equal(
444 rhs_info.ty,
445 InferType::Concrete(Type::BOOL),
446 rhs_info.span,
447 ));
448 InferType::Concrete(Type::BOOL)
449 }
450 },
451
452 InstData::Unary { op, operand } => match op {
453 UnaryOp::Neg => {
454 let operand_info = self.generate(*operand, ctx);
455 let result_ty = operand_info.ty.clone();
456 self.add_constraint(Constraint::is_signed(result_ty.clone(), span));
457 result_ty
458 }
459 UnaryOp::Not => {
460 let operand_info = self.generate(*operand, ctx);
461 self.add_constraint(Constraint::equal(
462 operand_info.ty,
463 InferType::Concrete(Type::BOOL),
464 operand_info.span,
465 ));
466 InferType::Concrete(Type::BOOL)
467 }
468 UnaryOp::BitNot => {
469 let operand_info = self.generate(*operand, ctx);
470 let result_ty = operand_info.ty.clone();
471 self.add_constraint(Constraint::is_integer(result_ty.clone(), span));
472 result_ty
473 }
474 },
475
476 // ADR-0062: `&x` / `&mut x` produces `Ref(T)` / `MutRef(T)`.
477 // The result type depends on the operand's resolved type, which
478 // inference may not have nailed down yet. Defer the construction
479 // of the actual `Ref`/`MutRef` type to sema (`analyze_inst`),
480 // and just propagate a fresh type variable that sema will set.
481 InstData::MakeRef { operand, .. } => {
482 let _ = self.generate(*operand, ctx);
483 InferType::Var(self.fresh_var())
484 }
485
486 // ADR-0064: `&arr[range]` / `&mut arr[range]` produces a slice.
487 // Defer the actual `Slice(T)` / `MutSlice(T)` type to sema; record
488 // the sub-expressions so they receive types.
489 InstData::MakeSlice { base, lo, hi, .. } => {
490 let _ = self.generate(*base, ctx);
491 if let Some(lo) = lo {
492 self.generate(*lo, ctx);
493 }
494 if let Some(hi) = hi {
495 self.generate(*hi, ctx);
496 }
497 InferType::Var(self.fresh_var())
498 }
499
500 // ADR-0064: a range subscript without `&` / `&mut`. Sema rejects.
501 InstData::BareRangeSubscript => InferType::Concrete(Type::ERROR),
502
503 // Variable reference
504 InstData::VarRef { name } => {
505 if let Some(local) = ctx.locals.get(name) {
506 local.ty.clone()
507 } else if let Some(param) = ctx.params.get(name) {
508 param.ty.clone()
509 } else {
510 // Unknown variable - will be caught during semantic analysis
511 InferType::Concrete(Type::ERROR)
512 }
513 }
514
515 // Parameter reference
516 InstData::ParamRef { name, .. } => {
517 if let Some(param) = ctx.params.get(name) {
518 param.ty.clone()
519 } else {
520 InferType::Concrete(Type::ERROR)
521 }
522 }
523
524 // Local variable allocation
525 InstData::Alloc {
526 directives_start: _,
527 directives_len: _,
528 name,
529 is_mut,
530 ty: type_annotation,
531 init,
532 } => {
533 let init_info = self.generate(*init, ctx);
534
535 let var_ty = if let Some(ty_sym) = type_annotation {
536 // Explicit type annotation - use it and constrain init to match
537 let ty_name = self.interner.resolve(ty_sym);
538 if let Some(annotated_ty) = self.resolve_type_name(ty_name) {
539 self.add_constraint(Constraint::equal(
540 init_info.ty,
541 annotated_ty.clone(),
542 span,
543 ));
544 annotated_ty
545 } else {
546 // Unknown type name (e.g., struct/enum) - use init type for now.
547 // Semantic analysis will catch undefined types and verify struct/enum
548 // field types match the definition.
549 init_info.ty
550 }
551 } else {
552 // No annotation - use the init expression's type
553 init_info.ty
554 };
555
556 // Record the variable in scope (if it has a name)
557 if let Some(var_name) = name {
558 ctx.insert_local(
559 *var_name,
560 LocalVarInfo {
561 ty: var_ty.clone(),
562 is_mut: *is_mut,
563 span,
564 },
565 );
566 }
567
568 // Alloc produces unit type
569 InferType::Concrete(Type::UNIT)
570 }
571
572 // Struct destructuring — register field bindings for type inference
573 InstData::StructDestructure {
574 type_name,
575 fields_start,
576 fields_len,
577 init,
578 } => {
579 self.generate(*init, ctx);
580
581 // Look up the struct type to get field types
582 if let Some(&struct_ty) = self.structs.get(type_name)
583 && let Some(struct_id) = struct_ty.as_struct()
584 {
585 let struct_def = self.type_pool.struct_def(struct_id);
586 let rir_fields = self.rir.get_destructure_fields(*fields_start, *fields_len);
587 for field in &rir_fields {
588 if field.is_wildcard {
589 continue;
590 }
591 let field_name = self.interner.resolve(&field.field_name);
592 if let Some((_, struct_field)) = struct_def.find_field(field_name) {
593 let binding_name = field.binding_name.unwrap_or(field.field_name);
594 ctx.insert_local(
595 binding_name,
596 LocalVarInfo {
597 ty: InferType::Concrete(struct_field.ty),
598 is_mut: field.is_mut,
599 span,
600 },
601 );
602 }
603 }
604 }
605
606 InferType::Concrete(Type::UNIT)
607 }
608
609 // Assignment
610 InstData::Assign { name, value } => {
611 let value_info = self.generate(*value, ctx);
612 // ADR-0076: a `MutRef(T)`-typed binding (param or local)
613 // makes bare-name assign a write-through; the value's
614 // type must match the referent `T`, not `MutRef(T)`. A
615 // `Ref(T)`-typed binding is a read-only ref — the
616 // through-write is rejected later by sema, but for
617 // constraint-generation symmetry we still constrain
618 // against the referent so the user gets a clean
619 // diagnostic from sema rather than a confused
620 // "literal out of range for `<ref>`".
621 let binding_ty = ctx
622 .locals
623 .get(name)
624 .map(|l| l.ty.clone())
625 .or_else(|| ctx.params.get(name).map(|p| p.ty.clone()));
626 if let Some(ty) = binding_ty {
627 let target_ty = self.auto_deref(ty);
628 // Both sides auto-deref so `a = b` where both bindings
629 // are `MutRef(T)` constrains `T ≈ T`, not `MutRef(T) ≈ T`.
630 let value_ty = self.auto_deref(value_info.ty);
631 self.add_constraint(Constraint::equal(value_ty, target_ty, span));
632 }
633 // Assignment produces unit
634 InferType::Concrete(Type::UNIT)
635 }
636
637 // Return statement
638 InstData::Ret(value) => {
639 if let Some(val_ref) = value {
640 let value_info = self.generate(*val_ref, ctx);
641 // Constrain return value to match function return type.
642 // ADR-0076: auto-deref so `return d` for a `Ref(T)` /
643 // `MutRef(T)` binding constrains against `T`. Sema
644 // separately rejects moving out of a borrow.
645 self.add_constraint(Constraint::equal(
646 self.auto_deref(value_info.ty),
647 InferType::Concrete(ctx.return_type),
648 span,
649 ));
650 } else {
651 // Return without value - function must return unit
652 self.add_constraint(Constraint::equal(
653 InferType::Concrete(Type::UNIT),
654 InferType::Concrete(ctx.return_type),
655 span,
656 ));
657 }
658 // Return diverges
659 InferType::Concrete(Type::NEVER)
660 }
661
662 // Function call
663 InstData::Call {
664 name,
665 args_start,
666 args_len,
667 } => {
668 let args = self.rir.get_call_args(*args_start, *args_len);
669 if let Some(func) = self.functions.get(name) {
670 // For generic functions, skip constraint generation for arguments.
671 // The types will be checked during specialization when we know
672 // the concrete type substitutions.
673 if func.is_generic {
674 // Process all arguments and build type substitution map
675 let mut type_subst: rustc_hash::FxHashMap<lasso::Spur, Type> =
676 rustc_hash::FxHashMap::default();
677
678 for (i, arg) in args.iter().enumerate() {
679 let arg_info = self.generate(arg.value, ctx);
680
681 // If this is a comptime parameter, extract the type for substitution.
682 if i < func.param_comptime.len() && func.param_comptime[i] {
683 let arg_inst = self.rir.get(arg.value);
684 // A bare comptime type-param identifier (e.g., `T`
685 // in `helper(Self, T, ...)`) parses as
686 // `Expr::Ident` and lowers to `VarRef`, not
687 // `TypeConst`. Its inferred type is the
688 // substituted concrete type (e.g., `i32`),
689 // which fails the `COMPTIME_TYPE` check below.
690 // Look it up in the outer `type_subst` so the
691 // callee's type parameter gets bound.
692 if let gruel_rir::InstData::VarRef { name } = &arg_inst.data
693 && let Some(subst) = self.type_subst
694 && let Some(&forwarded_ty) = subst.get(name)
695 && i < func.param_names.len()
696 {
697 type_subst.insert(func.param_names[i], forwarded_ty);
698 continue;
699 }
700 // The argument should be a TypeConst - extract the concrete type
701 if let InferType::Concrete(Type::COMPTIME_TYPE) = &arg_info.ty {
702 // This is a type value - get the actual type from the RIR
703 if let gruel_rir::InstData::TypeConst { type_name } =
704 &arg_inst.data
705 {
706 // Resolve `type_name` to a concrete Type.
707 // Checks (in order):
708 // 1. The outer method body's `type_subst`
709 // — covers `Self` and method-level
710 // `comptime T: type` parameters being
711 // forwarded to a callee.
712 // 2. Registered structs (`self.structs`)
713 // — covers user-named types passed
714 // directly: `helper(MyStruct)`.
715 // 3. A small primitive table — covers
716 // `i32`/`bool`/etc. parsed as
717 // `TypeLit` from primitive keywords,
718 // which never reach
719 // `register_type_names` so they
720 // aren't in `self.structs`.
721 let type_name_str = self.interner.resolve(type_name);
722 let concrete_ty = self
723 .type_subst
724 .and_then(|subst| subst.get(type_name).copied())
725 .or_else(|| self.structs.get(type_name).copied())
726 .unwrap_or(match type_name_str {
727 "i8" => Type::I8,
728 "i16" => Type::I16,
729 "i32" => Type::I32,
730 "i64" => Type::I64,
731 "isize" => Type::ISIZE,
732 "u8" => Type::U8,
733 "u16" => Type::U16,
734 "u32" => Type::U32,
735 "u64" => Type::U64,
736 "usize" => Type::USIZE,
737 "f16" => Type::F16,
738 "f32" => Type::F32,
739 "f64" => Type::F64,
740 "bool" => Type::BOOL,
741 "char" => Type::CHAR,
742 "()" => Type::UNIT,
743 _ => Type::ERROR,
744 });
745 if i < func.param_names.len() {
746 type_subst.insert(func.param_names[i], concrete_ty);
747 }
748 }
749 }
750 }
751 }
752
753 // Compute the actual return type by substituting type parameters
754
755 if func.return_type == InferType::Concrete(Type::COMPTIME_TYPE) {
756 // Return type position references at least one
757 // type parameter (declared as `Type::COMPTIME_TYPE`
758 // in the sig — see `references_type_param` in
759 // declarations.rs). Two shapes:
760 // 1. Bare type-param: `-> T` → look up
761 // `return_type_sym` ("T") in `type_subst`.
762 // 2. Compound: `-> Ptr(T)` / `-> MutRef(Vec(T))`
763 // → recursively resolve via the local subst
764 // so the inner `T` gets bound to the concrete
765 // type just gathered from the call's args.
766 let sym_str = self.interner.resolve(&func.return_type_sym);
767 if let Some(&concrete_ty) = type_subst.get(&func.return_type_sym) {
768 InferType::Concrete(concrete_ty)
769 } else if let Some(resolved) =
770 self.resolve_type_with_local_subst(sym_str, &type_subst)
771 {
772 resolved
773 } else {
774 func.return_type.clone()
775 }
776 } else {
777 func.return_type.clone()
778 }
779 } else if args.len() != func.param_types.len() {
780 // Check argument count matches parameter count.
781 // Semantic analysis will emit a proper error; we just need to avoid
782 // panicking and process what we can.
783 // Still process all arguments to catch type errors within them
784 for arg in args.iter() {
785 self.generate(arg.value, ctx);
786 }
787 // Return the declared return type (error will be caught in sema)
788 func.return_type.clone()
789 } else {
790 // Generate constraints for each argument.
791 // ADR-0056: skip the equality constraint when the
792 // parameter type is an interface — sema applies
793 // structural conformance (not equality) and inserts
794 // a `MakeInterfaceRef` coercion at the call site.
795 for (arg, param_ty) in args.iter().zip(func.param_types.iter()) {
796 let arg_info = self.generate(arg.value, ctx);
797 let is_iface = matches!(
798 param_ty,
799 InferType::Concrete(t) if matches!(
800 t.kind(),
801 crate::types::TypeKind::Interface(_)
802 )
803 );
804 if !is_iface {
805 // ADR-0076: implicit forwarding. If the
806 // callee expects `Ref(T)` / `MutRef(T)` and
807 // the arg is a bare reference to a
808 // Borrow/Inout-mode parameter, the arg's
809 // inferred type is the inner `T`. Relax
810 // the constraint to be against the inner
811 // referent type so the call type-checks.
812 let constraint_target = self
813 .relax_ref_param_for_implicit_forwarding(
814 arg.value, param_ty, ctx,
815 )
816 .unwrap_or_else(|| param_ty.clone());
817 self.add_constraint(Constraint::equal(
818 arg_info.ty,
819 constraint_target,
820 arg_info.span,
821 ));
822 }
823 }
824 func.return_type.clone()
825 }
826 } else {
827 // ADR-0082: when a `Call` targets a method on an
828 // anonymous struct (the dispatch flip emits these
829 // for `Vec(I32)::new()`, `v.push(x)`, etc.), the
830 // mangled name `{struct_name}::{method}` or
831 // `{struct_name}.{method}` won't be in `functions`.
832 // Parse it and look up `method_sigs` so the literal
833 // arg constraints are emitted, otherwise int literals
834 // default to `i32` and codegen sees i32-vs-usize
835 // mismatches at the call boundary.
836 let mangled = self.interner.resolve(name).to_string();
837 let split = mangled
838 .rfind("::")
839 .map(|i| (&mangled[..i], &mangled[i + 2..]))
840 .or_else(|| {
841 mangled
842 .rfind('.')
843 .map(|i| (&mangled[..i], &mangled[i + 1..]))
844 });
845 // Resolve the struct via the live type pool — anonymous
846 // structs created post-`build_inference_context` (which
847 // is when `populate_vec_instance` builds Vec(I32)
848 // instances) aren't in `self.structs` yet.
849 let struct_id_opt: Option<crate::types::StructId> = split
850 .and_then(|(struct_name, _)| self.interner.get(struct_name))
851 .and_then(|s| self.type_pool.get_struct_by_name(s))
852 .and_then(|i| i.pool_index())
853 .map(crate::types::StructId::from_pool_index);
854 let method_sym_opt =
855 split.and_then(|(_, method_name)| self.interner.get(method_name));
856 if let (Some(struct_id), Some(method_sym)) = (struct_id_opt, method_sym_opt)
857 && let Some(method) = self.methods.get(&(struct_id, method_sym))
858 {
859 // Check arg/param-count parity; constrain each
860 // arg to its expected param type. Mirrors the
861 // top-level fn-call logic above.
862 let m_param_types = method.param_types.clone();
863 if args.len() != m_param_types.len() {
864 for arg in args.iter() {
865 self.generate(arg.value, ctx);
866 }
867 } else {
868 for (arg, param_ty) in args.iter().zip(m_param_types.iter()) {
869 let arg_info = self.generate(arg.value, ctx);
870 let is_iface = matches!(
871 param_ty,
872 InferType::Concrete(t) if matches!(
873 t.kind(),
874 crate::types::TypeKind::Interface(_)
875 )
876 );
877 if !is_iface {
878 self.add_constraint(Constraint::equal(
879 arg_info.ty,
880 param_ty.clone(),
881 arg_info.span,
882 ));
883 }
884 }
885 }
886 method.return_type.clone()
887 } else {
888 // Unknown function - still process arguments for constraint generation
889 for arg in args.iter() {
890 self.generate(arg.value, ctx);
891 }
892 InferType::Concrete(Type::ERROR)
893 }
894 }
895 }
896
897 // Intrinsic call
898 InstData::Intrinsic {
899 name,
900 args_start,
901 args_len,
902 } => {
903 let intrinsic_name = self.interner.resolve(name);
904 let id = lookup_by_name(intrinsic_name).map(|d| d.id);
905 // Collect arg InstRefs so we can iterate without holding a
906 // borrow on self.rir across the dispatch match.
907 let arg_refs: Vec<InstRef> =
908 self.rir.get_inst_refs(*args_start, *args_len).to_vec();
909
910 // Visit args in a side-effectful pass so constraints on them
911 // are emitted regardless of which intrinsic we hit below.
912 let visit_args = |this: &mut Self, ctx: &mut ConstraintContext| {
913 for &arg_ref in arg_refs.iter() {
914 this.generate(arg_ref, ctx);
915 }
916 };
917
918 match id {
919 Some(IntrinsicId::Cast) => {
920 if let Some(&first) = arg_refs.first() {
921 let _ = self.generate(first, ctx);
922 }
923 InferType::Var(self.fresh_var())
924 }
925 // ADR-0087 Phase 3: @read_line / @parse_* / @random_*
926 // intrinsics retired in favour of prelude fns. Their
927 // inference arms went with them.
928 Some(IntrinsicId::Syscall) => {
929 visit_args(self, ctx);
930 InferType::Concrete(Type::I64)
931 }
932 Some(IntrinsicId::PtrToInt) => {
933 visit_args(self, ctx);
934 InferType::Concrete(Type::U64)
935 }
936 Some(IntrinsicId::PtrWrite) | Some(IntrinsicId::PtrWriteVolatile) => {
937 visit_args(self, ctx);
938 InferType::Concrete(Type::UNIT)
939 }
940 Some(IntrinsicId::IsNull) => {
941 visit_args(self, ctx);
942 InferType::Concrete(Type::BOOL)
943 }
944 Some(IntrinsicId::PtrRead) | Some(IntrinsicId::PtrReadVolatile) => {
945 // Return type depends on pointee type of the argument —
946 // resolved in sema once the concrete pointer type is known.
947 visit_args(self, ctx);
948 InferType::Var(self.fresh_var())
949 }
950 Some(IntrinsicId::PtrOffset) => {
951 // Return type matches the input pointer type.
952 visit_args(self, ctx);
953 InferType::Var(self.fresh_var())
954 }
955 Some(IntrinsicId::Raw) | Some(IntrinsicId::RawMut) => {
956 // Returns ptr const T / ptr mut T — resolved in sema.
957 visit_args(self, ctx);
958 InferType::Var(self.fresh_var())
959 }
960 Some(IntrinsicId::IntToPtr) | Some(IntrinsicId::NullPtr) => {
961 // Pointer type inferred from context.
962 visit_args(self, ctx);
963 InferType::Var(self.fresh_var())
964 }
965 Some(IntrinsicId::PtrCopy) => {
966 visit_args(self, ctx);
967 InferType::Concrete(Type::UNIT)
968 }
969 Some(IntrinsicId::TargetArch) => {
970 if let Some(arch_spur) = self.interner.get("Arch") {
971 if let Some(&arch_ty) = self.enums.get(&arch_spur) {
972 InferType::Concrete(arch_ty)
973 } else {
974 InferType::Concrete(Type::ERROR)
975 }
976 } else {
977 InferType::Concrete(Type::ERROR)
978 }
979 }
980 Some(IntrinsicId::TargetOs) => {
981 if let Some(os_spur) = self.interner.get("Os") {
982 if let Some(&os_ty) = self.enums.get(&os_spur) {
983 InferType::Concrete(os_ty)
984 } else {
985 InferType::Concrete(Type::ERROR)
986 }
987 } else {
988 InferType::Concrete(Type::ERROR)
989 }
990 }
991 Some(IntrinsicId::Range) => {
992 // @range: 1-3 integer args; returns the same integer type
993 // (used as an iterable in for-in loops).
994 if let Some((&first_ref, rest)) = arg_refs.split_first() {
995 let first = self.generate(first_ref, ctx);
996 for &arg_ref in rest {
997 let arg_info = self.generate(arg_ref, ctx);
998 self.add_constraint(Constraint::equal(
999 first.ty.clone(),
1000 arg_info.ty,
1001 span,
1002 ));
1003 }
1004 first.ty
1005 } else {
1006 InferType::Concrete(Type::ERROR)
1007 }
1008 }
1009 Some(IntrinsicId::Field) => {
1010 // Return type depends on which field is accessed — fresh var.
1011 visit_args(self, ctx);
1012 InferType::Var(self.fresh_var())
1013 }
1014 // ADR-0064: slice intrinsics. Sema produces the actual
1015 // type once it has the receiver/argument types resolved;
1016 // here we just emit a fresh variable.
1017 Some(IntrinsicId::SliceLen) => {
1018 visit_args(self, ctx);
1019 InferType::Concrete(Type::USIZE)
1020 }
1021 Some(IntrinsicId::SliceIsEmpty) => {
1022 visit_args(self, ctx);
1023 InferType::Concrete(Type::BOOL)
1024 }
1025 Some(IntrinsicId::SliceIndexRead) => {
1026 visit_args(self, ctx);
1027 InferType::Var(self.fresh_var())
1028 }
1029 Some(IntrinsicId::SliceIndexWrite) => {
1030 visit_args(self, ctx);
1031 InferType::Concrete(Type::UNIT)
1032 }
1033 Some(IntrinsicId::SlicePtr)
1034 | Some(IntrinsicId::SlicePtrMut)
1035 | Some(IntrinsicId::PartsToSlice)
1036 | Some(IntrinsicId::PartsToMutSlice) => {
1037 visit_args(self, ctx);
1038 InferType::Var(self.fresh_var())
1039 }
1040 Some(IntrinsicId::EmbedFile) => {
1041 // `@embed_file("path")` always produces a `Slice(u8)`,
1042 // independent of context. Visiting args is a no-op
1043 // here (string literal) but kept for consistency.
1044 visit_args(self, ctx);
1045 let slice_id = self.type_pool.intern_slice_from_type(Type::U8);
1046 InferType::Concrete(Type::new_slice(slice_id))
1047 }
1048 Some(IntrinsicId::Panic) | Some(IntrinsicId::CompileError) => {
1049 // Diverging intrinsics return Never so they unify with any
1050 // expected type (e.g. `if c { 42 } else { @panic("..") }`).
1051 visit_args(self, ctx);
1052 InferType::Concrete(Type::NEVER)
1053 }
1054 // ADR-0087 Phase 3: @utf8_validate retired — see prelude
1055 // `utf8_validate(s)` fn.
1056 Some(IntrinsicId::CStrToVec) => {
1057 // ADR-0072: returns Vec(u8).
1058 visit_args(self, ctx);
1059 let vec_id = self.type_pool.intern_vec_from_type(Type::U8);
1060 InferType::Concrete(Type::new_vec(vec_id))
1061 }
1062 // ADR-0087 Phase 4: @alloc / @realloc / @free retired —
1063 // see prelude `mem_alloc` / `mem_realloc` / `mem_free`
1064 // fns. `@ptr_cast` remains and still infers its result
1065 // from the binding context.
1066 Some(IntrinsicId::PtrCast) => {
1067 visit_args(self, ctx);
1068 InferType::Var(self.fresh_var())
1069 }
1070 // ADR-0087 Phase 3: @bytes_eq retired — see prelude
1071 // `bytes_eq(a, b, n)` fn.
1072 // ADR-0079 Phase 2b: `@field_set` is a unit-yielding side
1073 // effect on an uninit handle. `@finalize` returns the
1074 // host type — but at HM time we don't yet know the
1075 // handle's target type, so a fresh var lets sema
1076 // resolve it from the side-table. `@variant_field`
1077 // similarly resolves only with the handle's variant.
1078 Some(IntrinsicId::FieldSet) => {
1079 visit_args(self, ctx);
1080 InferType::Concrete(Type::UNIT)
1081 }
1082 Some(IntrinsicId::Finalize) | Some(IntrinsicId::VariantField) => {
1083 visit_args(self, ctx);
1084 InferType::Var(self.fresh_var())
1085 }
1086 // `@variant_uninit(T, tag)` and `@uninit(T)` only
1087 // appear in `let h = …` position, where sema captures
1088 // them via the side-table; the `Alloc` itself yields
1089 // unit. Use a fresh var so escapes (currently
1090 // diagnosed in sema) don't get a phantom unit
1091 // constraint that confuses error messages.
1092 Some(IntrinsicId::VariantUninit) => {
1093 visit_args(self, ctx);
1094 InferType::Var(self.fresh_var())
1095 }
1096 // ADR-0084: @spawn returns a JoinHandle(R) whose
1097 // R is the spawned function's return type. Sema's
1098 // analyze_spawn_intrinsic instantiates the
1099 // parameterized type and writes the concrete
1100 // result into AIR; the inference layer just
1101 // hands back a fresh var so HM doesn't constrain
1102 // it prematurely. (Mirrors the @uninit pattern.)
1103 Some(IntrinsicId::Spawn) => {
1104 visit_args(self, ctx);
1105 InferType::Var(self.fresh_var())
1106 }
1107 // ADR-0084: @thread_join's result type comes from
1108 // the binding context (let-annotation or function
1109 // return). Same pattern as @cast / @alloc — emit
1110 // a fresh var so HM unifies it with the user's
1111 // annotation.
1112 Some(IntrinsicId::ThreadJoin) => {
1113 visit_args(self, ctx);
1114 InferType::Var(self.fresh_var())
1115 }
1116 // Other intrinsics (@dbg, @assert, @test_preview_gate, @import)
1117 // and any unknown name return Unit. Sema handles the unknown case
1118 // with a proper diagnostic; we just pick a coherent type here.
1119 _ => {
1120 visit_args(self, ctx);
1121 InferType::Concrete(Type::UNIT)
1122 }
1123 }
1124 }
1125
1126 // Type intrinsic (@size_of, @align_of, @type_name, @type_info, @ownership)
1127 InstData::TypeIntrinsic { name, type_arg: _ } => {
1128 let intrinsic_name = self.interner.resolve(name);
1129 match lookup_by_name(intrinsic_name).map(|d| d.id) {
1130 Some(IntrinsicId::TypeName) => InferType::Concrete(Type::COMPTIME_STR),
1131 Some(IntrinsicId::TypeInfo) => {
1132 // @type_info returns a comptime struct — use a fresh var
1133 // since the actual type is determined by the comptime evaluator.
1134 InferType::Var(self.fresh_var())
1135 }
1136 Some(IntrinsicId::SizeOf) | Some(IntrinsicId::AlignOf) => {
1137 // @size_of / @align_of return `usize` (ADR-0054).
1138 InferType::Concrete(Type::USIZE)
1139 }
1140 Some(IntrinsicId::Ownership) => {
1141 // @ownership returns the built-in `Ownership` enum.
1142 if let Some(ownership_spur) = self.interner.get("Ownership") {
1143 if let Some(&ownership_ty) = self.enums.get(&ownership_spur) {
1144 InferType::Concrete(ownership_ty)
1145 } else {
1146 InferType::Concrete(Type::ERROR)
1147 }
1148 } else {
1149 InferType::Concrete(Type::ERROR)
1150 }
1151 }
1152 Some(IntrinsicId::ThreadSafety) => {
1153 // ADR-0084: @thread_safety returns the prelude
1154 // `ThreadSafety` enum. Same lookup shape as
1155 // @ownership.
1156 if let Some(spur) = self.interner.get("ThreadSafety") {
1157 if let Some(&ty) = self.enums.get(&spur) {
1158 InferType::Concrete(ty)
1159 } else {
1160 InferType::Concrete(Type::ERROR)
1161 }
1162 } else {
1163 InferType::Concrete(Type::ERROR)
1164 }
1165 }
1166 // ADR-0079 Phase 2b: `@uninit(T)` only appears in
1167 // `let h = …` position, where sema captures it via the
1168 // side-table. Use a fresh var so the alloc's
1169 // surrounding context resolves coherently.
1170 Some(IntrinsicId::Uninit) => InferType::Var(self.fresh_var()),
1171 // Fallback for unknown names.
1172 _ => InferType::Concrete(Type::I32),
1173 }
1174 }
1175
1176 // Type+interface intrinsic (@implements)
1177 InstData::TypeInterfaceIntrinsic { name, .. } => {
1178 let intrinsic_name = self.interner.resolve(name);
1179 match lookup_by_name(intrinsic_name).map(|d| d.id) {
1180 Some(IntrinsicId::Implements) => InferType::Concrete(Type::BOOL),
1181 _ => InferType::Concrete(Type::ERROR),
1182 }
1183 }
1184
1185 // Block
1186 InstData::Block { extra_start, len } => {
1187 ctx.push_scope();
1188 let mut last_ty = InferType::Concrete(Type::UNIT);
1189 let block_insts = self.rir.get_extra(*extra_start, *len);
1190 for &inst_raw in block_insts {
1191 let block_inst_ref = InstRef::from_raw(inst_raw);
1192 let info = self.generate(block_inst_ref, ctx);
1193 last_ty = info.ty;
1194 }
1195 ctx.pop_scope();
1196 last_ty
1197 }
1198
1199 // Branch (if/else). ADR-0079 follow-up: HM walks both
1200 // branches even for `comptime if`, since we don't yet
1201 // know the comptime-cond value here. Branches in our
1202 // use case (struct vs enum derive bodies) are
1203 // HM-permissive (uninit/finalize/field_set return fresh
1204 // vars), so this doesn't constrain. Sema does the
1205 // actual branch elision when it has the resolved type.
1206 InstData::Branch {
1207 cond,
1208 then_block,
1209 else_block,
1210 is_comptime: _,
1211 } => {
1212 let cond_info = self.generate(*cond, ctx);
1213 self.add_constraint(Constraint::equal(
1214 cond_info.ty,
1215 InferType::Concrete(Type::BOOL),
1216 cond_info.span,
1217 ));
1218
1219 let then_info = self.generate(*then_block, ctx);
1220
1221 if let Some(else_ref) = else_block {
1222 let else_info = self.generate(*else_ref, ctx);
1223
1224 // Handle Never type coercion:
1225 // - If one branch is Never, the if-else takes the other branch's type
1226 // - If both are Never, the result is Never
1227 // - Otherwise, both must unify to the same type
1228 let then_is_never = matches!(&then_info.ty, InferType::Concrete(Type::NEVER));
1229 let else_is_never = matches!(&else_info.ty, InferType::Concrete(Type::NEVER));
1230
1231 match (then_is_never, else_is_never) {
1232 (true, true) => {
1233 // Both diverge - result is Never
1234 InferType::Concrete(Type::NEVER)
1235 }
1236 (true, false) => {
1237 // Then diverges - result is else type
1238 else_info.ty
1239 }
1240 (false, true) => {
1241 // Else diverges - result is then type
1242 then_info.ty
1243 }
1244 (false, false) => {
1245 // Neither diverges - both must have the same type
1246 let result_var = self.fresh_var();
1247 let result_ty = InferType::Var(result_var);
1248 self.add_constraint(Constraint::equal(
1249 then_info.ty,
1250 result_ty.clone(),
1251 then_info.span,
1252 ));
1253 self.add_constraint(Constraint::equal(
1254 else_info.ty,
1255 result_ty.clone(),
1256 else_info.span,
1257 ));
1258 result_ty
1259 }
1260 }
1261 } else {
1262 // No else branch - the if expression has unit type
1263 // (or the then branch type if it's unit-compatible)
1264 InferType::Concrete(Type::UNIT)
1265 }
1266 }
1267
1268 // While loop
1269 InstData::Loop { cond, body } => {
1270 let cond_info = self.generate(*cond, ctx);
1271 self.add_constraint(Constraint::equal(
1272 cond_info.ty,
1273 InferType::Concrete(Type::BOOL),
1274 cond_info.span,
1275 ));
1276
1277 ctx.loop_depth += 1;
1278 self.generate(*body, ctx);
1279 ctx.loop_depth -= 1;
1280
1281 // Loops produce unit
1282 InferType::Concrete(Type::UNIT)
1283 }
1284
1285 // For-in loop (desugared to while in sema, but inference still sees it)
1286 InstData::For {
1287 binding,
1288 is_mut,
1289 iterable,
1290 body,
1291 } => {
1292 // Generate constraints for the iterable to determine the element type
1293 let iterable_info = self.generate(*iterable, ctx);
1294
1295 // Determine the binding type from the iterable:
1296 // - For @range: the iterable returns the integer type directly
1297 // - For arrays: extract the element type from InferType::Array
1298 // - For slices (ADR-0064): extract the element from
1299 // `Slice(T)` / `MutSlice(T)`
1300 let binding_ty = match &iterable_info.ty {
1301 InferType::Array { element, .. } => *element.clone(),
1302 InferType::Concrete(t) => match t.kind() {
1303 TypeKind::Slice(id) => InferType::Concrete(self.type_pool.slice_def(id)),
1304 TypeKind::MutSlice(id) => {
1305 InferType::Concrete(self.type_pool.mut_slice_def(id))
1306 }
1307 TypeKind::Vec(id) => InferType::Concrete(self.type_pool.vec_def(id)),
1308 _ => iterable_info.ty.clone(),
1309 },
1310 other => other.clone(),
1311 };
1312
1313 // Register the binding so the body can reference it
1314 ctx.insert_local(
1315 *binding,
1316 LocalVarInfo {
1317 ty: binding_ty,
1318 is_mut: *is_mut,
1319 span,
1320 },
1321 );
1322
1323 ctx.loop_depth += 1;
1324 self.generate(*body, ctx);
1325 ctx.loop_depth -= 1;
1326
1327 // For loops produce unit
1328 InferType::Concrete(Type::UNIT)
1329 }
1330
1331 // Infinite loop
1332 InstData::InfiniteLoop { body } => {
1333 ctx.loop_depth += 1;
1334 self.generate(*body, ctx);
1335 ctx.loop_depth -= 1;
1336
1337 // Infinite loop without break never returns
1338 InferType::Concrete(Type::NEVER)
1339 }
1340
1341 // Break/Continue
1342 InstData::Break | InstData::Continue => InferType::Concrete(Type::NEVER),
1343
1344 // Match expression
1345 InstData::Match {
1346 scrutinee,
1347 arms_start,
1348 arms_len,
1349 } => {
1350 let scrutinee_info = self.generate(*scrutinee, ctx);
1351 let arms = self.rir.get_match_arms(*arms_start, *arms_len);
1352
1353 // Collect arm types, handling Never coercion
1354 let mut arm_types: Vec<ExprInfo> = Vec::new();
1355 for (pattern, body) in arms.iter() {
1356 // Patterns constrain the scrutinee type
1357 let pattern_ty = self.pattern_type(pattern);
1358 self.add_constraint(Constraint::equal(
1359 scrutinee_info.ty.clone(),
1360 pattern_ty,
1361 pattern.span(),
1362 ));
1363
1364 // For DataVariant/StructVariant patterns, add bound variables to scope before
1365 // generating body constraints, so VarRef lookups resolve correctly.
1366 let bindings_to_remove = match pattern {
1367 gruel_rir::RirPattern::DataVariant {
1368 type_name,
1369 variant,
1370 bindings,
1371 ..
1372 } => {
1373 let mut added_bindings = Vec::new();
1374 if let Some(&enum_ty) = self.enums.get(type_name)
1375 && let Some(enum_id) = enum_ty.as_enum()
1376 {
1377 let enum_def = self.type_pool.enum_def(enum_id);
1378 let variant_name = self.interner.resolve(variant);
1379 if let Some(variant_idx) = enum_def.find_variant(variant_name) {
1380 let field_types = &enum_def.variants[variant_idx].fields;
1381 for (i, binding) in bindings.iter().enumerate() {
1382 let field_ty = if i < field_types.len() {
1383 InferType::Concrete(field_types[i])
1384 } else {
1385 InferType::Concrete(Type::ERROR)
1386 };
1387 self.register_binding(
1388 binding,
1389 field_ty,
1390 pattern.span(),
1391 ctx,
1392 &mut added_bindings,
1393 );
1394 }
1395 }
1396 } else {
1397 // Enum not found — likely a comptime type variable.
1398 // Register bindings with fresh type variables so body
1399 // constraint generation can still resolve variable references.
1400 for binding in bindings.iter() {
1401 let var = self.fresh_var();
1402 let ty = InferType::Var(var);
1403 self.register_binding(
1404 binding,
1405 ty,
1406 pattern.span(),
1407 ctx,
1408 &mut added_bindings,
1409 );
1410 }
1411 }
1412 added_bindings
1413 }
1414 gruel_rir::RirPattern::StructVariant {
1415 type_name,
1416 variant,
1417 field_bindings,
1418 ..
1419 } => {
1420 let mut added_bindings = Vec::new();
1421 if let Some(&enum_ty) = self.enums.get(type_name)
1422 && let Some(enum_id) = enum_ty.as_enum()
1423 {
1424 let enum_def = self.type_pool.enum_def(enum_id);
1425 let variant_name = self.interner.resolve(variant);
1426 if let Some(variant_idx) = enum_def.find_variant(variant_name) {
1427 let variant_def = &enum_def.variants[variant_idx];
1428 for fb in field_bindings {
1429 let field_name = self.interner.resolve(&fb.field_name);
1430 let field_ty =
1431 if let Some(idx) = variant_def.find_field(field_name) {
1432 InferType::Concrete(variant_def.fields[idx])
1433 } else {
1434 InferType::Concrete(Type::ERROR)
1435 };
1436 self.register_binding(
1437 &fb.binding,
1438 field_ty,
1439 pattern.span(),
1440 ctx,
1441 &mut added_bindings,
1442 );
1443 }
1444 }
1445 } else {
1446 // Enum not found — likely a comptime type variable.
1447 // Register bindings with fresh type variables.
1448 for fb in field_bindings {
1449 let var = self.fresh_var();
1450 let ty = InferType::Var(var);
1451 self.register_binding(
1452 &fb.binding,
1453 ty,
1454 pattern.span(),
1455 ctx,
1456 &mut added_bindings,
1457 );
1458 }
1459 }
1460 added_bindings
1461 }
1462 // ADR-0051 Phase 4c: register Ident-leaf bindings for
1463 // Tuple / Struct / Ident arm roots. We walk the
1464 // pattern tree recursively, pulling field types from
1465 // the scrutinee's concrete struct definition when
1466 // available; unknown types get fresh variables so
1467 // body constraint generation still finds the binding.
1468 gruel_rir::RirPattern::Ident { .. }
1469 | gruel_rir::RirPattern::Tuple { .. }
1470 | gruel_rir::RirPattern::Struct { .. } => {
1471 let mut added_bindings = Vec::new();
1472 self.collect_recursive_pattern_bindings(
1473 pattern,
1474 scrutinee_info.ty.clone(),
1475 ctx,
1476 &mut added_bindings,
1477 );
1478 added_bindings
1479 }
1480 _ => Vec::new(),
1481 };
1482
1483 // Generate body and collect its type
1484 let body_info = self.generate(*body, ctx);
1485 arm_types.push(body_info);
1486
1487 // Remove DataVariant bindings from scope after body generation
1488 for (name, old_val) in bindings_to_remove {
1489 match old_val {
1490 Some(prev) => {
1491 ctx.locals.insert(name, prev);
1492 }
1493 None => {
1494 ctx.locals.remove(&name);
1495 }
1496 }
1497 }
1498 }
1499
1500 // Handle Never type coercion:
1501 // Filter out Never arms and use the remaining non-Never types
1502 let non_never_arms: Vec<_> = arm_types
1503 .iter()
1504 .filter(|info| !matches!(&info.ty, InferType::Concrete(Type::NEVER)))
1505 .collect();
1506
1507 if non_never_arms.is_empty() {
1508 // All arms diverge - result is Never
1509 InferType::Concrete(Type::NEVER)
1510 } else {
1511 // Create constraints for non-Never arms to have the same type
1512 let result_var = self.fresh_var();
1513 let result_ty = InferType::Var(result_var);
1514 for arm_info in non_never_arms {
1515 self.add_constraint(Constraint::equal(
1516 arm_info.ty.clone(),
1517 result_ty.clone(),
1518 arm_info.span,
1519 ));
1520 }
1521 result_ty
1522 }
1523 }
1524
1525 // Struct initialization
1526 InstData::StructInit {
1527 type_name,
1528 fields_start,
1529 fields_len,
1530 ..
1531 } => {
1532 // Check type_subst first (for Self and type parameters in method bodies)
1533 let struct_ty = self
1534 .type_subst
1535 .and_then(|subst| subst.get(type_name).copied())
1536 .or_else(|| self.structs.get(type_name).copied());
1537
1538 if let Some(struct_ty) = struct_ty {
1539 let fields = self.rir.get_field_inits(*fields_start, *fields_len);
1540 // Generate constraints for each field
1541 for (_, value_ref) in fields.iter() {
1542 self.generate(*value_ref, ctx);
1543 }
1544 InferType::Concrete(struct_ty)
1545 } else {
1546 InferType::Concrete(Type::ERROR)
1547 }
1548 }
1549
1550 // Field access
1551 InstData::FieldGet { base, field } => {
1552 // Generate constraints for the base expression (needed for nested field access)
1553 let base_info = self.generate(*base, ctx);
1554 // ADR-0082: when the base has a concrete struct type
1555 // (whether normal or after auto-deref through Ref/MutRef
1556 // — `self: Ref(Self)` is the load-bearing case for vec
1557 // methods like `is_empty` whose body is `self.len == 0`),
1558 // look up the field's declared type so the integer
1559 // literal on the other side of the comparison is pinned
1560 // to it. Without this, FieldGet returns a fresh var,
1561 // the literal defaults to i32, and codegen sees an
1562 // i64-vs-i32 mismatch when the field is `usize`.
1563 let mut field_ty: Option<crate::types::Type> = None;
1564 if let InferType::Concrete(base_ty) = &base_info.ty {
1565 let resolved = match base_ty.kind() {
1566 crate::types::TypeKind::Struct(_) => Some(*base_ty),
1567 crate::types::TypeKind::Ref(id) => Some(self.type_pool.ref_def(id)),
1568 crate::types::TypeKind::MutRef(id) => Some(self.type_pool.mut_ref_def(id)),
1569 _ => None,
1570 };
1571 if let Some(struct_ty) = resolved
1572 && let Some(struct_id) = struct_ty.as_struct()
1573 {
1574 let struct_def = self.type_pool.struct_def(struct_id);
1575 let field_name = self.interner.resolve(field);
1576 if let Some((_, sf)) = struct_def.find_field(field_name) {
1577 field_ty = Some(sf.ty);
1578 }
1579 }
1580 }
1581 if let Some(ty) = field_ty {
1582 InferType::Concrete(ty)
1583 } else {
1584 // Fall back to fresh var if we don't yet know the
1585 // base's type (sema fixes this up post-HM).
1586 let result_var = self.fresh_var();
1587 InferType::Var(result_var)
1588 }
1589 }
1590
1591 // Field assignment
1592 InstData::FieldSet {
1593 base,
1594 field: _,
1595 value,
1596 } => {
1597 self.generate(*base, ctx);
1598 self.generate(*value, ctx);
1599 InferType::Concrete(Type::UNIT)
1600 }
1601
1602 // Enum variant (unit or path)
1603 InstData::EnumVariant { type_name, .. } => {
1604 if let Some(&enum_ty) = self.enums.get(type_name) {
1605 InferType::Concrete(enum_ty)
1606 } else {
1607 // May be a comptime type variable — use fresh var
1608 let var = self.fresh_var();
1609 InferType::Var(var)
1610 }
1611 }
1612
1613 // Enum struct variant construction
1614 InstData::EnumStructVariant {
1615 type_name,
1616 fields_start,
1617 fields_len,
1618 ..
1619 } => {
1620 // Generate constraints for field value expressions
1621 let fields = self.rir.get_field_inits(*fields_start, *fields_len);
1622 for (_, value_ref) in fields.iter() {
1623 self.generate(*value_ref, ctx);
1624 }
1625 if let Some(&enum_ty) = self.enums.get(type_name) {
1626 InferType::Concrete(enum_ty)
1627 } else {
1628 // May be a comptime type variable — use fresh var
1629 let var = self.fresh_var();
1630 InferType::Var(var)
1631 }
1632 }
1633
1634 // Array initialization
1635 InstData::ArrayInit {
1636 elems_start,
1637 elems_len,
1638 } => {
1639 let elements = self.rir.get_inst_refs(*elems_start, *elems_len);
1640 if elements.is_empty() {
1641 // Empty array - need type annotation to know element type
1642 // Use a fresh type variable for the element type
1643 let elem_var = self.fresh_var();
1644 InferType::Array {
1645 element: Box::new(InferType::Var(elem_var)),
1646 length: 0,
1647 }
1648 } else {
1649 // Get element type from first element, constrain rest to match
1650 let first_info = self.generate(elements[0], ctx);
1651 for elem_ref in elements.iter().skip(1) {
1652 let elem_info = self.generate(*elem_ref, ctx);
1653 self.add_constraint(Constraint::equal(
1654 elem_info.ty,
1655 first_info.ty.clone(),
1656 elem_info.span,
1657 ));
1658 }
1659 // Build the array type with the inferred element type
1660 InferType::Array {
1661 element: Box::new(first_info.ty),
1662 length: elements.len() as u64,
1663 }
1664 }
1665 }
1666
1667 // Array index
1668 InstData::IndexGet { base, index } => {
1669 let base_info = self.generate(*base, ctx);
1670 let index_info = self.generate(*index, ctx);
1671 // Index must be exactly `usize` (ADR-0054).
1672 self.add_constraint(Constraint::equal(
1673 InferType::Concrete(Type::USIZE),
1674 index_info.ty.clone(),
1675 index_info.span,
1676 ));
1677
1678 // Extract element type from array type.
1679 // If base is InferType::Array, we can get the element type directly.
1680 // Otherwise, we need a fresh variable that will be resolved later.
1681 match &base_info.ty {
1682 InferType::Array { element, .. } => (**element).clone(),
1683 _ => {
1684 // Base might be a type variable that will resolve to an array.
1685 // Use a fresh variable for the element type.
1686 let result_var = self.fresh_var();
1687 InferType::Var(result_var)
1688 }
1689 }
1690 }
1691
1692 // Array index assignment
1693 InstData::IndexSet { base, index, value } => {
1694 let base_info = self.generate(*base, ctx);
1695 let index_info = self.generate(*index, ctx);
1696 // Index must be exactly `usize` (ADR-0054).
1697 self.add_constraint(Constraint::equal(
1698 InferType::Concrete(Type::USIZE),
1699 index_info.ty.clone(),
1700 index_info.span,
1701 ));
1702
1703 let value_info = self.generate(*value, ctx);
1704
1705 // Constrain value type to match array element type
1706 if let InferType::Array { element, .. } = &base_info.ty {
1707 self.add_constraint(Constraint::equal(
1708 value_info.ty,
1709 (**element).clone(),
1710 value_info.span,
1711 ));
1712 }
1713
1714 InferType::Concrete(Type::UNIT)
1715 }
1716
1717 // Type declarations don't produce values
1718 InstData::FnDecl { .. }
1719 | InstData::StructDecl { .. }
1720 | InstData::EnumDecl { .. }
1721 | InstData::InterfaceDecl { .. }
1722 | InstData::InterfaceMethodSig { .. }
1723 | InstData::DeriveDecl { .. }
1724 | InstData::ConstDecl { .. } => InferType::Concrete(Type::UNIT),
1725
1726 // ADR-0057: anonymous interface type expressions yield a
1727 // comptime type value (parallel to AnonStructType /
1728 // AnonEnumType, which are typed COMPTIME_TYPE elsewhere via
1729 // their dedicated arms).
1730 InstData::AnonInterfaceType { .. } => InferType::Concrete(Type::COMPTIME_TYPE),
1731
1732 // Method call: receiver.method(args)
1733 InstData::MethodCall {
1734 receiver,
1735 method,
1736 args_start,
1737 args_len,
1738 } => {
1739 // Generate type for receiver
1740 let receiver_info = self.generate(*receiver, ctx);
1741 let args = self.rir.get_call_args(*args_start, *args_len);
1742
1743 // Get struct name from receiver type if it's a struct
1744 // If we can't determine the struct type, we still generate constraints
1745 // for the arguments and return a type variable (actual error is in sema)
1746
1747 if let InferType::Concrete(ty) = &receiver_info.ty {
1748 // ADR-0063: methods on `Ptr(T)` / `MutPtr(T)` resolve via
1749 // the POINTER_METHODS registry. Fan out per method here
1750 // so HM produces the right constraint shape; sema does
1751 // the real type-checking.
1752 if matches!(ty.kind(), TypeKind::PtrConst(_) | TypeKind::PtrMut(_)) {
1753 let method_str = self.interner.resolve(method);
1754 let pointee = match ty.kind() {
1755 TypeKind::PtrConst(id) => self.type_pool.ptr_const_def(id),
1756 TypeKind::PtrMut(id) => self.type_pool.ptr_mut_def(id),
1757 _ => unreachable!(),
1758 };
1759 // Generate inference for args (no constraints — sema
1760 // will type-check).
1761 for arg in args.iter() {
1762 self.generate(arg.value, ctx);
1763 }
1764 return ExprInfo {
1765 ty: match method_str {
1766 "read" | "read_volatile" => InferType::Concrete(pointee),
1767 "write" | "write_volatile" => InferType::Concrete(Type::UNIT),
1768 "offset" => InferType::Concrete(*ty),
1769 "is_null" => InferType::Concrete(Type::BOOL),
1770 "to_int" => InferType::Concrete(Type::U64),
1771 "copy_from" => InferType::Concrete(Type::UNIT),
1772 _ => InferType::Concrete(Type::ERROR),
1773 },
1774 span,
1775 };
1776 }
1777
1778 if let Some(struct_id) = ty.as_struct() {
1779 // Use StructId directly for method lookup
1780 let method_key = (struct_id, *method);
1781 if let Some(method_sig) = self.methods.get(&method_key) {
1782 // Generate constraints for arguments
1783 for (arg, param_type) in args.iter().zip(method_sig.param_types.iter())
1784 {
1785 let arg_info = self.generate(arg.value, ctx);
1786 self.add_constraint(Constraint::equal(
1787 arg_info.ty,
1788 param_type.clone(),
1789 arg_info.span,
1790 ));
1791 }
1792 method_sig.return_type.clone()
1793 } else {
1794 // Method not found in pre-built context - may be an anonymous
1795 // struct method registered during comptime evaluation.
1796 // Use a fresh type variable so inference doesn't poison
1797 // surrounding expressions with ERROR.
1798 for arg in args.iter() {
1799 self.generate(arg.value, ctx);
1800 }
1801 InferType::Var(self.fresh_var())
1802 }
1803 } else if let Some(enum_id) = ty.as_enum() {
1804 // Enum receiver - check enum_methods
1805 let method_key = (enum_id, *method);
1806 if let Some(method_sig) = self.enum_methods.get(&method_key) {
1807 for (arg, param_type) in args.iter().zip(method_sig.param_types.iter())
1808 {
1809 let arg_info = self.generate(arg.value, ctx);
1810 self.add_constraint(Constraint::equal(
1811 arg_info.ty,
1812 param_type.clone(),
1813 arg_info.span,
1814 ));
1815 }
1816 method_sig.return_type.clone()
1817 } else {
1818 // Enum method not found - use fresh var, sema reports error
1819 for arg in args.iter() {
1820 self.generate(arg.value, ctx);
1821 }
1822 InferType::Var(self.fresh_var())
1823 }
1824 } else {
1825 // Non-struct/non-enum receiver - sema will report the error
1826 for arg in args.iter() {
1827 self.generate(arg.value, ctx);
1828 }
1829 InferType::Concrete(Type::ERROR)
1830 }
1831 } else {
1832 // Non-concrete receiver type - use fresh var, sema resolves it
1833 for arg in args.iter() {
1834 self.generate(arg.value, ctx);
1835 }
1836 InferType::Var(self.fresh_var())
1837 }
1838 }
1839
1840 // Associated function call: Type::function(args)
1841 InstData::AssocFnCall {
1842 type_name,
1843 function,
1844 args_start,
1845 args_len,
1846 } => {
1847 let args = self.rir.get_call_args(*args_start, *args_len);
1848 // Get struct ID from type name for method lookup
1849 let struct_id = self.structs.get(type_name).and_then(|ty| ty.as_struct());
1850
1851 if let Some(struct_id) = struct_id {
1852 let method_key = (struct_id, *function);
1853 if let Some(method_sig) = self.methods.get(&method_key) {
1854 // Generate constraints for arguments
1855 for (arg, param_type) in args.iter().zip(method_sig.param_types.iter()) {
1856 let arg_info = self.generate(arg.value, ctx);
1857 self.add_constraint(Constraint::equal(
1858 arg_info.ty,
1859 param_type.clone(),
1860 arg_info.span,
1861 ));
1862 }
1863 method_sig.return_type.clone()
1864 } else {
1865 // Method not found - may be an anonymous struct method
1866 // registered during comptime. Use fresh var to avoid ERROR poison.
1867 for arg in args.iter() {
1868 self.generate(arg.value, ctx);
1869 }
1870 InferType::Var(self.fresh_var())
1871 }
1872 } else {
1873 // Type not found in pre-built context - may be a comptime type var.
1874 // Use fresh var so inference doesn't poison surrounding expressions.
1875 for arg in args.iter() {
1876 self.generate(arg.value, ctx);
1877 }
1878 InferType::Var(self.fresh_var())
1879 }
1880 }
1881
1882 // Comptime block: the type depends on whether evaluation succeeds at compile time.
1883 // For type inference, we use a fresh type variable that can unify with
1884 // whatever type is expected from the context (e.g., a let binding's type annotation).
1885 // Similar to integer literals, comptime blocks can adapt to their context.
1886 InstData::Comptime { expr: _ } => {
1887 // Comptime blocks are fully evaluated by the comptime interpreter in sema,
1888 // which handles its own type checking (comptime_str, TypeInfo structs, etc.).
1889 // We don't generate constraints for the inner expression because comptime
1890 // has types (comptime_str, anonymous structs from @typeInfo) that don't
1891 // exist in the regular type system and would cause false unification errors.
1892 // Use a fresh variable so comptime can unify with whatever the context expects.
1893 let var = self.fresh_var();
1894 self.int_literal_vars.push(var);
1895 InferType::Var(var)
1896 }
1897
1898 // Comptime unroll for: the iterable is evaluated at comptime, the body is unrolled.
1899 // Like regular for loops, the result type is unit.
1900 // We must generate constraints for the body so that type inference resolves
1901 // types for runtime expressions inside the loop body (e.g., `total + 1`).
1902 // The binding variable holds a comptime value and is handled by sema, but
1903 // we register it as an integer type variable so VarRef lookups don't fail.
1904 InstData::ComptimeUnrollFor {
1905 binding,
1906 iterable,
1907 body,
1908 } => {
1909 // Generate constraints for the iterable (it's a comptime block)
1910 self.generate(*iterable, ctx);
1911
1912 // Register the binding as a fresh type variable.
1913 // The actual comptime value type is determined by sema, but HM
1914 // inference needs the binding in scope so VarRef doesn't fail.
1915 let binding_ty = {
1916 let var = self.fresh_var();
1917 InferType::Var(var)
1918 };
1919 ctx.insert_local(
1920 *binding,
1921 LocalVarInfo {
1922 ty: binding_ty,
1923 is_mut: false,
1924 span,
1925 },
1926 );
1927
1928 // Generate constraints for the body
1929 self.generate(*body, ctx);
1930
1931 InferType::Concrete(Type::UNIT)
1932 }
1933
1934 // Checked block: for type inference purposes, the type is the type of the inner expression
1935 // The actual checking of unchecked operations happens in sema
1936 InstData::Checked { expr } => {
1937 // Generate constraints for the inner expression
1938 let inner_info = self.generate(*expr, ctx);
1939 inner_info.ty
1940 }
1941
1942 // Type constant: a type used as a value (e.g., `i32` in `identity(i32, 42)`)
1943 // This has the special ComptimeType type which indicates it's a type value.
1944 InstData::TypeConst { .. } => InferType::Concrete(Type::COMPTIME_TYPE),
1945
1946 // Anonymous struct type: a struct type used as a comptime value
1947 // This also has the ComptimeType type.
1948 InstData::AnonStructType { .. } => InferType::Concrete(Type::COMPTIME_TYPE),
1949
1950 // Anonymous enum type: an enum type used as a comptime value
1951 // This also has the ComptimeType type.
1952 InstData::AnonEnumType { .. } => InferType::Concrete(Type::COMPTIME_TYPE),
1953
1954 // Tuple lowering (ADR-0048): defer to a fresh type variable. The sema
1955 // layer resolves tuples to anonymous structs during analysis; inference
1956 // does not need a concrete shape here.
1957 InstData::TupleInit {
1958 elems_start,
1959 elems_len,
1960 } => {
1961 let elems = self.rir.get_inst_refs(*elems_start, *elems_len);
1962 for elem in elems {
1963 self.generate(elem, ctx);
1964 }
1965 let result_var = self.fresh_var();
1966 InferType::Var(result_var)
1967 }
1968
1969 // Anonymous function value (ADR-0055): sema lowers to a fresh anon
1970 // struct with a `__call` method, then emits a StructInit against
1971 // that struct. Inference defers to a fresh type variable here —
1972 // analysis.rs supplies the concrete struct type.
1973 InstData::AnonFnValue { .. } => {
1974 let result_var = self.fresh_var();
1975 InferType::Var(result_var)
1976 }
1977 };
1978
1979 // Record the type for this expression
1980 self.record_type(inst_ref, ty.clone());
1981 ExprInfo::new(ty, span)
1982 }
1983
1984 /// Generate constraints for a binary arithmetic operation (+, -, *, /, %).
1985 ///
1986 /// Operands must be numeric (integer or float).
1987 fn generate_binary_arith(
1988 &mut self,
1989 lhs: InstRef,
1990 rhs: InstRef,
1991 ctx: &mut ConstraintContext,
1992 ) -> InferType {
1993 let lhs_info = self.generate(lhs, ctx);
1994 let rhs_info = self.generate(rhs, ctx);
1995
1996 let result_var = self.fresh_var();
1997 let result_ty = InferType::Var(result_var);
1998
1999 self.add_constraint(Constraint::equal(
2000 self.auto_deref(lhs_info.ty),
2001 result_ty.clone(),
2002 lhs_info.span,
2003 ));
2004 self.add_constraint(Constraint::equal(
2005 self.auto_deref(rhs_info.ty),
2006 result_ty.clone(),
2007 rhs_info.span,
2008 ));
2009
2010 // Result must be numeric (integer or float)
2011 self.add_constraint(Constraint::is_numeric(result_ty.clone(), lhs_info.span));
2012
2013 result_ty
2014 }
2015
2016 /// Generate constraints for a binary bitwise operation (&, |, ^, <<, >>).
2017 ///
2018 /// Operands must be integers (floats are not allowed).
2019 fn generate_binary_bitwise(
2020 &mut self,
2021 lhs: InstRef,
2022 rhs: InstRef,
2023 ctx: &mut ConstraintContext,
2024 ) -> InferType {
2025 let lhs_info = self.generate(lhs, ctx);
2026 let rhs_info = self.generate(rhs, ctx);
2027
2028 let result_var = self.fresh_var();
2029 let result_ty = InferType::Var(result_var);
2030
2031 self.add_constraint(Constraint::equal(
2032 lhs_info.ty,
2033 result_ty.clone(),
2034 lhs_info.span,
2035 ));
2036 self.add_constraint(Constraint::equal(
2037 rhs_info.ty,
2038 result_ty.clone(),
2039 rhs_info.span,
2040 ));
2041
2042 // Result must be an integer type (no floats)
2043 self.add_constraint(Constraint::is_integer(result_ty.clone(), lhs_info.span));
2044
2045 result_ty
2046 }
2047
2048 /// Get the inferred type for a pattern.
2049 /// ADR-0051 Phase 4 part 2: register a single data/struct-variant
2050 /// field binding. If the binding is a flat named binding, insert it
2051 /// directly. If it carries a nested `sub_pattern`, walk into it via
2052 /// `collect_recursive_pattern_bindings` so nested Ident leaves
2053 /// become scope entries.
2054 fn register_binding(
2055 &mut self,
2056 binding: &gruel_rir::RirPatternBinding,
2057 field_ty: InferType,
2058 pattern_span: gruel_util::Span,
2059 ctx: &mut ConstraintContext,
2060 added_bindings: &mut Vec<(lasso::Spur, Option<LocalVarInfo>)>,
2061 ) {
2062 if let Some(sub) = &binding.sub_pattern {
2063 self.collect_recursive_pattern_bindings(sub, field_ty, ctx, added_bindings);
2064 return;
2065 }
2066 if binding.is_wildcard {
2067 return;
2068 }
2069 if let Some(name) = binding.name {
2070 let old = ctx.locals.insert(
2071 name,
2072 LocalVarInfo {
2073 ty: field_ty,
2074 is_mut: binding.is_mut,
2075 span: pattern_span,
2076 },
2077 );
2078 added_bindings.push((name, old));
2079 }
2080 }
2081
2082 /// ADR-0051 Phase 4c: walk a Tuple / Struct / Ident match-arm
2083 /// pattern, registering each Ident leaf in `ctx.locals` so body
2084 /// constraint generation resolves the variable. Field types are
2085 /// pulled from the concrete struct when `scr_ty` is known; fresh
2086 /// type variables take over otherwise.
2087 fn collect_recursive_pattern_bindings(
2088 &mut self,
2089 pattern: &gruel_rir::RirPattern,
2090 scr_ty: InferType,
2091 ctx: &mut ConstraintContext,
2092 added_bindings: &mut Vec<(lasso::Spur, Option<LocalVarInfo>)>,
2093 ) {
2094 match pattern {
2095 gruel_rir::RirPattern::Ident { name, is_mut, span } => {
2096 let old = ctx.locals.insert(
2097 *name,
2098 LocalVarInfo {
2099 ty: scr_ty,
2100 is_mut: *is_mut,
2101 span: *span,
2102 },
2103 );
2104 added_bindings.push((*name, old));
2105 }
2106 gruel_rir::RirPattern::Tuple { elems, .. } => {
2107 // Try to resolve scrutinee's struct type to extract field
2108 // types; fall back to fresh vars when unknown.
2109 let field_tys: Vec<InferType> = match &scr_ty {
2110 InferType::Concrete(ty) => {
2111 if let Some(struct_id) = ty.as_struct() {
2112 let def = self.type_pool.struct_def(struct_id);
2113 def.fields
2114 .iter()
2115 .map(|f| InferType::Concrete(f.ty))
2116 .collect()
2117 } else {
2118 elems
2119 .iter()
2120 .map(|_| InferType::Var(self.fresh_var()))
2121 .collect()
2122 }
2123 }
2124 _ => elems
2125 .iter()
2126 .map(|_| InferType::Var(self.fresh_var()))
2127 .collect(),
2128 };
2129 for (i, elem) in elems.iter().enumerate() {
2130 let elem_ty = field_tys
2131 .get(i)
2132 .cloned()
2133 .unwrap_or_else(|| InferType::Var(self.fresh_var()));
2134 self.collect_recursive_pattern_bindings(elem, elem_ty, ctx, added_bindings);
2135 }
2136 }
2137 gruel_rir::RirPattern::Struct { fields, .. } => {
2138 // For named-struct roots, we need field types by name.
2139 // If the scrutinee type is concrete, walk its field list.
2140 let struct_id_opt = match &scr_ty {
2141 InferType::Concrete(ty) => ty.as_struct(),
2142 _ => None,
2143 };
2144 for rf in fields {
2145 let field_ty = if let Some(sid) = struct_id_opt {
2146 let def = self.type_pool.struct_def(sid);
2147 let name = self.interner.resolve(&rf.field_name);
2148 def.fields
2149 .iter()
2150 .find(|sf| sf.name == name)
2151 .map(|sf| InferType::Concrete(sf.ty))
2152 .unwrap_or_else(|| InferType::Var(self.fresh_var()))
2153 } else {
2154 InferType::Var(self.fresh_var())
2155 };
2156 self.collect_recursive_pattern_bindings(
2157 &rf.pattern,
2158 field_ty,
2159 ctx,
2160 added_bindings,
2161 );
2162 }
2163 }
2164 gruel_rir::RirPattern::DataVariant {
2165 type_name,
2166 bindings,
2167 ..
2168 } => {
2169 // ADR-0052: nested refutable variant sub-pattern.
2170 // Resolve the enum (if possible) to thread field types
2171 // through each inner binding.
2172 let enum_id = self.enums.get(type_name).and_then(|ty| ty.as_enum());
2173 let field_tys: Vec<InferType> = if let Some(eid) = enum_id
2174 && let Some(variant_idx) = self.resolve_variant_index(pattern)
2175 {
2176 let def = self.type_pool.enum_def(eid);
2177 def.variants[variant_idx]
2178 .fields
2179 .iter()
2180 .map(|t| InferType::Concrete(*t))
2181 .collect()
2182 } else {
2183 bindings
2184 .iter()
2185 .map(|_| InferType::Var(self.fresh_var()))
2186 .collect()
2187 };
2188 for (i, binding) in bindings.iter().enumerate() {
2189 let ty = field_tys
2190 .get(i)
2191 .cloned()
2192 .unwrap_or_else(|| InferType::Var(self.fresh_var()));
2193 self.register_binding(binding, ty, pattern.span(), ctx, added_bindings);
2194 }
2195 }
2196 gruel_rir::RirPattern::StructVariant {
2197 type_name,
2198 field_bindings,
2199 ..
2200 } => {
2201 let enum_id = self.enums.get(type_name).and_then(|ty| ty.as_enum());
2202 for fb in field_bindings {
2203 let ty = if let Some(eid) = enum_id
2204 && let Some(variant_idx) = self.resolve_variant_index(pattern)
2205 {
2206 let def = self.type_pool.enum_def(eid);
2207 let variant_def = &def.variants[variant_idx];
2208 let name = self.interner.resolve(&fb.field_name);
2209 variant_def
2210 .find_field(name)
2211 .map(|idx| InferType::Concrete(variant_def.fields[idx]))
2212 .unwrap_or_else(|| InferType::Var(self.fresh_var()))
2213 } else {
2214 InferType::Var(self.fresh_var())
2215 };
2216 self.register_binding(&fb.binding, ty, pattern.span(), ctx, added_bindings);
2217 }
2218 }
2219 // Leaf literals and Path (unit variant) introduce no
2220 // additional bindings at this level.
2221 _ => {}
2222 }
2223 }
2224
2225 /// Helper for `collect_recursive_pattern_bindings`: resolve a
2226 /// DataVariant / StructVariant's variant index from the RIR shape.
2227 fn resolve_variant_index(&self, pattern: &gruel_rir::RirPattern) -> Option<usize> {
2228 let (type_name, variant) = match pattern {
2229 gruel_rir::RirPattern::DataVariant {
2230 type_name, variant, ..
2231 }
2232 | gruel_rir::RirPattern::StructVariant {
2233 type_name, variant, ..
2234 }
2235 | gruel_rir::RirPattern::Path {
2236 type_name, variant, ..
2237 } => (*type_name, *variant),
2238 _ => return None,
2239 };
2240 let enum_id = self.enums.get(&type_name)?.as_enum()?;
2241 let def = self.type_pool.enum_def(enum_id);
2242 def.find_variant(self.interner.resolve(&variant))
2243 }
2244
2245 fn pattern_type(&mut self, pattern: &gruel_rir::RirPattern) -> InferType {
2246 match pattern {
2247 gruel_rir::RirPattern::Wildcard(_) => {
2248 // Wildcard matches anything - use a fresh type variable
2249 let var = self.fresh_var();
2250 InferType::Var(var)
2251 }
2252 gruel_rir::RirPattern::Int(_, _) => InferType::IntLiteral,
2253 gruel_rir::RirPattern::Bool(_, _) => InferType::Concrete(Type::BOOL),
2254 gruel_rir::RirPattern::Path { type_name, .. }
2255 | gruel_rir::RirPattern::DataVariant { type_name, .. }
2256 | gruel_rir::RirPattern::StructVariant { type_name, .. } => {
2257 if let Some(&enum_ty) = self.enums.get(type_name) {
2258 InferType::Concrete(enum_ty)
2259 } else {
2260 // Enum type not found — may be a comptime type variable
2261 // (e.g., `let Opt = Option(i32); match x { Opt::Some(v) => ... }`).
2262 // Use a fresh type variable so arm bodies can still infer types.
2263 let var = self.fresh_var();
2264 InferType::Var(var)
2265 }
2266 }
2267 // ADR-0051 Phase 4b: top-level Ident / Tuple / Struct arms
2268 // don't constrain the scrutinee's type on their own — inference
2269 // ties them to the scrutinee through a fresh type variable. For
2270 // Struct arms whose `type_name` resolves to a known struct we
2271 // could tighten this, but the arm's internal field patterns
2272 // unify against the scrutinee's struct type elsewhere.
2273 gruel_rir::RirPattern::Ident { .. }
2274 | gruel_rir::RirPattern::Tuple { .. }
2275 | gruel_rir::RirPattern::Struct { .. } => {
2276 let var = self.fresh_var();
2277 InferType::Var(var)
2278 }
2279 // ADR-0079 Phase 3: an unroll-arm template constrains
2280 // the scrutinee to whatever type the post-expansion
2281 // arms imply; inference treats it as a fresh var here.
2282 gruel_rir::RirPattern::ComptimeUnrollArm { .. } => {
2283 let var = self.fresh_var();
2284 InferType::Var(var)
2285 }
2286 }
2287 }
2288
2289 /// Resolve a type name to an InferType, consulting `local_subst`
2290 /// before falling back to the body-level `self.type_subst`.
2291 ///
2292 /// Used at generic-call sites: the callee's `return_type_sym` may be
2293 /// a compound name like `Ptr(T)` where `T` is bound to a concrete
2294 /// type only by the per-call substitution map we just built. The
2295 /// regular `resolve_type_name` only sees `self.type_subst` (the
2296 /// outer body's subst), so it would resolve `T` to `Type::ERROR`
2297 /// and the call's return type would never become `Ptr(<concrete>)`.
2298 fn resolve_type_with_local_subst(
2299 &self,
2300 name: &str,
2301 local_subst: &rustc_hash::FxHashMap<lasso::Spur, Type>,
2302 ) -> Option<InferType> {
2303 // Local subst takes precedence — its bindings are call-site-specific.
2304 if let Some(name_spur) = self.interner.get(name)
2305 && let Some(&ty) = local_subst.get(&name_spur)
2306 {
2307 return Some(InferType::Concrete(ty));
2308 }
2309 // For compound names (`Ptr(T)`, `MutRef(T)`, `[T; N]`, …) recurse so
2310 // the inner type-arg goes through the same local-first lookup.
2311 if let Some((element_type_str, length)) = parse_array_type_syntax(name) {
2312 let element_ty = self.resolve_type_with_local_subst(&element_type_str, local_subst)?;
2313 return Some(InferType::Array {
2314 element: Box::new(element_ty),
2315 length,
2316 });
2317 }
2318 if let Some((callee_name, arg_strs)) = parse_type_call_syntax(name)
2319 && let Some(constructor) = gruel_builtins::get_builtin_type_constructor(&callee_name)
2320 && arg_strs.len() == constructor.arity
2321 {
2322 let arg_infer = self.resolve_type_with_local_subst(&arg_strs[0], local_subst)?;
2323 let arg_ty = match arg_infer {
2324 InferType::Concrete(ty) => ty,
2325 _ => return None,
2326 };
2327 let ptr_ty = match constructor.kind {
2328 BuiltinTypeConstructorKind::Ptr => {
2329 let id = self.type_pool.intern_ptr_const_from_type(arg_ty);
2330 Type::new_ptr_const(id)
2331 }
2332 BuiltinTypeConstructorKind::MutPtr => {
2333 let id = self.type_pool.intern_ptr_mut_from_type(arg_ty);
2334 Type::new_ptr_mut(id)
2335 }
2336 BuiltinTypeConstructorKind::Ref => {
2337 let id = self.type_pool.intern_ref_from_type(arg_ty);
2338 Type::new_ref(id)
2339 }
2340 BuiltinTypeConstructorKind::MutRef => {
2341 let id = self.type_pool.intern_mut_ref_from_type(arg_ty);
2342 Type::new_mut_ref(id)
2343 }
2344 BuiltinTypeConstructorKind::Slice => {
2345 let id = self.type_pool.intern_slice_from_type(arg_ty);
2346 Type::new_slice(id)
2347 }
2348 BuiltinTypeConstructorKind::MutSlice => {
2349 let id = self.type_pool.intern_mut_slice_from_type(arg_ty);
2350 Type::new_mut_slice(id)
2351 }
2352 BuiltinTypeConstructorKind::Vec => {
2353 let id = self.type_pool.intern_vec_from_type(arg_ty);
2354 Type::new_vec(id)
2355 }
2356 };
2357 return Some(InferType::Concrete(ptr_ty));
2358 }
2359 // Fall through to the regular resolver for primitives / structs / etc.
2360 self.resolve_type_name(name)
2361 }
2362
2363 /// Resolve a type name to an InferType.
2364 ///
2365 /// Handles primitive types, array syntax `[T; N]`, pointer syntax `ptr mut T` / `ptr const T`,
2366 /// and struct/enum types.
2367 fn resolve_type_name(&self, name: &str) -> Option<InferType> {
2368 // ADR-0082: type_subst lookup. When resolving a method body
2369 // for a parameterized struct instance (e.g. `Vec(I32)`), the
2370 // outer fn's comptime type bindings (`T → I32`, `Self →
2371 // <struct_type>`) are stored in `self.type_subst`. Consult it
2372 // before everything else so a bare `T` annotation in
2373 // `let p: MutPtr(T) = ...` (which decomposes to a recursive
2374 // resolve_type_name("T")) resolves to the concrete type.
2375 if let Some(subst) = self.type_subst
2376 && let Some(name_spur) = self.interner.get(name)
2377 && let Some(&ty) = subst.get(&name_spur)
2378 {
2379 return Some(InferType::Concrete(ty));
2380 }
2381
2382 // Check for array syntax first: [T; N]
2383 if let Some((element_type_str, length)) = parse_array_type_syntax(name) {
2384 // Recursively resolve the element type
2385 let element_ty = self.resolve_type_name(&element_type_str)?;
2386 return Some(InferType::Array {
2387 element: Box::new(element_ty),
2388 length,
2389 });
2390 }
2391
2392 // ADR-0061: built-in parameterized types (`Ptr(T)`, `MutPtr(T)`).
2393 if let Some((callee_name, arg_strs)) = parse_type_call_syntax(name)
2394 && let Some(constructor) = gruel_builtins::get_builtin_type_constructor(&callee_name)
2395 && arg_strs.len() == constructor.arity
2396 {
2397 let arg_infer = self.resolve_type_name(&arg_strs[0])?;
2398 let arg_ty = match arg_infer {
2399 InferType::Concrete(ty) => ty,
2400 _ => return None,
2401 };
2402 let ptr_ty = match constructor.kind {
2403 BuiltinTypeConstructorKind::Ptr => {
2404 let id = self.type_pool.intern_ptr_const_from_type(arg_ty);
2405 Type::new_ptr_const(id)
2406 }
2407 BuiltinTypeConstructorKind::MutPtr => {
2408 let id = self.type_pool.intern_ptr_mut_from_type(arg_ty);
2409 Type::new_ptr_mut(id)
2410 }
2411 BuiltinTypeConstructorKind::Ref => {
2412 let id = self.type_pool.intern_ref_from_type(arg_ty);
2413 Type::new_ref(id)
2414 }
2415 BuiltinTypeConstructorKind::MutRef => {
2416 let id = self.type_pool.intern_mut_ref_from_type(arg_ty);
2417 Type::new_mut_ref(id)
2418 }
2419 BuiltinTypeConstructorKind::Slice => {
2420 let id = self.type_pool.intern_slice_from_type(arg_ty);
2421 Type::new_slice(id)
2422 }
2423 BuiltinTypeConstructorKind::MutSlice => {
2424 let id = self.type_pool.intern_mut_slice_from_type(arg_ty);
2425 Type::new_mut_slice(id)
2426 }
2427 BuiltinTypeConstructorKind::Vec => {
2428 let id = self.type_pool.intern_vec_from_type(arg_ty);
2429 Type::new_vec(id)
2430 }
2431 };
2432 return Some(InferType::Concrete(ptr_ty));
2433 }
2434
2435 // Check for pointer syntax: ptr mut T / ptr const T
2436 if let Some((pointee_type_str, mutability)) = parse_pointer_type_syntax(name) {
2437 // Recursively resolve the pointee type
2438 let pointee_infer_ty = self.resolve_type_name(&pointee_type_str)?;
2439
2440 // Convert InferType to Type so we can intern the pointer
2441 let pointee_ty = match pointee_infer_ty {
2442 InferType::Concrete(ty) => ty,
2443 // Can't handle non-concrete types in pointer positions during constraint generation
2444 _ => return None,
2445 };
2446
2447 // Intern the pointer type
2448 let ptr_ty = match mutability {
2449 PtrMutability::Mut => {
2450 let ptr_id = self.type_pool.intern_ptr_mut_from_type(pointee_ty);
2451 Type::new_ptr_mut(ptr_id)
2452 }
2453 PtrMutability::Const => {
2454 let ptr_id = self.type_pool.intern_ptr_const_from_type(pointee_ty);
2455 Type::new_ptr_const(ptr_id)
2456 }
2457 };
2458 return Some(InferType::Concrete(ptr_ty));
2459 }
2460
2461 // Check primitives
2462 let ty = match name {
2463 "i8" => Type::I8,
2464 "i16" => Type::I16,
2465 "i32" => Type::I32,
2466 "i64" => Type::I64,
2467 "u8" => Type::U8,
2468 "u16" => Type::U16,
2469 "u32" => Type::U32,
2470 "u64" => Type::U64,
2471 "isize" => Type::ISIZE,
2472 "usize" => Type::USIZE,
2473 "f16" => Type::F16,
2474 "f32" => Type::F32,
2475 "f64" => Type::F64,
2476 "bool" => Type::BOOL,
2477 "()" => Type::UNIT,
2478 // ADR-0086: C named arithmetic primitive types. Resolved
2479 // identically to native primitives at the inference layer —
2480 // sema's resolve_type fires the preview gate before
2481 // inference runs. `c_void` is also recognised here (so the
2482 // resulting constraint can flag a mismatch) but downstream
2483 // sema rejects c_void in any value-bearing position.
2484 "c_schar" => Type::C_SCHAR,
2485 "c_short" => Type::C_SHORT,
2486 "c_int" => Type::C_INT,
2487 "c_long" => Type::C_LONG,
2488 "c_longlong" => Type::C_LONGLONG,
2489 "c_uchar" => Type::C_UCHAR,
2490 "c_ushort" => Type::C_USHORT,
2491 "c_uint" => Type::C_UINT,
2492 "c_ulong" => Type::C_ULONG,
2493 "c_ulonglong" => Type::C_ULONGLONG,
2494 "c_float" => Type::C_FLOAT,
2495 "c_double" => Type::C_DOUBLE,
2496 "c_void" => Type::C_VOID,
2497 _ => {
2498 // Check for struct types (including builtin String)
2499 if let Some(name_spur) = self.interner.get(name) {
2500 if let Some(&struct_ty) = self.structs.get(&name_spur) {
2501 return Some(InferType::Concrete(struct_ty));
2502 }
2503 if let Some(&enum_ty) = self.enums.get(&name_spur) {
2504 return Some(InferType::Concrete(enum_ty));
2505 }
2506 }
2507 return None;
2508 }
2509 };
2510 Some(InferType::Concrete(ty))
2511 }
2512}
2513
2514#[cfg(test)]
2515mod tests {
2516 use super::*;
2517 use lasso::ThreadedRodeo;
2518
2519 /// Helper to create a minimal RIR, interner, and type pool for testing.
2520 fn make_test_rir_interner_and_type_pool() -> (Rir, ThreadedRodeo, TypeInternPool) {
2521 let rir = Rir::new();
2522 let interner = ThreadedRodeo::new();
2523 let type_pool = TypeInternPool::new();
2524 (rir, interner, type_pool)
2525 }
2526
2527 #[test]
2528 fn test_constraint_generator_int_literal() {
2529 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
2530 let functions = HashMap::default();
2531 let structs = HashMap::default();
2532 let enums = HashMap::default();
2533 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
2534 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
2535
2536 // Add an integer constant to RIR
2537 let inst_ref = rir.add_inst(gruel_rir::Inst {
2538 data: InstData::IntConst(42),
2539 span: Span::new(0, 2),
2540 });
2541
2542 let infer_ctx = InferenceContext {
2543 func_sigs: functions.clone(),
2544 struct_types: structs.clone(),
2545 enum_types: enums.clone(),
2546 method_sigs: methods.clone(),
2547 enum_method_sigs: enum_methods.clone(),
2548 };
2549 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
2550 let params = HashMap::default();
2551 let mut ctx = ConstraintContext::new(¶ms, Type::I32);
2552
2553 let info = cgen.generate(inst_ref, &mut ctx);
2554
2555 // Integer literals now get a type variable (tracked as int literal var)
2556 assert!(matches!(info.ty, InferType::Var(_)));
2557 // The type variable should be tracked in int_literal_vars
2558 assert_eq!(cgen.int_literal_vars().len(), 1);
2559 // No constraints should be generated for a simple literal
2560 assert_eq!(cgen.constraints().len(), 0);
2561 }
2562
2563 #[test]
2564 fn test_constraint_generator_bool_literal() {
2565 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
2566 let functions = HashMap::default();
2567 let structs = HashMap::default();
2568 let enums = HashMap::default();
2569 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
2570 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
2571
2572 let inst_ref = rir.add_inst(gruel_rir::Inst {
2573 data: InstData::BoolConst(true),
2574 span: Span::new(0, 4),
2575 });
2576
2577 let infer_ctx = InferenceContext {
2578 func_sigs: functions.clone(),
2579 struct_types: structs.clone(),
2580 enum_types: enums.clone(),
2581 method_sigs: methods.clone(),
2582 enum_method_sigs: enum_methods.clone(),
2583 };
2584 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
2585 let params = HashMap::default();
2586 let mut ctx = ConstraintContext::new(¶ms, Type::BOOL);
2587
2588 let info = cgen.generate(inst_ref, &mut ctx);
2589
2590 assert_eq!(info.ty, InferType::Concrete(Type::BOOL));
2591 assert_eq!(cgen.constraints().len(), 0);
2592 }
2593
2594 #[test]
2595 fn test_constraint_generator_binary_add() {
2596 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
2597 let functions = HashMap::default();
2598 let structs = HashMap::default();
2599 let enums = HashMap::default();
2600 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
2601 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
2602
2603 // Create: 1 + 2
2604 let lhs = rir.add_inst(gruel_rir::Inst {
2605 data: InstData::IntConst(1),
2606 span: Span::new(0, 1),
2607 });
2608 let rhs = rir.add_inst(gruel_rir::Inst {
2609 data: InstData::IntConst(2),
2610 span: Span::new(4, 5),
2611 });
2612 let add = rir.add_inst(gruel_rir::Inst {
2613 data: InstData::Bin {
2614 op: BinOp::Add,
2615 lhs,
2616 rhs,
2617 },
2618 span: Span::new(0, 5),
2619 });
2620
2621 let infer_ctx = InferenceContext {
2622 func_sigs: functions.clone(),
2623 struct_types: structs.clone(),
2624 enum_types: enums.clone(),
2625 method_sigs: methods.clone(),
2626 enum_method_sigs: enum_methods.clone(),
2627 };
2628 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
2629 let params = HashMap::default();
2630 let mut ctx = ConstraintContext::new(¶ms, Type::I32);
2631
2632 let info = cgen.generate(add, &mut ctx);
2633
2634 // Result should be a type variable
2635 assert!(info.ty.is_var());
2636 // Should generate 3 constraints: lhs = result, rhs = result, IsNumeric(result)
2637 assert_eq!(cgen.constraints().len(), 3);
2638 // Verify the third constraint is IsNumeric
2639 match &cgen.constraints()[2] {
2640 Constraint::IsNumeric(_, _) => {}
2641 _ => panic!("Expected IsNumeric constraint for arithmetic result"),
2642 }
2643 }
2644
2645 #[test]
2646 fn test_constraint_generator_comparison() {
2647 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
2648 let functions = HashMap::default();
2649 let structs = HashMap::default();
2650 let enums = HashMap::default();
2651 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
2652 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
2653
2654 // Create: 1 < 2
2655 let lhs = rir.add_inst(gruel_rir::Inst {
2656 data: InstData::IntConst(1),
2657 span: Span::new(0, 1),
2658 });
2659 let rhs = rir.add_inst(gruel_rir::Inst {
2660 data: InstData::IntConst(2),
2661 span: Span::new(4, 5),
2662 });
2663 let lt = rir.add_inst(gruel_rir::Inst {
2664 data: InstData::Bin {
2665 op: BinOp::Lt,
2666 lhs,
2667 rhs,
2668 },
2669 span: Span::new(0, 5),
2670 });
2671
2672 let infer_ctx = InferenceContext {
2673 func_sigs: functions.clone(),
2674 struct_types: structs.clone(),
2675 enum_types: enums.clone(),
2676 method_sigs: methods.clone(),
2677 enum_method_sigs: enum_methods.clone(),
2678 };
2679 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
2680 let params = HashMap::default();
2681 let mut ctx = ConstraintContext::new(¶ms, Type::BOOL);
2682
2683 let info = cgen.generate(lt, &mut ctx);
2684
2685 // Comparisons always return Bool
2686 assert_eq!(info.ty, InferType::Concrete(Type::BOOL));
2687 // Should generate 1 constraint: lhs type = rhs type
2688 assert_eq!(cgen.constraints().len(), 1);
2689 }
2690
2691 #[test]
2692 fn test_constraint_generator_logical_and() {
2693 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
2694 let functions = HashMap::default();
2695 let structs = HashMap::default();
2696 let enums = HashMap::default();
2697 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
2698 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
2699
2700 // Create: true && false
2701 let lhs = rir.add_inst(gruel_rir::Inst {
2702 data: InstData::BoolConst(true),
2703 span: Span::new(0, 4),
2704 });
2705 let rhs = rir.add_inst(gruel_rir::Inst {
2706 data: InstData::BoolConst(false),
2707 span: Span::new(8, 13),
2708 });
2709 let and = rir.add_inst(gruel_rir::Inst {
2710 data: InstData::Bin {
2711 op: BinOp::And,
2712 lhs,
2713 rhs,
2714 },
2715 span: Span::new(0, 13),
2716 });
2717
2718 let infer_ctx = InferenceContext {
2719 func_sigs: functions.clone(),
2720 struct_types: structs.clone(),
2721 enum_types: enums.clone(),
2722 method_sigs: methods.clone(),
2723 enum_method_sigs: enum_methods.clone(),
2724 };
2725 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
2726 let params = HashMap::default();
2727 let mut ctx = ConstraintContext::new(¶ms, Type::BOOL);
2728
2729 let info = cgen.generate(and, &mut ctx);
2730
2731 // Logical operators return Bool
2732 assert_eq!(info.ty, InferType::Concrete(Type::BOOL));
2733 // Should generate 2 constraints: lhs = bool, rhs = bool
2734 assert_eq!(cgen.constraints().len(), 2);
2735 }
2736
2737 #[test]
2738 fn test_constraint_generator_negation() {
2739 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
2740 let functions = HashMap::default();
2741 let structs = HashMap::default();
2742 let enums = HashMap::default();
2743 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
2744 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
2745
2746 // Create: -42
2747 let operand = rir.add_inst(gruel_rir::Inst {
2748 data: InstData::IntConst(42),
2749 span: Span::new(1, 3),
2750 });
2751 let neg = rir.add_inst(gruel_rir::Inst {
2752 data: InstData::Unary {
2753 op: UnaryOp::Neg,
2754 operand,
2755 },
2756 span: Span::new(0, 3),
2757 });
2758
2759 let infer_ctx = InferenceContext {
2760 func_sigs: functions.clone(),
2761 struct_types: structs.clone(),
2762 enum_types: enums.clone(),
2763 method_sigs: methods.clone(),
2764 enum_method_sigs: enum_methods.clone(),
2765 };
2766 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
2767 let params = HashMap::default();
2768 let mut ctx = ConstraintContext::new(¶ms, Type::I32);
2769
2770 let info = cgen.generate(neg, &mut ctx);
2771
2772 // Negation preserves the operand type (now a type variable for the int literal)
2773 assert!(matches!(info.ty, InferType::Var(_)));
2774 // Should generate 1 constraint: IsSigned for the result
2775 assert_eq!(cgen.constraints().len(), 1);
2776 // Verify it's an IsSigned constraint
2777 match &cgen.constraints()[0] {
2778 Constraint::IsSigned(_, _) => {}
2779 _ => panic!("Expected IsSigned constraint"),
2780 }
2781 }
2782
2783 #[test]
2784 fn test_constraint_generator_return() {
2785 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
2786 let functions = HashMap::default();
2787 let structs = HashMap::default();
2788 let enums = HashMap::default();
2789 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
2790 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
2791
2792 // Create: return 42
2793 let value = rir.add_inst(gruel_rir::Inst {
2794 data: InstData::IntConst(42),
2795 span: Span::new(7, 9),
2796 });
2797 let ret = rir.add_inst(gruel_rir::Inst {
2798 data: InstData::Ret(Some(value)),
2799 span: Span::new(0, 9),
2800 });
2801
2802 let infer_ctx = InferenceContext {
2803 func_sigs: functions.clone(),
2804 struct_types: structs.clone(),
2805 enum_types: enums.clone(),
2806 method_sigs: methods.clone(),
2807 enum_method_sigs: enum_methods.clone(),
2808 };
2809 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
2810 let params = HashMap::default();
2811 let mut ctx = ConstraintContext::new(¶ms, Type::I32);
2812
2813 let info = cgen.generate(ret, &mut ctx);
2814
2815 // Return is divergent (Never type)
2816 assert_eq!(info.ty, InferType::Concrete(Type::NEVER));
2817 // Should generate 1 constraint: return value = return type
2818 assert_eq!(cgen.constraints().len(), 1);
2819 }
2820
2821 #[test]
2822 fn test_constraint_generator_if_else() {
2823 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
2824 let functions = HashMap::default();
2825 let structs = HashMap::default();
2826 let enums = HashMap::default();
2827 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
2828 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
2829
2830 // Create: if true { 1 } else { 2 }
2831 let cond = rir.add_inst(gruel_rir::Inst {
2832 data: InstData::BoolConst(true),
2833 span: Span::new(3, 7),
2834 });
2835 let then_val = rir.add_inst(gruel_rir::Inst {
2836 data: InstData::IntConst(1),
2837 span: Span::new(10, 11),
2838 });
2839 let else_val = rir.add_inst(gruel_rir::Inst {
2840 data: InstData::IntConst(2),
2841 span: Span::new(20, 21),
2842 });
2843 let branch = rir.add_inst(gruel_rir::Inst {
2844 data: InstData::Branch {
2845 cond,
2846 then_block: then_val,
2847 else_block: Some(else_val),
2848 is_comptime: false,
2849 },
2850 span: Span::new(0, 25),
2851 });
2852
2853 let infer_ctx = InferenceContext {
2854 func_sigs: functions.clone(),
2855 struct_types: structs.clone(),
2856 enum_types: enums.clone(),
2857 method_sigs: methods.clone(),
2858 enum_method_sigs: enum_methods.clone(),
2859 };
2860 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
2861 let params = HashMap::default();
2862 let mut ctx = ConstraintContext::new(¶ms, Type::I32);
2863
2864 let info = cgen.generate(branch, &mut ctx);
2865
2866 // Result should be a type variable (unified from both branches)
2867 assert!(info.ty.is_var());
2868 // Should generate 3 constraints: cond = bool, then = result, else = result
2869 assert_eq!(cgen.constraints().len(), 3);
2870 }
2871
2872 #[test]
2873 fn test_constraint_generator_while_loop() {
2874 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
2875 let functions = HashMap::default();
2876 let structs = HashMap::default();
2877 let enums = HashMap::default();
2878 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
2879 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
2880
2881 // Create: while true { 0 }
2882 let cond = rir.add_inst(gruel_rir::Inst {
2883 data: InstData::BoolConst(true),
2884 span: Span::new(6, 10),
2885 });
2886 let body = rir.add_inst(gruel_rir::Inst {
2887 data: InstData::IntConst(0),
2888 span: Span::new(13, 14),
2889 });
2890 let loop_inst = rir.add_inst(gruel_rir::Inst {
2891 data: InstData::Loop { cond, body },
2892 span: Span::new(0, 15),
2893 });
2894
2895 let infer_ctx = InferenceContext {
2896 func_sigs: functions.clone(),
2897 struct_types: structs.clone(),
2898 enum_types: enums.clone(),
2899 method_sigs: methods.clone(),
2900 enum_method_sigs: enum_methods.clone(),
2901 };
2902 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
2903 let params = HashMap::default();
2904 let mut ctx = ConstraintContext::new(¶ms, Type::UNIT);
2905
2906 let info = cgen.generate(loop_inst, &mut ctx);
2907
2908 // While loops produce Unit
2909 assert_eq!(info.ty, InferType::Concrete(Type::UNIT));
2910 // Should generate 1 constraint: cond = bool
2911 assert_eq!(cgen.constraints().len(), 1);
2912 }
2913
2914 #[test]
2915 fn test_constraint_context_scope() {
2916 let params = HashMap::default();
2917 let mut ctx = ConstraintContext::new(¶ms, Type::I32);
2918
2919 // Use an interner to create a symbol
2920 let interner = ThreadedRodeo::new();
2921 let sym = interner.get_or_intern("x");
2922 ctx.insert_local(
2923 sym,
2924 LocalVarInfo {
2925 ty: InferType::Concrete(Type::I32),
2926 is_mut: false,
2927 span: Span::new(0, 1),
2928 },
2929 );
2930
2931 assert!(ctx.locals.contains_key(&sym));
2932
2933 // Push a scope and shadow the variable
2934 ctx.push_scope();
2935 ctx.insert_local(
2936 sym,
2937 LocalVarInfo {
2938 ty: InferType::Concrete(Type::I64),
2939 is_mut: true,
2940 span: Span::new(10, 15),
2941 },
2942 );
2943
2944 // Should see the shadowed version
2945 let local = ctx.locals.get(&sym).unwrap();
2946 assert_eq!(local.ty, InferType::Concrete(Type::I64));
2947 assert!(local.is_mut);
2948
2949 // Pop scope - should restore original
2950 ctx.pop_scope();
2951 let local = ctx.locals.get(&sym).unwrap();
2952 assert_eq!(local.ty, InferType::Concrete(Type::I32));
2953 assert!(!local.is_mut);
2954 }
2955
2956 #[test]
2957 fn test_expr_info_creation() {
2958 let info = ExprInfo::new(InferType::IntLiteral, Span::new(5, 10));
2959 assert!(info.ty.is_int_literal());
2960 assert_eq!(info.span, Span::new(5, 10));
2961 }
2962
2963 /// Helper to create a non-generic FunctionSig for tests
2964 fn make_test_func_sig(param_types: Vec<InferType>, return_type: InferType) -> FunctionSig {
2965 let num_params = param_types.len();
2966 FunctionSig {
2967 param_types,
2968 return_type,
2969 is_generic: false,
2970 param_modes: vec![gruel_rir::RirParamMode::Normal; num_params],
2971 param_comptime: vec![false; num_params],
2972 param_names: vec![],
2973 return_type_sym: lasso::Spur::default(),
2974 }
2975 }
2976
2977 #[test]
2978 fn test_function_sig() {
2979 let sig = make_test_func_sig(
2980 vec![
2981 InferType::Concrete(Type::I32),
2982 InferType::Concrete(Type::BOOL),
2983 ],
2984 InferType::Concrete(Type::I64),
2985 );
2986 assert_eq!(sig.param_types.len(), 2);
2987 assert_eq!(sig.return_type, InferType::Concrete(Type::I64));
2988 }
2989
2990 #[test]
2991 fn test_constraint_generator_infinite_loop() {
2992 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
2993 let functions = HashMap::default();
2994 let structs = HashMap::default();
2995 let enums = HashMap::default();
2996 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
2997 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
2998
2999 // Create: loop { 0 }
3000 let body = rir.add_inst(gruel_rir::Inst {
3001 data: InstData::IntConst(0),
3002 span: Span::new(6, 7),
3003 });
3004 let loop_inst = rir.add_inst(gruel_rir::Inst {
3005 data: InstData::InfiniteLoop { body },
3006 span: Span::new(0, 10),
3007 });
3008
3009 let infer_ctx = InferenceContext {
3010 func_sigs: functions.clone(),
3011 struct_types: structs.clone(),
3012 enum_types: enums.clone(),
3013 method_sigs: methods.clone(),
3014 enum_method_sigs: enum_methods.clone(),
3015 };
3016 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
3017 let params = HashMap::default();
3018 let mut ctx = ConstraintContext::new(¶ms, Type::UNIT);
3019
3020 let info = cgen.generate(loop_inst, &mut ctx);
3021
3022 // Infinite loop produces Never (diverges)
3023 assert_eq!(info.ty, InferType::Concrete(Type::NEVER));
3024 // No constraints for infinite loop itself
3025 assert_eq!(cgen.constraints().len(), 0);
3026 }
3027
3028 #[test]
3029 fn test_constraint_generator_break_continue() {
3030 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
3031 let functions = HashMap::default();
3032 let structs = HashMap::default();
3033 let enums = HashMap::default();
3034 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
3035 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
3036
3037 let break_inst = rir.add_inst(gruel_rir::Inst {
3038 data: InstData::Break,
3039 span: Span::new(0, 5),
3040 });
3041
3042 let infer_ctx = InferenceContext {
3043 func_sigs: functions.clone(),
3044 struct_types: structs.clone(),
3045 enum_types: enums.clone(),
3046 method_sigs: methods.clone(),
3047 enum_method_sigs: enum_methods.clone(),
3048 };
3049 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
3050 let params = HashMap::default();
3051 let mut ctx = ConstraintContext::new(¶ms, Type::UNIT);
3052
3053 let info = cgen.generate(break_inst, &mut ctx);
3054
3055 // Break diverges
3056 assert_eq!(info.ty, InferType::Concrete(Type::NEVER));
3057 assert_eq!(cgen.constraints().len(), 0);
3058 }
3059
3060 #[test]
3061 fn test_constraint_generator_index_get() {
3062 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
3063 let functions = HashMap::default();
3064 let structs = HashMap::default();
3065 let enums = HashMap::default();
3066 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
3067 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
3068
3069 // Create: arr[0]
3070 let base = rir.add_inst(gruel_rir::Inst {
3071 data: InstData::IntConst(0), // Placeholder for array
3072 span: Span::new(0, 3),
3073 });
3074 let index = rir.add_inst(gruel_rir::Inst {
3075 data: InstData::IntConst(0),
3076 span: Span::new(4, 5),
3077 });
3078 let index_get = rir.add_inst(gruel_rir::Inst {
3079 data: InstData::IndexGet { base, index },
3080 span: Span::new(0, 6),
3081 });
3082
3083 let infer_ctx = InferenceContext {
3084 func_sigs: functions.clone(),
3085 struct_types: structs.clone(),
3086 enum_types: enums.clone(),
3087 method_sigs: methods.clone(),
3088 enum_method_sigs: enum_methods.clone(),
3089 };
3090 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
3091 let params = HashMap::default();
3092 let mut ctx = ConstraintContext::new(¶ms, Type::I32);
3093
3094 let info = cgen.generate(index_get, &mut ctx);
3095
3096 // Result is a type variable (element type unknown)
3097 assert!(info.ty.is_var());
3098 // Should generate 1 constraint: index must be `usize`
3099 assert_eq!(cgen.constraints().len(), 1);
3100 match &cgen.constraints()[0] {
3101 Constraint::Equal(lhs, _rhs, _) => {
3102 assert_eq!(*lhs, InferType::Concrete(Type::USIZE));
3103 }
3104 _ => panic!("Expected Equal(USIZE, _) constraint for index"),
3105 }
3106 }
3107
3108 #[test]
3109 fn test_constraint_generator_index_set() {
3110 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
3111 let functions = HashMap::default();
3112 let structs = HashMap::default();
3113 let enums = HashMap::default();
3114 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
3115 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
3116
3117 // Create: arr[0] = 42
3118 let base = rir.add_inst(gruel_rir::Inst {
3119 data: InstData::IntConst(0), // Placeholder for array
3120 span: Span::new(0, 3),
3121 });
3122 let index = rir.add_inst(gruel_rir::Inst {
3123 data: InstData::IntConst(0),
3124 span: Span::new(4, 5),
3125 });
3126 let value = rir.add_inst(gruel_rir::Inst {
3127 data: InstData::IntConst(42),
3128 span: Span::new(9, 11),
3129 });
3130 let index_set = rir.add_inst(gruel_rir::Inst {
3131 data: InstData::IndexSet { base, index, value },
3132 span: Span::new(0, 11),
3133 });
3134
3135 let infer_ctx = InferenceContext {
3136 func_sigs: functions.clone(),
3137 struct_types: structs.clone(),
3138 enum_types: enums.clone(),
3139 method_sigs: methods.clone(),
3140 enum_method_sigs: enum_methods.clone(),
3141 };
3142 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
3143 let params = HashMap::default();
3144 let mut ctx = ConstraintContext::new(¶ms, Type::UNIT);
3145
3146 let info = cgen.generate(index_set, &mut ctx);
3147
3148 // Index assignment produces Unit
3149 assert_eq!(info.ty, InferType::Concrete(Type::UNIT));
3150 // Should generate 1 constraint: index must be `usize`
3151 assert_eq!(cgen.constraints().len(), 1);
3152 match &cgen.constraints()[0] {
3153 Constraint::Equal(lhs, _rhs, _) => {
3154 assert_eq!(*lhs, InferType::Concrete(Type::USIZE));
3155 }
3156 _ => panic!("Expected Equal(USIZE, _) constraint for index"),
3157 }
3158 }
3159
3160 #[test]
3161 fn test_constraint_generator_empty_block() {
3162 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
3163 let functions = HashMap::default();
3164 let structs = HashMap::default();
3165 let enums = HashMap::default();
3166 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
3167 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
3168
3169 // Create: { } (empty block)
3170 let block = rir.add_inst(gruel_rir::Inst {
3171 data: InstData::Block {
3172 extra_start: 0,
3173 len: 0,
3174 },
3175 span: Span::new(0, 2),
3176 });
3177
3178 let infer_ctx = InferenceContext {
3179 func_sigs: functions.clone(),
3180 struct_types: structs.clone(),
3181 enum_types: enums.clone(),
3182 method_sigs: methods.clone(),
3183 enum_method_sigs: enum_methods.clone(),
3184 };
3185 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
3186 let params = HashMap::default();
3187 let mut ctx = ConstraintContext::new(¶ms, Type::UNIT);
3188
3189 let info = cgen.generate(block, &mut ctx);
3190
3191 // Empty block produces Unit
3192 assert_eq!(info.ty, InferType::Concrete(Type::UNIT));
3193 assert_eq!(cgen.constraints().len(), 0);
3194 }
3195
3196 #[test]
3197 fn test_constraint_generator_bitwise_not() {
3198 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
3199 let functions = HashMap::default();
3200 let structs = HashMap::default();
3201 let enums = HashMap::default();
3202 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
3203 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
3204
3205 // Create: !42 (bitwise NOT)
3206 let operand = rir.add_inst(gruel_rir::Inst {
3207 data: InstData::IntConst(42),
3208 span: Span::new(1, 3),
3209 });
3210 let bitnot = rir.add_inst(gruel_rir::Inst {
3211 data: InstData::Unary {
3212 op: UnaryOp::BitNot,
3213 operand,
3214 },
3215 span: Span::new(0, 3),
3216 });
3217
3218 let infer_ctx = InferenceContext {
3219 func_sigs: functions.clone(),
3220 struct_types: structs.clone(),
3221 enum_types: enums.clone(),
3222 method_sigs: methods.clone(),
3223 enum_method_sigs: enum_methods.clone(),
3224 };
3225 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
3226 let params = HashMap::default();
3227 let mut ctx = ConstraintContext::new(¶ms, Type::I32);
3228
3229 let info = cgen.generate(bitnot, &mut ctx);
3230
3231 // Bitwise NOT preserves the operand type (now a type variable for int literal)
3232 assert!(matches!(info.ty, InferType::Var(_)));
3233 // Should generate 1 constraint: IsInteger for the result
3234 assert_eq!(cgen.constraints().len(), 1);
3235 match &cgen.constraints()[0] {
3236 Constraint::IsInteger(_, _) => {}
3237 _ => panic!("Expected IsInteger constraint"),
3238 }
3239 }
3240
3241 #[test]
3242 fn test_constraint_generator_function_call_arg_count_mismatch() {
3243 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
3244 let mut functions = HashMap::default();
3245 let structs = HashMap::default();
3246 let enums = HashMap::default();
3247 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
3248 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
3249
3250 // Register a function that takes 2 parameters
3251 let func_name = interner.get_or_intern("foo");
3252 functions.insert(
3253 func_name,
3254 make_test_func_sig(
3255 vec![
3256 InferType::Concrete(Type::I32),
3257 InferType::Concrete(Type::I32),
3258 ],
3259 InferType::Concrete(Type::BOOL),
3260 ),
3261 );
3262
3263 // Create a call with only 1 argument (mismatch)
3264 let arg = rir.add_inst(gruel_rir::Inst {
3265 data: InstData::IntConst(42),
3266 span: Span::new(4, 6),
3267 });
3268 let (args_start, args_len) = rir.add_call_args(&[gruel_rir::RirCallArg {
3269 value: arg,
3270 mode: gruel_rir::RirArgMode::Normal,
3271 }]);
3272 let call = rir.add_inst(gruel_rir::Inst {
3273 data: InstData::Call {
3274 name: func_name,
3275 args_start,
3276 args_len,
3277 },
3278 span: Span::new(0, 7),
3279 });
3280
3281 let infer_ctx = InferenceContext {
3282 func_sigs: functions.clone(),
3283 struct_types: structs.clone(),
3284 enum_types: enums.clone(),
3285 method_sigs: methods.clone(),
3286 enum_method_sigs: enum_methods.clone(),
3287 };
3288 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
3289 let params = HashMap::default();
3290 let mut ctx = ConstraintContext::new(¶ms, Type::BOOL);
3291
3292 let info = cgen.generate(call, &mut ctx);
3293
3294 // Should still return the declared return type
3295 assert_eq!(info.ty, InferType::Concrete(Type::BOOL));
3296 // No constraints generated when arg count mismatches (error will be in sema)
3297 assert_eq!(cgen.constraints().len(), 0);
3298 }
3299
3300 #[test]
3301 fn test_constraint_generator_unknown_function() {
3302 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
3303 let functions = HashMap::default(); // Empty - no functions registered
3304 let structs = HashMap::default();
3305 let enums = HashMap::default();
3306 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
3307 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
3308
3309 // Create a call to an unknown function
3310 let unknown_func = interner.get_or_intern("unknown");
3311 let arg = rir.add_inst(gruel_rir::Inst {
3312 data: InstData::IntConst(42),
3313 span: Span::new(8, 10),
3314 });
3315 let (args_start, args_len) = rir.add_call_args(&[gruel_rir::RirCallArg {
3316 value: arg,
3317 mode: gruel_rir::RirArgMode::Normal,
3318 }]);
3319 let call = rir.add_inst(gruel_rir::Inst {
3320 data: InstData::Call {
3321 name: unknown_func,
3322 args_start,
3323 args_len,
3324 },
3325 span: Span::new(0, 11),
3326 });
3327
3328 let infer_ctx = InferenceContext {
3329 func_sigs: functions.clone(),
3330 struct_types: structs.clone(),
3331 enum_types: enums.clone(),
3332 method_sigs: methods.clone(),
3333 enum_method_sigs: enum_methods.clone(),
3334 };
3335 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
3336 let params = HashMap::default();
3337 let mut ctx = ConstraintContext::new(¶ms, Type::I32);
3338
3339 let info = cgen.generate(call, &mut ctx);
3340
3341 // Unknown function returns Error type
3342 assert_eq!(info.ty, InferType::Concrete(Type::ERROR));
3343 // Arguments should still be processed (but no constraints generated for them)
3344 assert_eq!(cgen.constraints().len(), 0);
3345 }
3346
3347 #[test]
3348 fn test_constraint_generator_match_multiple_arms() {
3349 let (mut rir, interner, type_pool) = make_test_rir_interner_and_type_pool();
3350 let functions = HashMap::default();
3351 let structs = HashMap::default();
3352 let enums = HashMap::default();
3353 let methods: HashMap<(StructId, Spur), MethodSig> = HashMap::default();
3354 let enum_methods: HashMap<(EnumId, Spur), MethodSig> = HashMap::default();
3355
3356 // Create: match x { 1 => 10, 2 => 20, _ => 30 }
3357 let scrutinee = rir.add_inst(gruel_rir::Inst {
3358 data: InstData::IntConst(5),
3359 span: Span::new(6, 7),
3360 });
3361
3362 // Arm 1: 1 => 10
3363 let body1 = rir.add_inst(gruel_rir::Inst {
3364 data: InstData::IntConst(10),
3365 span: Span::new(15, 17),
3366 });
3367 let pattern1 = gruel_rir::RirPattern::Int(1, Span::new(10, 11));
3368
3369 // Arm 2: 2 => 20
3370 let body2 = rir.add_inst(gruel_rir::Inst {
3371 data: InstData::IntConst(20),
3372 span: Span::new(25, 27),
3373 });
3374 let pattern2 = gruel_rir::RirPattern::Int(2, Span::new(20, 21));
3375
3376 // Arm 3: _ => 30
3377 let body3 = rir.add_inst(gruel_rir::Inst {
3378 data: InstData::IntConst(30),
3379 span: Span::new(35, 37),
3380 });
3381 let pattern3 = gruel_rir::RirPattern::Wildcard(Span::new(30, 31));
3382
3383 let arms = vec![(pattern1, body1), (pattern2, body2), (pattern3, body3)];
3384 let (arms_start, arms_len) = rir.add_match_arms(&arms);
3385 let match_inst = rir.add_inst(gruel_rir::Inst {
3386 data: InstData::Match {
3387 scrutinee,
3388 arms_start,
3389 arms_len,
3390 },
3391 span: Span::new(0, 40),
3392 });
3393
3394 let infer_ctx = InferenceContext {
3395 func_sigs: functions.clone(),
3396 struct_types: structs.clone(),
3397 enum_types: enums.clone(),
3398 method_sigs: methods.clone(),
3399 enum_method_sigs: enum_methods.clone(),
3400 };
3401 let mut cgen = ConstraintGenerator::new(&rir, &interner, &infer_ctx, &type_pool);
3402 let params = HashMap::default();
3403 let mut ctx = ConstraintContext::new(¶ms, Type::I32);
3404
3405 let info = cgen.generate(match_inst, &mut ctx);
3406
3407 // Result should be a type variable (unified from all arm bodies)
3408 assert!(info.ty.is_var());
3409
3410 // Should generate 6 constraints:
3411 // - 3 for pattern types matching scrutinee type (each arm)
3412 // - 3 for body types matching result type (each arm)
3413 assert_eq!(cgen.constraints().len(), 6);
3414
3415 // Verify all constraints are Equal constraints
3416 for constraint in cgen.constraints() {
3417 match constraint {
3418 Constraint::Equal(_, _, _) => {}
3419 _ => panic!("Expected Equal constraint in match"),
3420 }
3421 }
3422 }
3423}