Skip to main content

aver/interpreter/
mod.rs

1use crate::nan_value::{Arena, NanValue};
2use std::collections::{HashMap, HashSet};
3use std::path::{Path, PathBuf};
4use std::sync::Arc as Rc;
5
6use crate::ast::*;
7use crate::replay::{
8    EffectRecord, EffectReplayMode, EffectReplayState, JsonValue, RecordedOutcome,
9    SessionRecording, session_recording_to_string_pretty, value_to_json, values_to_json_lossy,
10};
11#[cfg(feature = "terminal")]
12use crate::services::terminal;
13use crate::services::{args, console, disk, env, http, http_server, random, tcp, time};
14use crate::source::{
15    canonicalize_path, find_module_file, parse_source, require_module_declaration,
16};
17use crate::types::{bool, byte, char, float, int, list, map, option, result, string, vector};
18// Re-export value types so existing `use aver::interpreter::Value` imports keep working.
19pub use crate::value::{Env, EnvFrame, RuntimeError, Value, aver_display, aver_repr};
20use crate::value::{list_len, list_view};
21
22#[derive(Debug, Clone)]
23struct CallFrame {
24    name: Rc<String>,
25    effects: Rc<Vec<String>>,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ExecutionMode {
30    Normal,
31    Record,
32    Replay,
33}
34
35const MEMO_CACHE_CAP_PER_FN: usize = 4096;
36
37#[derive(Debug, Clone)]
38struct MemoEntry {
39    id: u64,
40    args: Vec<Value>,
41    result: Value,
42}
43
44#[derive(Debug, Clone)]
45struct RecordingSink {
46    path: PathBuf,
47    request_id: String,
48    timestamp: String,
49    program_file: String,
50    module_root: String,
51    entry_fn: String,
52    input: JsonValue,
53}
54
55type MatchSiteKey = (usize, usize); // (line, arm_count)
56
57#[derive(Debug, Clone)]
58struct VerifyMatchCoverageTracker {
59    target_fn: String,
60    expected_arms: std::collections::BTreeMap<MatchSiteKey, usize>,
61    visited_arms: HashMap<MatchSiteKey, HashSet<usize>>,
62}
63
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct VerifyMatchCoverageMiss {
66    pub line: usize,
67    pub total_arms: usize,
68    pub missing_arms: Vec<usize>, // 0-based arm indices
69}
70
71#[derive(Debug, Clone)]
72pub struct RecordingConfig {
73    pub path: PathBuf,
74    pub request_id: String,
75    pub timestamp: String,
76    pub program_file: String,
77    pub module_root: String,
78    pub entry_fn: String,
79    pub input: JsonValue,
80}
81
82/// Per-function memo cache with collision-safe buckets and true LRU eviction.
83#[derive(Debug, Default, Clone)]
84struct FnMemoCache {
85    /// Primary index: hash(args) -> bucket of potentially colliding entries.
86    buckets: HashMap<u64, Vec<MemoEntry>>,
87    /// Entry id -> (bucket hash, index in bucket vec).
88    positions: HashMap<u64, (u64, usize)>,
89    /// LRU links: entry id -> (prev, next).
90    links: HashMap<u64, (Option<u64>, Option<u64>)>,
91    lru_head: Option<u64>,
92    lru_tail: Option<u64>,
93    next_id: u64,
94    len: usize,
95}
96
97impl FnMemoCache {
98    fn get(&mut self, hash: u64, args: &[Value]) -> Option<Value> {
99        let found = self
100            .buckets
101            .get_mut(&hash)
102            .and_then(|entries| entries.iter_mut().find(|entry| entry.args == args))
103            .map(|entry| (entry.id, entry.result.clone()));
104
105        if let Some((id, value)) = found {
106            self.touch(id);
107            Some(value)
108        } else {
109            None
110        }
111    }
112
113    fn insert(&mut self, hash: u64, args: Vec<Value>, result: Value, cap: usize) {
114        let update_hit = self
115            .buckets
116            .get_mut(&hash)
117            .and_then(|entries| entries.iter_mut().find(|entry| entry.args == args))
118            .map(|entry| {
119                entry.result = result.clone();
120                entry.id
121            });
122
123        if let Some(id) = update_hit {
124            self.touch(id);
125            return;
126        }
127
128        if self.len >= cap {
129            self.evict_lru();
130        }
131
132        let id = self.alloc_id();
133        let entry = MemoEntry { id, args, result };
134        let idx = self.buckets.entry(hash).or_default().len();
135        self.buckets.entry(hash).or_default().push(entry);
136        self.positions.insert(id, (hash, idx));
137        self.append_tail(id);
138        self.len += 1;
139    }
140
141    fn alloc_id(&mut self) -> u64 {
142        let id = self.next_id;
143        self.next_id = self.next_id.wrapping_add(1);
144        id
145    }
146
147    fn evict_lru(&mut self) {
148        if let Some(id) = self.lru_head {
149            self.remove_entry(id);
150        }
151    }
152
153    fn touch(&mut self, id: u64) {
154        if self.lru_tail == Some(id) {
155            return;
156        }
157        self.detach(id);
158        self.append_tail(id);
159    }
160
161    fn append_tail(&mut self, id: u64) {
162        let prev = self.lru_tail;
163        self.links.insert(id, (prev, None));
164        if let Some(tail) = prev {
165            if let Some((_, next)) = self.links.get_mut(&tail) {
166                *next = Some(id);
167            }
168        } else {
169            self.lru_head = Some(id);
170        }
171        self.lru_tail = Some(id);
172    }
173
174    fn detach(&mut self, id: u64) {
175        let Some((prev, next)) = self.links.get(&id).copied() else {
176            return;
177        };
178
179        if let Some(p) = prev {
180            if let Some((_, p_next)) = self.links.get_mut(&p) {
181                *p_next = next;
182            }
183        } else {
184            self.lru_head = next;
185        }
186
187        if let Some(n) = next {
188            if let Some((n_prev, _)) = self.links.get_mut(&n) {
189                *n_prev = prev;
190            }
191        } else {
192            self.lru_tail = prev;
193        }
194
195        if let Some(link) = self.links.get_mut(&id) {
196            *link = (None, None);
197        }
198    }
199
200    fn remove_entry(&mut self, id: u64) {
201        let Some((hash, idx)) = self.positions.remove(&id) else {
202            return;
203        };
204        self.detach(id);
205        self.links.remove(&id);
206
207        let mut remove_bucket = false;
208        if let Some(entries) = self.buckets.get_mut(&hash) {
209            entries.swap_remove(idx);
210            if idx < entries.len() {
211                let moved_id = entries[idx].id;
212                self.positions.insert(moved_id, (hash, idx));
213            }
214            remove_bucket = entries.is_empty();
215        }
216        if remove_bucket {
217            self.buckets.remove(&hash);
218        }
219        self.len = self.len.saturating_sub(1);
220    }
221
222    /// NanValue-native get — converts NanValue args to Value for comparison,
223    /// returns cached Value. Arena conversion done by caller.
224    fn get_nv_as_value(&mut self, hash: u64, nv_args: &[NanValue], arena: &Arena) -> Option<Value> {
225        let args: Vec<Value> = nv_args.iter().map(|nv| nv.to_value(arena)).collect();
226        self.get(hash, &args)
227    }
228
229    /// NanValue-native insert — stores args and result as Value (bridge).
230    fn insert_nv(
231        &mut self,
232        hash: u64,
233        nv_args: Vec<NanValue>,
234        nv_result: NanValue,
235        arena: &Arena,
236        cap: usize,
237    ) {
238        let args: Vec<Value> = nv_args.iter().map(|nv| nv.to_value(arena)).collect();
239        let result = nv_result.to_value(arena);
240        self.insert(hash, args, result, cap);
241    }
242}
243
244pub struct Interpreter {
245    pub env: Env,
246    /// Base index into `env` for the current function's frames.
247    /// lookup_ref sees env[0] (global) + env[env_base..] (current fn).
248    /// Caller frames in env[1..env_base] are invisible.
249    env_base: usize,
250    /// Arena for NaN-boxed value storage.
251    pub arena: Arena,
252    module_cache: HashMap<String, Value>,
253    mounted_module_paths: HashSet<String>,
254    /// Record field order schemas by type name (used to validate and
255    /// canonicalize `RecordCreate` runtime values).
256    record_schemas: HashMap<String, Vec<String>>,
257    call_stack: Vec<CallFrame>,
258    /// Active slot mapping for resolved function bodies.
259    /// Set when entering a resolved fn, cleared on exit.
260    active_local_slots: Option<Rc<HashMap<String, u16>>>,
261    /// Names of pure recursive functions eligible for auto-memoization.
262    memo_fns: HashSet<String>,
263    /// Per-function memo cache with collision-safe entries and LRU eviction.
264    memo_cache: HashMap<String, FnMemoCache>,
265    replay_state: EffectReplayState,
266    recording_sink: Option<RecordingSink>,
267    verify_match_coverage: Option<VerifyMatchCoverageTracker>,
268    /// Runtime policy from `aver.toml` — constrains Http hosts, Disk paths, etc.
269    runtime_policy: Option<crate::config::ProjectConfig>,
270    /// Command-line arguments passed to the Aver program (available via `Args.get()`).
271    cli_args: Vec<String>,
272    /// Source line of the current call expression (set before dispatch, used for
273    /// effect recording and error decoration). 0 = unknown.
274    pub(crate) last_call_line: usize,
275}
276
277mod api;
278mod builtins;
279mod core;
280mod effects;
281mod eval;
282mod exec;
283mod ir_bridge;
284pub(crate) mod lowered;
285mod ops;
286mod patterns;
287
288#[cfg(test)]
289mod memo_cache_tests {
290    use super::*;
291
292    #[test]
293    fn collision_bucket_is_exact_match_on_args() {
294        let mut cache = FnMemoCache::default();
295        cache.insert(1, vec![Value::Int(1)], Value::Int(10), 8);
296        cache.insert(1, vec![Value::Int(2)], Value::Int(20), 8);
297
298        assert_eq!(cache.get(1, &[Value::Int(1)]), Some(Value::Int(10)));
299        assert_eq!(cache.get(1, &[Value::Int(2)]), Some(Value::Int(20)));
300        assert_eq!(cache.get(1, &[Value::Int(3)]), None);
301    }
302
303    #[test]
304    fn lru_evicts_least_recently_used() {
305        let mut cache = FnMemoCache::default();
306        cache.insert(11, vec![Value::Int(1)], Value::Int(10), 2);
307        cache.insert(22, vec![Value::Int(2)], Value::Int(20), 2);
308
309        // Touch key=11 so key=22 becomes LRU.
310        assert_eq!(cache.get(11, &[Value::Int(1)]), Some(Value::Int(10)));
311        cache.insert(33, vec![Value::Int(3)], Value::Int(30), 2);
312
313        assert_eq!(cache.get(11, &[Value::Int(1)]), Some(Value::Int(10)));
314        assert_eq!(cache.get(22, &[Value::Int(2)]), None);
315        assert_eq!(cache.get(33, &[Value::Int(3)]), Some(Value::Int(30)));
316    }
317}
318
319#[cfg(test)]
320mod ir_bridge_tests {
321    use aver_rt::AverVector;
322
323    use crate::ir::CallLowerCtx;
324
325    use super::ir_bridge::InterpreterLowerCtx;
326    use super::lowered::{
327        self, ExprId, LoweredDirectCallTarget, LoweredExpr, LoweredForwardArg, LoweredLeafOp,
328        LoweredMatchArm, LoweredTailCallTarget,
329    };
330    use super::*;
331
332    /// Shorthand: wrap an Expr in Spanned::bare (line=0).
333    fn sb(expr: Expr) -> Spanned<Expr> {
334        Spanned::bare(expr)
335    }
336
337    /// Shorthand: wrap an Expr in Box<Spanned::bare(...)>.
338    fn sbb(expr: Expr) -> Box<Spanned<Expr>> {
339        Box::new(Spanned::bare(expr))
340    }
341
342    fn register_task_event_type(interpreter: &mut Interpreter) {
343        interpreter.register_type_def(&TypeDef::Sum {
344            name: "TaskEvent".to_string(),
345            variants: vec![
346                TypeVariant {
347                    name: "TaskCreated".to_string(),
348                    fields: vec!["String".to_string()],
349                },
350                TypeVariant {
351                    name: "TaskMoved".to_string(),
352                    fields: vec!["String".to_string(), "Int".to_string()],
353                },
354            ],
355            line: 1,
356        });
357
358        let mut members = HashMap::new();
359        members.insert(
360            "TaskEvent".to_string(),
361            interpreter
362                .lookup("TaskEvent")
363                .expect("TaskEvent namespace should be defined"),
364        );
365        interpreter
366            .define_module_path(
367                "Domain.Types",
368                Value::Namespace {
369                    name: "Types".to_string(),
370                    members,
371                },
372            )
373            .expect("module path should be mountable");
374    }
375
376    fn register_expr_type(interpreter: &mut Interpreter) {
377        interpreter.register_type_def(&TypeDef::Sum {
378            name: "Expr".to_string(),
379            variants: vec![TypeVariant {
380                name: "ExprInt".to_string(),
381                fields: vec!["Int".to_string()],
382            }],
383            line: 1,
384        });
385    }
386
387    fn register_token_type(interpreter: &mut Interpreter) {
388        interpreter.register_type_def(&TypeDef::Sum {
389            name: "Token".to_string(),
390            variants: vec![
391                TypeVariant {
392                    name: "TkInt".to_string(),
393                    fields: vec!["Int".to_string()],
394                },
395                TypeVariant {
396                    name: "TkQuestion".to_string(),
397                    fields: vec![],
398                },
399            ],
400            line: 1,
401        });
402
403        let mut members = HashMap::new();
404        members.insert(
405            "Token".to_string(),
406            interpreter
407                .lookup("Token")
408                .expect("Token type namespace should be defined"),
409        );
410        interpreter
411            .define_module_path(
412                "Domain.Token",
413                Value::Namespace {
414                    name: "Domain.Token".to_string(),
415                    members,
416                },
417            )
418            .expect("module path should be mountable");
419    }
420
421    #[test]
422    fn eval_constructor_uses_shared_semantics_for_wrappers_and_qualified_variants() {
423        let mut interpreter = Interpreter::new();
424        register_task_event_type(&mut interpreter);
425
426        let ok_expr = sb(Expr::Constructor(
427            "Ok".to_string(),
428            Some(sbb(Expr::Literal(Literal::Int(7)))),
429        ));
430        let created_expr = sb(Expr::Constructor(
431            "Domain.Types.TaskEvent.TaskCreated".to_string(),
432            Some(sbb(Expr::Literal(Literal::Str("now".to_string())))),
433        ));
434
435        assert_eq!(
436            interpreter
437                .eval_expr(&ok_expr)
438                .expect("Ok constructor should evaluate"),
439            Value::Ok(Box::new(Value::Int(7)))
440        );
441
442        match interpreter
443            .eval_expr(&created_expr)
444            .expect("qualified constructor should build a variant")
445        {
446            Value::Variant {
447                type_name,
448                variant,
449                fields,
450            } => {
451                assert_eq!(type_name, "TaskEvent");
452                assert_eq!(variant, "TaskCreated");
453                assert_eq!(fields.as_ref(), &[Value::Str("now".to_string())]);
454            }
455            other => panic!("expected variant, got {other:?}"),
456        }
457    }
458
459    #[test]
460    fn qualified_module_function_call_prefers_deepest_module_prefix_over_type_namespace() {
461        let mut interpreter = Interpreter::new();
462        register_expr_type(&mut interpreter);
463
464        let parse_expr = FnDef {
465            name: "parseExpr".to_string(),
466            line: 1,
467            params: vec![
468                ("tokens".to_string(), "List<Int>".to_string()),
469                ("pos".to_string(), "Int".to_string()),
470            ],
471            return_type: "Int".to_string(),
472            effects: vec![],
473            desc: None,
474            body: Rc::new(FnBody::from_expr(sb(Expr::Literal(Literal::Int(7))))),
475            resolution: None,
476        };
477        interpreter
478            .exec_fn_def(&parse_expr)
479            .expect("parseExpr function should register");
480
481        let mut members = HashMap::new();
482        members.insert(
483            "parseExpr".to_string(),
484            interpreter
485                .lookup("parseExpr")
486                .expect("parseExpr function should be available"),
487        );
488        interpreter
489            .define_module_path(
490                "Domain.Parser.Expr",
491                Value::Namespace {
492                    name: "Domain.Parser.Expr".to_string(),
493                    members,
494                },
495            )
496            .expect("module path should be mountable");
497
498        let ctx = InterpreterLowerCtx::new(&interpreter);
499        assert_eq!(
500            ctx.resolve_module_call("Domain.Parser.Expr.parseExpr"),
501            Some(("Domain.Parser.Expr", "parseExpr"))
502        );
503
504        let call = sb(Expr::FnCall(
505            sbb(Expr::Attr(
506                sbb(Expr::Attr(
507                    sbb(Expr::Attr(
508                        sbb(Expr::Ident("Domain".to_string())),
509                        "Parser".to_string(),
510                    )),
511                    "Expr".to_string(),
512                )),
513                "parseExpr".to_string(),
514            )),
515            vec![
516                sb(Expr::List(vec![sb(Expr::Literal(Literal::Int(1)))])),
517                sb(Expr::Literal(Literal::Int(0))),
518            ],
519        ));
520
521        assert_eq!(
522            interpreter
523                .eval_expr(&call)
524                .expect("qualified module function call should run"),
525            Value::Int(7)
526        );
527    }
528
529    #[test]
530    fn qualified_constructor_call_keeps_outer_module_prefix_when_type_namespace_exists() {
531        let mut interpreter = Interpreter::new();
532        register_expr_type(&mut interpreter);
533
534        let mut members = HashMap::new();
535        members.insert(
536            "Expr".to_string(),
537            interpreter
538                .lookup("Expr")
539                .expect("Expr type namespace should be available"),
540        );
541        interpreter
542            .define_module_path(
543                "Domain.Ast",
544                Value::Namespace {
545                    name: "Domain.Ast".to_string(),
546                    members,
547                },
548            )
549            .expect("module path should be mountable");
550
551        let ctx = InterpreterLowerCtx::new(&interpreter);
552        assert_eq!(
553            ctx.resolve_module_call("Domain.Ast.Expr.ExprInt"),
554            Some(("Domain.Ast", "Expr.ExprInt"))
555        );
556
557        let ctor_call = sb(Expr::FnCall(
558            sbb(Expr::Attr(
559                sbb(Expr::Attr(
560                    sbb(Expr::Attr(
561                        sbb(Expr::Ident("Domain".to_string())),
562                        "Ast".to_string(),
563                    )),
564                    "Expr".to_string(),
565                )),
566                "ExprInt".to_string(),
567            )),
568            vec![sb(Expr::Literal(Literal::Int(7)))],
569        ));
570
571        match interpreter
572            .eval_expr(&ctor_call)
573            .expect("qualified constructor call should run")
574        {
575            Value::Variant {
576                type_name,
577                variant,
578                fields,
579            } => {
580                assert_eq!(type_name, "Expr");
581                assert_eq!(variant, "ExprInt");
582                assert_eq!(fields.as_ref(), &[Value::Int(7)]);
583            }
584            other => panic!("expected variant, got {other:?}"),
585        }
586    }
587
588    #[test]
589    fn short_type_aliases_are_not_misclassified_as_module_prefixes() {
590        let mut interpreter = Interpreter::new();
591        register_token_type(&mut interpreter);
592
593        let ctx = InterpreterLowerCtx::new(&interpreter);
594        assert_eq!(ctx.resolve_module_call("Token.TkQuestion"), None);
595
596        let pattern = Pattern::Constructor("Token.TkInt".to_string(), vec!["n".to_string()]);
597        let value = Value::Variant {
598            type_name: "Token".to_string(),
599            variant: "TkInt".to_string(),
600            fields: vec![Value::Int(1)].into(),
601        };
602
603        assert_eq!(
604            interpreter.match_pattern(&pattern, &value),
605            Some(vec![("n".to_string(), Value::Int(1))])
606        );
607
608        let nv = NanValue::from_value(&value, &mut interpreter.arena);
609        let bindings = interpreter
610            .match_pattern_nv(&pattern, nv)
611            .expect("short type alias constructor pattern should match");
612        assert_eq!(bindings.len(), 1);
613        assert_eq!(bindings[0].0, "n");
614        assert_eq!(bindings[0].1.to_value(&interpreter.arena), Value::Int(1));
615    }
616
617    #[test]
618    fn qualified_constructor_patterns_use_shared_semantics_in_both_match_paths() {
619        let mut interpreter = Interpreter::new();
620        register_task_event_type(&mut interpreter);
621
622        let pattern = Pattern::Constructor(
623            "Domain.Types.TaskEvent.TaskCreated".to_string(),
624            vec!["at".to_string()],
625        );
626        let value = Value::Variant {
627            type_name: "TaskEvent".to_string(),
628            variant: "TaskCreated".to_string(),
629            fields: vec![Value::Str("now".to_string())].into(),
630        };
631
632        assert_eq!(
633            interpreter.match_pattern(&pattern, &value),
634            Some(vec![("at".to_string(), Value::Str("now".to_string()))])
635        );
636
637        let nv = NanValue::from_value(&value, &mut interpreter.arena);
638        let bindings = interpreter
639            .match_pattern_nv(&pattern, nv)
640            .expect("nan-value pattern path should match");
641        assert_eq!(bindings.len(), 1);
642        assert_eq!(bindings[0].0, "at");
643        assert_eq!(
644            bindings[0].1.to_value(&interpreter.arena),
645            Value::Str("now".to_string())
646        );
647    }
648
649    #[test]
650    fn constructor_patterns_match_inline_single_field_variants_in_nan_path() {
651        let mut interpreter = Interpreter::new();
652        interpreter.register_type_def(&TypeDef::Sum {
653            name: "Expr".to_string(),
654            variants: vec![
655                TypeVariant {
656                    name: "Int".to_string(),
657                    fields: vec!["Int".to_string()],
658                },
659                TypeVariant {
660                    name: "Text".to_string(),
661                    fields: vec!["String".to_string()],
662                },
663            ],
664            line: 1,
665        });
666
667        let pattern = Pattern::Constructor("Expr.Int".to_string(), vec!["n".to_string()]);
668        let value = Value::Variant {
669            type_name: "Expr".to_string(),
670            variant: "Int".to_string(),
671            fields: vec![Value::Int(7)].into(),
672        };
673
674        let nv = NanValue::from_value(&value, &mut interpreter.arena);
675        let bindings = interpreter
676            .match_pattern_nv(&pattern, nv)
677            .expect("inline one-field variant should match in nan path");
678        assert_eq!(bindings.len(), 1);
679        assert_eq!(bindings[0].0, "n");
680        assert_eq!(bindings[0].1.to_value(&interpreter.arena), Value::Int(7));
681    }
682
683    #[test]
684    fn runtime_match_dispatch_plan_selects_bool_list_and_wrapper_arms() {
685        let mut interpreter = Interpreter::new();
686
687        let bool_arms = vec![
688            LoweredMatchArm {
689                pattern: Pattern::Literal(Literal::Bool(true)),
690                body: ExprId(0),
691            },
692            LoweredMatchArm {
693                pattern: Pattern::Ident("other".to_string()),
694                body: ExprId(1),
695            },
696        ];
697        let (bool_arm, bool_bindings) = interpreter
698            .try_dispatch_match_plan_nv(NanValue::FALSE, &bool_arms)
699            .expect("bool match plan should dispatch");
700        assert_eq!(bool_arm, 1);
701        assert_eq!(bool_bindings.len(), 1);
702        assert_eq!(bool_bindings[0].0, "other");
703        assert_eq!(bool_bindings[0].1.bits(), NanValue::FALSE.bits());
704
705        let non_empty_list = NanValue::new_list(interpreter.arena.push_list(vec![NanValue::TRUE]));
706        let list_arms = vec![
707            LoweredMatchArm {
708                pattern: Pattern::EmptyList,
709                body: ExprId(0),
710            },
711            LoweredMatchArm {
712                pattern: Pattern::Cons("head".to_string(), "tail".to_string()),
713                body: ExprId(1),
714            },
715        ];
716        let (list_arm, list_bindings) = interpreter
717            .try_dispatch_match_plan_nv(non_empty_list, &list_arms)
718            .expect("list match plan should dispatch");
719        assert_eq!(list_arm, 1);
720        assert_eq!(list_bindings.len(), 2);
721        assert_eq!(list_bindings[0].0, "head");
722        assert_eq!(list_bindings[0].1.bits(), NanValue::TRUE.bits());
723
724        let wrapper_arms = vec![
725            LoweredMatchArm {
726                pattern: Pattern::Constructor("Option.None".to_string(), vec![]),
727                body: ExprId(0),
728            },
729            LoweredMatchArm {
730                pattern: Pattern::Constructor("Option.Some".to_string(), vec!["x".to_string()]),
731                body: ExprId(1),
732            },
733            LoweredMatchArm {
734                pattern: Pattern::Ident("fallback".to_string()),
735                body: ExprId(2),
736            },
737        ];
738        let some_subject = NanValue::new_some_value(
739            NanValue::new_int(7, &mut interpreter.arena),
740            &mut interpreter.arena,
741        );
742        let (wrapper_arm, wrapper_bindings) = interpreter
743            .try_dispatch_match_plan_nv(some_subject, &wrapper_arms)
744            .expect("wrapper match plan should dispatch");
745        assert_eq!(wrapper_arm, 1);
746        assert_eq!(wrapper_bindings.len(), 1);
747        assert_eq!(wrapper_bindings[0].0, "x");
748        assert_eq!(wrapper_bindings[0].1.as_int(&interpreter.arena), 7);
749
750        let (default_arm, default_bindings) = interpreter
751            .try_dispatch_match_plan_nv(NanValue::TRUE, &wrapper_arms)
752            .expect("dispatch table default arm should match");
753        assert_eq!(default_arm, 2);
754        assert_eq!(default_bindings.len(), 1);
755        assert_eq!(default_bindings[0].0, "fallback");
756        assert_eq!(default_bindings[0].1.bits(), NanValue::TRUE.bits());
757    }
758
759    #[test]
760    fn lowered_roots_classify_shared_builtin_leaf_ops() {
761        let interpreter = Interpreter::new();
762        let ctx = InterpreterLowerCtx::new(&interpreter);
763
764        let map_get = sb(Expr::FnCall(
765            sbb(Expr::Attr(
766                sbb(Expr::Ident("Map".to_string())),
767                "get".to_string(),
768            )),
769            vec![
770                sb(Expr::Ident("m".to_string())),
771                sb(Expr::Literal(Literal::Str("k".to_string()))),
772            ],
773        ));
774        let (lowered_map_get, map_get_root) = lowered::lower_expr_root(&map_get, &ctx);
775        assert!(matches!(
776            lowered_map_get.expr(map_get_root),
777            LoweredExpr::Leaf(LoweredLeafOp::MapGet { .. })
778        ));
779
780        let vec_default = sb(Expr::FnCall(
781            sbb(Expr::Attr(
782                sbb(Expr::Ident("Option".to_string())),
783                "withDefault".to_string(),
784            )),
785            vec![
786                sb(Expr::FnCall(
787                    sbb(Expr::Attr(
788                        sbb(Expr::Ident("Vector".to_string())),
789                        "get".to_string(),
790                    )),
791                    vec![
792                        sb(Expr::Ident("v".to_string())),
793                        sb(Expr::Ident("idx".to_string())),
794                    ],
795                )),
796                sb(Expr::Literal(Literal::Int(0))),
797            ],
798        ));
799        let (lowered_vec_default, vec_default_root) = lowered::lower_expr_root(&vec_default, &ctx);
800        assert!(matches!(
801            lowered_vec_default.expr(vec_default_root),
802            LoweredExpr::Leaf(LoweredLeafOp::VectorGetOrDefaultLiteral {
803                default_literal: Literal::Int(0),
804                ..
805            })
806        ));
807    }
808
809    #[test]
810    fn runtime_executes_shared_leaf_ops_in_host_interpreter() {
811        let mut interpreter = Interpreter::new();
812
813        let mut map_value = HashMap::new();
814        map_value.insert(Value::Str("k".to_string()), Value::Int(7));
815        interpreter.define("m".to_string(), Value::Map(map_value));
816        interpreter.define(
817            "v".to_string(),
818            Value::Vector(AverVector::from_vec(vec![Value::Int(10), Value::Int(20)])),
819        );
820        interpreter.define("idx".to_string(), Value::Int(1));
821        interpreter.define("miss".to_string(), Value::Int(5));
822
823        let map_get = sb(Expr::FnCall(
824            sbb(Expr::Attr(
825                sbb(Expr::Ident("Map".to_string())),
826                "get".to_string(),
827            )),
828            vec![
829                sb(Expr::Ident("m".to_string())),
830                sb(Expr::Literal(Literal::Str("k".to_string()))),
831            ],
832        ));
833        assert_eq!(
834            interpreter
835                .eval_expr(&map_get)
836                .expect("Map.get leaf should run"),
837            Value::Some(Box::new(Value::Int(7)))
838        );
839
840        let vec_hit = sb(Expr::FnCall(
841            sbb(Expr::Attr(
842                sbb(Expr::Ident("Option".to_string())),
843                "withDefault".to_string(),
844            )),
845            vec![
846                sb(Expr::FnCall(
847                    sbb(Expr::Attr(
848                        sbb(Expr::Ident("Vector".to_string())),
849                        "get".to_string(),
850                    )),
851                    vec![
852                        sb(Expr::Ident("v".to_string())),
853                        sb(Expr::Ident("idx".to_string())),
854                    ],
855                )),
856                sb(Expr::Literal(Literal::Int(0))),
857            ],
858        ));
859        assert_eq!(
860            interpreter
861                .eval_expr(&vec_hit)
862                .expect("Vector.get default leaf should return hit"),
863            Value::Int(20)
864        );
865
866        let vec_miss = sb(Expr::FnCall(
867            sbb(Expr::Attr(
868                sbb(Expr::Ident("Option".to_string())),
869                "withDefault".to_string(),
870            )),
871            vec![
872                sb(Expr::FnCall(
873                    sbb(Expr::Attr(
874                        sbb(Expr::Ident("Vector".to_string())),
875                        "get".to_string(),
876                    )),
877                    vec![
878                        sb(Expr::Ident("v".to_string())),
879                        sb(Expr::Ident("miss".to_string())),
880                    ],
881                )),
882                sb(Expr::Literal(Literal::Int(0))),
883            ],
884        ));
885        assert_eq!(
886            interpreter
887                .eval_expr(&vec_miss)
888                .expect("Vector.get default leaf should return fallback"),
889            Value::Int(0)
890        );
891    }
892
893    #[test]
894    fn lowered_roots_classify_shared_call_plans_for_builtin_and_function_calls() {
895        let mut interpreter = Interpreter::new();
896        register_task_event_type(&mut interpreter);
897        let ctx = InterpreterLowerCtx::new(&interpreter);
898
899        let list_len = sb(Expr::FnCall(
900            sbb(Expr::Attr(
901                sbb(Expr::Ident("List".to_string())),
902                "len".to_string(),
903            )),
904            vec![sb(Expr::Ident("xs".to_string()))],
905        ));
906        let (lowered_builtin, builtin_root) = lowered::lower_expr_root(&list_len, &ctx);
907        assert!(matches!(
908            lowered_builtin.expr(builtin_root),
909            LoweredExpr::DirectCall {
910                target: LoweredDirectCallTarget::Builtin(name),
911                ..
912            } if name == "List.len"
913        ));
914
915        let identity_call = sb(Expr::FnCall(
916            sbb(Expr::Ident("identity".to_string())),
917            vec![sb(Expr::Literal(Literal::Int(7)))],
918        ));
919        let (lowered_fn, fn_root) = lowered::lower_expr_root(&identity_call, &ctx);
920        assert!(matches!(
921            lowered_fn.expr(fn_root),
922            LoweredExpr::DirectCall {
923                target: LoweredDirectCallTarget::Function(name),
924                ..
925            } if name == "identity"
926        ));
927
928        let wrapper_call = sb(Expr::FnCall(
929            sbb(Expr::Attr(
930                sbb(Expr::Ident("Result".to_string())),
931                "Ok".to_string(),
932            )),
933            vec![sb(Expr::Literal(Literal::Int(1)))],
934        ));
935        let (lowered_wrapper, wrapper_root) = lowered::lower_expr_root(&wrapper_call, &ctx);
936        assert!(matches!(
937            lowered_wrapper.expr(wrapper_root),
938            LoweredExpr::DirectCall {
939                target: LoweredDirectCallTarget::Wrapper(crate::ir::WrapperKind::ResultOk),
940                ..
941            }
942        ));
943
944        let none_call = sb(Expr::FnCall(
945            sbb(Expr::Attr(
946                sbb(Expr::Ident("Option".to_string())),
947                "None".to_string(),
948            )),
949            vec![],
950        ));
951        let (lowered_none, none_root) = lowered::lower_expr_root(&none_call, &ctx);
952        assert!(matches!(
953            lowered_none.expr(none_root),
954            LoweredExpr::DirectCall {
955                target: LoweredDirectCallTarget::NoneValue,
956                ..
957            }
958        ));
959
960        let ctor_call = sb(Expr::FnCall(
961            sbb(Expr::Attr(
962                sbb(Expr::Attr(
963                    sbb(Expr::Attr(
964                        sbb(Expr::Ident("Domain".to_string())),
965                        "Types".to_string(),
966                    )),
967                    "TaskEvent".to_string(),
968                )),
969                "TaskCreated".to_string(),
970            )),
971            vec![sb(Expr::Literal(Literal::Str("now".to_string())))],
972        ));
973        let (lowered_ctor, ctor_root) = lowered::lower_expr_root(&ctor_call, &ctx);
974        assert!(matches!(
975            lowered_ctor.expr(ctor_root),
976            LoweredExpr::DirectCall {
977                target: LoweredDirectCallTarget::TypeConstructor {
978                    qualified_type_name,
979                    variant_name,
980                },
981                ..
982            } if qualified_type_name == "Domain.Types.TaskEvent" && variant_name == "TaskCreated"
983        ));
984    }
985
986    #[test]
987    fn runtime_executes_shared_direct_calls_in_host_interpreter() {
988        let mut interpreter = Interpreter::new();
989        register_task_event_type(&mut interpreter);
990
991        let identity = FnDef {
992            name: "identity".to_string(),
993            line: 1,
994            params: vec![("x".to_string(), "Int".to_string())],
995            return_type: "Int".to_string(),
996            effects: vec![],
997            desc: None,
998            body: Rc::new(FnBody::from_expr(sb(Expr::Resolved(0)))),
999            resolution: Some(FnResolution {
1000                local_slots: Rc::new(HashMap::from([(String::from("x"), 0u16)])),
1001                local_count: 1,
1002            }),
1003        };
1004        interpreter
1005            .exec_fn_def(&identity)
1006            .expect("identity function should register");
1007
1008        let list_len = sb(Expr::FnCall(
1009            sbb(Expr::Attr(
1010                sbb(Expr::Ident("List".to_string())),
1011                "len".to_string(),
1012            )),
1013            vec![sb(Expr::List(vec![
1014                sb(Expr::Literal(Literal::Int(1))),
1015                sb(Expr::Literal(Literal::Int(2))),
1016            ]))],
1017        ));
1018        assert_eq!(
1019            interpreter
1020                .eval_expr(&list_len)
1021                .expect("direct builtin call should run"),
1022            Value::Int(2)
1023        );
1024
1025        let identity_call = sb(Expr::FnCall(
1026            sbb(Expr::Ident("identity".to_string())),
1027            vec![sb(Expr::Literal(Literal::Int(9)))],
1028        ));
1029        assert_eq!(
1030            interpreter
1031                .eval_expr(&identity_call)
1032                .expect("direct function call should run"),
1033            Value::Int(9)
1034        );
1035
1036        let wrapper_call = sb(Expr::FnCall(
1037            sbb(Expr::Attr(
1038                sbb(Expr::Ident("Result".to_string())),
1039                "Ok".to_string(),
1040            )),
1041            vec![sb(Expr::Literal(Literal::Int(5)))],
1042        ));
1043        assert_eq!(
1044            interpreter
1045                .eval_expr(&wrapper_call)
1046                .expect("direct wrapper call should run"),
1047            Value::Ok(Box::new(Value::Int(5)))
1048        );
1049
1050        let none_call = sb(Expr::FnCall(
1051            sbb(Expr::Attr(
1052                sbb(Expr::Ident("Option".to_string())),
1053                "None".to_string(),
1054            )),
1055            vec![],
1056        ));
1057        assert_eq!(
1058            interpreter
1059                .eval_expr(&none_call)
1060                .expect("direct none call should run"),
1061            Value::None
1062        );
1063
1064        let ctor_call = sb(Expr::FnCall(
1065            sbb(Expr::Attr(
1066                sbb(Expr::Attr(
1067                    sbb(Expr::Attr(
1068                        sbb(Expr::Ident("Domain".to_string())),
1069                        "Types".to_string(),
1070                    )),
1071                    "TaskEvent".to_string(),
1072                )),
1073                "TaskCreated".to_string(),
1074            )),
1075            vec![sb(Expr::Literal(Literal::Str("now".to_string())))],
1076        ));
1077        match interpreter
1078            .eval_expr(&ctor_call)
1079            .expect("direct qualified ctor call should run")
1080        {
1081            Value::Variant {
1082                type_name,
1083                variant,
1084                fields,
1085            } => {
1086                assert_eq!(type_name, "TaskEvent");
1087                assert_eq!(variant, "TaskCreated");
1088                assert_eq!(fields.as_ref(), &[Value::Str("now".to_string())]);
1089            }
1090            other => panic!("expected variant, got {other:?}"),
1091        }
1092
1093        let multi_ctor_call = sb(Expr::FnCall(
1094            sbb(Expr::Attr(
1095                sbb(Expr::Attr(
1096                    sbb(Expr::Attr(
1097                        sbb(Expr::Ident("Domain".to_string())),
1098                        "Types".to_string(),
1099                    )),
1100                    "TaskEvent".to_string(),
1101                )),
1102                "TaskMoved".to_string(),
1103            )),
1104            vec![
1105                sb(Expr::Literal(Literal::Str("later".to_string()))),
1106                sb(Expr::Literal(Literal::Int(3))),
1107            ],
1108        ));
1109        match interpreter
1110            .eval_expr(&multi_ctor_call)
1111            .expect("direct qualified multi-field ctor call should run")
1112        {
1113            Value::Variant {
1114                type_name,
1115                variant,
1116                fields,
1117            } => {
1118                assert_eq!(type_name, "TaskEvent");
1119                assert_eq!(variant, "TaskMoved");
1120                assert_eq!(
1121                    fields.as_ref(),
1122                    &[Value::Str("later".to_string()), Value::Int(3)]
1123                );
1124            }
1125            other => panic!("expected variant, got {other:?}"),
1126        }
1127    }
1128
1129    #[test]
1130    fn lowered_fn_bodies_classify_forward_calls_through_shared_ir() {
1131        let interpreter = Interpreter::new();
1132        let ctx = InterpreterLowerCtx::new(&interpreter);
1133        let body = FnBody::from_expr(sb(Expr::FnCall(
1134            sbb(Expr::Ident("first".to_string())),
1135            vec![sb(Expr::Resolved(1)), sb(Expr::Resolved(0))],
1136        )));
1137        let lowered = lowered::lower_fn_body(&body, &ctx, "swap");
1138
1139        assert!(matches!(
1140            lowered.expr(ExprId(0)),
1141            LoweredExpr::ForwardCall {
1142                target: LoweredDirectCallTarget::Function(name),
1143                args,
1144                ..
1145            } if name == "first"
1146                && matches!(
1147                    args.as_ref(),
1148                    [LoweredForwardArg::Slot(1), LoweredForwardArg::Slot(0)]
1149                )
1150        ));
1151    }
1152
1153    #[test]
1154    fn runtime_executes_forward_calls_without_evaling_arg_exprs() {
1155        let mut interpreter = Interpreter::new();
1156
1157        let first = FnDef {
1158            name: "first".to_string(),
1159            line: 1,
1160            params: vec![
1161                ("x".to_string(), "Int".to_string()),
1162                ("y".to_string(), "Int".to_string()),
1163            ],
1164            return_type: "Int".to_string(),
1165            effects: vec![],
1166            desc: None,
1167            body: Rc::new(FnBody::from_expr(sb(Expr::Resolved(0)))),
1168            resolution: Some(FnResolution {
1169                local_slots: Rc::new(HashMap::from([
1170                    (String::from("x"), 0u16),
1171                    (String::from("y"), 1u16),
1172                ])),
1173                local_count: 2,
1174            }),
1175        };
1176        interpreter
1177            .exec_fn_def(&first)
1178            .expect("first function should register");
1179
1180        let swap = FnDef {
1181            name: "swap".to_string(),
1182            line: 2,
1183            params: vec![
1184                ("a".to_string(), "Int".to_string()),
1185                ("b".to_string(), "Int".to_string()),
1186            ],
1187            return_type: "Int".to_string(),
1188            effects: vec![],
1189            desc: None,
1190            body: Rc::new(FnBody::from_expr(sb(Expr::FnCall(
1191                sbb(Expr::Ident("first".to_string())),
1192                vec![sb(Expr::Resolved(1)), sb(Expr::Resolved(0))],
1193            )))),
1194            resolution: Some(FnResolution {
1195                local_slots: Rc::new(HashMap::from([
1196                    (String::from("a"), 0u16),
1197                    (String::from("b"), 1u16),
1198                ])),
1199                local_count: 2,
1200            }),
1201        };
1202        interpreter
1203            .exec_fn_def(&swap)
1204            .expect("swap function should register");
1205
1206        let swap_call = sb(Expr::FnCall(
1207            sbb(Expr::Ident("swap".to_string())),
1208            vec![
1209                sb(Expr::Literal(Literal::Int(3))),
1210                sb(Expr::Literal(Literal::Int(7))),
1211            ],
1212        ));
1213        assert_eq!(
1214            interpreter
1215                .eval_expr(&swap_call)
1216                .expect("forward call should run"),
1217            Value::Int(7)
1218        );
1219    }
1220
1221    #[test]
1222    fn lowered_fn_bodies_classify_tail_calls_through_shared_ir() {
1223        let interpreter = Interpreter::new();
1224        let ctx = InterpreterLowerCtx::new(&interpreter);
1225
1226        let self_body = FnBody::from_expr(sb(Expr::TailCall(Box::new((
1227            "loop".to_string(),
1228            vec![sb(Expr::Literal(Literal::Int(1)))],
1229        )))));
1230        let lowered_self = lowered::lower_fn_body(&self_body, &ctx, "loop");
1231        assert!(matches!(
1232            lowered_self.expr(ExprId(1)),
1233            LoweredExpr::TailCall {
1234                target: LoweredTailCallTarget::SelfCall,
1235                ..
1236            }
1237        ));
1238
1239        let known_body = FnBody::from_expr(sb(Expr::TailCall(Box::new((
1240            "other".to_string(),
1241            vec![sb(Expr::Literal(Literal::Int(2)))],
1242        )))));
1243        let lowered_known = lowered::lower_fn_body(&known_body, &ctx, "loop");
1244        assert!(matches!(
1245            lowered_known.expr(ExprId(1)),
1246            LoweredExpr::TailCall {
1247                target: LoweredTailCallTarget::KnownFunction(name),
1248                ..
1249            } if name == "other"
1250        ));
1251
1252        let unknown_body = FnBody::from_expr(sb(Expr::TailCall(Box::new((
1253            "Result.Ok".to_string(),
1254            vec![sb(Expr::Literal(Literal::Int(3)))],
1255        )))));
1256        let lowered_unknown = lowered::lower_fn_body(&unknown_body, &ctx, "loop");
1257        assert!(matches!(
1258            lowered_unknown.expr(ExprId(1)),
1259            LoweredExpr::TailCall {
1260                target: LoweredTailCallTarget::Unknown(name),
1261                ..
1262            } if name == "Result.Ok"
1263        ));
1264    }
1265}