Skip to main content

yulang_runtime/validate/
mod.rs

1//! Validate typed runtime IR.
2//!
3//! Validation checks type consistency, binding availability, runtime type
4//! representability, and residual unknowns.  It complements `invariant`: this
5//! module checks typed relationships, while `invariant` checks structural
6//! promises such as `AddId` wrapping a thunk.
7
8use std::collections::{BTreeMap, BTreeSet, HashMap};
9
10use yulang_typed_ir as typed_ir;
11
12use crate::diagnostic::{RuntimeError, RuntimeResult, TypeSource};
13use crate::ir::{
14    Binding, Expr, ExprKind, HandleArm, HandleEffect, MatchArm, Module, Pattern, RecordSpreadExpr,
15    RecordSpreadPattern, ResumeBinding, Root, Stmt, Type as RuntimeType, TypeInstantiation,
16};
17use crate::types::{
18    BoundsChoice, choose_bounds_type, collect_type_vars, core_types_compatible,
19    diagnostic_core_type, effect_compatible, is_qualified_runtime_path,
20    project_runtime_hir_type_with_vars, runtime_core_type, runtime_type_contains_unknown,
21    strict_core_type as core_type,
22};
23
24mod expr;
25mod helpers;
26mod pattern;
27mod types;
28
29use expr::*;
30use helpers::*;
31use pattern::*;
32use types::*;
33
34#[derive(Debug, Clone)]
35pub(super) struct BindingInfo {
36    pub(super) ty: RuntimeType,
37    pub(super) type_params: Vec<typed_ir::TypeVar>,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub(super) enum TypeArgKind {
42    Value,
43    Effect,
44}
45
46pub(super) type TypeArgKinds = HashMap<typed_ir::Path, Vec<TypeArgKind>>;
47
48pub fn validate_module(module: &Module) -> RuntimeResult<()> {
49    let mut bindings = HashMap::new();
50    for binding in &module.bindings {
51        let info = BindingInfo {
52            ty: binding.body.ty.clone(),
53            type_params: binding.type_params.clone(),
54        };
55        match bindings.get_mut(&binding.name) {
56            Some(current) if binding_info_generality(&info) > binding_info_generality(current) => {
57                *current = info;
58            }
59            Some(_) => {}
60            None => {
61                bindings.insert(binding.name.clone(), info);
62            }
63        }
64    }
65    let type_arg_kinds = infer_type_arg_kinds(&module.bindings);
66    for root in &module.roots {
67        match root {
68            Root::Binding(path) if !bindings.contains_key(path) => {
69                return Err(RuntimeError::UnboundVariable { path: path.clone() });
70            }
71            Root::Expr(index) if *index >= module.root_exprs.len() => {
72                return Err(RuntimeError::MissingRootType { index: *index });
73            }
74            Root::Binding(_) | Root::Expr(_) => {}
75        }
76    }
77    for binding in &module.bindings {
78        if let Err(error) = validate_binding(binding, &bindings, &type_arg_kinds) {
79            if std::env::var_os("YULANG_DEBUG_MONO_PIPELINE").is_some() {
80                eprintln!("runtime validation failed in binding {:?}", binding.name);
81            }
82            return Err(error);
83        }
84    }
85    for (index, expr) in module.root_exprs.iter().enumerate() {
86        if let Err(error) = validate_expr(expr, &bindings, &type_arg_kinds, &mut HashMap::new()) {
87            if std::env::var_os("YULANG_DEBUG_MONO_PIPELINE").is_some() {
88                eprintln!("runtime validation failed in root expression {index}");
89            }
90            return Err(error);
91        }
92    }
93    Ok(())
94}
95
96fn validate_binding(
97    binding: &Binding,
98    bindings: &HashMap<typed_ir::Path, BindingInfo>,
99    type_arg_kinds: &TypeArgKinds,
100) -> RuntimeResult<()> {
101    if !binding.type_params.is_empty() {
102        return Ok(());
103    }
104    validate_hir_type_no_any(&binding.body.ty, TypeSource::Validation, type_arg_kinds)?;
105    validate_expr(&binding.body, bindings, type_arg_kinds, &mut HashMap::new())?;
106    let mut vars = BTreeSet::new();
107    collect_type_vars(&binding.scheme.body, &mut vars);
108    require_same_hir_type(
109        &project_runtime_hir_type_with_vars(&binding.scheme.body, &vars),
110        &binding.body.ty,
111        TypeSource::BindingScheme,
112    )
113}
114
115fn infer_type_arg_kinds(bindings: &[Binding]) -> TypeArgKinds {
116    let mut out = TypeArgKinds::new();
117    for binding in bindings {
118        infer_type_arg_kinds_from_hir(&binding.body.ty, &mut out);
119        infer_type_arg_kinds_from_core(&binding.scheme.body, &mut out);
120        infer_type_arg_kinds_from_expr(&binding.body, &mut out);
121    }
122    for binding in bindings {
123        infer_concrete_effect_args_from_expr(&binding.body, &mut out);
124    }
125    for binding in bindings {
126        infer_concrete_effect_args_from_hir(&binding.body.ty, &mut out);
127        infer_concrete_effect_args_from_core(&binding.scheme.body, &mut out);
128    }
129    out
130}
131
132fn infer_type_arg_kinds_from_hir(ty: &RuntimeType, out: &mut TypeArgKinds) {
133    let mut vars = HashMap::<typed_ir::TypeVar, TypeArgKind>::new();
134    collect_hir_type_var_kinds(ty, TypeArgKind::Value, &mut vars);
135    collect_hir_named_arg_kinds(ty, &vars, out);
136}
137
138fn infer_type_arg_kinds_from_core(ty: &typed_ir::Type, out: &mut TypeArgKinds) {
139    let mut vars = HashMap::<typed_ir::TypeVar, TypeArgKind>::new();
140    collect_core_type_var_kinds(ty, TypeArgKind::Value, &mut vars);
141    collect_core_named_arg_kinds(ty, &vars, out);
142}
143
144fn infer_type_arg_kinds_from_expr(expr: &Expr, out: &mut TypeArgKinds) {
145    infer_type_arg_kinds_from_hir(&expr.ty, out);
146    match &expr.kind {
147        ExprKind::Lambda { body, .. }
148        | ExprKind::BindHere { expr: body }
149        | ExprKind::LocalPushId { body, .. }
150        | ExprKind::Pack { expr: body, .. } => infer_type_arg_kinds_from_expr(body, out),
151        ExprKind::Apply { callee, arg, .. } => {
152            infer_type_arg_kinds_from_expr(callee, out);
153            infer_type_arg_kinds_from_expr(arg, out);
154        }
155        ExprKind::If {
156            cond,
157            then_branch,
158            else_branch,
159            ..
160        } => {
161            infer_type_arg_kinds_from_expr(cond, out);
162            infer_type_arg_kinds_from_expr(then_branch, out);
163            infer_type_arg_kinds_from_expr(else_branch, out);
164        }
165        ExprKind::Tuple(items) => {
166            for item in items {
167                infer_type_arg_kinds_from_expr(item, out);
168            }
169        }
170        ExprKind::Record { fields, spread } => {
171            for field in fields {
172                infer_type_arg_kinds_from_expr(&field.value, out);
173            }
174            infer_type_arg_kinds_from_record_spread(spread, out);
175        }
176        ExprKind::Variant { value, .. } => {
177            if let Some(value) = value {
178                infer_type_arg_kinds_from_expr(value, out);
179            }
180        }
181        ExprKind::Select { base, .. } => infer_type_arg_kinds_from_expr(base, out),
182        ExprKind::Match {
183            scrutinee, arms, ..
184        } => {
185            infer_type_arg_kinds_from_expr(scrutinee, out);
186            for arm in arms {
187                infer_type_arg_kinds_from_pattern(&arm.pattern, out);
188                if let Some(guard) = &arm.guard {
189                    infer_type_arg_kinds_from_expr(guard, out);
190                }
191                infer_type_arg_kinds_from_expr(&arm.body, out);
192            }
193        }
194        ExprKind::Block { stmts, tail } => {
195            for stmt in stmts {
196                infer_type_arg_kinds_from_stmt(stmt, out);
197            }
198            if let Some(tail) = tail {
199                infer_type_arg_kinds_from_expr(tail, out);
200            }
201        }
202        ExprKind::Handle { body, arms, .. } => {
203            infer_type_arg_kinds_from_expr(body, out);
204            for arm in arms {
205                infer_type_arg_kinds_from_pattern(&arm.payload, out);
206                if let Some(resume) = &arm.resume {
207                    infer_type_arg_kinds_from_hir(&resume.ty, out);
208                }
209                if let Some(guard) = &arm.guard {
210                    infer_type_arg_kinds_from_expr(guard, out);
211                }
212                infer_type_arg_kinds_from_expr(&arm.body, out);
213            }
214        }
215        ExprKind::Thunk {
216            effect,
217            value,
218            expr,
219        } => {
220            infer_type_arg_kinds_from_core(effect, out);
221            infer_type_arg_kinds_from_hir(value, out);
222            infer_type_arg_kinds_from_expr(expr, out);
223        }
224        ExprKind::AddId { allowed, thunk, .. } => {
225            infer_type_arg_kinds_from_core(allowed, out);
226            infer_type_arg_kinds_from_expr(thunk, out);
227        }
228        ExprKind::Coerce { from, to, expr } => {
229            infer_type_arg_kinds_from_core(from, out);
230            infer_type_arg_kinds_from_core(to, out);
231            infer_type_arg_kinds_from_expr(expr, out);
232        }
233        ExprKind::Var(_)
234        | ExprKind::EffectOp(_)
235        | ExprKind::PrimitiveOp(_)
236        | ExprKind::Lit(_)
237        | ExprKind::PeekId
238        | ExprKind::FindId { .. } => {}
239    }
240}
241
242fn collect_hir_type_var_kinds(
243    ty: &RuntimeType,
244    slot: TypeArgKind,
245    out: &mut HashMap<typed_ir::TypeVar, TypeArgKind>,
246) {
247    match ty {
248        RuntimeType::Unknown => {}
249        RuntimeType::Core(ty) => collect_core_type_var_kinds(ty, slot, out),
250        RuntimeType::Fun { param, ret } => {
251            collect_hir_type_var_kinds(param, TypeArgKind::Value, out);
252            collect_hir_type_var_kinds(ret, TypeArgKind::Value, out);
253        }
254        RuntimeType::Thunk { effect, value } => {
255            collect_core_type_var_kinds(effect, TypeArgKind::Effect, out);
256            collect_hir_type_var_kinds(value, TypeArgKind::Value, out);
257        }
258    }
259}
260
261fn collect_core_type_var_kinds(
262    ty: &typed_ir::Type,
263    slot: TypeArgKind,
264    out: &mut HashMap<typed_ir::TypeVar, TypeArgKind>,
265) {
266    match ty {
267        typed_ir::Type::Var(var) => merge_var_kind(out, var.clone(), slot),
268        typed_ir::Type::Named { args, .. } => {
269            for arg in args {
270                match arg {
271                    typed_ir::TypeArg::Type(ty) => {
272                        collect_core_type_var_kinds(ty, TypeArgKind::Value, out)
273                    }
274                    typed_ir::TypeArg::Bounds(bounds) => {
275                        if let Some(lower) = bounds.lower.as_deref() {
276                            collect_core_type_var_kinds(lower, TypeArgKind::Value, out);
277                        }
278                        if let Some(upper) = bounds.upper.as_deref() {
279                            collect_core_type_var_kinds(upper, TypeArgKind::Value, out);
280                        }
281                    }
282                }
283            }
284        }
285        typed_ir::Type::Fun {
286            param,
287            param_effect,
288            ret_effect,
289            ret,
290        } => {
291            collect_core_type_var_kinds(param, TypeArgKind::Value, out);
292            collect_core_type_var_kinds(param_effect, TypeArgKind::Effect, out);
293            collect_core_type_var_kinds(ret_effect, TypeArgKind::Effect, out);
294            collect_core_type_var_kinds(ret, TypeArgKind::Value, out);
295        }
296        typed_ir::Type::Tuple(items)
297        | typed_ir::Type::Union(items)
298        | typed_ir::Type::Inter(items) => {
299            for item in items {
300                collect_core_type_var_kinds(item, slot, out);
301            }
302        }
303        typed_ir::Type::Record(record) => {
304            for field in &record.fields {
305                collect_core_type_var_kinds(&field.value, TypeArgKind::Value, out);
306            }
307            if let Some(spread) = &record.spread {
308                match spread {
309                    typed_ir::RecordSpread::Head(ty) | typed_ir::RecordSpread::Tail(ty) => {
310                        collect_core_type_var_kinds(ty, TypeArgKind::Value, out);
311                    }
312                }
313            }
314        }
315        typed_ir::Type::Variant(variant) => {
316            for case in &variant.cases {
317                for payload in &case.payloads {
318                    collect_core_type_var_kinds(payload, TypeArgKind::Value, out);
319                }
320            }
321            if let Some(tail) = variant.tail.as_deref() {
322                collect_core_type_var_kinds(tail, TypeArgKind::Value, out);
323            }
324        }
325        typed_ir::Type::Row { items, tail } => {
326            for item in items {
327                collect_core_type_var_kinds(item, TypeArgKind::Effect, out);
328            }
329            collect_core_type_var_kinds(tail, TypeArgKind::Effect, out);
330        }
331        typed_ir::Type::Recursive { body, .. } => collect_core_type_var_kinds(body, slot, out),
332        typed_ir::Type::Unknown | typed_ir::Type::Never | typed_ir::Type::Any => {}
333    }
334}
335
336fn collect_hir_named_arg_kinds(
337    ty: &RuntimeType,
338    vars: &HashMap<typed_ir::TypeVar, TypeArgKind>,
339    out: &mut TypeArgKinds,
340) {
341    match ty {
342        RuntimeType::Unknown => {}
343        RuntimeType::Core(ty) => collect_core_named_arg_kinds(ty, vars, out),
344        RuntimeType::Fun { param, ret } => {
345            collect_hir_named_arg_kinds(param, vars, out);
346            collect_hir_named_arg_kinds(ret, vars, out);
347        }
348        RuntimeType::Thunk { effect, value } => {
349            collect_core_named_arg_kinds(effect, vars, out);
350            collect_hir_named_arg_kinds(value, vars, out);
351        }
352    }
353}
354
355fn collect_core_named_arg_kinds(
356    ty: &typed_ir::Type,
357    vars: &HashMap<typed_ir::TypeVar, TypeArgKind>,
358    out: &mut TypeArgKinds,
359) {
360    match ty {
361        typed_ir::Type::Named { path, args } => {
362            for (index, arg) in args.iter().enumerate() {
363                if let Some(var) = type_arg_single_var(arg)
364                    && let Some(kind) = vars.get(&var).copied()
365                {
366                    merge_type_arg_kind(out, path.clone(), index, kind);
367                }
368                match arg {
369                    typed_ir::TypeArg::Type(ty) => collect_core_named_arg_kinds(ty, vars, out),
370                    typed_ir::TypeArg::Bounds(bounds) => {
371                        if let Some(lower) = bounds.lower.as_deref() {
372                            collect_core_named_arg_kinds(lower, vars, out);
373                        }
374                        if let Some(upper) = bounds.upper.as_deref() {
375                            collect_core_named_arg_kinds(upper, vars, out);
376                        }
377                    }
378                }
379            }
380        }
381        typed_ir::Type::Fun {
382            param,
383            param_effect,
384            ret_effect,
385            ret,
386        } => {
387            collect_core_named_arg_kinds(param, vars, out);
388            collect_core_named_arg_kinds(param_effect, vars, out);
389            collect_core_named_arg_kinds(ret_effect, vars, out);
390            collect_core_named_arg_kinds(ret, vars, out);
391        }
392        typed_ir::Type::Tuple(items)
393        | typed_ir::Type::Union(items)
394        | typed_ir::Type::Inter(items) => {
395            for item in items {
396                collect_core_named_arg_kinds(item, vars, out);
397            }
398        }
399        typed_ir::Type::Record(record) => {
400            for field in &record.fields {
401                collect_core_named_arg_kinds(&field.value, vars, out);
402            }
403            if let Some(spread) = &record.spread {
404                match spread {
405                    typed_ir::RecordSpread::Head(ty) | typed_ir::RecordSpread::Tail(ty) => {
406                        collect_core_named_arg_kinds(ty, vars, out);
407                    }
408                }
409            }
410        }
411        typed_ir::Type::Variant(variant) => {
412            for case in &variant.cases {
413                for payload in &case.payloads {
414                    collect_core_named_arg_kinds(payload, vars, out);
415                }
416            }
417            if let Some(tail) = variant.tail.as_deref() {
418                collect_core_named_arg_kinds(tail, vars, out);
419            }
420        }
421        typed_ir::Type::Row { items, tail } => {
422            for item in items {
423                collect_core_named_arg_kinds(item, vars, out);
424            }
425            collect_core_named_arg_kinds(tail, vars, out);
426        }
427        typed_ir::Type::Recursive { body, .. } => collect_core_named_arg_kinds(body, vars, out),
428        typed_ir::Type::Unknown
429        | typed_ir::Type::Var(_)
430        | typed_ir::Type::Never
431        | typed_ir::Type::Any => {}
432    }
433}
434
435fn type_arg_single_var(arg: &typed_ir::TypeArg) -> Option<typed_ir::TypeVar> {
436    match arg {
437        typed_ir::TypeArg::Type(typed_ir::Type::Var(var)) => Some(var.clone()),
438        typed_ir::TypeArg::Bounds(bounds) => {
439            match (bounds.lower.as_deref(), bounds.upper.as_deref()) {
440                (Some(typed_ir::Type::Var(lower)), Some(typed_ir::Type::Var(upper)))
441                    if lower == upper =>
442                {
443                    Some(lower.clone())
444                }
445                _ => None,
446            }
447        }
448        _ => None,
449    }
450}
451
452fn merge_var_kind(
453    out: &mut HashMap<typed_ir::TypeVar, TypeArgKind>,
454    var: typed_ir::TypeVar,
455    kind: TypeArgKind,
456) {
457    let entry = out.entry(var).or_insert(kind);
458    if kind == TypeArgKind::Effect {
459        *entry = TypeArgKind::Effect;
460    }
461}
462
463fn merge_type_arg_kind(
464    out: &mut TypeArgKinds,
465    path: typed_ir::Path,
466    index: usize,
467    kind: TypeArgKind,
468) {
469    let kinds = out.entry(path).or_default();
470    if kinds.len() <= index {
471        kinds.resize(index + 1, TypeArgKind::Value);
472    }
473    if kind == TypeArgKind::Effect {
474        kinds[index] = TypeArgKind::Effect;
475    }
476}
477
478fn infer_concrete_effect_args_from_hir(ty: &RuntimeType, out: &mut TypeArgKinds) {
479    match ty {
480        RuntimeType::Unknown => {}
481        RuntimeType::Core(ty) => infer_concrete_effect_args_from_core(ty, out),
482        RuntimeType::Fun { param, ret } => {
483            infer_concrete_effect_args_from_hir(param, out);
484            infer_concrete_effect_args_from_hir(ret, out);
485        }
486        RuntimeType::Thunk { effect, value } => {
487            infer_concrete_effect_args_from_core(effect, out);
488            infer_concrete_effect_args_from_hir(value, out);
489        }
490    }
491}
492
493fn infer_concrete_effect_args_from_expr(expr: &Expr, out: &mut TypeArgKinds) {
494    infer_concrete_effect_args_from_hir(&expr.ty, out);
495    match &expr.kind {
496        ExprKind::Lambda { body, .. }
497        | ExprKind::BindHere { expr: body }
498        | ExprKind::LocalPushId { body, .. }
499        | ExprKind::Pack { expr: body, .. } => infer_concrete_effect_args_from_expr(body, out),
500        ExprKind::Apply { callee, arg, .. } => {
501            infer_concrete_effect_args_from_expr(callee, out);
502            infer_concrete_effect_args_from_expr(arg, out);
503        }
504        ExprKind::If {
505            cond,
506            then_branch,
507            else_branch,
508            ..
509        } => {
510            infer_concrete_effect_args_from_expr(cond, out);
511            infer_concrete_effect_args_from_expr(then_branch, out);
512            infer_concrete_effect_args_from_expr(else_branch, out);
513        }
514        ExprKind::Tuple(items) => {
515            for item in items {
516                infer_concrete_effect_args_from_expr(item, out);
517            }
518        }
519        ExprKind::Record { fields, spread } => {
520            for field in fields {
521                infer_concrete_effect_args_from_expr(&field.value, out);
522            }
523            infer_concrete_effect_args_from_record_spread(spread, out);
524        }
525        ExprKind::Variant { value, .. } => {
526            if let Some(value) = value {
527                infer_concrete_effect_args_from_expr(value, out);
528            }
529        }
530        ExprKind::Select { base, .. } => infer_concrete_effect_args_from_expr(base, out),
531        ExprKind::Match {
532            scrutinee, arms, ..
533        } => {
534            infer_concrete_effect_args_from_expr(scrutinee, out);
535            for arm in arms {
536                infer_concrete_effect_args_from_pattern(&arm.pattern, out);
537                if let Some(guard) = &arm.guard {
538                    infer_concrete_effect_args_from_expr(guard, out);
539                }
540                infer_concrete_effect_args_from_expr(&arm.body, out);
541            }
542        }
543        ExprKind::Block { stmts, tail } => {
544            for stmt in stmts {
545                infer_concrete_effect_args_from_stmt(stmt, out);
546            }
547            if let Some(tail) = tail {
548                infer_concrete_effect_args_from_expr(tail, out);
549            }
550        }
551        ExprKind::Handle { body, arms, .. } => {
552            infer_concrete_effect_args_from_expr(body, out);
553            for arm in arms {
554                infer_concrete_effect_args_from_pattern(&arm.payload, out);
555                if let Some(resume) = &arm.resume {
556                    infer_concrete_effect_args_from_hir(&resume.ty, out);
557                }
558                if let Some(guard) = &arm.guard {
559                    infer_concrete_effect_args_from_expr(guard, out);
560                }
561                infer_concrete_effect_args_from_expr(&arm.body, out);
562            }
563        }
564        ExprKind::Thunk {
565            effect,
566            value,
567            expr,
568        } => {
569            infer_concrete_effect_args_from_core(effect, out);
570            infer_concrete_effect_args_from_hir(value, out);
571            infer_concrete_effect_args_from_expr(expr, out);
572        }
573        ExprKind::AddId { allowed, thunk, .. } => {
574            infer_concrete_effect_args_from_core(allowed, out);
575            infer_concrete_effect_args_from_expr(thunk, out);
576        }
577        ExprKind::Coerce { from, to, expr } => {
578            infer_concrete_effect_args_from_core(from, out);
579            infer_concrete_effect_args_from_core(to, out);
580            infer_concrete_effect_args_from_expr(expr, out);
581        }
582        ExprKind::Var(_)
583        | ExprKind::EffectOp(_)
584        | ExprKind::PrimitiveOp(_)
585        | ExprKind::Lit(_)
586        | ExprKind::PeekId
587        | ExprKind::FindId { .. } => {}
588    }
589}
590
591fn infer_concrete_effect_args_from_core(ty: &typed_ir::Type, out: &mut TypeArgKinds) {
592    match ty {
593        typed_ir::Type::Named { path, args } => {
594            for (index, arg) in args.iter().enumerate() {
595                match arg {
596                    typed_ir::TypeArg::Type(arg_ty) => {
597                        if matches!(arg_ty, typed_ir::Type::Row { .. }) {
598                            merge_type_arg_kind(out, path.clone(), index, TypeArgKind::Effect);
599                        }
600                        infer_concrete_effect_args_from_core(arg_ty, out);
601                    }
602                    typed_ir::TypeArg::Bounds(bounds) => {
603                        if let Some(lower) = bounds.lower.as_deref() {
604                            infer_concrete_effect_args_from_core(lower, out);
605                        }
606                        if let Some(upper) = bounds.upper.as_deref() {
607                            infer_concrete_effect_args_from_core(upper, out);
608                        }
609                    }
610                }
611            }
612        }
613        typed_ir::Type::Fun {
614            param,
615            param_effect,
616            ret_effect,
617            ret,
618        } => {
619            infer_concrete_effect_args_from_core(param, out);
620            infer_concrete_effect_args_from_core(param_effect, out);
621            infer_concrete_effect_args_from_core(ret_effect, out);
622            infer_concrete_effect_args_from_core(ret, out);
623        }
624        typed_ir::Type::Tuple(items)
625        | typed_ir::Type::Union(items)
626        | typed_ir::Type::Inter(items) => {
627            for item in items {
628                infer_concrete_effect_args_from_core(item, out);
629            }
630        }
631        typed_ir::Type::Record(record) => {
632            for field in &record.fields {
633                infer_concrete_effect_args_from_core(&field.value, out);
634            }
635            if let Some(spread) = &record.spread {
636                match spread {
637                    typed_ir::RecordSpread::Head(ty) | typed_ir::RecordSpread::Tail(ty) => {
638                        infer_concrete_effect_args_from_core(ty, out);
639                    }
640                }
641            }
642        }
643        typed_ir::Type::Variant(variant) => {
644            for case in &variant.cases {
645                for payload in &case.payloads {
646                    infer_concrete_effect_args_from_core(payload, out);
647                }
648            }
649            if let Some(tail) = variant.tail.as_deref() {
650                infer_concrete_effect_args_from_core(tail, out);
651            }
652        }
653        typed_ir::Type::Row { items, tail } => {
654            for item in items {
655                infer_concrete_effect_args_from_core(item, out);
656            }
657            infer_concrete_effect_args_from_core(tail, out);
658        }
659        typed_ir::Type::Recursive { body, .. } => infer_concrete_effect_args_from_core(body, out),
660        typed_ir::Type::Unknown
661        | typed_ir::Type::Var(_)
662        | typed_ir::Type::Never
663        | typed_ir::Type::Any => {}
664    }
665}
666
667fn infer_type_arg_kinds_from_stmt(stmt: &Stmt, out: &mut TypeArgKinds) {
668    match stmt {
669        Stmt::Let { pattern, value } => {
670            infer_type_arg_kinds_from_pattern(pattern, out);
671            infer_type_arg_kinds_from_expr(value, out);
672        }
673        Stmt::Expr(expr) => infer_type_arg_kinds_from_expr(expr, out),
674        Stmt::Module { body, .. } => infer_type_arg_kinds_from_expr(body, out),
675    }
676}
677
678fn infer_concrete_effect_args_from_stmt(stmt: &Stmt, out: &mut TypeArgKinds) {
679    match stmt {
680        Stmt::Let { pattern, value } => {
681            infer_concrete_effect_args_from_pattern(pattern, out);
682            infer_concrete_effect_args_from_expr(value, out);
683        }
684        Stmt::Expr(expr) => infer_concrete_effect_args_from_expr(expr, out),
685        Stmt::Module { body, .. } => infer_concrete_effect_args_from_expr(body, out),
686    }
687}
688
689fn infer_type_arg_kinds_from_pattern(pattern: &Pattern, out: &mut TypeArgKinds) {
690    infer_type_arg_kinds_from_hir(pattern_ty(pattern), out);
691    match pattern {
692        Pattern::Tuple { items, .. } => {
693            for item in items {
694                infer_type_arg_kinds_from_pattern(item, out);
695            }
696        }
697        Pattern::List {
698            prefix,
699            spread,
700            suffix,
701            ..
702        } => {
703            for item in prefix.iter().chain(suffix) {
704                infer_type_arg_kinds_from_pattern(item, out);
705            }
706            if let Some(spread) = spread {
707                infer_type_arg_kinds_from_pattern(spread, out);
708            }
709        }
710        Pattern::Record { fields, spread, .. } => {
711            for field in fields {
712                infer_type_arg_kinds_from_pattern(&field.pattern, out);
713                if let Some(default) = &field.default {
714                    infer_type_arg_kinds_from_expr(default, out);
715                }
716            }
717            infer_type_arg_kinds_from_record_spread_pattern(spread, out);
718        }
719        Pattern::Variant { value, .. } => {
720            if let Some(value) = value {
721                infer_type_arg_kinds_from_pattern(value, out);
722            }
723        }
724        Pattern::Or { left, right, .. } => {
725            infer_type_arg_kinds_from_pattern(left, out);
726            infer_type_arg_kinds_from_pattern(right, out);
727        }
728        Pattern::As { pattern, .. } => infer_type_arg_kinds_from_pattern(pattern, out),
729        Pattern::Wildcard { .. } | Pattern::Bind { .. } | Pattern::Lit { .. } => {}
730    }
731}
732
733fn infer_concrete_effect_args_from_pattern(pattern: &Pattern, out: &mut TypeArgKinds) {
734    infer_concrete_effect_args_from_hir(pattern_ty(pattern), out);
735    match pattern {
736        Pattern::Tuple { items, .. } => {
737            for item in items {
738                infer_concrete_effect_args_from_pattern(item, out);
739            }
740        }
741        Pattern::List {
742            prefix,
743            spread,
744            suffix,
745            ..
746        } => {
747            for item in prefix.iter().chain(suffix) {
748                infer_concrete_effect_args_from_pattern(item, out);
749            }
750            if let Some(spread) = spread {
751                infer_concrete_effect_args_from_pattern(spread, out);
752            }
753        }
754        Pattern::Record { fields, spread, .. } => {
755            for field in fields {
756                infer_concrete_effect_args_from_pattern(&field.pattern, out);
757                if let Some(default) = &field.default {
758                    infer_concrete_effect_args_from_expr(default, out);
759                }
760            }
761            infer_concrete_effect_args_from_record_spread_pattern(spread, out);
762        }
763        Pattern::Variant { value, .. } => {
764            if let Some(value) = value {
765                infer_concrete_effect_args_from_pattern(value, out);
766            }
767        }
768        Pattern::Or { left, right, .. } => {
769            infer_concrete_effect_args_from_pattern(left, out);
770            infer_concrete_effect_args_from_pattern(right, out);
771        }
772        Pattern::As { pattern, .. } => infer_concrete_effect_args_from_pattern(pattern, out),
773        Pattern::Wildcard { .. } | Pattern::Bind { .. } | Pattern::Lit { .. } => {}
774    }
775}
776
777fn infer_type_arg_kinds_from_record_spread(
778    spread: &Option<RecordSpreadExpr>,
779    out: &mut TypeArgKinds,
780) {
781    if let Some(RecordSpreadExpr::Head(expr) | RecordSpreadExpr::Tail(expr)) = spread {
782        infer_type_arg_kinds_from_expr(expr, out);
783    }
784}
785
786fn infer_concrete_effect_args_from_record_spread(
787    spread: &Option<RecordSpreadExpr>,
788    out: &mut TypeArgKinds,
789) {
790    if let Some(RecordSpreadExpr::Head(expr) | RecordSpreadExpr::Tail(expr)) = spread {
791        infer_concrete_effect_args_from_expr(expr, out);
792    }
793}
794
795fn infer_type_arg_kinds_from_record_spread_pattern(
796    spread: &Option<RecordSpreadPattern>,
797    out: &mut TypeArgKinds,
798) {
799    if let Some(RecordSpreadPattern::Head(pattern) | RecordSpreadPattern::Tail(pattern)) = spread {
800        infer_type_arg_kinds_from_pattern(pattern, out);
801    }
802}
803
804fn infer_concrete_effect_args_from_record_spread_pattern(
805    spread: &Option<RecordSpreadPattern>,
806    out: &mut TypeArgKinds,
807) {
808    if let Some(RecordSpreadPattern::Head(pattern) | RecordSpreadPattern::Tail(pattern)) = spread {
809        infer_concrete_effect_args_from_pattern(pattern, out);
810    }
811}