Skip to main content

bock_interp/
interp.rs

1//! Tree-walking interpreter for Bock AIR expressions.
2
3use std::collections::{BTreeMap, BTreeSet, HashMap};
4use std::sync::{Arc, Mutex};
5
6use async_recursion::async_recursion;
7use futures::future::BoxFuture;
8
9use bock_air::{
10    AIRNode, AirArg, AirInterpolationPart, AirRecordField, EnumVariantPayload, NodeKind,
11    ResultVariant,
12};
13use bock_ast::{AssignOp, BinOp, Literal, TypePath, UnaryOp};
14
15use crate::builtins::{BuiltinRegistry, CallbackInvoker, TypeTag};
16use crate::env::{EffectStack, Environment};
17use crate::error::RuntimeError;
18use crate::value::{BockString, EnumValue, FnValue, IteratorNext, OrdF64, RecordValue, Value};
19
20// ─── Closure ──────────────────────────────────────────────────────────────────
21
22/// A reference-counted native constructor function.
23type NativeFn = std::sync::Arc<dyn Fn(&[Value]) -> Value + Send + Sync>;
24
25/// The body of a closure — either an AIR node, a composition, or a native Rust function.
26#[derive(Clone)]
27enum ClosureBody {
28    /// A regular lambda / function body.
29    Air(Box<AIRNode>),
30    /// A composed function: apply `inner` first, pass result to `outer`.
31    Composed { inner: u64, outer: u64 },
32    /// A native constructor function implemented in Rust.
33    Native(NativeFn),
34}
35
36impl std::fmt::Debug for ClosureBody {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        match self {
39            ClosureBody::Air(node) => f.debug_tuple("Air").field(node).finish(),
40            ClosureBody::Composed { inner, outer } => f
41                .debug_struct("Composed")
42                .field("inner", inner)
43                .field("outer", outer)
44                .finish(),
45            ClosureBody::Native(_) => f.debug_tuple("Native").field(&"<fn>").finish(),
46        }
47    }
48}
49
50/// A closure: parameter names, body, and the environment captured at definition.
51#[derive(Debug, Clone)]
52struct Closure {
53    params: Vec<String>,
54    body: ClosureBody,
55    captured: Environment,
56    /// True for named top-level functions. When called, these use the current
57    /// interpreter environment (which contains all registered globals) instead
58    /// of the captured snapshot. This enables recursion, mutual recursion, and
59    /// forward references between top-level functions.
60    is_toplevel: bool,
61    /// True for `async fn` declarations. When true, calling the closure
62    /// spawns the body as a tokio task and returns `Value::Future` immediately.
63    is_async: bool,
64}
65
66/// A registered user-defined `impl` method.
67#[derive(Debug, Clone)]
68struct MethodEntry {
69    /// The method's parameter names, in declaration order. For instance methods
70    /// the first entry is `"self"`.
71    params: Vec<String>,
72    /// True when the receiver was declared `mut self`. Mutations made to `self`
73    /// inside the body must then be written back to the caller's receiver
74    /// lvalue (e.g. an iterator cursor advanced by `next()`).
75    self_is_mut: bool,
76    /// The method body to evaluate.
77    body: AIRNode,
78}
79
80/// The result of dispatching a user-defined `impl` method.
81struct MethodOutcome {
82    /// The method's return value.
83    value: Value,
84    /// For `mut self` instance methods, the post-call `self` value to write
85    /// back to the receiver lvalue; `None` for static or non-`mut`-self methods.
86    updated_self: Option<Value>,
87}
88
89// ─── Interpreter ──────────────────────────────────────────────────────────────
90
91/// Tree-walking interpreter for Bock AIR.
92///
93/// Evaluates expressions against a typed AIR tree. The `Environment` manages
94/// lexical variable bindings; the `EffectStack` will be used by P5.5 for
95/// algebraic effect dispatch.
96#[derive(Clone)]
97pub struct Interpreter {
98    /// Current variable bindings (nested scopes).
99    pub env: Environment,
100    /// Algebraic effect handler stack (populated by P5.5).
101    pub effect_handlers: EffectStack,
102    /// Maps `FnValue::id` to the corresponding closure implementation.
103    fn_registry: HashMap<u64, Closure>,
104    /// Built-in method and global function dispatch table.
105    pub builtins: BuiltinRegistry,
106    /// User-defined impl methods: type_name → method_name → [`MethodEntry`].
107    method_table: HashMap<String, HashMap<String, MethodEntry>>,
108    /// Maps effect operation names to their parent effect name.
109    /// Used to dispatch `log(msg)` → look up handler for `Logger` → call it.
110    effect_operations: HashMap<String, String>,
111}
112
113impl CallbackInvoker for Interpreter {
114    fn invoke<'a>(
115        &'a mut self,
116        callable: &'a Value,
117        args: &'a [Value],
118    ) -> BoxFuture<'a, Result<Value, RuntimeError>> {
119        Box::pin(async move { self.invoke_callback(callable, args).await })
120    }
121}
122
123impl Default for Interpreter {
124    fn default() -> Self {
125        Self::new()
126    }
127}
128
129impl Interpreter {
130    /// Create a new interpreter with an empty global environment
131    /// and default built-in functions registered.
132    #[must_use]
133    pub fn new() -> Self {
134        let mut builtins = BuiltinRegistry::new();
135        builtins.register_defaults();
136        let mut interp = Self {
137            env: Environment::new(),
138            effect_handlers: EffectStack::new(),
139            fn_registry: HashMap::new(),
140            builtins,
141            method_table: HashMap::new(),
142            effect_operations: HashMap::new(),
143        };
144        interp.register_prelude_constructors();
145        interp
146    }
147
148    /// Register the built-in `Ok`, `Err`, `Some`, and `None` constructors
149    /// in the global environment so they are available at runtime.
150    fn register_prelude_constructors(&mut self) {
151        // None is a plain value, not a function.
152        self.env.define("None", Value::Optional(None));
153
154        // Some(x) → Value::Optional(Some(x))
155        self.register_native_constructor("Some", 1, |args| {
156            Value::Optional(Some(Box::new(args[0].clone())))
157        });
158
159        // Ok(x) → Value::Result(Ok(x))
160        self.register_native_constructor("Ok", 1, |args| {
161            Value::Result(Ok(Box::new(args[0].clone())))
162        });
163
164        // Err(x) → Value::Result(Err(x))
165        self.register_native_constructor("Err", 1, |args| {
166            Value::Result(Err(Box::new(args[0].clone())))
167        });
168    }
169
170    /// Register a native constructor function (not backed by an AIR body).
171    ///
172    /// The `build` closure receives the validated arguments and returns the
173    /// constructed `Value`.
174    fn register_native_constructor(
175        &mut self,
176        name: &str,
177        arity: usize,
178        build: impl Fn(&[Value]) -> Value + Send + Sync + 'static,
179    ) {
180        let fn_val = FnValue::new_named(name);
181        let id = fn_val.id;
182        let params: Vec<String> = (0..arity).map(|i| format!("__arg{i}")).collect();
183        self.fn_registry.insert(
184            id,
185            Closure {
186                params,
187                body: ClosureBody::Native(std::sync::Arc::new(build)),
188                captured: Environment::new(),
189                is_toplevel: true,
190                is_async: false,
191            },
192        );
193        self.env.define(name, Value::Function(fn_val));
194    }
195
196    /// Register all variants of an enum declaration in the environment.
197    ///
198    /// - Unit variants become `Value::Enum` values.
199    /// - Tuple variants become constructor functions wrapping args in `Value::Enum`.
200    /// - Record (struct) variants become constructor functions producing `Value::Record`.
201    pub fn register_enum(&mut self, enum_name: &str, variants: &[AIRNode]) {
202        let type_name = enum_name.to_string();
203        for variant in variants {
204            if let NodeKind::EnumVariant { name, payload } = &variant.kind {
205                let variant_name = name.name.clone();
206                match payload {
207                    EnumVariantPayload::Unit => {
208                        self.env.define(
209                            variant_name.clone(),
210                            Value::Enum(EnumValue {
211                                type_name: type_name.clone(),
212                                variant: variant_name,
213                                payload: None,
214                            }),
215                        );
216                    }
217                    EnumVariantPayload::Tuple(fields) => {
218                        let arity = fields.len();
219                        let tn = type_name.clone();
220                        let vn = variant_name.clone();
221                        if arity == 1 {
222                            self.register_native_constructor(&variant_name, arity, move |args| {
223                                Value::Enum(EnumValue {
224                                    type_name: tn.clone(),
225                                    variant: vn.clone(),
226                                    payload: Some(Box::new(args[0].clone())),
227                                })
228                            });
229                        } else {
230                            self.register_native_constructor(&variant_name, arity, move |args| {
231                                Value::Enum(EnumValue {
232                                    type_name: tn.clone(),
233                                    variant: vn.clone(),
234                                    payload: Some(Box::new(Value::Tuple(args.to_vec()))),
235                                })
236                            });
237                        }
238                    }
239                    EnumVariantPayload::Struct(fields) => {
240                        let arity = fields.len();
241                        let vn = variant_name.clone();
242                        let field_names: Vec<String> =
243                            fields.iter().map(|f| f.name.name.clone()).collect();
244                        self.register_native_constructor(&variant_name, arity, move |args| {
245                            let mut field_map = std::collections::BTreeMap::new();
246                            for (fname, val) in field_names.iter().zip(args.iter()) {
247                                field_map.insert(fname.clone(), val.clone());
248                            }
249                            Value::Record(RecordValue {
250                                type_name: vn.clone(),
251                                fields: field_map,
252                            })
253                        });
254                    }
255                }
256            }
257        }
258    }
259
260    /// Register a named function in the global environment.
261    ///
262    /// The function is stored in the registry and bound by name so that
263    /// `Identifier` nodes referencing it will resolve to `Value::Function`.
264    pub fn register_fn(&mut self, name: &str, params: Vec<String>, body: AIRNode) {
265        self.register_fn_with_async(name, params, body, false);
266    }
267
268    /// Register a named function whose body may be `async`.
269    ///
270    /// When `is_async` is true, calling the function spawns a tokio task and
271    /// returns a `Value::Future`; when false, it runs inline like a regular
272    /// function call.
273    pub fn register_fn_with_async(
274        &mut self,
275        name: &str,
276        params: Vec<String>,
277        body: AIRNode,
278        is_async: bool,
279    ) {
280        let fn_val = FnValue::new_named(name);
281        let id = fn_val.id;
282        self.env.define(name, Value::Function(fn_val));
283        self.fn_registry.insert(
284            id,
285            Closure {
286                params,
287                body: ClosureBody::Air(Box::new(body)),
288                captured: Environment::new(),
289                is_toplevel: true,
290                is_async,
291            },
292        );
293    }
294
295    /// Register methods from an `impl` block in the method table.
296    ///
297    /// Extracts the target type name and each method's parameter names + body,
298    /// storing them in `method_table[type_name][method_name]`.
299    pub fn register_impl(&mut self, target: &AIRNode, methods: &[AIRNode]) {
300        // Extract the type name from the target node (TypeNamed path).
301        let type_name = match &target.kind {
302            NodeKind::TypeNamed { path, .. } => path
303                .segments
304                .last()
305                .map(|s| s.name.clone())
306                .unwrap_or_default(),
307            _ => return,
308        };
309
310        let type_methods = self.method_table.entry(type_name).or_default();
311        for method in methods {
312            if let NodeKind::FnDecl {
313                name, params, body, ..
314            } = &method.kind
315            {
316                let mut param_names: Vec<String> = Vec::with_capacity(params.len());
317                let mut self_is_mut = false;
318                for p in params {
319                    if let NodeKind::Param { pattern, .. } = &p.kind {
320                        if let NodeKind::BindPat {
321                            name,
322                            is_mut: bind_is_mut,
323                        } = &pattern.kind
324                        {
325                            // The receiver's `mut` marker tells us whether self
326                            // mutations must be persisted back to the caller.
327                            if param_names.is_empty() && name.name == "self" {
328                                self_is_mut = *bind_is_mut;
329                            }
330                            param_names.push(name.name.clone());
331                        }
332                    }
333                }
334                type_methods.insert(
335                    name.name.clone(),
336                    MethodEntry {
337                        params: param_names,
338                        self_is_mut,
339                        body: *body.clone(),
340                    },
341                );
342            }
343        }
344    }
345
346    /// Register an effect declaration's operations so they can be dispatched
347    /// at runtime through the effect handler stack.
348    ///
349    /// For each operation in the effect, records a mapping from the operation
350    /// name to the effect name. When a call like `log(msg)` is evaluated,
351    /// the interpreter checks this map, finds the effect (`Logger`), resolves
352    /// the handler, and dispatches the call.
353    pub fn register_effect(&mut self, effect_name: &str, operations: &[AIRNode]) {
354        // Also define the effect name in the environment (type-level marker).
355        self.env.define(effect_name, Value::Void);
356
357        for op in operations {
358            if let NodeKind::FnDecl { name, .. } = &op.kind {
359                self.effect_operations
360                    .insert(name.name.clone(), effect_name.to_string());
361            }
362        }
363    }
364
365    /// Invoke a callable (function value) using the interpreter's closure machinery.
366    ///
367    /// This is the public entry point for callback invocation from builtins.
368    #[async_recursion]
369    pub async fn invoke_callback(
370        &mut self,
371        callable: &Value,
372        args: &[Value],
373    ) -> Result<Value, RuntimeError> {
374        let fn_id = match callable {
375            Value::Function(fv) => fv.id,
376            other => {
377                return Err(RuntimeError::NotCallable {
378                    value: other.to_string(),
379                })
380            }
381        };
382        let closure =
383            self.fn_registry
384                .get(&fn_id)
385                .cloned()
386                .ok_or_else(|| RuntimeError::NotCallable {
387                    value: format!("unregistered fn #{fn_id}"),
388                })?;
389        self.call_closure(&closure, args.to_vec()).await
390    }
391
392    // ── Main evaluation entry point ────────────────────────────────────────
393
394    /// Evaluate a single AIR expression node and return its runtime value.
395    #[async_recursion]
396    pub async fn eval_expr(&mut self, node: &AIRNode) -> Result<Value, RuntimeError> {
397        match &node.kind {
398            NodeKind::Literal { lit } => self.eval_literal(lit),
399
400            NodeKind::Identifier { name } => {
401                self.env
402                    .get(&name.name)
403                    .cloned()
404                    .ok_or_else(|| RuntimeError::UndefinedVariable {
405                        name: name.name.clone(),
406                    })
407            }
408
409            NodeKind::BinaryOp { op, left, right } => {
410                // DQ29 (§18.5): `==`/`!=` the checker stamped `"impl"` carry an
411                // explicit `impl Equatable`, whose `eq` defines the type's
412                // equality — dispatch through it, exactly as the compiled
413                // targets do. (`bock run` type-checks the same AIR it
414                // interprets, so the stamp is present.) The structural lanes
415                // fall through to `eval_binary_op`'s native `Value` equality,
416                // which is already field-wise and (`BTreeMap`/`BTreeSet`)
417                // order-independent. The key literal matches
418                // `bock_types::checker::USER_EQ_META_KEY` (bock-interp sits
419                // below bock-types in the crate graph, so the constant cannot
420                // be imported).
421                if matches!(op, BinOp::Eq | BinOp::Ne)
422                    && matches!(
423                        node.metadata.get("user_eq"),
424                        Some(bock_air::Value::String(kind)) if kind == "impl"
425                    )
426                {
427                    let l = self.eval_expr(left).await?;
428                    let r = self.eval_expr(right).await?;
429                    let eq = match self.try_call_impl_method(&l, "eq", vec![r.clone()]).await? {
430                        Some(outcome) => matches!(outcome.value, Value::Bool(true)),
431                        // No callable `eq` on this receiver shape (e.g. a
432                        // value form the method table does not cover): fall
433                        // back to structural equality rather than failing.
434                        None => l == r,
435                    };
436                    return Ok(Value::Bool(if matches!(op, BinOp::Eq) { eq } else { !eq }));
437                }
438                // DQ31 (§18.5 container equality defers to element conformance):
439                // the `"deep_custom"` lane — a container (`List`/`Map`/`Set`/
440                // `Optional`/`Result`/tuple, or a record/enum carrying one)
441                // whose element tree has an explicit `impl Equatable`. Native
442                // `Value` equality is structural and would ignore the element's
443                // custom `eq`; instead recurse, dispatching element comparison
444                // (and Map-key / Set-member matching) through that `eq`, the
445                // same observable result the compiled targets produce.
446                if matches!(op, BinOp::Eq | BinOp::Ne)
447                    && matches!(
448                        node.metadata.get("user_eq"),
449                        Some(bock_air::Value::String(kind)) if kind == "deep_custom"
450                    )
451                {
452                    let l = self.eval_expr(left).await?;
453                    let r = self.eval_expr(right).await?;
454                    let eq = self.values_equal_custom(&l, &r).await?;
455                    return Ok(Value::Bool(if matches!(op, BinOp::Eq) { eq } else { !eq }));
456                }
457                self.eval_binary_op(*op, left, right).await
458            }
459
460            NodeKind::UnaryOp { op, operand } => self.eval_unary_op(*op, operand).await,
461
462            NodeKind::Assign { op, target, value } => self.eval_assign(*op, target, value).await,
463
464            NodeKind::Call { callee, args, .. } => self.eval_call(callee, args).await,
465
466            NodeKind::MethodCall {
467                receiver,
468                method,
469                args,
470                ..
471            } => {
472                self.eval_method_call(receiver, &method.name.clone(), args)
473                    .await
474            }
475
476            NodeKind::FieldAccess { object, field } => {
477                self.eval_field_access(object, &field.name.clone()).await
478            }
479
480            NodeKind::Index { object, index } => self.eval_index(object, index).await,
481
482            NodeKind::Propagate { expr } => self.eval_propagate(expr).await,
483
484            NodeKind::Lambda { params, body } => self.eval_lambda(params, body),
485
486            NodeKind::Pipe { left, right } => self.eval_pipe(left, right).await,
487
488            NodeKind::Compose { left, right } => self.eval_compose(left, right).await,
489
490            // ── Collection literals ────────────────────────────────────────
491            NodeKind::ListLiteral { elems } => {
492                let mut values = Vec::with_capacity(elems.len());
493                for elem in elems {
494                    values.push(self.eval_expr(elem).await?);
495                }
496                Ok(Value::List(values))
497            }
498
499            NodeKind::MapLiteral { entries } => {
500                let mut map = BTreeMap::new();
501                for entry in entries {
502                    let k = self.eval_expr(&entry.key).await?;
503                    let v = self.eval_expr(&entry.value).await?;
504                    map.insert(k, v);
505                }
506                Ok(Value::Map(map))
507            }
508
509            NodeKind::SetLiteral { elems } => {
510                let mut set = BTreeSet::new();
511                for elem in elems {
512                    set.insert(self.eval_expr(elem).await?);
513                }
514                Ok(Value::Set(set))
515            }
516
517            NodeKind::TupleLiteral { elems } => {
518                let mut values = Vec::with_capacity(elems.len());
519                for elem in elems {
520                    values.push(self.eval_expr(elem).await?);
521                }
522                Ok(Value::Tuple(values))
523            }
524
525            NodeKind::RecordConstruct {
526                path,
527                fields,
528                spread,
529            } => {
530                self.eval_record_construct(path, fields, spread.as_deref())
531                    .await
532            }
533
534            NodeKind::Interpolation { parts } => self.eval_interpolation(parts).await,
535
536            NodeKind::Range { lo, hi, inclusive } => self.eval_range(lo, hi, *inclusive).await,
537
538            NodeKind::ResultConstruct { variant, value } => {
539                let inner = match value {
540                    Some(v) => self.eval_expr(v).await?,
541                    None => Value::Void,
542                };
543                match variant {
544                    ResultVariant::Ok => Ok(Value::Result(Ok(Box::new(inner)))),
545                    ResultVariant::Err => Ok(Value::Result(Err(Box::new(inner)))),
546                }
547            }
548
549            // ── Control flow ───────────────────────────────────────────────
550            NodeKind::Block { stmts, tail } => self.eval_block(stmts, tail.as_deref()).await,
551
552            NodeKind::If {
553                let_pattern,
554                condition,
555                then_block,
556                else_block,
557            } => {
558                self.eval_if(
559                    let_pattern.as_deref(),
560                    condition,
561                    then_block,
562                    else_block.as_deref(),
563                )
564                .await
565            }
566
567            NodeKind::Match { scrutinee, arms } => self.eval_match(scrutinee, arms).await,
568
569            NodeKind::Return { value } => {
570                let v = match value {
571                    Some(e) => self.eval_expr(e).await?,
572                    None => Value::Void,
573                };
574                Err(RuntimeError::Return(Box::new(v)))
575            }
576
577            NodeKind::Break { value } => {
578                let v = match value {
579                    Some(e) => Some(Box::new(self.eval_expr(e).await?)),
580                    None => None,
581                };
582                Err(RuntimeError::Break(v))
583            }
584
585            NodeKind::Continue => Err(RuntimeError::Continue),
586
587            NodeKind::For {
588                pattern,
589                iterable,
590                body,
591            } => self.eval_for(pattern, iterable, body).await,
592
593            NodeKind::While { condition, body } => self.eval_while(condition, body).await,
594
595            NodeKind::Loop { body } => self.eval_loop(body).await,
596
597            NodeKind::Guard {
598                let_pattern,
599                condition,
600                else_block,
601            } => {
602                self.eval_guard(let_pattern.as_deref(), condition, else_block)
603                    .await
604            }
605
606            NodeKind::Unreachable => Err(RuntimeError::Unreachable),
607
608            // ── Ownership annotations (pass-through) ───────────────────────
609            NodeKind::Move { expr }
610            | NodeKind::Borrow { expr }
611            | NodeKind::MutableBorrow { expr } => self.eval_expr(expr).await,
612
613            // ── Async — `await` resolves a `Value::Future` to its inner value.
614            NodeKind::Await { expr } => {
615                let val = self.eval_expr(expr).await?;
616                match val {
617                    Value::Future(handle) => {
618                        let h = handle.lock().unwrap().take();
619                        match h {
620                            Some(jh) => match jh.await {
621                                Ok(inner) => inner,
622                                Err(e) => Err(RuntimeError::TypeError(format!(
623                                    "async task panicked: {e}"
624                                ))),
625                            },
626                            None => Err(RuntimeError::TypeError(
627                                "future already awaited".to_string(),
628                            )),
629                        }
630                    }
631                    other => Ok(other),
632                }
633            }
634
635            // ── Let binding (also appears as a statement inside blocks) ─────
636            NodeKind::LetBinding { pattern, value, .. } => {
637                let v = self.eval_expr(value).await?;
638                self.bind_pattern(pattern, v).await?;
639                Ok(Value::Void)
640            }
641
642            // ── Placeholder `_` ────────────────────────────────────────────
643            NodeKind::Placeholder => Ok(Value::Void),
644
645            // ── Effects ──────────────────────────────────────────────────────
646
647            // Effect declaration: register effect name, evaluate to Void.
648            NodeKind::EffectDecl { name, .. } => {
649                // Effect declarations are type-level; at runtime we just
650                // record the name so it can be referenced.
651                self.env.define(&name.name, Value::Void);
652                Ok(Value::Void)
653            }
654
655            // Module-level `handle Effect with handler`
656            NodeKind::ModuleHandle { effect, handler } => {
657                let handler_val = self.eval_expr(handler).await?;
658                let effect_name = self.type_path_to_name(effect);
659                self.effect_handlers
660                    .set_module_handler(effect_name, handler_val);
661                Ok(Value::Void)
662            }
663
664            // Effect operation invocation: resolve handler, call it.
665            NodeKind::EffectOp {
666                effect,
667                operation,
668                args,
669            } => {
670                let effect_name = self.type_path_to_name(effect);
671                let handler = self.effect_handlers.resolve(&effect_name).cloned().ok_or(
672                    RuntimeError::NoEffectHandler {
673                        effect: effect_name,
674                    },
675                )?;
676
677                // Evaluate arguments
678                let mut arg_values = Vec::with_capacity(args.len());
679                for arg in args {
680                    arg_values.push(self.eval_expr(&arg.value).await?);
681                }
682
683                // Call handler.operation(args...)
684                // The handler is a record value whose fields are the operation
685                // implementations, or a function if the effect has a single op.
686                self.dispatch_effect_op(&handler, &operation.name, arg_values)
687                    .await
688            }
689
690            // Handling block: push handlers, execute body, pop handlers.
691            NodeKind::HandlingBlock { handlers, body } => {
692                let mut frame = std::collections::HashMap::new();
693                for pair in handlers {
694                    let handler_val = self.eval_expr(&pair.handler).await?;
695                    let effect_name = self.type_path_to_name(&pair.effect);
696                    frame.insert(effect_name, handler_val);
697                }
698                self.effect_handlers.push_handlers(frame);
699                let result = self.eval_expr(body).await;
700                self.effect_handlers.pop_handlers();
701                result
702            }
703
704            // Effect reference in type position — no runtime behavior.
705            NodeKind::EffectRef { .. } => Ok(Value::Void),
706
707            other => Err(RuntimeError::NotImplemented(
708                format!("{other:?}").chars().take(60).collect(),
709            )),
710        }
711    }
712
713    // ── Effect helpers ─────────────────────────────────────────────────────
714
715    /// Convert a `TypePath` to a dot-separated effect name string.
716    fn type_path_to_name(&self, tp: &TypePath) -> String {
717        tp.segments
718            .iter()
719            .map(|s| s.name.as_str())
720            .collect::<Vec<_>>()
721            .join(".")
722    }
723
724    /// Dispatch an effect operation call to a handler value.
725    ///
726    /// Resolution order for a record handler:
727    ///   1. A field on the record whose value is a function — call it
728    ///      with the operation arguments.
729    ///   2. An `impl EffectTrait for TypeName` method in the interpreter's
730    ///      method table — dispatched as a regular instance method.
731    ///
732    /// If the handler is a plain function, it's called directly (for
733    /// single-operation effects).
734    #[async_recursion]
735    async fn dispatch_effect_op(
736        &mut self,
737        handler: &Value,
738        operation: &str,
739        args: Vec<Value>,
740    ) -> Result<Value, RuntimeError> {
741        match handler {
742            // Record handler: look up operation as a field first, then
743            // fall back to impl-block methods.
744            Value::Record(rec) => {
745                if let Some(op_fn) = rec.fields.get(operation).cloned() {
746                    return self.call_fn_value(&op_fn, args).await;
747                }
748                if let Some(outcome) = self.try_call_impl_method(handler, operation, args).await? {
749                    // The effect-handler value is a temporary; there is no
750                    // caller lvalue to write a mutated `self` back to.
751                    return Ok(outcome.value);
752                }
753                Err(RuntimeError::FieldNotFound {
754                    field: operation.to_string(),
755                    type_name: rec.type_name.clone(),
756                })
757            }
758            // Function handler: single-operation effect, call directly
759            Value::Function(_) => {
760                let handler = handler.clone();
761                self.call_fn_value(&handler, args).await
762            }
763            other => Err(RuntimeError::TypeError(format!(
764                "effect handler must be a record or function, got {other}"
765            ))),
766        }
767    }
768
769    /// Call a `Value::Function` by its identity, looking up the closure in
770    /// the function registry.
771    /// Call a function value with the given arguments.
772    #[async_recursion]
773    pub async fn call_fn_value(
774        &mut self,
775        val: &Value,
776        args: Vec<Value>,
777    ) -> Result<Value, RuntimeError> {
778        let fn_id = match val {
779            Value::Function(fv) => fv.id,
780            other => {
781                return Err(RuntimeError::NotCallable {
782                    value: other.to_string(),
783                })
784            }
785        };
786        let closure =
787            self.fn_registry
788                .get(&fn_id)
789                .cloned()
790                .ok_or_else(|| RuntimeError::NotCallable {
791                    value: format!("unregistered fn #{fn_id}"),
792                })?;
793        self.call_closure(&closure, args).await
794    }
795
796    // ── Literal evaluation ─────────────────────────────────────────────────
797
798    fn eval_literal(&self, lit: &Literal) -> Result<Value, RuntimeError> {
799        match lit {
800            Literal::Int(s) => {
801                // Strip type suffix (e.g., _u8) before parsing.
802                let (numeric, _) = bock_ast::strip_type_suffix(s);
803                // Support 0x, 0o, 0b prefixes and optional _ separators.
804                let clean = numeric.replace('_', "");
805                let n = if clean.starts_with("0x") || clean.starts_with("0X") {
806                    i64::from_str_radix(&clean[2..], 16)
807                } else if clean.starts_with("0o") || clean.starts_with("0O") {
808                    i64::from_str_radix(&clean[2..], 8)
809                } else if clean.starts_with("0b") || clean.starts_with("0B") {
810                    i64::from_str_radix(&clean[2..], 2)
811                } else {
812                    clean.parse::<i64>()
813                };
814                n.map(Value::Int)
815                    .map_err(|_| RuntimeError::IntParseFailed(s.clone()))
816            }
817            Literal::Float(s) => {
818                // Strip type suffix (e.g., _f32) before parsing.
819                let (numeric, _) = bock_ast::strip_type_suffix(s);
820                numeric
821                    .replace('_', "")
822                    .parse::<f64>()
823                    .map(|f| Value::Float(OrdF64(f)))
824                    .map_err(|_| RuntimeError::FloatParseFailed(s.clone()))
825            }
826            Literal::Bool(b) => Ok(Value::Bool(*b)),
827            Literal::Char(s) => Ok(Value::Char(s.chars().next().unwrap_or('\0'))),
828            Literal::String(s) => Ok(Value::String(BockString::new(s.clone()))),
829            Literal::Unit => Ok(Value::Void),
830        }
831    }
832
833    // ── Binary operator evaluation ─────────────────────────────────────────
834
835    #[async_recursion]
836    async fn eval_binary_op(
837        &mut self,
838        op: BinOp,
839        left: &AIRNode,
840        right: &AIRNode,
841    ) -> Result<Value, RuntimeError> {
842        // Short-circuit logical operators.
843        match op {
844            BinOp::And => {
845                let l = self.eval_expr(left).await?;
846                return match l {
847                    Value::Bool(false) => Ok(Value::Bool(false)),
848                    Value::Bool(true) => self.eval_expr(right).await,
849                    other => Err(RuntimeError::TypeError(format!(
850                        "expected Bool in &&, got {other}"
851                    ))),
852                };
853            }
854            BinOp::Or => {
855                let l = self.eval_expr(left).await?;
856                return match l {
857                    Value::Bool(true) => Ok(Value::Bool(true)),
858                    Value::Bool(false) => self.eval_expr(right).await,
859                    other => Err(RuntimeError::TypeError(format!(
860                        "expected Bool in ||, got {other}"
861                    ))),
862                };
863            }
864            _ => {}
865        }
866
867        let l = self.eval_expr(left).await?;
868        let r = self.eval_expr(right).await?;
869
870        match (op, l, r) {
871            // ── Arithmetic ────────────────────────────────────────────────
872            (BinOp::Add, Value::Int(a), Value::Int(b)) => a
873                .checked_add(b)
874                .map(Value::Int)
875                .ok_or(RuntimeError::IntOverflow),
876            (BinOp::Add, Value::Float(a), Value::Float(b)) => Ok(Value::Float(OrdF64(a.0 + b.0))),
877            (BinOp::Add, Value::String(a), Value::String(b)) => {
878                Ok(Value::String(BockString::new(format!("{a}{b}"))))
879            }
880            // `List + List` is value-returning concatenation (Q-interp-list-concat):
881            // the checker accepts it and all five codegen targets evaluate it, so
882            // the interpreter — the cross-target equivalence oracle (routing R11) —
883            // must too. Produces a fresh list; neither operand is mutated.
884            (BinOp::Add, Value::List(a), Value::List(b)) => {
885                let mut out = Vec::with_capacity(a.len() + b.len());
886                out.extend(a);
887                out.extend(b);
888                Ok(Value::List(out))
889            }
890
891            (BinOp::Sub, Value::Int(a), Value::Int(b)) => a
892                .checked_sub(b)
893                .map(Value::Int)
894                .ok_or(RuntimeError::IntOverflow),
895            (BinOp::Sub, Value::Float(a), Value::Float(b)) => Ok(Value::Float(OrdF64(a.0 - b.0))),
896
897            (BinOp::Mul, Value::Int(a), Value::Int(b)) => a
898                .checked_mul(b)
899                .map(Value::Int)
900                .ok_or(RuntimeError::IntOverflow),
901            (BinOp::Mul, Value::Float(a), Value::Float(b)) => Ok(Value::Float(OrdF64(a.0 * b.0))),
902
903            (BinOp::Div, Value::Int(a), Value::Int(b)) => {
904                if b == 0 {
905                    Err(RuntimeError::DivisionByZero)
906                } else {
907                    Ok(Value::Int(a / b))
908                }
909            }
910            (BinOp::Div, Value::Float(a), Value::Float(b)) => Ok(Value::Float(OrdF64(a.0 / b.0))),
911
912            (BinOp::Rem, Value::Int(a), Value::Int(b)) => {
913                if b == 0 {
914                    Err(RuntimeError::DivisionByZero)
915                } else {
916                    Ok(Value::Int(a % b))
917                }
918            }
919            (BinOp::Rem, Value::Float(a), Value::Float(b)) => Ok(Value::Float(OrdF64(a.0 % b.0))),
920
921            (BinOp::Pow, Value::Int(a), Value::Int(b)) => {
922                if b < 0 {
923                    Err(RuntimeError::TypeError(
924                        "negative integer exponent".to_string(),
925                    ))
926                } else if b > u32::MAX as i64 {
927                    Err(RuntimeError::IntOverflow)
928                } else {
929                    a.checked_pow(b as u32)
930                        .map(Value::Int)
931                        .ok_or(RuntimeError::IntOverflow)
932                }
933            }
934            (BinOp::Pow, Value::Float(a), Value::Float(b)) => {
935                Ok(Value::Float(OrdF64(a.0.powf(b.0))))
936            }
937
938            // ── Comparison ────────────────────────────────────────────────
939            (BinOp::Eq, l, r) => Ok(Value::Bool(l == r)),
940            (BinOp::Ne, l, r) => Ok(Value::Bool(l != r)),
941            (BinOp::Lt, l, r) => Ok(Value::Bool(l < r)),
942            (BinOp::Le, l, r) => Ok(Value::Bool(l <= r)),
943            (BinOp::Gt, l, r) => Ok(Value::Bool(l > r)),
944            (BinOp::Ge, l, r) => Ok(Value::Bool(l >= r)),
945
946            // ── Bitwise ───────────────────────────────────────────────────
947            (BinOp::BitAnd, Value::Int(a), Value::Int(b)) => Ok(Value::Int(a & b)),
948            (BinOp::BitOr, Value::Int(a), Value::Int(b)) => Ok(Value::Int(a | b)),
949            (BinOp::BitXor, Value::Int(a), Value::Int(b)) => Ok(Value::Int(a ^ b)),
950            // ── Function composition (`>>`) ────────────────────────────────
951            // Note: Compose as a BinOp is separate from NodeKind::Compose.
952            (BinOp::Compose, Value::Function(f), Value::Function(g)) => {
953                let fn_val = FnValue::new_anonymous();
954                let id = fn_val.id;
955                self.fn_registry.insert(
956                    id,
957                    Closure {
958                        params: vec!["__x".to_string()],
959                        body: ClosureBody::Composed {
960                            inner: f.id,
961                            outer: g.id,
962                        },
963                        captured: self.env.clone(),
964                        is_toplevel: false,
965                        is_async: false,
966                    },
967                );
968                Ok(Value::Function(fn_val))
969            }
970
971            // ── Is (type check) ───────────────────────────────────────────
972            (BinOp::Is, value, Value::String(type_name)) => {
973                let tag = TypeTag::of(&value);
974                Ok(Value::Bool(tag.name() == type_name.as_str()))
975            }
976
977            // ── Trait dispatch fallback ────────────────────────────────────
978            // If the inline handler didn't match, try dispatching via
979            // registered trait methods (e.g., Add.add, Comparable.compare).
980            (op, l, r) => {
981                let tag = TypeTag::of(&l);
982                let method = match op {
983                    BinOp::Add => Some("add"),
984                    BinOp::Sub => Some("sub"),
985                    BinOp::Mul => Some("mul"),
986                    BinOp::Div => Some("div"),
987                    BinOp::Rem => Some("rem"),
988                    BinOp::Pow => Some("pow"),
989                    _ => None,
990                };
991                if let Some(name) = method {
992                    if let Some(result) = self.builtins.call(tag, name, &[l.clone(), r.clone()]) {
993                        return result;
994                    }
995                }
996                Err(RuntimeError::TypeError(format!(
997                    "operator {op:?} not supported for {l} and {r}"
998                )))
999            }
1000        }
1001    }
1002
1003    // ── Unary operator evaluation ──────────────────────────────────────────
1004
1005    #[async_recursion]
1006    async fn eval_unary_op(
1007        &mut self,
1008        op: UnaryOp,
1009        operand: &AIRNode,
1010    ) -> Result<Value, RuntimeError> {
1011        let val = self.eval_expr(operand).await?;
1012        match (op, val) {
1013            (UnaryOp::Neg, Value::Int(n)) => n
1014                .checked_neg()
1015                .map(Value::Int)
1016                .ok_or(RuntimeError::IntOverflow),
1017            (UnaryOp::Neg, Value::Float(f)) => Ok(Value::Float(OrdF64(-f.0))),
1018            (UnaryOp::Not, Value::Bool(b)) => Ok(Value::Bool(!b)),
1019            (UnaryOp::BitNot, Value::Int(n)) => Ok(Value::Int(!n)),
1020            (op, val) => Err(RuntimeError::TypeError(format!(
1021                "unary operator {op:?} not supported for {val}"
1022            ))),
1023        }
1024    }
1025
1026    // ── Assignment ─────────────────────────────────────────────────────────
1027
1028    #[async_recursion]
1029    async fn eval_assign(
1030        &mut self,
1031        op: AssignOp,
1032        target: &AIRNode,
1033        value: &AIRNode,
1034    ) -> Result<Value, RuntimeError> {
1035        let rhs = self.eval_expr(value).await?;
1036        match &target.kind {
1037            NodeKind::Identifier { name } => {
1038                let name = name.name.clone();
1039                let new_val = match op {
1040                    AssignOp::Assign => rhs,
1041                    compound => {
1042                        let current = self.env.get(&name).cloned().ok_or_else(|| {
1043                            RuntimeError::UndefinedVariable { name: name.clone() }
1044                        })?;
1045                        self.apply_assign_op(compound, current, rhs)?
1046                    }
1047                };
1048                if !self.env.assign(&name, new_val) {
1049                    return Err(RuntimeError::UndefinedVariable { name });
1050                }
1051                Ok(Value::Void)
1052            }
1053            NodeKind::FieldAccess { object, field } => {
1054                let field_name = field.name.clone();
1055                let obj = self.eval_expr(object).await?;
1056                match obj {
1057                    Value::Record(mut rv) => {
1058                        let new_val = match op {
1059                            AssignOp::Assign => rhs,
1060                            compound => {
1061                                let current =
1062                                    rv.fields.get(&field_name).cloned().ok_or_else(|| {
1063                                        RuntimeError::FieldNotFound {
1064                                            field: field_name.clone(),
1065                                            type_name: rv.type_name.clone(),
1066                                        }
1067                                    })?;
1068                                self.apply_assign_op(compound, current, rhs)?
1069                            }
1070                        };
1071                        rv.fields.insert(field_name, new_val);
1072                        // Re-bind the object in the environment
1073                        if let NodeKind::Identifier { name: obj_name } = &object.kind {
1074                            let updated = Value::Record(rv);
1075                            if !self.env.assign(&obj_name.name, updated) {
1076                                return Err(RuntimeError::UndefinedVariable {
1077                                    name: obj_name.name.clone(),
1078                                });
1079                            }
1080                        }
1081                        Ok(Value::Void)
1082                    }
1083                    other => Err(RuntimeError::TypeError(format!(
1084                        "cannot assign to field '{field_name}' on {other}"
1085                    ))),
1086                }
1087            }
1088            NodeKind::Index { object, index } => {
1089                let idx = self.eval_expr(index).await?;
1090                let obj = self.eval_expr(object).await?;
1091                match (obj, idx) {
1092                    (Value::List(mut items), Value::Int(i)) => {
1093                        if i < 0 || i as usize >= items.len() {
1094                            return Err(RuntimeError::IndexOutOfBounds {
1095                                index: i,
1096                                len: items.len(),
1097                            });
1098                        }
1099                        let new_val = match op {
1100                            AssignOp::Assign => rhs,
1101                            compound => {
1102                                let current = items[i as usize].clone();
1103                                self.apply_assign_op(compound, current, rhs)?
1104                            }
1105                        };
1106                        items[i as usize] = new_val;
1107                        if let NodeKind::Identifier { name: obj_name } = &object.kind {
1108                            let updated = Value::List(items);
1109                            if !self.env.assign(&obj_name.name, updated) {
1110                                return Err(RuntimeError::UndefinedVariable {
1111                                    name: obj_name.name.clone(),
1112                                });
1113                            }
1114                        }
1115                        Ok(Value::Void)
1116                    }
1117                    (Value::Map(mut map), key) => {
1118                        let new_val = match op {
1119                            AssignOp::Assign => rhs,
1120                            compound => {
1121                                let current = map.get(&key).cloned().ok_or_else(|| {
1122                                    RuntimeError::TypeError(format!("key not found: {key}"))
1123                                })?;
1124                                self.apply_assign_op(compound, current, rhs)?
1125                            }
1126                        };
1127                        map.insert(key, new_val);
1128                        if let NodeKind::Identifier { name: obj_name } = &object.kind {
1129                            let updated = Value::Map(map);
1130                            if !self.env.assign(&obj_name.name, updated) {
1131                                return Err(RuntimeError::UndefinedVariable {
1132                                    name: obj_name.name.clone(),
1133                                });
1134                            }
1135                        }
1136                        Ok(Value::Void)
1137                    }
1138                    (obj, idx) => Err(RuntimeError::TypeError(format!(
1139                        "cannot index-assign {obj} with {idx}"
1140                    ))),
1141                }
1142            }
1143            _ => Err(RuntimeError::NotImplemented(
1144                "unsupported assignment target".to_string(),
1145            )),
1146        }
1147    }
1148
1149    fn apply_assign_op(&self, op: AssignOp, lhs: Value, rhs: Value) -> Result<Value, RuntimeError> {
1150        match (op, lhs, rhs) {
1151            (AssignOp::AddAssign, Value::Int(a), Value::Int(b)) => a
1152                .checked_add(b)
1153                .map(Value::Int)
1154                .ok_or(RuntimeError::IntOverflow),
1155            (AssignOp::SubAssign, Value::Int(a), Value::Int(b)) => a
1156                .checked_sub(b)
1157                .map(Value::Int)
1158                .ok_or(RuntimeError::IntOverflow),
1159            (AssignOp::MulAssign, Value::Int(a), Value::Int(b)) => a
1160                .checked_mul(b)
1161                .map(Value::Int)
1162                .ok_or(RuntimeError::IntOverflow),
1163            (AssignOp::DivAssign, Value::Int(a), Value::Int(b)) => {
1164                if b == 0 {
1165                    Err(RuntimeError::DivisionByZero)
1166                } else {
1167                    Ok(Value::Int(a / b))
1168                }
1169            }
1170            (AssignOp::RemAssign, Value::Int(a), Value::Int(b)) => {
1171                if b == 0 {
1172                    Err(RuntimeError::DivisionByZero)
1173                } else {
1174                    Ok(Value::Int(a % b))
1175                }
1176            }
1177            (AssignOp::AddAssign, Value::Float(a), Value::Float(b)) => {
1178                Ok(Value::Float(OrdF64(a.0 + b.0)))
1179            }
1180            (AssignOp::SubAssign, Value::Float(a), Value::Float(b)) => {
1181                Ok(Value::Float(OrdF64(a.0 - b.0)))
1182            }
1183            (AssignOp::MulAssign, Value::Float(a), Value::Float(b)) => {
1184                Ok(Value::Float(OrdF64(a.0 * b.0)))
1185            }
1186            (AssignOp::DivAssign, Value::Float(a), Value::Float(b)) => {
1187                Ok(Value::Float(OrdF64(a.0 / b.0)))
1188            }
1189            (AssignOp::AddAssign, Value::String(a), Value::String(b)) => {
1190                Ok(Value::String(BockString::new(format!("{a}{b}"))))
1191            }
1192            (op, l, r) => Err(RuntimeError::TypeError(format!(
1193                "compound assignment {op:?} not supported for {l} and {r}"
1194            ))),
1195        }
1196    }
1197
1198    // ── Function calls ─────────────────────────────────────────────────────
1199
1200    #[async_recursion]
1201    async fn eval_call(
1202        &mut self,
1203        callee: &AIRNode,
1204        args: &[AirArg],
1205    ) -> Result<Value, RuntimeError> {
1206        // Check for global built-in functions first (e.g., print, println, debug).
1207        if let NodeKind::Identifier { name } = &callee.kind {
1208            if self.builtins.has_global(&name.name) {
1209                let mut arg_values: Vec<Value> = Vec::with_capacity(args.len());
1210                for a in args {
1211                    arg_values.push(self.eval_expr(&a.value).await?);
1212                }
1213                return self
1214                    .builtins
1215                    .call_global(&name.name, &arg_values)
1216                    .expect("has_global check confirmed builtin exists");
1217            }
1218
1219            // Check for effect operation dispatch: log(msg) → Logger handler
1220            if let Some(effect_name) = self.effect_operations.get(&name.name).cloned() {
1221                let handler = self.effect_handlers.resolve(&effect_name).cloned().ok_or(
1222                    RuntimeError::NoEffectHandler {
1223                        effect: effect_name,
1224                    },
1225                )?;
1226                let mut arg_values = Vec::with_capacity(args.len());
1227                for arg in args {
1228                    arg_values.push(self.eval_expr(&arg.value).await?);
1229                }
1230                return self
1231                    .dispatch_effect_op(&handler, &name.name, arg_values)
1232                    .await;
1233            }
1234        }
1235
1236        // Check for associated function calls: `Type.method(args)` is lowered
1237        // to `Call(FieldAccess(Type, method), args)` with NO self prepended.
1238        // Associated functions are registered as qualified globals like
1239        // `Duration.seconds`; detect this before the desugared-method path.
1240        if let NodeKind::FieldAccess { object, field } = &callee.kind {
1241            if let NodeKind::Identifier { name: type_name } = &object.kind {
1242                let qualified = format!("{}.{}", type_name.name, field.name);
1243                if self.builtins.has_global(&qualified) {
1244                    let mut arg_values = Vec::with_capacity(args.len());
1245                    for a in args {
1246                        arg_values.push(self.eval_expr(&a.value).await?);
1247                    }
1248                    return self
1249                        .builtins
1250                        .call_global(&qualified, &arg_values)
1251                        .expect("has_global check confirmed builtin exists");
1252                }
1253                // User-defined associated function on a named type, e.g. a
1254                // trait-impl's `Target.from(source)`. The receiver names the
1255                // *type* (not a value), so we dispatch by name through the
1256                // method table. Only attempt this when the name is a registered
1257                // type with a matching static method — otherwise fall through so
1258                // genuine value receivers (a variable holding a record) still
1259                // reach the desugared-method path below.
1260                if self.method_table.contains_key(&type_name.name)
1261                    && self.env.get(&type_name.name).is_none()
1262                {
1263                    let mut arg_values = Vec::with_capacity(args.len());
1264                    for a in args {
1265                        arg_values.push(self.eval_expr(&a.value).await?);
1266                    }
1267                    if let Some(result) = self
1268                        .try_call_assoc_fn(&type_name.name, &field.name, arg_values)
1269                        .await?
1270                    {
1271                        return Ok(result);
1272                    }
1273                }
1274            }
1275        }
1276
1277        // Check for desugared method calls: the lowerer converts `obj.method(args)`
1278        // into `Call(FieldAccess(obj, method), [obj, ...args])`. Try builtin dispatch
1279        // before falling through to field-access evaluation.
1280        if let NodeKind::FieldAccess { object, field } = &callee.kind {
1281            let recv = self.eval_expr(object).await?;
1282            let type_tag = TypeTag::of(&recv);
1283            // For record receivers, a user-defined `impl` method shadows the
1284            // universal record `to_string` builtin. The compiled targets
1285            // dispatch the user method here (e.g. a `Displayable.to_string`
1286            // impl), so the interpreter must too — checking the builtin first
1287            // would silently call `universal_to_string` and diverge. The
1288            // test-harness matchers (`to_equal`, …) are reserved (see
1289            // `user_impl_may_shadow_record_builtin`).
1290            if matches!(recv, Value::Record(_))
1291                && user_impl_may_shadow_record_builtin(&field.name)
1292                && self
1293                    .method_table
1294                    .get(record_type_name(&recv).unwrap_or(""))
1295                    .is_some_and(|m| m.contains_key(&field.name))
1296            {
1297                let mut method_args = Vec::with_capacity(args.len().saturating_sub(1));
1298                for a in args.iter().skip(1) {
1299                    method_args.push(self.eval_expr(&a.value).await?);
1300                }
1301                if let Some(outcome) = self
1302                    .try_call_impl_method(&recv, &field.name, method_args)
1303                    .await?
1304                {
1305                    if let Some(updated) = outcome.updated_self {
1306                        self.write_back_receiver(object, updated);
1307                    }
1308                    return Ok(outcome.value);
1309                }
1310            }
1311            // DQ18/DQ30: an in-place `List` mutator mutates the receiver
1312            // *place* (and `pop`/`remove_at` return non-list values), so it is
1313            // intercepted BEFORE the builtin registry, whose legacy List
1314            // entries return new lists without any write-back.
1315            if let Value::List(items) = &recv {
1316                if Self::LIST_INPLACE_MUTATORS.contains(&field.name.as_str()) {
1317                    let items = items.clone();
1318                    let mut m_args = Vec::with_capacity(args.len().saturating_sub(1));
1319                    for a in args.iter().skip(1) {
1320                        m_args.push(self.eval_expr(&a.value).await?);
1321                    }
1322                    return self.run_list_inplace_mutator(object, items, &field.name, m_args);
1323                }
1324            }
1325            if self.builtins.has_method(type_tag, &field.name) {
1326                let mut builtin_args = Vec::with_capacity(args.len());
1327                builtin_args.push(recv);
1328                // Skip the first arg (self/receiver inserted by lowerer)
1329                for a in args.iter().skip(1) {
1330                    builtin_args.push(self.eval_expr(&a.value).await?);
1331                }
1332                // Try higher-order builtin first (needs callback invoker)
1333                if let Some(ho_func) = self.builtins.get_ho_method(type_tag, &field.name) {
1334                    return ho_func(&builtin_args, self).await;
1335                }
1336                return self
1337                    .builtins
1338                    .call(type_tag, &field.name, &builtin_args)
1339                    .expect("has_method check confirmed builtin exists");
1340            }
1341            // Try user-defined impl methods (skip first arg which is receiver duplicate).
1342            let mut method_args = Vec::with_capacity(args.len().saturating_sub(1));
1343            for a in args.iter().skip(1) {
1344                method_args.push(self.eval_expr(&a.value).await?);
1345            }
1346            if let Some(outcome) = self
1347                .try_call_impl_method(&recv, &field.name, method_args)
1348                .await?
1349            {
1350                // Persist `mut self` mutations back to the receiver lvalue.
1351                if let Some(updated) = outcome.updated_self {
1352                    self.write_back_receiver(object, updated);
1353                }
1354                return Ok(outcome.value);
1355            }
1356            // Fallback: try inline collection method dispatch (contains, map,
1357            // filter, keys, first, etc.) which lives in eval_method_call.
1358            return self.eval_method_call(object, &field.name, &args[1..]).await;
1359        }
1360
1361        let fn_val = self.eval_expr(callee).await?;
1362        let fn_id = match &fn_val {
1363            Value::Function(fv) => fv.id,
1364            other => {
1365                return Err(RuntimeError::NotCallable {
1366                    value: other.to_string(),
1367                })
1368            }
1369        };
1370        let mut arg_values: Vec<Value> = Vec::with_capacity(args.len());
1371        for a in args {
1372            arg_values.push(self.eval_expr(&a.value).await?);
1373        }
1374        let closure =
1375            self.fn_registry
1376                .get(&fn_id)
1377                .cloned()
1378                .ok_or_else(|| RuntimeError::NotCallable {
1379                    value: format!("unregistered fn #{fn_id}"),
1380                })?;
1381        self.call_closure(&closure, arg_values).await
1382    }
1383
1384    #[async_recursion]
1385    async fn call_closure(
1386        &mut self,
1387        closure: &Closure,
1388        args: Vec<Value>,
1389    ) -> Result<Value, RuntimeError> {
1390        match &closure.body {
1391            ClosureBody::Air(body) => {
1392                if closure.params.len() != args.len() {
1393                    return Err(RuntimeError::ArityMismatch {
1394                        expected: closure.params.len(),
1395                        got: args.len(),
1396                    });
1397                }
1398                let body = *body.clone();
1399
1400                if closure.is_async {
1401                    // Spawn an async task with a CLONE of the interpreter so it
1402                    // owns its env, fn_registry, etc. for the duration of the
1403                    // call. The current interpreter resumes immediately with a
1404                    // `Value::Future` that resolves when the task completes.
1405                    let mut sub = self.clone();
1406                    if !closure.is_toplevel {
1407                        sub.env = closure.captured.clone();
1408                    }
1409                    sub.env.push_scope();
1410                    for (name, val) in closure.params.iter().zip(args) {
1411                        sub.env.define(name.clone(), val);
1412                    }
1413                    let handle: tokio::task::JoinHandle<Result<Value, RuntimeError>> =
1414                        tokio::spawn(async move {
1415                            match sub.eval_expr(&body).await {
1416                                // `return` and `?`-propagation (§7.10) both
1417                                // surface their carried value as the async
1418                                // function's result.
1419                                Err(RuntimeError::Return(v) | RuntimeError::Propagated(v)) => {
1420                                    Ok(*v)
1421                                }
1422                                other => other,
1423                            }
1424                        });
1425                    return Ok(Value::Future(Arc::new(Mutex::new(Some(handle)))));
1426                }
1427
1428                // Top-level functions use the current env (all globals visible);
1429                // closures/lambdas use their captured env (lexical scoping).
1430                let saved_env = if closure.is_toplevel {
1431                    self.env.clone()
1432                } else {
1433                    std::mem::replace(&mut self.env, closure.captured.clone())
1434                };
1435                self.env.push_scope();
1436                for (name, val) in closure.params.iter().zip(args) {
1437                    self.env.define(name.clone(), val);
1438                }
1439                let result = self.eval_expr(&body).await;
1440                self.env = saved_env;
1441                // Convert a `return` signal into the return value. A
1442                // `?`-propagation signal (§7.10) is likewise caught HERE, at
1443                // the function-call boundary: the propagated `Err(e)`/`None`
1444                // becomes the function's return value, which the caller
1445                // observes as a normal `Result`/`Optional` — matching the
1446                // compiled targets' early-return lowering instead of aborting
1447                // the whole program (Q-interp-question-propagation).
1448                match result {
1449                    Err(RuntimeError::Return(v) | RuntimeError::Propagated(v)) => Ok(*v),
1450                    other => other,
1451                }
1452            }
1453            ClosureBody::Composed { inner, outer } => {
1454                let inner_id = *inner;
1455                let outer_id = *outer;
1456                let inner_closure = self.fn_registry.get(&inner_id).cloned().ok_or_else(|| {
1457                    RuntimeError::NotCallable {
1458                        value: format!("composed inner fn #{inner_id}"),
1459                    }
1460                })?;
1461                let intermediate = self.call_closure(&inner_closure, args).await?;
1462                let outer_closure = self.fn_registry.get(&outer_id).cloned().ok_or_else(|| {
1463                    RuntimeError::NotCallable {
1464                        value: format!("composed outer fn #{outer_id}"),
1465                    }
1466                })?;
1467                self.call_closure(&outer_closure, vec![intermediate]).await
1468            }
1469            ClosureBody::Native(build) => {
1470                if closure.params.len() != args.len() {
1471                    return Err(RuntimeError::ArityMismatch {
1472                        expected: closure.params.len(),
1473                        got: args.len(),
1474                    });
1475                }
1476                Ok(build(&args))
1477            }
1478        }
1479    }
1480
1481    // ── Lambda / closure creation ──────────────────────────────────────────
1482
1483    fn eval_lambda(&mut self, params: &[AIRNode], body: &AIRNode) -> Result<Value, RuntimeError> {
1484        let param_names: Vec<String> = params
1485            .iter()
1486            .map(|p| match &p.kind {
1487                NodeKind::Param { pattern, .. } => match &pattern.kind {
1488                    NodeKind::BindPat { name, .. } => name.name.clone(),
1489                    NodeKind::WildcardPat => "_".to_string(),
1490                    _ => "_".to_string(),
1491                },
1492                _ => "_".to_string(),
1493            })
1494            .collect();
1495
1496        let fn_val = FnValue::new_anonymous();
1497        let id = fn_val.id;
1498        let captured = self.env.clone();
1499        self.fn_registry.insert(
1500            id,
1501            Closure {
1502                params: param_names,
1503                body: ClosureBody::Air(Box::new(body.clone())),
1504                captured,
1505                is_toplevel: false,
1506                is_async: false,
1507            },
1508        );
1509        Ok(Value::Function(fn_val))
1510    }
1511
1512    // ── Pipe operator ──────────────────────────────────────────────────────
1513
1514    #[async_recursion]
1515    async fn eval_pipe(&mut self, left: &AIRNode, right: &AIRNode) -> Result<Value, RuntimeError> {
1516        let lhs = self.eval_expr(left).await?;
1517        // If the right-hand side is itself a call, prepend lhs to its args.
1518        match &right.kind.clone() {
1519            NodeKind::Call { callee, args, .. } => {
1520                let callee = callee.clone();
1521                let args: Vec<AirArg> = args.clone();
1522                let fn_val = self.eval_expr(&callee).await?;
1523                let fn_id = match &fn_val {
1524                    Value::Function(fv) => fv.id,
1525                    other => {
1526                        return Err(RuntimeError::NotCallable {
1527                            value: other.to_string(),
1528                        })
1529                    }
1530                };
1531                let mut arg_values = vec![lhs];
1532                for a in &args {
1533                    arg_values.push(self.eval_expr(&a.value).await?);
1534                }
1535                let closure = self.fn_registry.get(&fn_id).cloned().ok_or_else(|| {
1536                    RuntimeError::NotCallable {
1537                        value: format!("unregistered fn #{fn_id}"),
1538                    }
1539                })?;
1540                self.call_closure(&closure, arg_values).await
1541            }
1542            _ => {
1543                // Evaluate right as a function, pass lhs as single argument.
1544                let fn_val = self.eval_expr(right).await?;
1545                let fn_id = match &fn_val {
1546                    Value::Function(fv) => fv.id,
1547                    other => {
1548                        return Err(RuntimeError::NotCallable {
1549                            value: other.to_string(),
1550                        })
1551                    }
1552                };
1553                let closure = self.fn_registry.get(&fn_id).cloned().ok_or_else(|| {
1554                    RuntimeError::NotCallable {
1555                        value: format!("unregistered fn #{fn_id}"),
1556                    }
1557                })?;
1558                self.call_closure(&closure, vec![lhs]).await
1559            }
1560        }
1561    }
1562
1563    // ── Function composition ───────────────────────────────────────────────
1564
1565    #[async_recursion]
1566    async fn eval_compose(
1567        &mut self,
1568        left: &AIRNode,
1569        right: &AIRNode,
1570    ) -> Result<Value, RuntimeError> {
1571        let f = self.eval_expr(left).await?;
1572        let g = self.eval_expr(right).await?;
1573        let f_id = match &f {
1574            Value::Function(fv) => fv.id,
1575            other => {
1576                return Err(RuntimeError::TypeError(format!(
1577                    ">> requires functions, got {other}"
1578                )))
1579            }
1580        };
1581        let g_id = match &g {
1582            Value::Function(fv) => fv.id,
1583            other => {
1584                return Err(RuntimeError::TypeError(format!(
1585                    ">> requires functions, got {other}"
1586                )))
1587            }
1588        };
1589        let fn_val = FnValue::new_anonymous();
1590        let id = fn_val.id;
1591        self.fn_registry.insert(
1592            id,
1593            Closure {
1594                params: vec!["__x".to_string()],
1595                body: ClosureBody::Composed {
1596                    inner: f_id,
1597                    outer: g_id,
1598                },
1599                captured: self.env.clone(),
1600                is_toplevel: false,
1601                is_async: false,
1602            },
1603        );
1604        Ok(Value::Function(fn_val))
1605    }
1606
1607    // ── Method calls ───────────────────────────────────────────────────────
1608
1609    #[async_recursion]
1610    async fn eval_method_call(
1611        &mut self,
1612        receiver: &AIRNode,
1613        method: &str,
1614        args: &[AirArg],
1615    ) -> Result<Value, RuntimeError> {
1616        let recv = self.eval_expr(receiver).await?;
1617        let method = method.to_string();
1618        let mut arg_values: Vec<Value> = Vec::with_capacity(args.len());
1619        for a in args {
1620            arg_values.push(self.eval_expr(&a.value).await?);
1621        }
1622
1623        // For record receivers, a user-defined `impl` method shadows the
1624        // universal record `to_string` builtin, matching the compiled targets.
1625        // Check the method table before the builtin registry so a user
1626        // `Displayable.to_string` impl wins. The test-harness matchers are
1627        // reserved (see `user_impl_may_shadow_record_builtin`).
1628        if matches!(recv, Value::Record(_))
1629            && user_impl_may_shadow_record_builtin(&method)
1630            && self
1631                .method_table
1632                .get(record_type_name(&recv).unwrap_or(""))
1633                .is_some_and(|m| m.contains_key(&method))
1634        {
1635            if let Some(outcome) = self
1636                .try_call_impl_method(&recv, &method, arg_values.clone())
1637                .await?
1638            {
1639                if let Some(updated) = outcome.updated_self {
1640                    self.write_back_receiver(receiver, updated);
1641                }
1642                return Ok(outcome.value);
1643            }
1644        }
1645
1646        // DQ18/DQ30: in-place `List` mutators mutate the receiver place —
1647        // intercept before the value-returning builtin registry (see
1648        // `run_list_inplace_mutator`).
1649        if let Value::List(items) = &recv {
1650            if Self::LIST_INPLACE_MUTATORS.contains(&method.as_str()) {
1651                let items = items.clone();
1652                return self.run_list_inplace_mutator(receiver, items, &method, arg_values);
1653            }
1654        }
1655
1656        // Check the builtin registry first.
1657        let type_tag = TypeTag::of(&recv);
1658        {
1659            let mut builtin_args = Vec::with_capacity(1 + arg_values.len());
1660            builtin_args.push(recv.clone());
1661            builtin_args.extend(arg_values.iter().cloned());
1662            // Try higher-order builtin first (needs callback invoker)
1663            if let Some(ho_func) = self.builtins.get_ho_method(type_tag, &method) {
1664                return ho_func(&builtin_args, self).await;
1665            }
1666            if let Some(result) = self.builtins.call(type_tag, &method, &builtin_args) {
1667                return result;
1668            }
1669        }
1670
1671        match (&recv, method.as_str()) {
1672            // ── Universal ─────────────────────────────────────────────────
1673            (_, "to_string") => Ok(Value::String(BockString::new(recv.to_string()))),
1674
1675            // DQ29 (§18.5 structural Equatable): `a.eq(b)` reached through an
1676            // `Equatable` bound whose instantiation is a STRUCTURAL type — a
1677            // record/enum/collection with no explicit `impl Equatable` (an
1678            // impl would have been dispatched by the method-table check
1679            // above), and not a primitive (the builtins' eq bridge above
1680            // covers those). Falls back to the interpreter's structural
1681            // `Value` equality, mirroring how the compiled targets lower the
1682            // bridge natively. `Fn` values are excluded: functions are not
1683            // Equatable, and the checker rejects such instantiations.
1684            (recv_v, "eq") if !matches!(recv_v, Value::Function(_)) => {
1685                let other = arg_values.first().ok_or(RuntimeError::ArityMismatch {
1686                    expected: 1,
1687                    got: 0,
1688                })?;
1689                Ok(Value::Bool(recv == *other))
1690            }
1691
1692            // ── List ──────────────────────────────────────────────────────
1693            (Value::List(items), "len") => Ok(Value::Int(items.len() as i64)),
1694            (Value::List(items), "is_empty") => Ok(Value::Bool(items.is_empty())),
1695            (Value::List(items), "first") => {
1696                Ok(Value::Optional(items.first().cloned().map(Box::new)))
1697            }
1698            (Value::List(items), "last") => {
1699                Ok(Value::Optional(items.last().cloned().map(Box::new)))
1700            }
1701            (Value::List(items), "get") => {
1702                let idx = arg_values.first().ok_or(RuntimeError::ArityMismatch {
1703                    expected: 1,
1704                    got: 0,
1705                })?;
1706                if let Value::Int(i) = idx {
1707                    let i = *i;
1708                    if i < 0 || i as usize >= items.len() {
1709                        Ok(Value::Optional(None))
1710                    } else {
1711                        Ok(Value::Optional(Some(Box::new(items[i as usize].clone()))))
1712                    }
1713                } else {
1714                    Err(RuntimeError::TypeError(
1715                        "List.get expects an Int index".to_string(),
1716                    ))
1717                }
1718            }
1719            // The in-place mutators (`push`/`append`/`pop`/`remove_at`/
1720            // `insert`/`reverse`/`set`) are intercepted before this match —
1721            // see `run_list_inplace_mutator`.
1722            (Value::List(items), "contains") => {
1723                let v = arg_values.first().ok_or(RuntimeError::ArityMismatch {
1724                    expected: 1,
1725                    got: 0,
1726                })?;
1727                Ok(Value::Bool(items.contains(v)))
1728            }
1729            (Value::List(items), "map") => {
1730                let fn_val = arg_values
1731                    .into_iter()
1732                    .next()
1733                    .ok_or(RuntimeError::ArityMismatch {
1734                        expected: 1,
1735                        got: 0,
1736                    })?;
1737                let fn_id = match fn_val {
1738                    Value::Function(ref fv) => fv.id,
1739                    other => {
1740                        return Err(RuntimeError::TypeError(format!(
1741                            "List.map expects a function, got {other}"
1742                        )))
1743                    }
1744                };
1745                let closure = self.fn_registry.get(&fn_id).cloned().ok_or_else(|| {
1746                    RuntimeError::NotCallable {
1747                        value: "unregistered fn".to_string(),
1748                    }
1749                })?;
1750                let items = items.clone();
1751                let mut result = Vec::with_capacity(items.len());
1752                for item in items {
1753                    result.push(self.call_closure(&closure, vec![item]).await?);
1754                }
1755                Ok(Value::List(result))
1756            }
1757            (Value::List(items), "filter") => {
1758                let fn_val = arg_values
1759                    .into_iter()
1760                    .next()
1761                    .ok_or(RuntimeError::ArityMismatch {
1762                        expected: 1,
1763                        got: 0,
1764                    })?;
1765                let fn_id = match fn_val {
1766                    Value::Function(ref fv) => fv.id,
1767                    other => {
1768                        return Err(RuntimeError::TypeError(format!(
1769                            "List.filter expects a function, got {other}"
1770                        )))
1771                    }
1772                };
1773                let closure = self.fn_registry.get(&fn_id).cloned().ok_or_else(|| {
1774                    RuntimeError::NotCallable {
1775                        value: "unregistered fn".to_string(),
1776                    }
1777                })?;
1778                let items = items.clone();
1779                let mut result = Vec::new();
1780                for item in items {
1781                    if let Value::Bool(true) =
1782                        self.call_closure(&closure, vec![item.clone()]).await?
1783                    {
1784                        result.push(item);
1785                    }
1786                }
1787                Ok(Value::List(result))
1788            }
1789            (Value::List(items), "fold") | (Value::List(items), "reduce") => {
1790                if arg_values.len() != 2 {
1791                    return Err(RuntimeError::ArityMismatch {
1792                        expected: 2,
1793                        got: arg_values.len(),
1794                    });
1795                }
1796                let mut acc = arg_values.remove(0);
1797                let fn_val = arg_values.remove(0);
1798                let fn_id = match fn_val {
1799                    Value::Function(ref fv) => fv.id,
1800                    other => {
1801                        return Err(RuntimeError::TypeError(format!(
1802                            "List.fold expects a function, got {other}"
1803                        )))
1804                    }
1805                };
1806                let closure = self.fn_registry.get(&fn_id).cloned().ok_or_else(|| {
1807                    RuntimeError::NotCallable {
1808                        value: "unregistered fn".to_string(),
1809                    }
1810                })?;
1811                let items = items.clone();
1812                for item in items {
1813                    acc = self.call_closure(&closure, vec![acc, item]).await?;
1814                }
1815                Ok(acc)
1816            }
1817            (Value::List(items), "any") => {
1818                let fn_val = arg_values
1819                    .into_iter()
1820                    .next()
1821                    .ok_or(RuntimeError::ArityMismatch {
1822                        expected: 1,
1823                        got: 0,
1824                    })?;
1825                let fn_id = match fn_val {
1826                    Value::Function(ref fv) => fv.id,
1827                    other => {
1828                        return Err(RuntimeError::TypeError(format!(
1829                            "List.any expects a function, got {other}"
1830                        )))
1831                    }
1832                };
1833                let closure = self.fn_registry.get(&fn_id).cloned().ok_or_else(|| {
1834                    RuntimeError::NotCallable {
1835                        value: "unregistered fn".to_string(),
1836                    }
1837                })?;
1838                let items = items.clone();
1839                for item in items {
1840                    if let Value::Bool(true) = self.call_closure(&closure, vec![item]).await? {
1841                        return Ok(Value::Bool(true));
1842                    }
1843                }
1844                Ok(Value::Bool(false))
1845            }
1846            (Value::List(items), "all") => {
1847                let fn_val = arg_values
1848                    .into_iter()
1849                    .next()
1850                    .ok_or(RuntimeError::ArityMismatch {
1851                        expected: 1,
1852                        got: 0,
1853                    })?;
1854                let fn_id = match fn_val {
1855                    Value::Function(ref fv) => fv.id,
1856                    other => {
1857                        return Err(RuntimeError::TypeError(format!(
1858                            "List.all expects a function, got {other}"
1859                        )))
1860                    }
1861                };
1862                let closure = self.fn_registry.get(&fn_id).cloned().ok_or_else(|| {
1863                    RuntimeError::NotCallable {
1864                        value: "unregistered fn".to_string(),
1865                    }
1866                })?;
1867                let items = items.clone();
1868                for item in items {
1869                    if let Value::Bool(false) = self.call_closure(&closure, vec![item]).await? {
1870                        return Ok(Value::Bool(false));
1871                    }
1872                }
1873                Ok(Value::Bool(true))
1874            }
1875            (Value::List(items), "find") => {
1876                let fn_val = arg_values
1877                    .into_iter()
1878                    .next()
1879                    .ok_or(RuntimeError::ArityMismatch {
1880                        expected: 1,
1881                        got: 0,
1882                    })?;
1883                let fn_id = match fn_val {
1884                    Value::Function(ref fv) => fv.id,
1885                    other => {
1886                        return Err(RuntimeError::TypeError(format!(
1887                            "List.find expects a function, got {other}"
1888                        )))
1889                    }
1890                };
1891                let closure = self.fn_registry.get(&fn_id).cloned().ok_or_else(|| {
1892                    RuntimeError::NotCallable {
1893                        value: "unregistered fn".to_string(),
1894                    }
1895                })?;
1896                let items = items.clone();
1897                for item in items {
1898                    if let Value::Bool(true) =
1899                        self.call_closure(&closure, vec![item.clone()]).await?
1900                    {
1901                        return Ok(Value::Optional(Some(Box::new(item))));
1902                    }
1903                }
1904                Ok(Value::Optional(None))
1905            }
1906            (Value::List(items), "for_each") => {
1907                let fn_val = arg_values
1908                    .into_iter()
1909                    .next()
1910                    .ok_or(RuntimeError::ArityMismatch {
1911                        expected: 1,
1912                        got: 0,
1913                    })?;
1914                let fn_id = match fn_val {
1915                    Value::Function(ref fv) => fv.id,
1916                    other => {
1917                        return Err(RuntimeError::TypeError(format!(
1918                            "List.for_each expects a function, got {other}"
1919                        )))
1920                    }
1921                };
1922                let closure = self.fn_registry.get(&fn_id).cloned().ok_or_else(|| {
1923                    RuntimeError::NotCallable {
1924                        value: "unregistered fn".to_string(),
1925                    }
1926                })?;
1927                let items = items.clone();
1928                for item in items {
1929                    self.call_closure(&closure, vec![item]).await?;
1930                }
1931                Ok(Value::Void)
1932            }
1933            (Value::List(items), "flat_map") => {
1934                let fn_val = arg_values
1935                    .into_iter()
1936                    .next()
1937                    .ok_or(RuntimeError::ArityMismatch {
1938                        expected: 1,
1939                        got: 0,
1940                    })?;
1941                let fn_id = match fn_val {
1942                    Value::Function(ref fv) => fv.id,
1943                    other => {
1944                        return Err(RuntimeError::TypeError(format!(
1945                            "List.flat_map expects a function, got {other}"
1946                        )))
1947                    }
1948                };
1949                let closure = self.fn_registry.get(&fn_id).cloned().ok_or_else(|| {
1950                    RuntimeError::NotCallable {
1951                        value: "unregistered fn".to_string(),
1952                    }
1953                })?;
1954                let items = items.clone();
1955                let mut result = Vec::new();
1956                for item in items {
1957                    match self.call_closure(&closure, vec![item]).await? {
1958                        Value::List(inner) => result.extend(inner),
1959                        other => result.push(other),
1960                    }
1961                }
1962                Ok(Value::List(result))
1963            }
1964            (Value::List(items), "sort") => {
1965                let mut v = items.clone();
1966                v.sort();
1967                Ok(Value::List(v))
1968            }
1969            (Value::List(items), "dedup") => {
1970                let mut v = items.clone();
1971                v.dedup();
1972                Ok(Value::List(v))
1973            }
1974            (Value::List(items), "flatten") => {
1975                let mut result = Vec::new();
1976                for item in items {
1977                    match item {
1978                        Value::List(inner) => result.extend(inner.iter().cloned()),
1979                        other => result.push(other.clone()),
1980                    }
1981                }
1982                Ok(Value::List(result))
1983            }
1984            (Value::List(items), "zip") => {
1985                let other = match arg_values.first() {
1986                    Some(Value::List(l)) => l,
1987                    _ => {
1988                        return Err(RuntimeError::TypeError(
1989                            "List.zip expects a List argument".to_string(),
1990                        ))
1991                    }
1992                };
1993                let pairs: Vec<Value> = items
1994                    .iter()
1995                    .zip(other.iter())
1996                    .map(|(a, b)| Value::Tuple(vec![a.clone(), b.clone()]))
1997                    .collect();
1998                Ok(Value::List(pairs))
1999            }
2000            (Value::List(items), "concat") => {
2001                let other = match arg_values.first() {
2002                    Some(Value::List(l)) => l,
2003                    _ => {
2004                        return Err(RuntimeError::TypeError(
2005                            "List.concat expects a List argument".to_string(),
2006                        ))
2007                    }
2008                };
2009                let mut new_list = items.clone();
2010                new_list.extend_from_slice(other);
2011                Ok(Value::List(new_list))
2012            }
2013            (Value::List(items), "slice") => {
2014                if arg_values.len() < 2 {
2015                    return Err(RuntimeError::ArityMismatch {
2016                        expected: 2,
2017                        got: arg_values.len(),
2018                    });
2019                }
2020                let start = match &arg_values[0] {
2021                    Value::Int(i) => *i,
2022                    _ => {
2023                        return Err(RuntimeError::TypeError(
2024                            "List.slice expects Int arguments".to_string(),
2025                        ))
2026                    }
2027                };
2028                let end = match &arg_values[1] {
2029                    Value::Int(i) => *i,
2030                    _ => {
2031                        return Err(RuntimeError::TypeError(
2032                            "List.slice expects Int arguments".to_string(),
2033                        ))
2034                    }
2035                };
2036                let len = items.len() as i64;
2037                let start = start.max(0).min(len) as usize;
2038                let end = end.max(0).min(len) as usize;
2039                if start >= end {
2040                    Ok(Value::List(vec![]))
2041                } else {
2042                    Ok(Value::List(items[start..end].to_vec()))
2043                }
2044            }
2045            (Value::List(items), "take") => {
2046                let n = match arg_values.first() {
2047                    Some(Value::Int(i)) => *i,
2048                    _ => return Err(RuntimeError::TypeError("List.take expects Int".to_string())),
2049                };
2050                let n = (n.max(0) as usize).min(items.len());
2051                Ok(Value::List(items[..n].to_vec()))
2052            }
2053            (Value::List(items), "skip") => {
2054                let n = match arg_values.first() {
2055                    Some(Value::Int(i)) => *i,
2056                    _ => return Err(RuntimeError::TypeError("List.skip expects Int".to_string())),
2057                };
2058                let n = (n.max(0) as usize).min(items.len());
2059                Ok(Value::List(items[n..].to_vec()))
2060            }
2061            (Value::List(items), "enumerate") => {
2062                let result: Vec<Value> = items
2063                    .iter()
2064                    .enumerate()
2065                    .map(|(i, v)| Value::Tuple(vec![Value::Int(i as i64), v.clone()]))
2066                    .collect();
2067                Ok(Value::List(result))
2068            }
2069            (Value::List(items), "count") => Ok(Value::Int(items.len() as i64)),
2070            (Value::List(items), "index_of") => {
2071                let needle = arg_values.first().ok_or(RuntimeError::ArityMismatch {
2072                    expected: 1,
2073                    got: 0,
2074                })?;
2075                match items.iter().position(|v| v == needle) {
2076                    Some(pos) => Ok(Value::Optional(Some(Box::new(Value::Int(pos as i64))))),
2077                    None => Ok(Value::Optional(None)),
2078                }
2079            }
2080            (Value::List(items), "join") => {
2081                let sep = match arg_values.first() {
2082                    Some(Value::String(s)) => s.as_str().to_owned(),
2083                    _ => String::new(),
2084                };
2085                let parts: Vec<String> = items.iter().map(|v| v.to_string()).collect();
2086                Ok(Value::String(BockString::new(parts.join(&sep))))
2087            }
2088            (Value::List(items), "to_set") => {
2089                let set: std::collections::BTreeSet<Value> = items.iter().cloned().collect();
2090                Ok(Value::Set(set))
2091            }
2092
2093            // ── String ────────────────────────────────────────────────────
2094            (Value::String(s), "len") => Ok(Value::Int(s.as_str().chars().count() as i64)),
2095            (Value::String(s), "is_empty") => Ok(Value::Bool(s.as_str().is_empty())),
2096            (Value::String(s), "to_upper") => {
2097                Ok(Value::String(BockString::new(s.as_str().to_uppercase())))
2098            }
2099            (Value::String(s), "to_lower") => {
2100                Ok(Value::String(BockString::new(s.as_str().to_lowercase())))
2101            }
2102            (Value::String(s), "trim") => Ok(Value::String(BockString::new(s.as_str().trim()))),
2103            (Value::String(s), "contains") => {
2104                if let Some(Value::String(sub)) = arg_values.first() {
2105                    Ok(Value::Bool(s.as_str().contains(sub.as_str())))
2106                } else {
2107                    Err(RuntimeError::TypeError(
2108                        "String.contains expects a String".to_string(),
2109                    ))
2110                }
2111            }
2112            (Value::String(s), "starts_with") => {
2113                if let Some(Value::String(prefix)) = arg_values.first() {
2114                    Ok(Value::Bool(s.as_str().starts_with(prefix.as_str())))
2115                } else {
2116                    Err(RuntimeError::TypeError(
2117                        "String.starts_with expects a String".to_string(),
2118                    ))
2119                }
2120            }
2121            (Value::String(s), "ends_with") => {
2122                if let Some(Value::String(suffix)) = arg_values.first() {
2123                    Ok(Value::Bool(s.as_str().ends_with(suffix.as_str())))
2124                } else {
2125                    Err(RuntimeError::TypeError(
2126                        "String.ends_with expects a String".to_string(),
2127                    ))
2128                }
2129            }
2130            (Value::String(s), "split") => {
2131                let sep = match arg_values.first() {
2132                    Some(Value::String(sep)) => sep.as_str().to_owned(),
2133                    _ => {
2134                        return Err(RuntimeError::TypeError(
2135                            "String.split expects a String separator".to_string(),
2136                        ))
2137                    }
2138                };
2139                let parts: Vec<Value> = s
2140                    .as_str()
2141                    .split(&sep)
2142                    .map(|p| Value::String(BockString::new(p)))
2143                    .collect();
2144                Ok(Value::List(parts))
2145            }
2146            (Value::String(s), "replace") => {
2147                if arg_values.len() < 2 {
2148                    return Err(RuntimeError::ArityMismatch {
2149                        expected: 2,
2150                        got: arg_values.len(),
2151                    });
2152                }
2153                let old = match &arg_values[0] {
2154                    Value::String(s) => s.as_str().to_owned(),
2155                    _ => {
2156                        return Err(RuntimeError::TypeError(
2157                            "String.replace expects String arguments".to_string(),
2158                        ))
2159                    }
2160                };
2161                let new_s = match &arg_values[1] {
2162                    Value::String(s) => s.as_str().to_owned(),
2163                    _ => {
2164                        return Err(RuntimeError::TypeError(
2165                            "String.replace expects String arguments".to_string(),
2166                        ))
2167                    }
2168                };
2169                Ok(Value::String(BockString::new(
2170                    s.as_str().replace(&old, &new_s),
2171                )))
2172            }
2173            (Value::String(s), "chars") => {
2174                let chars: Vec<Value> = s.as_str().chars().map(Value::Char).collect();
2175                Ok(Value::List(chars))
2176            }
2177            (Value::String(s), "substring") => {
2178                if arg_values.len() < 2 {
2179                    return Err(RuntimeError::ArityMismatch {
2180                        expected: 2,
2181                        got: arg_values.len(),
2182                    });
2183                }
2184                let start = match &arg_values[0] {
2185                    Value::Int(i) => *i,
2186                    _ => {
2187                        return Err(RuntimeError::TypeError(
2188                            "String.substring expects Int arguments".to_string(),
2189                        ))
2190                    }
2191                };
2192                let end = match &arg_values[1] {
2193                    Value::Int(i) => *i,
2194                    _ => {
2195                        return Err(RuntimeError::TypeError(
2196                            "String.substring expects Int arguments".to_string(),
2197                        ))
2198                    }
2199                };
2200                let chars: Vec<char> = s.as_str().chars().collect();
2201                let len = chars.len() as i64;
2202                let start = start.max(0).min(len) as usize;
2203                let end = end.max(0).min(len) as usize;
2204                if start >= end {
2205                    Ok(Value::String(BockString::new("")))
2206                } else {
2207                    Ok(Value::String(BockString::new(
2208                        chars[start..end].iter().collect::<String>(),
2209                    )))
2210                }
2211            }
2212
2213            // ── Map ───────────────────────────────────────────────────────
2214            (Value::Map(map), "len") => Ok(Value::Int(map.len() as i64)),
2215            (Value::Map(map), "is_empty") => Ok(Value::Bool(map.is_empty())),
2216            (Value::Map(map), "contains_key") => {
2217                let k = arg_values.first().ok_or(RuntimeError::ArityMismatch {
2218                    expected: 1,
2219                    got: 0,
2220                })?;
2221                Ok(Value::Bool(map.contains_key(k)))
2222            }
2223            (Value::Map(map), "get") => {
2224                let k = arg_values.first().ok_or(RuntimeError::ArityMismatch {
2225                    expected: 1,
2226                    got: 0,
2227                })?;
2228                Ok(Value::Optional(map.get(k).cloned().map(Box::new)))
2229            }
2230            (Value::Map(map), "set") => {
2231                if arg_values.len() < 2 {
2232                    return Err(RuntimeError::ArityMismatch {
2233                        expected: 2,
2234                        got: arg_values.len(),
2235                    });
2236                }
2237                let mut new_map = map.clone();
2238                new_map.insert(arg_values.remove(0), arg_values.remove(0));
2239                Ok(Value::Map(new_map))
2240            }
2241            (Value::Map(map), "delete") => {
2242                let k = arg_values.first().ok_or(RuntimeError::ArityMismatch {
2243                    expected: 1,
2244                    got: 0,
2245                })?;
2246                let mut new_map = map.clone();
2247                new_map.remove(k);
2248                Ok(Value::Map(new_map))
2249            }
2250            (Value::Map(map), "merge") => {
2251                let other = match arg_values.first() {
2252                    Some(Value::Map(m)) => m,
2253                    _ => {
2254                        return Err(RuntimeError::TypeError(
2255                            "Map.merge expects a Map argument".to_string(),
2256                        ))
2257                    }
2258                };
2259                let mut new_map = map.clone();
2260                for (k, v) in other {
2261                    new_map.insert(k.clone(), v.clone());
2262                }
2263                Ok(Value::Map(new_map))
2264            }
2265            (Value::Map(map), "keys") => Ok(Value::List(map.keys().cloned().collect())),
2266            (Value::Map(map), "values") => Ok(Value::List(map.values().cloned().collect())),
2267            (Value::Map(map), "entries") => {
2268                let entries: Vec<Value> = map
2269                    .iter()
2270                    .map(|(k, v)| Value::Tuple(vec![k.clone(), v.clone()]))
2271                    .collect();
2272                Ok(Value::List(entries))
2273            }
2274
2275            // ── Set ───────────────────────────────────────────────────────
2276            (Value::Set(set), "len") => Ok(Value::Int(set.len() as i64)),
2277            (Value::Set(set), "is_empty") => Ok(Value::Bool(set.is_empty())),
2278            (Value::Set(set), "contains") => {
2279                let v = arg_values.first().ok_or(RuntimeError::ArityMismatch {
2280                    expected: 1,
2281                    got: 0,
2282                })?;
2283                Ok(Value::Bool(set.contains(v)))
2284            }
2285
2286            // ── Range ─────────────────────────────────────────────────────
2287            (
2288                Value::Range {
2289                    start,
2290                    end,
2291                    inclusive,
2292                    ..
2293                },
2294                "step",
2295            ) => {
2296                let s = start;
2297                let e = end;
2298                let i = inclusive;
2299                if let Some(Value::Int(step)) = arg_values.first() {
2300                    Ok(Value::Range {
2301                        start: *s,
2302                        end: *e,
2303                        inclusive: *i,
2304                        step: *step,
2305                    })
2306                } else {
2307                    Err(RuntimeError::TypeError(
2308                        "Range.step expects an Int".to_string(),
2309                    ))
2310                }
2311            }
2312            (
2313                Value::Range {
2314                    start,
2315                    end,
2316                    inclusive,
2317                    step,
2318                },
2319                "to_list",
2320            ) => Ok(Value::List(range_to_vec(*start, *end, *inclusive, *step))),
2321
2322            // ── Optional / Result ─────────────────────────────────────────
2323            (Value::Optional(Some(inner)), "unwrap") => Ok(*inner.clone()),
2324            (Value::Optional(None), "unwrap") => {
2325                Err(RuntimeError::TypeError("unwrapped None".to_string()))
2326            }
2327            (Value::Optional(opt), "unwrap_or") => {
2328                let default = arg_values
2329                    .into_iter()
2330                    .next()
2331                    .ok_or(RuntimeError::ArityMismatch {
2332                        expected: 1,
2333                        got: 0,
2334                    })?;
2335                Ok(opt.as_deref().cloned().unwrap_or(default))
2336            }
2337            (Value::Result(Ok(inner)), "unwrap") => Ok(*inner.clone()),
2338            (Value::Result(Err(e)), "unwrap") => {
2339                Err(RuntimeError::TypeError(format!("unwrapped Err({e})")))
2340            }
2341
2342            // ── Fallback: check user-defined impl methods, then free functions
2343            (recv_val, _) => {
2344                // Try user-defined impl methods from method_table.
2345                if let Some(outcome) = self
2346                    .try_call_impl_method(recv_val, &method, arg_values.clone())
2347                    .await?
2348                {
2349                    // Persist `mut self` mutations back to the receiver lvalue.
2350                    if let Some(updated) = outcome.updated_self {
2351                        self.write_back_receiver(receiver, updated);
2352                    }
2353                    return Ok(outcome.value);
2354                }
2355                if let Some(fn_val) = self.env.get(&method).cloned() {
2356                    let fn_id = match &fn_val {
2357                        Value::Function(fv) => fv.id,
2358                        _ => {
2359                            return Err(RuntimeError::TypeError(format!(
2360                                "method '{method}' not found on {type_tag}"
2361                            )))
2362                        }
2363                    };
2364                    let closure = self.fn_registry.get(&fn_id).cloned().ok_or_else(|| {
2365                        RuntimeError::NotCallable {
2366                            value: format!("unregistered method '{method}'"),
2367                        }
2368                    })?;
2369                    let mut all_args = vec![recv_val.clone()];
2370                    all_args.extend(arg_values);
2371                    self.call_closure(&closure, all_args).await
2372                } else {
2373                    Err(RuntimeError::TypeError(format!(
2374                        "method '{method}' not found on {type_tag}"
2375                    )))
2376                }
2377            }
2378        }
2379    }
2380
2381    // ── User-defined impl method dispatch ──────────────────────────────────
2382
2383    /// Try to dispatch a method call via the user-defined method table.
2384    ///
2385    /// Returns `Ok(Some(outcome))` if the method was found and called,
2386    /// `Ok(None)` if no matching method exists, or `Err` on runtime error.
2387    ///
2388    /// The [`MethodOutcome`] carries the method's return value and, for
2389    /// `mut self` instance methods, the post-call `self` value so the caller
2390    /// can persist receiver mutations to the original lvalue. Without this
2391    /// write-back, cursor-advancing methods (e.g. an iterator's `next(mut self)`)
2392    /// would never make progress under the interpreter, hanging `loop`-driven
2393    /// iteration that terminates fine on the compiled targets.
2394    #[async_recursion]
2395    async fn try_call_impl_method(
2396        &mut self,
2397        receiver: &Value,
2398        method: &str,
2399        args: Vec<Value>,
2400    ) -> Result<Option<MethodOutcome>, RuntimeError> {
2401        let type_name = match receiver {
2402            Value::Record(rv) => &rv.type_name,
2403            _ => return Ok(None),
2404        };
2405
2406        let entry = self
2407            .method_table
2408            .get(type_name)
2409            .and_then(|methods| methods.get(method))
2410            .cloned();
2411
2412        let MethodEntry {
2413            params: param_names,
2414            self_is_mut,
2415            body,
2416        } = match entry {
2417            Some(e) => e,
2418            None => return Ok(None),
2419        };
2420
2421        // Instance method (first param is `self`): bind the receiver as `self`.
2422        // Anything else is a static / associated function reached through an
2423        // instance receiver — bind args positionally with no `self`.
2424        if param_names.first().map(|s| s.as_str()) == Some("self") {
2425            let (value, updated_self) = self
2426                .run_method_body(
2427                    &param_names,
2428                    &body,
2429                    Some(receiver.clone()),
2430                    self_is_mut,
2431                    args,
2432                )
2433                .await?;
2434            Ok(Some(MethodOutcome {
2435                value,
2436                updated_self,
2437            }))
2438        } else {
2439            let (value, _) = self
2440                .run_method_body(&param_names, &body, None, false, args)
2441                .await?;
2442            Ok(Some(MethodOutcome {
2443                value,
2444                updated_self: None,
2445            }))
2446        }
2447    }
2448
2449    /// DQ31 (§18.5 container equality defers to element conformance):
2450    /// recursively compare two values for the `"deep_custom"` lane, dispatching
2451    /// any record element that carries a custom `impl Equatable` through its
2452    /// `eq` method. Containers recurse element-wise; `Map`/`Set` match keys /
2453    /// members order-independently under the same recursive equality so an
2454    /// element's custom `eq` governs key-matching and membership/dedup, not
2455    /// only the final comparison. Any non-custom value compares structurally
2456    /// (the native `Value` `==`), so all-structural sub-trees behave exactly
2457    /// as the `"deep"`/`"structural"` lanes' native equality.
2458    #[async_recursion]
2459    async fn values_equal_custom(&mut self, a: &Value, b: &Value) -> Result<bool, RuntimeError> {
2460        match (a, b) {
2461            // A record whose type registers a custom `eq` defines its own
2462            // equality; dispatch through it. A record without an `eq` method
2463            // (structural default) falls through to recursive field comparison
2464            // via the catch-all below.
2465            (Value::Record(rv), Value::Record(_))
2466                if self
2467                    .method_table
2468                    .get(&rv.type_name)
2469                    .is_some_and(|m| m.contains_key("eq")) =>
2470            {
2471                match self.try_call_impl_method(a, "eq", vec![b.clone()]).await? {
2472                    Some(outcome) => Ok(matches!(outcome.value, Value::Bool(true))),
2473                    None => Ok(a == b),
2474                }
2475            }
2476            (Value::Record(ra), Value::Record(rb)) => {
2477                if ra.type_name != rb.type_name || ra.fields.len() != rb.fields.len() {
2478                    return Ok(false);
2479                }
2480                for (k, av) in &ra.fields {
2481                    match rb.fields.get(k) {
2482                        Some(bv) if self.values_equal_custom(av, bv).await? => {}
2483                        _ => return Ok(false),
2484                    }
2485                }
2486                Ok(true)
2487            }
2488            (Value::List(xs), Value::List(ys)) | (Value::Tuple(xs), Value::Tuple(ys)) => {
2489                if xs.len() != ys.len() {
2490                    return Ok(false);
2491                }
2492                for (x, y) in xs.iter().zip(ys.iter()) {
2493                    if !self.values_equal_custom(x, y).await? {
2494                        return Ok(false);
2495                    }
2496                }
2497                Ok(true)
2498            }
2499            (Value::Optional(x), Value::Optional(y)) => match (x, y) {
2500                (None, None) => Ok(true),
2501                (Some(xv), Some(yv)) => self.values_equal_custom(xv, yv).await,
2502                _ => Ok(false),
2503            },
2504            (Value::Result(x), Value::Result(y)) => match (x, y) {
2505                (Ok(xv), Ok(yv)) | (Err(xv), Err(yv)) => self.values_equal_custom(xv, yv).await,
2506                _ => Ok(false),
2507            },
2508            (Value::Map(xs), Value::Map(ys)) => {
2509                if xs.len() != ys.len() {
2510                    return Ok(false);
2511                }
2512                // Order-independent, key-matched under recursive custom eq.
2513                let ys: Vec<(&Value, &Value)> = ys.iter().collect();
2514                for (xk, xv) in xs {
2515                    let mut found = false;
2516                    for (yk, yv) in &ys {
2517                        if self.values_equal_custom(xk, yk).await?
2518                            && self.values_equal_custom(xv, yv).await?
2519                        {
2520                            found = true;
2521                            break;
2522                        }
2523                    }
2524                    if !found {
2525                        return Ok(false);
2526                    }
2527                }
2528                Ok(true)
2529            }
2530            (Value::Set(xs), Value::Set(ys)) => {
2531                if xs.len() != ys.len() {
2532                    return Ok(false);
2533                }
2534                let ys: Vec<&Value> = ys.iter().collect();
2535                for x in xs {
2536                    let mut found = false;
2537                    for y in &ys {
2538                        if self.values_equal_custom(x, y).await? {
2539                            found = true;
2540                            break;
2541                        }
2542                    }
2543                    if !found {
2544                        return Ok(false);
2545                    }
2546                }
2547                Ok(true)
2548            }
2549            (Value::Enum(ea), Value::Enum(eb)) => {
2550                if ea.type_name != eb.type_name || ea.variant != eb.variant {
2551                    return Ok(false);
2552                }
2553                match (&ea.payload, &eb.payload) {
2554                    (None, None) => Ok(true),
2555                    (Some(x), Some(y)) => self.values_equal_custom(x, y).await,
2556                    _ => Ok(false),
2557                }
2558            }
2559            // Primitives and any other shape: native structural equality.
2560            _ => Ok(a == b),
2561        }
2562    }
2563
2564    /// Try to dispatch a `Type.method(args)` call against a user-defined
2565    /// associated (static, no-`self`) function in the method table.
2566    ///
2567    /// Associated functions registered by `impl`/trait-`impl` blocks (e.g. a
2568    /// user `From[Source] for Target`'s `from`) live in `method_table[Type]`
2569    /// keyed by the implementing type, but with no `self` parameter. The
2570    /// `Type.from(x)` call site names the *type* directly, so there is no
2571    /// receiver value to drive [`try_call_impl_method`]; this dispatches by the
2572    /// type *name* instead. Returns `Ok(None)` when no such associated function
2573    /// exists (so the caller can fall through to other resolution paths).
2574    #[async_recursion]
2575    async fn try_call_assoc_fn(
2576        &mut self,
2577        type_name: &str,
2578        method: &str,
2579        args: Vec<Value>,
2580    ) -> Result<Option<Value>, RuntimeError> {
2581        let entry = self
2582            .method_table
2583            .get(type_name)
2584            .and_then(|methods| methods.get(method))
2585            .cloned();
2586
2587        let MethodEntry {
2588            params: param_names,
2589            body,
2590            ..
2591        } = match entry {
2592            // Only treat it as an associated function when the first param is
2593            // not `self`; an instance method named via the type is not a valid
2594            // `Type.method(args)` static call.
2595            Some(e) if e.params.first().map(|s| s.as_str()) != Some("self") => e,
2596            _ => return Ok(None),
2597        };
2598
2599        let (value, _) = self
2600            .run_method_body(&param_names, &body, None, false, args)
2601            .await?;
2602        Ok(Some(value))
2603    }
2604
2605    /// Evaluate a method/associated-function body in a fresh env seeded with the
2606    /// program globals (top-level fns, constructors, `Some`/`None`/`Ok`/`Err`)
2607    /// so those names resolve inside the body — mirroring how top-level function
2608    /// calls clone the current env. A bare `Environment::new()` would hide every
2609    /// global from the body.
2610    ///
2611    /// When `receiver` is `Some`, it is bound as `self` and `args` fill the
2612    /// remaining params (instance method). When `None`, `args` fill all params
2613    /// (static / associated function). Returns the body's value and, for
2614    /// `self_is_mut`, the post-call `self` value for caller write-back.
2615    #[async_recursion]
2616    async fn run_method_body(
2617        &mut self,
2618        param_names: &[String],
2619        body: &AIRNode,
2620        receiver: Option<Value>,
2621        self_is_mut: bool,
2622        args: Vec<Value>,
2623    ) -> Result<(Value, Option<Value>), RuntimeError> {
2624        let method_env = Environment::with_globals(&self.env);
2625        let saved_env = std::mem::replace(&mut self.env, method_env);
2626        self.env.push_scope();
2627        let is_instance = receiver.is_some();
2628        if let Some(recv) = receiver {
2629            self.env.define("self", recv);
2630        }
2631        let value_params: &[String] = if is_instance {
2632            &param_names[1.min(param_names.len())..]
2633        } else {
2634            param_names
2635        };
2636        for (name, val) in value_params.iter().zip(args) {
2637            self.env.define(name.clone(), val);
2638        }
2639        let result = self.eval_expr(body).await;
2640        // For `mut self`, capture the post-body `self` binding *before*
2641        // restoring the caller env so the caller can write it back.
2642        let updated_self = if self_is_mut {
2643            self.env.get("self").cloned()
2644        } else {
2645            None
2646        };
2647        self.env = saved_env;
2648        // Like `call_closure`: a `return` signal becomes the method's value,
2649        // and so does a `?`-propagation signal (§7.10) — methods are function
2650        // boundaries too, so a propagated `Err(e)`/`None` is returned to the
2651        // method's caller rather than escaping further.
2652        let value = match result {
2653            Err(RuntimeError::Return(v) | RuntimeError::Propagated(v)) => *v,
2654            other => other?,
2655        };
2656        Ok((value, updated_self))
2657    }
2658
2659    /// The in-place `List` mutator names (DQ18 `push`/`append`; DQ30
2660    /// `pop`/`remove_at`/`insert`/`reverse`/`set`). These mutate the receiver
2661    /// *place* (via [`Self::write_back_receiver`]) instead of returning a new
2662    /// list, so they are intercepted before the value-returning builtin
2663    /// registry — whose legacy `push`/`pop`/`insert`/`remove`/`reverse`
2664    /// entries (returning new lists) are unreachable from checked Bock source
2665    /// for these names.
2666    const LIST_INPLACE_MUTATORS: &'static [&'static str] = &[
2667        "push",
2668        "append",
2669        "pop",
2670        "remove_at",
2671        "insert",
2672        "reverse",
2673        "set",
2674    ];
2675
2676    /// Execute an in-place `List` mutator against the receiver lvalue,
2677    /// mirroring the compiled targets exactly (R11 oracle parity):
2678    ///
2679    /// - `push`/`append` (DQ18): append; `Void`;
2680    /// - `pop`: remove/return the **last** element as `Optional[T]` — `None`
2681    ///   on empty (emptiness is a normal state, never an abort);
2682    /// - `remove_at(i)`: remove/return the element at `i`; out-of-bounds
2683    ///   (including negative) aborts with [`RuntimeError::ListIndexAbort`];
2684    /// - `insert(i, x)`: insert before `i`; valid range `0..=len` (`len` is
2685    ///   the append position); out-of-bounds aborts — never Python's clamp;
2686    /// - `reverse`: reverse in place; `Void`;
2687    /// - `set(i, x)`: overwrite the element at `i`; out-of-bounds aborts.
2688    ///
2689    /// The mutated list is written back through
2690    /// [`Self::write_back_receiver`]; a non-assignable receiver (temporary)
2691    /// mutates an unobservable copy, matching the compiled targets.
2692    fn run_list_inplace_mutator(
2693        &mut self,
2694        receiver: &AIRNode,
2695        mut items: Vec<Value>,
2696        method: &str,
2697        mut args: Vec<Value>,
2698    ) -> Result<Value, RuntimeError> {
2699        let arity = |expected: usize, args: &[Value]| -> Result<(), RuntimeError> {
2700            if args.len() < expected {
2701                return Err(RuntimeError::ArityMismatch {
2702                    expected,
2703                    got: args.len(),
2704                });
2705            }
2706            Ok(())
2707        };
2708        let int_index = |v: &Value, method: &str| -> Result<i64, RuntimeError> {
2709            match v {
2710                Value::Int(i) => Ok(*i),
2711                other => Err(RuntimeError::TypeError(format!(
2712                    "List.{method} expects an Int index, got {other}"
2713                ))),
2714            }
2715        };
2716        match method {
2717            "push" | "append" => {
2718                arity(1, &args)?;
2719                items.push(args.remove(0));
2720                self.write_back_receiver(receiver, Value::List(items));
2721                Ok(Value::Void)
2722            }
2723            "pop" => match items.pop() {
2724                Some(last) => {
2725                    self.write_back_receiver(receiver, Value::List(items));
2726                    Ok(Value::Optional(Some(Box::new(last))))
2727                }
2728                None => Ok(Value::Optional(None)),
2729            },
2730            "remove_at" => {
2731                arity(1, &args)?;
2732                let i = int_index(&args[0], "remove_at")?;
2733                if i < 0 || i as usize >= items.len() {
2734                    return Err(RuntimeError::ListIndexAbort {
2735                        op: "remove_at",
2736                        index: i,
2737                        len: items.len(),
2738                    });
2739                }
2740                let removed = items.remove(i as usize);
2741                self.write_back_receiver(receiver, Value::List(items));
2742                Ok(removed)
2743            }
2744            "insert" => {
2745                arity(2, &args)?;
2746                let i = int_index(&args[0], "insert")?;
2747                // Valid range 0..=len: `len` is the append position.
2748                if i < 0 || i as usize > items.len() {
2749                    return Err(RuntimeError::ListIndexAbort {
2750                        op: "insert",
2751                        index: i,
2752                        len: items.len(),
2753                    });
2754                }
2755                items.insert(i as usize, args.remove(1));
2756                self.write_back_receiver(receiver, Value::List(items));
2757                Ok(Value::Void)
2758            }
2759            "reverse" => {
2760                items.reverse();
2761                self.write_back_receiver(receiver, Value::List(items));
2762                Ok(Value::Void)
2763            }
2764            "set" => {
2765                arity(2, &args)?;
2766                let i = int_index(&args[0], "set")?;
2767                if i < 0 || i as usize >= items.len() {
2768                    return Err(RuntimeError::ListIndexAbort {
2769                        op: "set",
2770                        index: i,
2771                        len: items.len(),
2772                    });
2773                }
2774                items[i as usize] = args.remove(1);
2775                self.write_back_receiver(receiver, Value::List(items));
2776                Ok(Value::Void)
2777            }
2778            other => Err(RuntimeError::TypeError(format!(
2779                "method '{other}' is not an in-place List mutator"
2780            ))),
2781        }
2782    }
2783
2784    /// Write a (possibly mutated) `self` value back to the receiver lvalue
2785    /// after a `mut self` method call. Supports the assignable receiver shapes
2786    /// the lowerer can produce for a method receiver: a plain variable and a
2787    /// record field path. Non-assignable receivers (temporaries, literals,
2788    /// call results) are silently ignored — mutating those has no observable
2789    /// caller-side effect, matching the compiled targets.
2790    fn write_back_receiver(&mut self, receiver: &AIRNode, updated: Value) {
2791        match &receiver.kind {
2792            NodeKind::Identifier { name } => {
2793                self.env.assign(&name.name, updated);
2794            }
2795            NodeKind::FieldAccess { object, field } => {
2796                if let NodeKind::Identifier { name: obj_name } = &object.kind {
2797                    if let Some(Value::Record(mut rv)) = self.env.get(&obj_name.name).cloned() {
2798                        rv.fields.insert(field.name.clone(), updated);
2799                        self.env.assign(&obj_name.name, Value::Record(rv));
2800                    }
2801                }
2802            }
2803            _ => {}
2804        }
2805    }
2806
2807    // ── Field access ───────────────────────────────────────────────────────
2808
2809    #[async_recursion]
2810    async fn eval_field_access(
2811        &mut self,
2812        object: &AIRNode,
2813        field: &str,
2814    ) -> Result<Value, RuntimeError> {
2815        let obj = self.eval_expr(object).await?;
2816        match obj {
2817            Value::Record(rv) => {
2818                rv.fields
2819                    .get(field)
2820                    .cloned()
2821                    .ok_or_else(|| RuntimeError::FieldNotFound {
2822                        field: field.to_string(),
2823                        type_name: rv.type_name.clone(),
2824                    })
2825            }
2826            Value::Enum(ev) => {
2827                if field == "variant" {
2828                    Ok(Value::String(BockString::new(ev.variant.clone())))
2829                } else {
2830                    Err(RuntimeError::FieldNotFound {
2831                        field: field.to_string(),
2832                        type_name: ev.type_name.clone(),
2833                    })
2834                }
2835            }
2836            other => Err(RuntimeError::TypeError(format!(
2837                "cannot access field '{field}' on {other}"
2838            ))),
2839        }
2840    }
2841
2842    // ── Index access ───────────────────────────────────────────────────────
2843
2844    #[async_recursion]
2845    async fn eval_index(
2846        &mut self,
2847        object: &AIRNode,
2848        index: &AIRNode,
2849    ) -> Result<Value, RuntimeError> {
2850        let obj = self.eval_expr(object).await?;
2851        let idx = self.eval_expr(index).await?;
2852        match (obj, idx) {
2853            (Value::List(items), Value::Int(i)) => {
2854                if i < 0 || i as usize >= items.len() {
2855                    Err(RuntimeError::IndexOutOfBounds {
2856                        index: i,
2857                        len: items.len(),
2858                    })
2859                } else {
2860                    Ok(items[i as usize].clone())
2861                }
2862            }
2863            (Value::Tuple(items), Value::Int(i)) => {
2864                if i < 0 || i as usize >= items.len() {
2865                    Err(RuntimeError::IndexOutOfBounds {
2866                        index: i,
2867                        len: items.len(),
2868                    })
2869                } else {
2870                    Ok(items[i as usize].clone())
2871                }
2872            }
2873            (Value::Map(map), key) => map
2874                .get(&key)
2875                .cloned()
2876                .ok_or_else(|| RuntimeError::TypeError(format!("key not found: {key}"))),
2877            (Value::String(s), Value::Int(i)) => {
2878                let chars: Vec<char> = s.as_str().chars().collect();
2879                if i < 0 || i as usize >= chars.len() {
2880                    Err(RuntimeError::IndexOutOfBounds {
2881                        index: i,
2882                        len: chars.len(),
2883                    })
2884                } else {
2885                    Ok(Value::Char(chars[i as usize]))
2886                }
2887            }
2888            (obj, idx) => Err(RuntimeError::TypeError(format!(
2889                "cannot index {obj} with {idx}"
2890            ))),
2891        }
2892    }
2893
2894    // ── Error propagation (`?`) ────────────────────────────────────────────
2895
2896    /// Evaluate `expr?` (§7.10): unwrap `Ok(v)`/`Some(v)` in place; for
2897    /// `Err(e)`/`None` raise [`RuntimeError::Propagated`] carrying the **whole
2898    /// propagating value** (`Result(Err(e))` / `Optional(None)`), so the
2899    /// enclosing function-call boundary ([`Self::call_closure`] /
2900    /// [`Self::run_method_body`]) can hand it to the caller unchanged — the
2901    /// caller observes a normal `Result`/`Optional` value, exactly as the
2902    /// compiled targets early-return it.
2903    #[async_recursion]
2904    async fn eval_propagate(&mut self, expr: &AIRNode) -> Result<Value, RuntimeError> {
2905        let val = self.eval_expr(expr).await?;
2906        match val {
2907            Value::Optional(Some(inner)) => Ok(*inner),
2908            Value::Optional(None) => Err(RuntimeError::Propagated(Box::new(Value::Optional(None)))),
2909            Value::Result(Ok(inner)) => Ok(*inner),
2910            Value::Result(Err(e)) => Err(RuntimeError::Propagated(Box::new(Value::Result(Err(e))))),
2911            other => Err(RuntimeError::TypeError(format!(
2912                "? applied to non-Optional/Result: {other}"
2913            ))),
2914        }
2915    }
2916
2917    // ── Record construction ────────────────────────────────────────────────
2918
2919    #[async_recursion]
2920    async fn eval_record_construct(
2921        &mut self,
2922        path: &TypePath,
2923        fields: &[AirRecordField],
2924        spread: Option<&AIRNode>,
2925    ) -> Result<Value, RuntimeError> {
2926        let type_name = path
2927            .segments
2928            .last()
2929            .map(|s| s.name.as_str())
2930            .unwrap_or("")
2931            .to_string();
2932        let mut record_fields: BTreeMap<String, Value> = BTreeMap::new();
2933
2934        // Apply spread first (base record).
2935        if let Some(spread_expr) = spread {
2936            let spread_val = self.eval_expr(spread_expr).await?;
2937            if let Value::Record(rv) = spread_val {
2938                record_fields = rv.fields;
2939            }
2940        }
2941
2942        // Apply explicit fields (override spread values).
2943        for field in fields {
2944            let val = match &field.value {
2945                Some(v) => self.eval_expr(v).await?,
2946                None => {
2947                    // Shorthand: field name resolves as a variable.
2948                    self.env.get(&field.name.name).cloned().ok_or_else(|| {
2949                        RuntimeError::UndefinedVariable {
2950                            name: field.name.name.clone(),
2951                        }
2952                    })?
2953                }
2954            };
2955            record_fields.insert(field.name.name.clone(), val);
2956        }
2957
2958        Ok(Value::Record(RecordValue {
2959            type_name,
2960            fields: record_fields,
2961        }))
2962    }
2963
2964    // ── String interpolation ───────────────────────────────────────────────
2965
2966    #[async_recursion]
2967    async fn eval_interpolation(
2968        &mut self,
2969        parts: &[AirInterpolationPart],
2970    ) -> Result<Value, RuntimeError> {
2971        let mut result = String::new();
2972        for part in parts {
2973            match part {
2974                AirInterpolationPart::Literal(s) => result.push_str(s),
2975                AirInterpolationPart::Expr(expr) => {
2976                    let val = self.eval_expr(expr).await?;
2977                    let displayed = self.display_string(&val).await?;
2978                    result.push_str(&displayed);
2979                }
2980            }
2981        }
2982        Ok(Value::String(BockString::new(result)))
2983    }
2984
2985    /// Render a value to its display `String` for a `${…}` interpolation part,
2986    /// dispatching through the value's `Displayable` impl when one exists
2987    /// (Q-displayable-interpolation-dispatch). A user record/class that declares
2988    /// `impl Displayable for T { fn to_string(self) -> String }` (or a `display`
2989    /// method) must be rendered via that method — not the structural fallback
2990    /// (`Point {x: 3, y: 7}`), matching how the compiled targets dispatch a
2991    /// `${p}` through the type's `to_string`. Resolution order: a user impl
2992    /// `to_string`/`display` method (the §18.2 `Displayable` contract), then the
2993    /// builtin `display` (primitives' canonical stringification), then the
2994    /// structural `Value::to_string` fallback.
2995    #[async_recursion]
2996    async fn display_string(&mut self, val: &Value) -> Result<String, RuntimeError> {
2997        // A user `Displayable` impl on a record/class: call its `to_string`
2998        // (or `display`) method through the user method table.
2999        if matches!(val, Value::Record(_)) {
3000            for method in ["to_string", "display"] {
3001                if let Some(outcome) = self.try_call_impl_method(val, method, Vec::new()).await? {
3002                    if let Value::String(s) = outcome.value {
3003                        return Ok(s.to_string());
3004                    }
3005                }
3006            }
3007        }
3008        // Primitives' canonical stringification (the builtin `display`), else
3009        // the structural fallback.
3010        let tag = TypeTag::of(val);
3011        Ok(
3012            match self
3013                .builtins
3014                .call(tag, "display", std::slice::from_ref(val))
3015            {
3016                Some(Ok(Value::String(s))) => s.to_string(),
3017                _ => val.to_string(),
3018            },
3019        )
3020    }
3021
3022    // ── Range construction ─────────────────────────────────────────────────
3023
3024    #[async_recursion]
3025    async fn eval_range(
3026        &mut self,
3027        lo: &AIRNode,
3028        hi: &AIRNode,
3029        inclusive: bool,
3030    ) -> Result<Value, RuntimeError> {
3031        let lo_val = self.eval_expr(lo).await?;
3032        let hi_val = self.eval_expr(hi).await?;
3033        match (lo_val, hi_val) {
3034            (Value::Int(start), Value::Int(end)) => Ok(Value::Range {
3035                start,
3036                end,
3037                inclusive,
3038                step: 1,
3039            }),
3040            (lo, hi) => Err(RuntimeError::TypeError(format!(
3041                "range bounds must be Int, got {lo} and {hi}"
3042            ))),
3043        }
3044    }
3045
3046    // ── Block evaluation ───────────────────────────────────────────────────
3047
3048    /// Evaluate a block: push scope, execute statements, eval tail expression.
3049    #[async_recursion]
3050    pub async fn eval_block(
3051        &mut self,
3052        stmts: &[AIRNode],
3053        tail: Option<&AIRNode>,
3054    ) -> Result<Value, RuntimeError> {
3055        self.env.push_scope();
3056
3057        for stmt in stmts {
3058            match self.eval_expr(stmt).await {
3059                Ok(_) => {} // discard intermediate statement result
3060                Err(e) => {
3061                    self.env.pop_scope();
3062                    return Err(e);
3063                }
3064            }
3065        }
3066
3067        let result = match tail {
3068            Some(expr) => self.eval_expr(expr).await,
3069            None => Ok(Value::Void),
3070        };
3071
3072        self.env.pop_scope();
3073        result
3074    }
3075
3076    // ── If expression ──────────────────────────────────────────────────────
3077
3078    #[async_recursion]
3079    async fn eval_if(
3080        &mut self,
3081        let_pattern: Option<&AIRNode>,
3082        condition: &AIRNode,
3083        then_block: &AIRNode,
3084        else_block: Option<&AIRNode>,
3085    ) -> Result<Value, RuntimeError> {
3086        if let Some(pat) = let_pattern {
3087            // `if (let pat = expr) { ... }` — condition is the expression to match.
3088            let val = self.eval_expr(condition).await?;
3089            self.env.push_scope();
3090            let matched = self.try_match_pattern(pat, &val).await;
3091            if matched {
3092                let result = self.eval_expr(then_block).await;
3093                self.env.pop_scope();
3094                result
3095            } else {
3096                self.env.pop_scope();
3097                match else_block {
3098                    Some(eb) => self.eval_expr(eb).await,
3099                    None => Ok(Value::Void),
3100                }
3101            }
3102        } else {
3103            let cond = self.eval_expr(condition).await?;
3104            match cond {
3105                Value::Bool(true) => self.eval_expr(then_block).await,
3106                Value::Bool(false) => match else_block {
3107                    Some(eb) => self.eval_expr(eb).await,
3108                    None => Ok(Value::Void),
3109                },
3110                other => Err(RuntimeError::TypeError(format!(
3111                    "if condition must be Bool, got {other}"
3112                ))),
3113            }
3114        }
3115    }
3116
3117    // ── Match expression ───────────────────────────────────────────────────
3118
3119    #[async_recursion]
3120    async fn eval_match(
3121        &mut self,
3122        scrutinee: &AIRNode,
3123        arms: &[AIRNode],
3124    ) -> Result<Value, RuntimeError> {
3125        let val = self.eval_expr(scrutinee).await?;
3126
3127        // M-075: Check for non-exhaustive match patterns.
3128        // If the scrutinee is an enum and no arm uses a wildcard or catch-all
3129        // bind pattern, emit a warning.
3130        if matches!(&val, Value::Enum(_)) {
3131            let has_catch_all = arms.iter().any(|arm| {
3132                if let NodeKind::MatchArm { pattern, guard, .. } = &arm.kind {
3133                    guard.is_none()
3134                        && matches!(
3135                            pattern.kind,
3136                            NodeKind::WildcardPat | NodeKind::BindPat { .. }
3137                        )
3138                } else {
3139                    false
3140                }
3141            });
3142            if !has_catch_all {
3143                eprintln!(
3144                    "warning: match on enum value may not be exhaustive (no wildcard `_` arm)"
3145                );
3146            }
3147        }
3148
3149        for arm in arms {
3150            if let NodeKind::MatchArm {
3151                pattern,
3152                guard,
3153                body,
3154            } = &arm.kind
3155            {
3156                self.env.push_scope();
3157                let matched = self.try_match_pattern(pattern, &val).await;
3158
3159                let should_exec = if matched {
3160                    if let Some(guard_expr) = guard {
3161                        match self.eval_expr(guard_expr).await {
3162                            Ok(Value::Bool(b)) => b,
3163                            Ok(_) => false,
3164                            Err(_) => false,
3165                        }
3166                    } else {
3167                        true
3168                    }
3169                } else {
3170                    false
3171                };
3172
3173                if should_exec {
3174                    let result = self.eval_expr(body).await;
3175                    self.env.pop_scope();
3176                    return result;
3177                }
3178                self.env.pop_scope();
3179            }
3180        }
3181
3182        Err(RuntimeError::MatchFailed)
3183    }
3184
3185    // ── Pattern matching ───────────────────────────────────────────────────
3186
3187    /// Attempt to match `value` against `pattern`, binding names into the
3188    /// current scope. Returns `true` on a successful match.
3189    #[async_recursion]
3190    pub async fn try_match_pattern(&mut self, pattern: &AIRNode, value: &Value) -> bool {
3191        match &pattern.kind.clone() {
3192            NodeKind::WildcardPat | NodeKind::RestPat => true,
3193
3194            NodeKind::BindPat { name, .. } => {
3195                self.env.define(name.name.clone(), value.clone());
3196                true
3197            }
3198
3199            NodeKind::LiteralPat { lit } => {
3200                matches!(self.eval_literal(lit), Ok(lit_val) if lit_val == *value)
3201            }
3202
3203            NodeKind::ConstructorPat { path, fields } => {
3204                let variant_name = path.segments.last().map(|s| s.name.as_str()).unwrap_or("");
3205                match (variant_name, value) {
3206                    ("Some", Value::Optional(Some(inner))) => {
3207                        fields.len() == 1 && self.try_match_pattern(&fields[0], inner).await
3208                    }
3209                    ("None", Value::Optional(None)) => true,
3210                    ("Ok", Value::Result(Ok(inner))) => {
3211                        fields.is_empty()
3212                            || (fields.len() == 1
3213                                && self.try_match_pattern(&fields[0], inner).await)
3214                    }
3215                    ("Err", Value::Result(Err(inner))) => {
3216                        fields.is_empty()
3217                            || (fields.len() == 1
3218                                && self.try_match_pattern(&fields[0], inner).await)
3219                    }
3220                    (name, Value::Enum(ev)) if ev.variant == name => {
3221                        match (&ev.payload, fields.len()) {
3222                            (None, 0) => true,
3223                            (Some(inner), 1) => self.try_match_pattern(&fields[0], inner).await,
3224                            _ => false,
3225                        }
3226                    }
3227                    _ => false,
3228                }
3229            }
3230
3231            NodeKind::RecordPat { path, fields, rest } => {
3232                if let Value::Record(rv) = value {
3233                    let type_name = path.segments.last().map(|s| s.name.as_str()).unwrap_or("");
3234                    if rv.type_name != type_name {
3235                        return false;
3236                    }
3237                    if !rest && fields.len() != rv.fields.len() {
3238                        return false;
3239                    }
3240                    for field in fields {
3241                        let field_val = match rv.fields.get(&field.name.name) {
3242                            Some(v) => v.clone(),
3243                            None => return false,
3244                        };
3245                        if let Some(pat) = &field.pattern {
3246                            if !self.try_match_pattern(pat, &field_val).await {
3247                                return false;
3248                            }
3249                        } else {
3250                            self.env.define(field.name.name.clone(), field_val);
3251                        }
3252                    }
3253                    true
3254                } else {
3255                    false
3256                }
3257            }
3258
3259            NodeKind::TuplePat { elems } => {
3260                if let Value::Tuple(vals) = value {
3261                    if elems.len() != vals.len() {
3262                        return false;
3263                    }
3264                    let pairs: Vec<_> = elems
3265                        .iter()
3266                        .zip(vals.iter())
3267                        .map(|(p, v)| (p.clone(), v.clone()))
3268                        .collect();
3269                    for (pat, val) in pairs {
3270                        if !self.try_match_pattern(&pat, &val).await {
3271                            return false;
3272                        }
3273                    }
3274                    true
3275                } else {
3276                    false
3277                }
3278            }
3279
3280            NodeKind::ListPat { elems, rest } => {
3281                if let Value::List(vals) = value {
3282                    if elems.len() > vals.len() {
3283                        return false;
3284                    }
3285                    if rest.is_none() && elems.len() != vals.len() {
3286                        return false;
3287                    }
3288                    let pairs: Vec<_> = elems
3289                        .iter()
3290                        .zip(vals.iter())
3291                        .map(|(p, v)| (p.clone(), v.clone()))
3292                        .collect();
3293                    for (pat, val) in pairs {
3294                        if !self.try_match_pattern(&pat, &val).await {
3295                            return false;
3296                        }
3297                    }
3298                    if let Some(rest_pat) = rest {
3299                        let rest_vals = Value::List(vals[elems.len()..].to_vec());
3300                        let rest_pat = rest_pat.clone();
3301                        self.try_match_pattern(&rest_pat, &rest_vals).await;
3302                    }
3303                    true
3304                } else {
3305                    false
3306                }
3307            }
3308
3309            NodeKind::OrPat { alternatives } => {
3310                let alts: Vec<_> = alternatives.to_vec();
3311                for alt in alts {
3312                    if self.try_match_pattern(&alt, value).await {
3313                        return true;
3314                    }
3315                }
3316                false
3317            }
3318
3319            NodeKind::RangePat { lo, hi, inclusive } => {
3320                let lo = lo.clone();
3321                let hi = hi.clone();
3322                let inclusive = *inclusive;
3323                let lo_val = match self.eval_expr(&lo).await {
3324                    Ok(v) => v,
3325                    Err(_) => return false,
3326                };
3327                let hi_val = match self.eval_expr(&hi).await {
3328                    Ok(v) => v,
3329                    Err(_) => return false,
3330                };
3331                if inclusive {
3332                    *value >= lo_val && *value <= hi_val
3333                } else {
3334                    *value >= lo_val && *value < hi_val
3335                }
3336            }
3337
3338            _ => false,
3339        }
3340    }
3341
3342    /// Bind `value` to `pattern` in the current scope, for use in `let`.
3343    #[async_recursion]
3344    pub async fn bind_pattern(
3345        &mut self,
3346        pattern: &AIRNode,
3347        value: Value,
3348    ) -> Result<(), RuntimeError> {
3349        match &pattern.kind.clone() {
3350            NodeKind::BindPat { name, .. } => {
3351                self.env.define(name.name.clone(), value);
3352                Ok(())
3353            }
3354            NodeKind::WildcardPat => Ok(()),
3355            NodeKind::TuplePat { elems } => {
3356                if let Value::Tuple(vals) = value {
3357                    if elems.len() != vals.len() {
3358                        return Err(RuntimeError::MatchFailed);
3359                    }
3360                    let pairs: Vec<_> = elems
3361                        .iter()
3362                        .zip(vals)
3363                        .map(|(p, v)| (p.clone(), v))
3364                        .collect();
3365                    for (pat, val) in pairs {
3366                        self.bind_pattern(&pat, val).await?;
3367                    }
3368                    Ok(())
3369                } else {
3370                    Err(RuntimeError::MatchFailed)
3371                }
3372            }
3373            _ => {
3374                if self.try_match_pattern(pattern, &value).await {
3375                    Ok(())
3376                } else {
3377                    Err(RuntimeError::MatchFailed)
3378                }
3379            }
3380        }
3381    }
3382
3383    // ── Loop evaluation ────────────────────────────────────────────────────
3384
3385    #[async_recursion]
3386    async fn eval_for(
3387        &mut self,
3388        pattern: &AIRNode,
3389        iterable: &AIRNode,
3390        body: &AIRNode,
3391    ) -> Result<Value, RuntimeError> {
3392        let iter_val = self.eval_expr(iterable).await?;
3393        match iter_val {
3394            Value::List(items) => self.for_loop_items(pattern, body, items).await,
3395            Value::Range {
3396                start,
3397                end,
3398                inclusive,
3399                step,
3400            } => {
3401                let items = range_to_vec(start, end, inclusive, step);
3402                self.for_loop_items(pattern, body, items).await
3403            }
3404            Value::Set(set) => {
3405                let items: Vec<Value> = set.into_iter().collect();
3406                self.for_loop_items(pattern, body, items).await
3407            }
3408            Value::Map(map) => {
3409                let items: Vec<Value> = map
3410                    .into_iter()
3411                    .map(|(k, v)| Value::Tuple(vec![k, v]))
3412                    .collect();
3413                self.for_loop_items(pattern, body, items).await
3414            }
3415            Value::Iterator(it) => self.for_loop_iterator(pattern, body, &it).await,
3416            other => Err(RuntimeError::TypeError(format!(
3417                "cannot iterate over {other}"
3418            ))),
3419        }
3420    }
3421
3422    #[async_recursion]
3423    async fn for_loop_items(
3424        &mut self,
3425        pattern: &AIRNode,
3426        body: &AIRNode,
3427        items: Vec<Value>,
3428    ) -> Result<Value, RuntimeError> {
3429        for item in items {
3430            self.env.push_scope();
3431            let bind_result = self.bind_pattern(pattern, item).await;
3432            if let Err(e) = bind_result {
3433                self.env.pop_scope();
3434                return Err(e);
3435            }
3436            let result = self.eval_expr(body).await;
3437            self.env.pop_scope();
3438            match result {
3439                Ok(_) | Err(RuntimeError::Continue) => {}
3440                Err(RuntimeError::Break(None)) => return Ok(Value::Void),
3441                Err(RuntimeError::Break(Some(v))) => return Ok(*v),
3442                Err(e) => return Err(e),
3443            }
3444        }
3445        Ok(Value::Void)
3446    }
3447
3448    #[async_recursion]
3449    async fn for_loop_iterator(
3450        &mut self,
3451        pattern: &AIRNode,
3452        body: &AIRNode,
3453        it: &crate::value::IteratorValue,
3454    ) -> Result<Value, RuntimeError> {
3455        loop {
3456            let next = {
3457                let mut kind = it.kind.lock().unwrap();
3458                kind.next()
3459            };
3460            match next {
3461                IteratorNext::Some(val) => {
3462                    self.env.push_scope();
3463                    let bind_result = self.bind_pattern(pattern, val).await;
3464                    if let Err(e) = bind_result {
3465                        self.env.pop_scope();
3466                        return Err(e);
3467                    }
3468                    let result = self.eval_expr(body).await;
3469                    self.env.pop_scope();
3470                    match result {
3471                        Ok(_) | Err(RuntimeError::Continue) => {}
3472                        Err(RuntimeError::Break(None)) => return Ok(Value::Void),
3473                        Err(RuntimeError::Break(Some(v))) => return Ok(*v),
3474                        Err(e) => return Err(e),
3475                    }
3476                }
3477                IteratorNext::Done => break,
3478                IteratorNext::NeedsMapCallback { value, func } => {
3479                    let fn_val = Value::Function(func);
3480                    let mapped = self.invoke_callback(&fn_val, &[value]).await?;
3481                    self.env.push_scope();
3482                    let bind_result = self.bind_pattern(pattern, mapped).await;
3483                    if let Err(e) = bind_result {
3484                        self.env.pop_scope();
3485                        return Err(e);
3486                    }
3487                    let result = self.eval_expr(body).await;
3488                    self.env.pop_scope();
3489                    match result {
3490                        Ok(_) | Err(RuntimeError::Continue) => {}
3491                        Err(RuntimeError::Break(None)) => return Ok(Value::Void),
3492                        Err(RuntimeError::Break(Some(v))) => return Ok(*v),
3493                        Err(e) => return Err(e),
3494                    }
3495                }
3496                IteratorNext::NeedsFilterCallback { value, func } => {
3497                    let fn_val = Value::Function(func);
3498                    let keep = self
3499                        .invoke_callback(&fn_val, std::slice::from_ref(&value))
3500                        .await?;
3501                    if keep == Value::Bool(true) {
3502                        self.env.push_scope();
3503                        let bind_result = self.bind_pattern(pattern, value).await;
3504                        if let Err(e) = bind_result {
3505                            self.env.pop_scope();
3506                            return Err(e);
3507                        }
3508                        let result = self.eval_expr(body).await;
3509                        self.env.pop_scope();
3510                        match result {
3511                            Ok(_) | Err(RuntimeError::Continue) => {}
3512                            Err(RuntimeError::Break(None)) => return Ok(Value::Void),
3513                            Err(RuntimeError::Break(Some(v))) => return Ok(*v),
3514                            Err(e) => return Err(e),
3515                        }
3516                    }
3517                }
3518            }
3519        }
3520        Ok(Value::Void)
3521    }
3522
3523    #[async_recursion]
3524    async fn eval_while(
3525        &mut self,
3526        condition: &AIRNode,
3527        body: &AIRNode,
3528    ) -> Result<Value, RuntimeError> {
3529        loop {
3530            let cond = self.eval_expr(condition).await?;
3531            match cond {
3532                Value::Bool(false) => break,
3533                Value::Bool(true) => {}
3534                other => {
3535                    return Err(RuntimeError::TypeError(format!(
3536                        "while condition must be Bool, got {other}"
3537                    )))
3538                }
3539            }
3540            match self.eval_expr(body).await {
3541                Ok(_) | Err(RuntimeError::Continue) => {}
3542                Err(RuntimeError::Break(None)) => return Ok(Value::Void),
3543                Err(RuntimeError::Break(Some(v))) => return Ok(*v),
3544                Err(e) => return Err(e),
3545            }
3546        }
3547        Ok(Value::Void)
3548    }
3549
3550    #[async_recursion]
3551    async fn eval_loop(&mut self, body: &AIRNode) -> Result<Value, RuntimeError> {
3552        loop {
3553            match self.eval_expr(body).await {
3554                Ok(_) | Err(RuntimeError::Continue) => {}
3555                Err(RuntimeError::Break(None)) => return Ok(Value::Void),
3556                Err(RuntimeError::Break(Some(v))) => return Ok(*v),
3557                Err(e) => return Err(e),
3558            }
3559        }
3560    }
3561
3562    #[async_recursion]
3563    async fn eval_guard(
3564        &mut self,
3565        let_pattern: Option<&AIRNode>,
3566        condition: &AIRNode,
3567        else_block: &AIRNode,
3568    ) -> Result<Value, RuntimeError> {
3569        if let Some(pat) = let_pattern {
3570            // guard (let pat = expr) — try pattern match, bind into current scope
3571            let val = self.eval_expr(condition).await?;
3572            let matched = self.try_match_pattern(pat, &val).await;
3573            if matched {
3574                Ok(Value::Void)
3575            } else {
3576                self.eval_expr(else_block).await?;
3577                Ok(Value::Void)
3578            }
3579        } else {
3580            let cond = self.eval_expr(condition).await?;
3581            match cond {
3582                Value::Bool(true) => Ok(Value::Void),
3583                Value::Bool(false) => {
3584                    // Execute the divergent else block (return/break/continue/never).
3585                    // We propagate whatever control-flow signal it raises.
3586                    self.eval_expr(else_block).await?;
3587                    Ok(Value::Void)
3588                }
3589                other => Err(RuntimeError::TypeError(format!(
3590                    "guard condition must be Bool, got {other}"
3591                ))),
3592            }
3593        }
3594    }
3595
3596    // ── Statement-level execution ──────────────────────────────────────────
3597
3598    /// Execute a statement node.
3599    ///
3600    /// Returns `None` for pure statements (let bindings, for/while loops,
3601    /// guard) and `Some(value)` for expression-statements (including `loop`
3602    /// which can yield a break value, and blocks).
3603    #[async_recursion]
3604    pub async fn exec_stmt(&mut self, node: &AIRNode) -> Result<Option<Value>, RuntimeError> {
3605        match &node.kind {
3606            NodeKind::LetBinding { .. }
3607            | NodeKind::For { .. }
3608            | NodeKind::While { .. }
3609            | NodeKind::Guard { .. } => {
3610                self.eval_expr(node).await?;
3611                Ok(None)
3612            }
3613            _ => Ok(Some(self.eval_expr(node).await?)),
3614        }
3615    }
3616
3617    /// Execute a block node, returning the value of its tail expression.
3618    ///
3619    /// Handles `NodeKind::Block` directly; falls back to `eval_expr` for any
3620    /// other node kind so callers can pass bare expression nodes too.
3621    #[async_recursion]
3622    pub async fn exec_block(&mut self, block: &AIRNode) -> Result<Value, RuntimeError> {
3623        match &block.kind {
3624            NodeKind::Block { stmts, tail } => self.eval_block(stmts, tail.as_deref()).await,
3625            _ => self.eval_expr(block).await,
3626        }
3627    }
3628}
3629
3630// ─── Helpers ──────────────────────────────────────────────────────────────────
3631
3632/// Builtin method names on `TypeTag::Record` that belong to the interpreter's
3633/// test harness (`register_test_builtins`) and must NOT be shadowed by a
3634/// user-defined `impl` method.
3635///
3636/// These matchers operate on the builtin-constructed `Expectation` /
3637/// `BoolExpectation` values produced by the builtin `expect()` / `expect_bool()`
3638/// (which store the captured value under the `actual` field). The `core.test`
3639/// stdlib defines parallel *user* `Expectation`/`BoolExpectation` records (field
3640/// `value`) with same-named matcher impls for the compiled targets; under
3641/// `bock test` both are registered, so without this reservation the user impl
3642/// would shadow the builtin and read a non-existent `value` field off the
3643/// builtin-constructed record. Every *other* record builtin (notably the
3644/// universal `to_string`) is overridable so a user `Displayable.to_string` impl
3645/// wins, matching the compiled targets.
3646const HARNESS_RESERVED_RECORD_METHODS: &[&str] = &[
3647    "to_equal",
3648    "to_be_ok",
3649    "to_be_err",
3650    "to_be_some",
3651    "to_be_none",
3652    "to_throw",
3653    "to_be_true",
3654    "to_be_false",
3655];
3656
3657/// Whether a user `impl` method may shadow a same-named builtin on a record
3658/// receiver. True for everything except the test-harness matchers.
3659fn user_impl_may_shadow_record_builtin(method: &str) -> bool {
3660    !HARNESS_RESERVED_RECORD_METHODS.contains(&method)
3661}
3662
3663/// The type name of a record value, or `None` for non-records. Used to look up
3664/// user `impl` methods in the method table for shadowing decisions.
3665fn record_type_name(v: &Value) -> Option<&str> {
3666    match v {
3667        Value::Record(rv) => Some(&rv.type_name),
3668        _ => None,
3669    }
3670}
3671
3672/// Materialise a Range into a `Vec<Value::Int>`.
3673fn range_to_vec(start: i64, end: i64, inclusive: bool, step: i64) -> Vec<Value> {
3674    // Determine effective step: if step is the default (1) and the range is
3675    // descending, automatically use -1 so `5..1` produces `[5, 4, 3, 2]`.
3676    let effective_step = if step == 1 && start > end {
3677        -1
3678    } else if step == 0 {
3679        return Vec::new();
3680    } else {
3681        step
3682    };
3683
3684    let mut result = Vec::new();
3685    let mut i = start;
3686    if effective_step > 0 {
3687        while if inclusive { i <= end } else { i < end } {
3688            result.push(Value::Int(i));
3689            i = match i.checked_add(effective_step) {
3690                Some(next) => next,
3691                None => break,
3692            };
3693        }
3694    } else {
3695        // Descending range
3696        while if inclusive { i >= end } else { i > end } {
3697            result.push(Value::Int(i));
3698            i = match i.checked_add(effective_step) {
3699                Some(next) => next,
3700                None => break,
3701            };
3702        }
3703    }
3704    result
3705}
3706
3707// ─── Tests ────────────────────────────────────────────────────────────────────
3708
3709#[cfg(test)]
3710mod tests {
3711    use super::*;
3712    use bock_air::{AirHandlerPair, AirMapEntry, NodeId, NodeIdGen};
3713    use bock_ast::{AssignOp, BinOp, Ident, Literal, TypePath, UnaryOp};
3714    use bock_errors::{FileId, Span};
3715
3716    fn span() -> Span {
3717        Span {
3718            file: FileId(0),
3719            start: 0,
3720            end: 0,
3721        }
3722    }
3723
3724    fn ident(name: &str) -> Ident {
3725        Ident {
3726            name: name.to_string(),
3727            span: span(),
3728        }
3729    }
3730
3731    fn type_path(name: &str) -> TypePath {
3732        TypePath {
3733            segments: vec![ident(name)],
3734            span: span(),
3735        }
3736    }
3737
3738    fn gen() -> NodeIdGen {
3739        NodeIdGen::new()
3740    }
3741
3742    fn node(id: NodeId, kind: NodeKind) -> AIRNode {
3743        AIRNode::new(id, span(), kind)
3744    }
3745
3746    fn int_lit(g: &NodeIdGen, n: i64) -> AIRNode {
3747        node(
3748            g.next(),
3749            NodeKind::Literal {
3750                lit: Literal::Int(n.to_string()),
3751            },
3752        )
3753    }
3754
3755    fn float_lit(g: &NodeIdGen, f: f64) -> AIRNode {
3756        node(
3757            g.next(),
3758            NodeKind::Literal {
3759                lit: Literal::Float(f.to_string()),
3760            },
3761        )
3762    }
3763
3764    fn bool_lit(g: &NodeIdGen, b: bool) -> AIRNode {
3765        node(
3766            g.next(),
3767            NodeKind::Literal {
3768                lit: Literal::Bool(b),
3769            },
3770        )
3771    }
3772
3773    fn str_lit(g: &NodeIdGen, s: &str) -> AIRNode {
3774        node(
3775            g.next(),
3776            NodeKind::Literal {
3777                lit: Literal::String(s.to_string()),
3778            },
3779        )
3780    }
3781
3782    fn var(g: &NodeIdGen, name: &str) -> AIRNode {
3783        node(g.next(), NodeKind::Identifier { name: ident(name) })
3784    }
3785
3786    // ── Literals ──────────────────────────────────────────────────────────
3787
3788    #[tokio::test]
3789    async fn eval_int_literal() {
3790        let mut interp = Interpreter::new();
3791        let g = gen();
3792        assert_eq!(interp.eval_expr(&int_lit(&g, 42)).await, Ok(Value::Int(42)));
3793    }
3794
3795    #[tokio::test]
3796    async fn eval_float_literal() {
3797        let mut interp = Interpreter::new();
3798        let g = gen();
3799        let result = interp.eval_expr(&float_lit(&g, 3.5)).await;
3800        assert!(matches!(result, Ok(Value::Float(_))));
3801    }
3802
3803    #[tokio::test]
3804    async fn eval_bool_literal() {
3805        let mut interp = Interpreter::new();
3806        let g = gen();
3807        assert_eq!(
3808            interp.eval_expr(&bool_lit(&g, true)).await,
3809            Ok(Value::Bool(true))
3810        );
3811        assert_eq!(
3812            interp.eval_expr(&bool_lit(&g, false)).await,
3813            Ok(Value::Bool(false))
3814        );
3815    }
3816
3817    #[tokio::test]
3818    async fn eval_string_literal() {
3819        let mut interp = Interpreter::new();
3820        let g = gen();
3821        assert_eq!(
3822            interp.eval_expr(&str_lit(&g, "hello")).await,
3823            Ok(Value::String(BockString::new("hello")))
3824        );
3825    }
3826
3827    #[tokio::test]
3828    async fn eval_hex_literal() {
3829        let mut interp = Interpreter::new();
3830        let g = gen();
3831        let n = node(
3832            g.next(),
3833            NodeKind::Literal {
3834                lit: Literal::Int("0xFF".to_string()),
3835            },
3836        );
3837        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Int(255)));
3838    }
3839
3840    #[tokio::test]
3841    async fn eval_unit_literal() {
3842        let mut interp = Interpreter::new();
3843        let g = gen();
3844        let n = node(g.next(), NodeKind::Literal { lit: Literal::Unit });
3845        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Void));
3846    }
3847
3848    // ── Arithmetic ────────────────────────────────────────────────────────
3849
3850    #[tokio::test]
3851    async fn eval_add_ints() {
3852        let mut interp = Interpreter::new();
3853        let g = gen();
3854        let n = node(
3855            g.next(),
3856            NodeKind::BinaryOp {
3857                op: BinOp::Add,
3858                left: Box::new(int_lit(&g, 3)),
3859                right: Box::new(int_lit(&g, 4)),
3860            },
3861        );
3862        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Int(7)));
3863    }
3864
3865    #[tokio::test]
3866    async fn eval_add_strings() {
3867        let mut interp = Interpreter::new();
3868        let g = gen();
3869        let n = node(
3870            g.next(),
3871            NodeKind::BinaryOp {
3872                op: BinOp::Add,
3873                left: Box::new(str_lit(&g, "foo")),
3874                right: Box::new(str_lit(&g, "bar")),
3875            },
3876        );
3877        assert_eq!(
3878            interp.eval_expr(&n).await,
3879            Ok(Value::String(BockString::new("foobar")))
3880        );
3881    }
3882
3883    #[tokio::test]
3884    async fn eval_sub_ints() {
3885        let mut interp = Interpreter::new();
3886        let g = gen();
3887        let n = node(
3888            g.next(),
3889            NodeKind::BinaryOp {
3890                op: BinOp::Sub,
3891                left: Box::new(int_lit(&g, 10)),
3892                right: Box::new(int_lit(&g, 3)),
3893            },
3894        );
3895        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Int(7)));
3896    }
3897
3898    #[tokio::test]
3899    async fn eval_mul_ints() {
3900        let mut interp = Interpreter::new();
3901        let g = gen();
3902        let n = node(
3903            g.next(),
3904            NodeKind::BinaryOp {
3905                op: BinOp::Mul,
3906                left: Box::new(int_lit(&g, 6)),
3907                right: Box::new(int_lit(&g, 7)),
3908            },
3909        );
3910        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Int(42)));
3911    }
3912
3913    #[tokio::test]
3914    async fn eval_div_ints() {
3915        let mut interp = Interpreter::new();
3916        let g = gen();
3917        let n = node(
3918            g.next(),
3919            NodeKind::BinaryOp {
3920                op: BinOp::Div,
3921                left: Box::new(int_lit(&g, 10)),
3922                right: Box::new(int_lit(&g, 2)),
3923            },
3924        );
3925        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Int(5)));
3926    }
3927
3928    #[tokio::test]
3929    async fn eval_div_by_zero() {
3930        let mut interp = Interpreter::new();
3931        let g = gen();
3932        let n = node(
3933            g.next(),
3934            NodeKind::BinaryOp {
3935                op: BinOp::Div,
3936                left: Box::new(int_lit(&g, 1)),
3937                right: Box::new(int_lit(&g, 0)),
3938            },
3939        );
3940        assert!(matches!(
3941            interp.eval_expr(&n).await,
3942            Err(RuntimeError::DivisionByZero)
3943        ));
3944    }
3945
3946    #[tokio::test]
3947    async fn eval_pow() {
3948        let mut interp = Interpreter::new();
3949        let g = gen();
3950        let n = node(
3951            g.next(),
3952            NodeKind::BinaryOp {
3953                op: BinOp::Pow,
3954                left: Box::new(int_lit(&g, 2)),
3955                right: Box::new(int_lit(&g, 10)),
3956            },
3957        );
3958        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Int(1024)));
3959    }
3960
3961    // ── Comparison ────────────────────────────────────────────────────────
3962
3963    #[tokio::test]
3964    async fn eval_eq() {
3965        let mut interp = Interpreter::new();
3966        let g = gen();
3967        let n = node(
3968            g.next(),
3969            NodeKind::BinaryOp {
3970                op: BinOp::Eq,
3971                left: Box::new(int_lit(&g, 5)),
3972                right: Box::new(int_lit(&g, 5)),
3973            },
3974        );
3975        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Bool(true)));
3976    }
3977
3978    #[tokio::test]
3979    async fn eval_ne() {
3980        let mut interp = Interpreter::new();
3981        let g = gen();
3982        let n = node(
3983            g.next(),
3984            NodeKind::BinaryOp {
3985                op: BinOp::Ne,
3986                left: Box::new(int_lit(&g, 3)),
3987                right: Box::new(int_lit(&g, 5)),
3988            },
3989        );
3990        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Bool(true)));
3991    }
3992
3993    #[tokio::test]
3994    async fn eval_lt_gt() {
3995        let mut interp = Interpreter::new();
3996        let g = gen();
3997        let lt = node(
3998            g.next(),
3999            NodeKind::BinaryOp {
4000                op: BinOp::Lt,
4001                left: Box::new(int_lit(&g, 1)),
4002                right: Box::new(int_lit(&g, 2)),
4003            },
4004        );
4005        let gt = node(
4006            g.next(),
4007            NodeKind::BinaryOp {
4008                op: BinOp::Gt,
4009                left: Box::new(int_lit(&g, 3)),
4010                right: Box::new(int_lit(&g, 2)),
4011            },
4012        );
4013        assert_eq!(interp.eval_expr(&lt).await, Ok(Value::Bool(true)));
4014        assert_eq!(interp.eval_expr(&gt).await, Ok(Value::Bool(true)));
4015    }
4016
4017    // ── Logical ───────────────────────────────────────────────────────────
4018
4019    #[tokio::test]
4020    async fn eval_and_short_circuit() {
4021        let mut interp = Interpreter::new();
4022        let g = gen();
4023        let n = node(
4024            g.next(),
4025            NodeKind::BinaryOp {
4026                op: BinOp::And,
4027                left: Box::new(bool_lit(&g, false)),
4028                right: Box::new(bool_lit(&g, true)),
4029            },
4030        );
4031        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Bool(false)));
4032    }
4033
4034    #[tokio::test]
4035    async fn eval_or_short_circuit() {
4036        let mut interp = Interpreter::new();
4037        let g = gen();
4038        let n = node(
4039            g.next(),
4040            NodeKind::BinaryOp {
4041                op: BinOp::Or,
4042                left: Box::new(bool_lit(&g, true)),
4043                right: Box::new(bool_lit(&g, false)),
4044            },
4045        );
4046        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Bool(true)));
4047    }
4048
4049    // ── Unary ─────────────────────────────────────────────────────────────
4050
4051    #[tokio::test]
4052    async fn eval_neg() {
4053        let mut interp = Interpreter::new();
4054        let g = gen();
4055        let n = node(
4056            g.next(),
4057            NodeKind::UnaryOp {
4058                op: UnaryOp::Neg,
4059                operand: Box::new(int_lit(&g, 7)),
4060            },
4061        );
4062        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Int(-7)));
4063    }
4064
4065    #[tokio::test]
4066    async fn eval_not() {
4067        let mut interp = Interpreter::new();
4068        let g = gen();
4069        let n = node(
4070            g.next(),
4071            NodeKind::UnaryOp {
4072                op: UnaryOp::Not,
4073                operand: Box::new(bool_lit(&g, false)),
4074            },
4075        );
4076        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Bool(true)));
4077    }
4078
4079    // ── Variable lookup ───────────────────────────────────────────────────
4080
4081    #[tokio::test]
4082    async fn eval_identifier() {
4083        let mut interp = Interpreter::new();
4084        let g = gen();
4085        interp.env.define("x", Value::Int(99));
4086        assert_eq!(interp.eval_expr(&var(&g, "x")).await, Ok(Value::Int(99)));
4087    }
4088
4089    #[tokio::test]
4090    async fn eval_undefined_variable() {
4091        let mut interp = Interpreter::new();
4092        let g = gen();
4093        assert!(matches!(
4094            interp.eval_expr(&var(&g, "y")).await,
4095            Err(RuntimeError::UndefinedVariable { .. })
4096        ));
4097    }
4098
4099    // ── Collection literals ───────────────────────────────────────────────
4100
4101    #[tokio::test]
4102    async fn eval_list_literal() {
4103        let mut interp = Interpreter::new();
4104        let g = gen();
4105        let n = node(
4106            g.next(),
4107            NodeKind::ListLiteral {
4108                elems: vec![int_lit(&g, 1), int_lit(&g, 2), int_lit(&g, 3)],
4109            },
4110        );
4111        assert_eq!(
4112            interp.eval_expr(&n).await,
4113            Ok(Value::List(vec![
4114                Value::Int(1),
4115                Value::Int(2),
4116                Value::Int(3)
4117            ]))
4118        );
4119    }
4120
4121    #[tokio::test]
4122    async fn eval_tuple_literal() {
4123        let mut interp = Interpreter::new();
4124        let g = gen();
4125        let n = node(
4126            g.next(),
4127            NodeKind::TupleLiteral {
4128                elems: vec![int_lit(&g, 1), bool_lit(&g, true)],
4129            },
4130        );
4131        assert_eq!(
4132            interp.eval_expr(&n).await,
4133            Ok(Value::Tuple(vec![Value::Int(1), Value::Bool(true)]))
4134        );
4135    }
4136
4137    #[tokio::test]
4138    async fn eval_map_literal() {
4139        let mut interp = Interpreter::new();
4140        let g = gen();
4141        let n = node(
4142            g.next(),
4143            NodeKind::MapLiteral {
4144                entries: vec![AirMapEntry {
4145                    key: str_lit(&g, "a"),
4146                    value: int_lit(&g, 1),
4147                }],
4148            },
4149        );
4150        let result = interp.eval_expr(&n).await.unwrap();
4151        if let Value::Map(map) = result {
4152            assert_eq!(
4153                map.get(&Value::String(BockString::new("a"))),
4154                Some(&Value::Int(1))
4155            );
4156        } else {
4157            panic!("expected Map");
4158        }
4159    }
4160
4161    #[tokio::test]
4162    async fn eval_set_literal() {
4163        let mut interp = Interpreter::new();
4164        let g = gen();
4165        let n = node(
4166            g.next(),
4167            NodeKind::SetLiteral {
4168                elems: vec![int_lit(&g, 1), int_lit(&g, 2), int_lit(&g, 1)],
4169            },
4170        );
4171        if let Ok(Value::Set(set)) = interp.eval_expr(&n).await {
4172            assert_eq!(set.len(), 2); // duplicates removed
4173        } else {
4174            panic!("expected Set");
4175        }
4176    }
4177
4178    // ── Record construction & field access ────────────────────────────────
4179
4180    #[tokio::test]
4181    async fn eval_record_construct_and_field_access() {
4182        let mut interp = Interpreter::new();
4183        let g = gen();
4184
4185        let record = node(
4186            g.next(),
4187            NodeKind::RecordConstruct {
4188                path: type_path("Point"),
4189                fields: vec![
4190                    AirRecordField {
4191                        name: ident("x"),
4192                        value: Some(Box::new(int_lit(&g, 3))),
4193                    },
4194                    AirRecordField {
4195                        name: ident("y"),
4196                        value: Some(Box::new(int_lit(&g, 4))),
4197                    },
4198                ],
4199                spread: None,
4200            },
4201        );
4202
4203        let rec_val = interp.eval_expr(&record).await.unwrap();
4204        interp.env.define("p", rec_val);
4205
4206        let field_node = node(
4207            g.next(),
4208            NodeKind::FieldAccess {
4209                object: Box::new(var(&g, "p")),
4210                field: ident("x"),
4211            },
4212        );
4213        assert_eq!(interp.eval_expr(&field_node).await, Ok(Value::Int(3)));
4214    }
4215
4216    #[tokio::test]
4217    async fn eval_record_spread() {
4218        let mut interp = Interpreter::new();
4219        let g = gen();
4220
4221        // Build base record {x: 1, y: 2}
4222        let base = node(
4223            g.next(),
4224            NodeKind::RecordConstruct {
4225                path: type_path("Point"),
4226                fields: vec![
4227                    AirRecordField {
4228                        name: ident("x"),
4229                        value: Some(Box::new(int_lit(&g, 1))),
4230                    },
4231                    AirRecordField {
4232                        name: ident("y"),
4233                        value: Some(Box::new(int_lit(&g, 2))),
4234                    },
4235                ],
4236                spread: None,
4237            },
4238        );
4239        let base_val = interp.eval_expr(&base).await.unwrap();
4240        interp.env.define("base", base_val);
4241
4242        // Spread + override y: Point { ..base, y: 99 }
4243        let spread_record = node(
4244            g.next(),
4245            NodeKind::RecordConstruct {
4246                path: type_path("Point"),
4247                fields: vec![AirRecordField {
4248                    name: ident("y"),
4249                    value: Some(Box::new(int_lit(&g, 99))),
4250                }],
4251                spread: Some(Box::new(var(&g, "base"))),
4252            },
4253        );
4254        if let Ok(Value::Record(rv)) = interp.eval_expr(&spread_record).await {
4255            assert_eq!(rv.fields["x"], Value::Int(1));
4256            assert_eq!(rv.fields["y"], Value::Int(99));
4257        } else {
4258            panic!("expected Record");
4259        }
4260    }
4261
4262    // ── Lambda & function call ─────────────────────────────────────────────
4263
4264    #[tokio::test]
4265    async fn eval_lambda_and_call() {
4266        let mut interp = Interpreter::new();
4267        let g = gen();
4268
4269        // (x) => x * 2
4270        let param = node(
4271            g.next(),
4272            NodeKind::Param {
4273                pattern: Box::new(node(
4274                    g.next(),
4275                    NodeKind::BindPat {
4276                        name: ident("x"),
4277                        is_mut: false,
4278                    },
4279                )),
4280                ty: None,
4281                default: None,
4282            },
4283        );
4284        let body = node(
4285            g.next(),
4286            NodeKind::BinaryOp {
4287                op: BinOp::Mul,
4288                left: Box::new(var(&g, "x")),
4289                right: Box::new(int_lit(&g, 2)),
4290            },
4291        );
4292        let lambda = node(
4293            g.next(),
4294            NodeKind::Lambda {
4295                params: vec![param],
4296                body: Box::new(body),
4297            },
4298        );
4299
4300        let fn_val = interp.eval_expr(&lambda).await.unwrap();
4301        interp.env.define("double", fn_val);
4302
4303        let call = node(
4304            g.next(),
4305            NodeKind::Call {
4306                callee: Box::new(var(&g, "double")),
4307                args: vec![AirArg {
4308                    label: None,
4309                    value: int_lit(&g, 5),
4310                }],
4311                type_args: vec![],
4312            },
4313        );
4314        assert_eq!(interp.eval_expr(&call).await, Ok(Value::Int(10)));
4315    }
4316
4317    #[tokio::test]
4318    async fn eval_closure_captures_env() {
4319        let mut interp = Interpreter::new();
4320        let g = gen();
4321
4322        interp.env.define("factor", Value::Int(3));
4323
4324        // (x) => x * factor
4325        let param = node(
4326            g.next(),
4327            NodeKind::Param {
4328                pattern: Box::new(node(
4329                    g.next(),
4330                    NodeKind::BindPat {
4331                        name: ident("x"),
4332                        is_mut: false,
4333                    },
4334                )),
4335                ty: None,
4336                default: None,
4337            },
4338        );
4339        let body = node(
4340            g.next(),
4341            NodeKind::BinaryOp {
4342                op: BinOp::Mul,
4343                left: Box::new(var(&g, "x")),
4344                right: Box::new(var(&g, "factor")),
4345            },
4346        );
4347        let lambda = node(
4348            g.next(),
4349            NodeKind::Lambda {
4350                params: vec![param],
4351                body: Box::new(body),
4352            },
4353        );
4354        let fn_val = interp.eval_expr(&lambda).await.unwrap();
4355        interp.env.define("triple", fn_val);
4356
4357        let call = node(
4358            g.next(),
4359            NodeKind::Call {
4360                callee: Box::new(var(&g, "triple")),
4361                args: vec![AirArg {
4362                    label: None,
4363                    value: int_lit(&g, 4),
4364                }],
4365                type_args: vec![],
4366            },
4367        );
4368        assert_eq!(interp.eval_expr(&call).await, Ok(Value::Int(12)));
4369    }
4370
4371    // ── Pipe operator ─────────────────────────────────────────────────────
4372
4373    #[tokio::test]
4374    async fn eval_pipe_to_function() {
4375        let mut interp = Interpreter::new();
4376        let g = gen();
4377
4378        // Register (x) => x + 1 as "inc"
4379        let param = node(
4380            g.next(),
4381            NodeKind::Param {
4382                pattern: Box::new(node(
4383                    g.next(),
4384                    NodeKind::BindPat {
4385                        name: ident("x"),
4386                        is_mut: false,
4387                    },
4388                )),
4389                ty: None,
4390                default: None,
4391            },
4392        );
4393        let body = node(
4394            g.next(),
4395            NodeKind::BinaryOp {
4396                op: BinOp::Add,
4397                left: Box::new(var(&g, "x")),
4398                right: Box::new(int_lit(&g, 1)),
4399            },
4400        );
4401        let lambda = node(
4402            g.next(),
4403            NodeKind::Lambda {
4404                params: vec![param],
4405                body: Box::new(body),
4406            },
4407        );
4408        let fn_val = interp.eval_expr(&lambda).await.unwrap();
4409        interp.env.define("inc", fn_val);
4410
4411        // 5 |> inc
4412        let pipe = node(
4413            g.next(),
4414            NodeKind::Pipe {
4415                left: Box::new(int_lit(&g, 5)),
4416                right: Box::new(var(&g, "inc")),
4417            },
4418        );
4419        assert_eq!(interp.eval_expr(&pipe).await, Ok(Value::Int(6)));
4420    }
4421
4422    // ── If expression ─────────────────────────────────────────────────────
4423
4424    #[tokio::test]
4425    async fn eval_if_true_branch() {
4426        let mut interp = Interpreter::new();
4427        let g = gen();
4428        let n = node(
4429            g.next(),
4430            NodeKind::If {
4431                let_pattern: None,
4432                condition: Box::new(bool_lit(&g, true)),
4433                then_block: Box::new(int_lit(&g, 1)),
4434                else_block: Some(Box::new(int_lit(&g, 2))),
4435            },
4436        );
4437        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Int(1)));
4438    }
4439
4440    #[tokio::test]
4441    async fn eval_if_false_branch() {
4442        let mut interp = Interpreter::new();
4443        let g = gen();
4444        let n = node(
4445            g.next(),
4446            NodeKind::If {
4447                let_pattern: None,
4448                condition: Box::new(bool_lit(&g, false)),
4449                then_block: Box::new(int_lit(&g, 1)),
4450                else_block: Some(Box::new(int_lit(&g, 2))),
4451            },
4452        );
4453        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Int(2)));
4454    }
4455
4456    // ── Match expression ──────────────────────────────────────────────────
4457
4458    #[tokio::test]
4459    async fn eval_match_literal_pattern() {
4460        let mut interp = Interpreter::new();
4461        let g = gen();
4462
4463        let arm1 = node(
4464            g.next(),
4465            NodeKind::MatchArm {
4466                pattern: Box::new(node(
4467                    g.next(),
4468                    NodeKind::LiteralPat {
4469                        lit: Literal::Int("1".to_string()),
4470                    },
4471                )),
4472                guard: None,
4473                body: Box::new(str_lit(&g, "one")),
4474            },
4475        );
4476        let arm2 = node(
4477            g.next(),
4478            NodeKind::MatchArm {
4479                pattern: Box::new(node(g.next(), NodeKind::WildcardPat)),
4480                guard: None,
4481                body: Box::new(str_lit(&g, "other")),
4482            },
4483        );
4484        let m = node(
4485            g.next(),
4486            NodeKind::Match {
4487                scrutinee: Box::new(int_lit(&g, 1)),
4488                arms: vec![arm1, arm2],
4489            },
4490        );
4491        assert_eq!(
4492            interp.eval_expr(&m).await,
4493            Ok(Value::String(BockString::new("one")))
4494        );
4495    }
4496
4497    #[tokio::test]
4498    async fn eval_match_bind_pattern() {
4499        let mut interp = Interpreter::new();
4500        let g = gen();
4501
4502        let arm = node(
4503            g.next(),
4504            NodeKind::MatchArm {
4505                pattern: Box::new(node(
4506                    g.next(),
4507                    NodeKind::BindPat {
4508                        name: ident("n"),
4509                        is_mut: false,
4510                    },
4511                )),
4512                guard: None,
4513                body: Box::new(node(
4514                    g.next(),
4515                    NodeKind::BinaryOp {
4516                        op: BinOp::Mul,
4517                        left: Box::new(var(&g, "n")),
4518                        right: Box::new(int_lit(&g, 2)),
4519                    },
4520                )),
4521            },
4522        );
4523        let m = node(
4524            g.next(),
4525            NodeKind::Match {
4526                scrutinee: Box::new(int_lit(&g, 5)),
4527                arms: vec![arm],
4528            },
4529        );
4530        assert_eq!(interp.eval_expr(&m).await, Ok(Value::Int(10)));
4531    }
4532
4533    // ── Error propagation ─────────────────────────────────────────────────
4534
4535    #[tokio::test]
4536    async fn eval_propagate_ok() {
4537        let mut interp = Interpreter::new();
4538        let g = gen();
4539        // Ok(42)?  →  42
4540        let ok_node = node(
4541            g.next(),
4542            NodeKind::ResultConstruct {
4543                variant: ResultVariant::Ok,
4544                value: Some(Box::new(int_lit(&g, 42))),
4545            },
4546        );
4547        let prop = node(
4548            g.next(),
4549            NodeKind::Propagate {
4550                expr: Box::new(ok_node),
4551            },
4552        );
4553        assert_eq!(interp.eval_expr(&prop).await, Ok(Value::Int(42)));
4554    }
4555
4556    #[tokio::test]
4557    async fn eval_propagate_err() {
4558        let mut interp = Interpreter::new();
4559        let g = gen();
4560        // Err("boom")?  →  Propagated
4561        let err_node = node(
4562            g.next(),
4563            NodeKind::ResultConstruct {
4564                variant: ResultVariant::Err,
4565                value: Some(Box::new(str_lit(&g, "boom"))),
4566            },
4567        );
4568        let prop = node(
4569            g.next(),
4570            NodeKind::Propagate {
4571                expr: Box::new(err_node),
4572            },
4573        );
4574        assert!(matches!(
4575            interp.eval_expr(&prop).await,
4576            Err(RuntimeError::Propagated(_))
4577        ));
4578    }
4579
4580    #[tokio::test]
4581    async fn eval_propagate_some() {
4582        let mut interp = Interpreter::new();
4583        let g = gen();
4584        // Some(7)? → 7
4585        interp
4586            .env
4587            .define("opt", Value::Optional(Some(Box::new(Value::Int(7)))));
4588        let prop = node(
4589            g.next(),
4590            NodeKind::Propagate {
4591                expr: Box::new(var(&g, "opt")),
4592            },
4593        );
4594        assert_eq!(interp.eval_expr(&prop).await, Ok(Value::Int(7)));
4595    }
4596
4597    #[tokio::test]
4598    async fn eval_propagate_none() {
4599        let mut interp = Interpreter::new();
4600        let g = gen();
4601        interp.env.define("opt", Value::Optional(None));
4602        let prop = node(
4603            g.next(),
4604            NodeKind::Propagate {
4605                expr: Box::new(var(&g, "opt")),
4606            },
4607        );
4608        assert!(matches!(
4609            interp.eval_expr(&prop).await,
4610            Err(RuntimeError::Propagated(_))
4611        ));
4612    }
4613
4614    /// §7.10: `?` on `Err(e)` early-returns the `Err` from the *enclosing
4615    /// function* — the caller observes a normal `Result` value, and the
4616    /// statements after the propagation site never run
4617    /// (Q-interp-question-propagation).
4618    #[tokio::test]
4619    async fn call_returns_propagated_err_to_caller() {
4620        let mut interp = Interpreter::new();
4621        let g = gen();
4622        // fn fails() { Err("boom")?  99 }  — must return Err("boom"), not 99,
4623        // and must not abort the evaluation.
4624        let err_node = node(
4625            g.next(),
4626            NodeKind::ResultConstruct {
4627                variant: ResultVariant::Err,
4628                value: Some(Box::new(str_lit(&g, "boom"))),
4629            },
4630        );
4631        let prop = node(
4632            g.next(),
4633            NodeKind::Propagate {
4634                expr: Box::new(err_node),
4635            },
4636        );
4637        let body = node(
4638            g.next(),
4639            NodeKind::Block {
4640                stmts: vec![prop],
4641                tail: Some(Box::new(int_lit(&g, 99))),
4642            },
4643        );
4644        interp.register_fn("fails", vec![], body);
4645        let call = node(
4646            g.next(),
4647            NodeKind::Call {
4648                callee: Box::new(var(&g, "fails")),
4649                args: vec![],
4650                type_args: vec![],
4651            },
4652        );
4653        assert_eq!(
4654            interp.eval_expr(&call).await,
4655            Ok(Value::Result(Err(Box::new(Value::String(
4656                BockString::new("boom")
4657            )))))
4658        );
4659    }
4660
4661    /// §7.10: `?` on `None` early-returns `None` from the enclosing function;
4662    /// the caller observes a normal `Optional` value.
4663    #[tokio::test]
4664    async fn call_returns_propagated_none_to_caller() {
4665        let mut interp = Interpreter::new();
4666        let g = gen();
4667        interp.env.define("opt", Value::Optional(None));
4668        let prop = node(
4669            g.next(),
4670            NodeKind::Propagate {
4671                expr: Box::new(var(&g, "opt")),
4672            },
4673        );
4674        let body = node(
4675            g.next(),
4676            NodeKind::Block {
4677                stmts: vec![prop],
4678                tail: Some(Box::new(int_lit(&g, 99))),
4679            },
4680        );
4681        interp.register_fn("fails_opt", vec![], body);
4682        let call = node(
4683            g.next(),
4684            NodeKind::Call {
4685                callee: Box::new(var(&g, "fails_opt")),
4686                args: vec![],
4687                type_args: vec![],
4688            },
4689        );
4690        assert_eq!(interp.eval_expr(&call).await, Ok(Value::Optional(None)));
4691    }
4692
4693    /// The `Ok`-path complement: `?` on `Ok(v)` unwraps in place, the function
4694    /// continues, and the caller sees the function's own (non-propagated)
4695    /// result.
4696    #[tokio::test]
4697    async fn call_with_ok_propagation_continues_to_tail() {
4698        let mut interp = Interpreter::new();
4699        let g = gen();
4700        let ok_node = node(
4701            g.next(),
4702            NodeKind::ResultConstruct {
4703                variant: ResultVariant::Ok,
4704                value: Some(Box::new(int_lit(&g, 42))),
4705            },
4706        );
4707        let prop = node(
4708            g.next(),
4709            NodeKind::Propagate {
4710                expr: Box::new(ok_node),
4711            },
4712        );
4713        let body = node(
4714            g.next(),
4715            NodeKind::Block {
4716                stmts: vec![prop],
4717                tail: Some(Box::new(int_lit(&g, 99))),
4718            },
4719        );
4720        interp.register_fn("succeeds", vec![], body);
4721        let call = node(
4722            g.next(),
4723            NodeKind::Call {
4724                callee: Box::new(var(&g, "succeeds")),
4725                args: vec![],
4726                type_args: vec![],
4727            },
4728        );
4729        assert_eq!(interp.eval_expr(&call).await, Ok(Value::Int(99)));
4730    }
4731
4732    // ── String interpolation ──────────────────────────────────────────────
4733
4734    #[tokio::test]
4735    async fn eval_interpolation() {
4736        let mut interp = Interpreter::new();
4737        let g = gen();
4738        interp
4739            .env
4740            .define("name", Value::String(BockString::new("world")));
4741        let n = node(
4742            g.next(),
4743            NodeKind::Interpolation {
4744                parts: vec![
4745                    AirInterpolationPart::Literal("Hello, ".to_string()),
4746                    AirInterpolationPart::Expr(Box::new(var(&g, "name"))),
4747                    AirInterpolationPart::Literal("!".to_string()),
4748                ],
4749            },
4750        );
4751        assert_eq!(
4752            interp.eval_expr(&n).await,
4753            Ok(Value::String(BockString::new("Hello, world!")))
4754        );
4755    }
4756
4757    // ── Range ─────────────────────────────────────────────────────────────
4758
4759    #[tokio::test]
4760    async fn eval_range_exclusive() {
4761        let mut interp = Interpreter::new();
4762        let g = gen();
4763        let n = node(
4764            g.next(),
4765            NodeKind::Range {
4766                lo: Box::new(int_lit(&g, 1)),
4767                hi: Box::new(int_lit(&g, 4)),
4768                inclusive: false,
4769            },
4770        );
4771        assert_eq!(
4772            interp.eval_expr(&n).await,
4773            Ok(Value::Range {
4774                start: 1,
4775                end: 4,
4776                inclusive: false,
4777                step: 1
4778            })
4779        );
4780    }
4781
4782    #[tokio::test]
4783    async fn eval_range_inclusive() {
4784        let mut interp = Interpreter::new();
4785        let g = gen();
4786        let n = node(
4787            g.next(),
4788            NodeKind::Range {
4789                lo: Box::new(int_lit(&g, 1)),
4790                hi: Box::new(int_lit(&g, 3)),
4791                inclusive: true,
4792            },
4793        );
4794        assert_eq!(
4795            interp.eval_expr(&n).await,
4796            Ok(Value::Range {
4797                start: 1,
4798                end: 3,
4799                inclusive: true,
4800                step: 1
4801            })
4802        );
4803    }
4804
4805    // ── Block with let binding ─────────────────────────────────────────────
4806
4807    #[tokio::test]
4808    async fn eval_block_with_let_binding() {
4809        let mut interp = Interpreter::new();
4810        let g = gen();
4811
4812        // { let x = 10; x + 5 }
4813        let let_stmt = node(
4814            g.next(),
4815            NodeKind::LetBinding {
4816                is_mut: false,
4817                pattern: Box::new(node(
4818                    g.next(),
4819                    NodeKind::BindPat {
4820                        name: ident("x"),
4821                        is_mut: false,
4822                    },
4823                )),
4824                ty: None,
4825                value: Box::new(int_lit(&g, 10)),
4826            },
4827        );
4828        let tail = node(
4829            g.next(),
4830            NodeKind::BinaryOp {
4831                op: BinOp::Add,
4832                left: Box::new(var(&g, "x")),
4833                right: Box::new(int_lit(&g, 5)),
4834            },
4835        );
4836        let block = node(
4837            g.next(),
4838            NodeKind::Block {
4839                stmts: vec![let_stmt],
4840                tail: Some(Box::new(tail)),
4841            },
4842        );
4843        assert_eq!(interp.eval_expr(&block).await, Ok(Value::Int(15)));
4844    }
4845
4846    // ── Index access ──────────────────────────────────────────────────────
4847
4848    #[tokio::test]
4849    async fn eval_index_list() {
4850        let mut interp = Interpreter::new();
4851        let g = gen();
4852        interp
4853            .env
4854            .define("lst", Value::List(vec![Value::Int(10), Value::Int(20)]));
4855        let n = node(
4856            g.next(),
4857            NodeKind::Index {
4858                object: Box::new(var(&g, "lst")),
4859                index: Box::new(int_lit(&g, 1)),
4860            },
4861        );
4862        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Int(20)));
4863    }
4864
4865    // ── Result construction ───────────────────────────────────────────────
4866
4867    #[tokio::test]
4868    async fn eval_result_ok() {
4869        let mut interp = Interpreter::new();
4870        let g = gen();
4871        let n = node(
4872            g.next(),
4873            NodeKind::ResultConstruct {
4874                variant: ResultVariant::Ok,
4875                value: Some(Box::new(int_lit(&g, 42))),
4876            },
4877        );
4878        assert_eq!(
4879            interp.eval_expr(&n).await,
4880            Ok(Value::Result(Ok(Box::new(Value::Int(42)))))
4881        );
4882    }
4883
4884    #[tokio::test]
4885    async fn eval_result_err() {
4886        let mut interp = Interpreter::new();
4887        let g = gen();
4888        let n = node(
4889            g.next(),
4890            NodeKind::ResultConstruct {
4891                variant: ResultVariant::Err,
4892                value: Some(Box::new(str_lit(&g, "oops"))),
4893            },
4894        );
4895        assert_eq!(
4896            interp.eval_expr(&n).await,
4897            Ok(Value::Result(Err(Box::new(Value::String(
4898                BockString::new("oops")
4899            )))))
4900        );
4901    }
4902
4903    // ── Function composition ──────────────────────────────────────────────
4904
4905    #[tokio::test]
4906    async fn eval_compose_functions() {
4907        let mut interp = Interpreter::new();
4908        let g = gen();
4909
4910        // Register inc = (x) => x + 1
4911        let double_body = node(
4912            g.next(),
4913            NodeKind::BinaryOp {
4914                op: BinOp::Mul,
4915                left: Box::new(var(&g, "x")),
4916                right: Box::new(int_lit(&g, 2)),
4917            },
4918        );
4919        interp.register_fn("double", vec!["x".to_string()], double_body);
4920
4921        let inc_body = node(
4922            g.next(),
4923            NodeKind::BinaryOp {
4924                op: BinOp::Add,
4925                left: Box::new(var(&g, "x")),
4926                right: Box::new(int_lit(&g, 1)),
4927            },
4928        );
4929        interp.register_fn("inc", vec!["x".to_string()], inc_body);
4930
4931        // double >> inc  — apply double first, then inc
4932        let compose = node(
4933            g.next(),
4934            NodeKind::Compose {
4935                left: Box::new(var(&g, "double")),
4936                right: Box::new(var(&g, "inc")),
4937            },
4938        );
4939        let fn_val = interp.eval_expr(&compose).await.unwrap();
4940        interp.env.define("double_then_inc", fn_val);
4941
4942        let call = node(
4943            g.next(),
4944            NodeKind::Call {
4945                callee: Box::new(var(&g, "double_then_inc")),
4946                args: vec![AirArg {
4947                    label: None,
4948                    value: int_lit(&g, 5),
4949                }],
4950                type_args: vec![],
4951            },
4952        );
4953        // double(5) = 10, inc(10) = 11
4954        assert_eq!(interp.eval_expr(&call).await, Ok(Value::Int(11)));
4955    }
4956
4957    // ── Method calls ──────────────────────────────────────────────────────
4958
4959    #[tokio::test]
4960    async fn eval_list_len_method() {
4961        let mut interp = Interpreter::new();
4962        let g = gen();
4963        interp.env.define(
4964            "lst",
4965            Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
4966        );
4967        let n = node(
4968            g.next(),
4969            NodeKind::MethodCall {
4970                receiver: Box::new(var(&g, "lst")),
4971                method: ident("len"),
4972                type_args: vec![],
4973                args: vec![],
4974            },
4975        );
4976        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Int(3)));
4977    }
4978
4979    #[tokio::test]
4980    async fn eval_list_map_method() {
4981        let mut interp = Interpreter::new();
4982        let g = gen();
4983
4984        // Register double = (x) => x * 2
4985        let body = node(
4986            g.next(),
4987            NodeKind::BinaryOp {
4988                op: BinOp::Mul,
4989                left: Box::new(var(&g, "x")),
4990                right: Box::new(int_lit(&g, 2)),
4991            },
4992        );
4993        interp.register_fn("double", vec!["x".to_string()], body);
4994
4995        interp.env.define(
4996            "lst",
4997            Value::List(vec![Value::Int(1), Value::Int(2), Value::Int(3)]),
4998        );
4999
5000        let n = node(
5001            g.next(),
5002            NodeKind::MethodCall {
5003                receiver: Box::new(var(&g, "lst")),
5004                method: ident("map"),
5005                type_args: vec![],
5006                args: vec![AirArg {
5007                    label: None,
5008                    value: var(&g, "double"),
5009                }],
5010            },
5011        );
5012        assert_eq!(
5013            interp.eval_expr(&n).await,
5014            Ok(Value::List(vec![
5015                Value::Int(2),
5016                Value::Int(4),
5017                Value::Int(6)
5018            ]))
5019        );
5020    }
5021
5022    // ── Bitwise ───────────────────────────────────────────────────────────
5023
5024    #[tokio::test]
5025    async fn eval_bitwise_and() {
5026        let mut interp = Interpreter::new();
5027        let g = gen();
5028        let n = node(
5029            g.next(),
5030            NodeKind::BinaryOp {
5031                op: BinOp::BitAnd,
5032                left: Box::new(int_lit(&g, 0b1100)),
5033                right: Box::new(int_lit(&g, 0b1010)),
5034            },
5035        );
5036        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Int(0b1000)));
5037    }
5038
5039    #[tokio::test]
5040    async fn eval_bitwise_or() {
5041        let mut interp = Interpreter::new();
5042        let g = gen();
5043        let n = node(
5044            g.next(),
5045            NodeKind::BinaryOp {
5046                op: BinOp::BitOr,
5047                left: Box::new(int_lit(&g, 0b1100)),
5048                right: Box::new(int_lit(&g, 0b1010)),
5049            },
5050        );
5051        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Int(0b1110)));
5052    }
5053
5054    // ── Statement execution helpers ────────────────────────────────────────
5055
5056    fn bind_pat(g: &NodeIdGen, name: &str) -> AIRNode {
5057        node(
5058            g.next(),
5059            NodeKind::BindPat {
5060                name: ident(name),
5061                is_mut: false,
5062            },
5063        )
5064    }
5065
5066    fn let_stmt(g: &NodeIdGen, pat: AIRNode, val: AIRNode) -> AIRNode {
5067        node(
5068            g.next(),
5069            NodeKind::LetBinding {
5070                is_mut: false,
5071                pattern: Box::new(pat),
5072                ty: None,
5073                value: Box::new(val),
5074            },
5075        )
5076    }
5077
5078    fn assign_node(g: &NodeIdGen, name: &str, val: AIRNode) -> AIRNode {
5079        node(
5080            g.next(),
5081            NodeKind::Assign {
5082                op: AssignOp::Assign,
5083                target: Box::new(var(g, name)),
5084                value: Box::new(val),
5085            },
5086        )
5087    }
5088
5089    fn add(g: &NodeIdGen, left: AIRNode, right: AIRNode) -> AIRNode {
5090        node(
5091            g.next(),
5092            NodeKind::BinaryOp {
5093                op: BinOp::Add,
5094                left: Box::new(left),
5095                right: Box::new(right),
5096            },
5097        )
5098    }
5099
5100    fn lt(g: &NodeIdGen, left: AIRNode, right: AIRNode) -> AIRNode {
5101        node(
5102            g.next(),
5103            NodeKind::BinaryOp {
5104                op: BinOp::Lt,
5105                left: Box::new(left),
5106                right: Box::new(right),
5107            },
5108        )
5109    }
5110
5111    fn block(g: &NodeIdGen, stmts: Vec<AIRNode>, tail: Option<AIRNode>) -> AIRNode {
5112        node(
5113            g.next(),
5114            NodeKind::Block {
5115                stmts,
5116                tail: tail.map(Box::new),
5117            },
5118        )
5119    }
5120
5121    fn list_lit(g: &NodeIdGen, elems: Vec<AIRNode>) -> AIRNode {
5122        node(g.next(), NodeKind::ListLiteral { elems })
5123    }
5124
5125    // ── exec_stmt / exec_block tests ───────────────────────────────────────
5126
5127    #[tokio::test]
5128    async fn exec_stmt_let_binding_returns_none() {
5129        let mut interp = Interpreter::new();
5130        let g = gen();
5131        let stmt = let_stmt(&g, bind_pat(&g, "x"), int_lit(&g, 99));
5132        let result = interp.exec_stmt(&stmt).await.unwrap();
5133        assert_eq!(result, None);
5134        assert_eq!(interp.env.get("x"), Some(&Value::Int(99)));
5135    }
5136
5137    #[tokio::test]
5138    async fn exec_block_returns_tail_expression() {
5139        // { let a = 3; a + 4 }  =>  7
5140        let mut interp = Interpreter::new();
5141        let g = gen();
5142        let blk = block(
5143            &g,
5144            vec![let_stmt(&g, bind_pat(&g, "a"), int_lit(&g, 3))],
5145            Some(add(&g, var(&g, "a"), int_lit(&g, 4))),
5146        );
5147        assert_eq!(interp.exec_block(&blk).await, Ok(Value::Int(7)));
5148    }
5149
5150    #[tokio::test]
5151    async fn block_scope_variables_do_not_leak() {
5152        // { let inner = 99 }  — inner should not be visible afterward
5153        let mut interp = Interpreter::new();
5154        let g = gen();
5155        let blk = block(
5156            &g,
5157            vec![let_stmt(&g, bind_pat(&g, "inner"), int_lit(&g, 99))],
5158            None,
5159        );
5160        interp.exec_block(&blk).await.unwrap();
5161        assert_eq!(interp.env.get("inner"), None);
5162    }
5163
5164    #[tokio::test]
5165    async fn for_loop_iterates_over_list() {
5166        // sum = 0; for x in [1, 2, 3] { sum = sum + x }  => sum == 6
5167        let mut interp = Interpreter::new();
5168        let g = gen();
5169        interp.env.define("sum", Value::Int(0));
5170        let for_node = node(
5171            g.next(),
5172            NodeKind::For {
5173                pattern: Box::new(bind_pat(&g, "x")),
5174                iterable: Box::new(list_lit(
5175                    &g,
5176                    vec![int_lit(&g, 1), int_lit(&g, 2), int_lit(&g, 3)],
5177                )),
5178                body: Box::new(assign_node(
5179                    &g,
5180                    "sum",
5181                    add(&g, var(&g, "sum"), var(&g, "x")),
5182                )),
5183            },
5184        );
5185        assert_eq!(interp.eval_expr(&for_node).await, Ok(Value::Void));
5186        assert_eq!(interp.env.get("sum"), Some(&Value::Int(6)));
5187    }
5188
5189    #[tokio::test]
5190    async fn for_loop_break_exits_early() {
5191        // for x in [1, 2, 3] { break }  — completes without error
5192        let mut interp = Interpreter::new();
5193        let g = gen();
5194        let break_node = node(g.next(), NodeKind::Break { value: None });
5195        let for_node = node(
5196            g.next(),
5197            NodeKind::For {
5198                pattern: Box::new(bind_pat(&g, "x")),
5199                iterable: Box::new(list_lit(
5200                    &g,
5201                    vec![int_lit(&g, 1), int_lit(&g, 2), int_lit(&g, 3)],
5202                )),
5203                body: Box::new(break_node),
5204            },
5205        );
5206        assert_eq!(interp.eval_expr(&for_node).await, Ok(Value::Void));
5207    }
5208
5209    #[tokio::test]
5210    async fn while_loop_does_not_execute_when_false() {
5211        // while (false) { <never reached> }
5212        let mut interp = Interpreter::new();
5213        let g = gen();
5214        let cond = bool_lit(&g, false);
5215        let body = block(&g, vec![], None);
5216        let while_node = node(
5217            g.next(),
5218            NodeKind::While {
5219                condition: Box::new(cond),
5220                body: Box::new(body),
5221            },
5222        );
5223        assert_eq!(interp.eval_expr(&while_node).await, Ok(Value::Void));
5224    }
5225
5226    #[tokio::test]
5227    async fn while_loop_counts_to_three() {
5228        // count = 0; while (count < 3) { count = count + 1 }  => count == 3
5229        let mut interp = Interpreter::new();
5230        let g = gen();
5231        interp.env.define("count", Value::Int(0));
5232        let cond = lt(&g, var(&g, "count"), int_lit(&g, 3));
5233        let body = assign_node(&g, "count", add(&g, var(&g, "count"), int_lit(&g, 1)));
5234        let while_node = node(
5235            g.next(),
5236            NodeKind::While {
5237                condition: Box::new(cond),
5238                body: Box::new(body),
5239            },
5240        );
5241        assert_eq!(interp.eval_expr(&while_node).await, Ok(Value::Void));
5242        assert_eq!(interp.env.get("count"), Some(&Value::Int(3)));
5243    }
5244
5245    #[tokio::test]
5246    async fn loop_break_with_value() {
5247        // loop { break 42 }  => 42
5248        let mut interp = Interpreter::new();
5249        let g = gen();
5250        let break_node = node(
5251            g.next(),
5252            NodeKind::Break {
5253                value: Some(Box::new(int_lit(&g, 42))),
5254            },
5255        );
5256        let loop_node = node(
5257            g.next(),
5258            NodeKind::Loop {
5259                body: Box::new(break_node),
5260            },
5261        );
5262        assert_eq!(interp.eval_expr(&loop_node).await, Ok(Value::Int(42)));
5263    }
5264
5265    #[tokio::test]
5266    async fn loop_break_without_value() {
5267        // loop { break }  => Void
5268        let mut interp = Interpreter::new();
5269        let g = gen();
5270        let break_node = node(g.next(), NodeKind::Break { value: None });
5271        let loop_node = node(
5272            g.next(),
5273            NodeKind::Loop {
5274                body: Box::new(break_node),
5275            },
5276        );
5277        assert_eq!(interp.eval_expr(&loop_node).await, Ok(Value::Void));
5278    }
5279
5280    #[tokio::test]
5281    async fn guard_passes_when_condition_true() {
5282        // guard (true) else { return () }  => Void (else block not executed)
5283        let mut interp = Interpreter::new();
5284        let g = gen();
5285        let else_blk = node(g.next(), NodeKind::Return { value: None });
5286        let guard_node = node(
5287            g.next(),
5288            NodeKind::Guard {
5289                let_pattern: None,
5290                condition: Box::new(bool_lit(&g, true)),
5291                else_block: Box::new(else_blk),
5292            },
5293        );
5294        assert_eq!(interp.eval_expr(&guard_node).await, Ok(Value::Void));
5295    }
5296
5297    #[tokio::test]
5298    async fn guard_else_diverges_when_condition_false() {
5299        // guard (false) else { return () }  => propagates Return signal
5300        let mut interp = Interpreter::new();
5301        let g = gen();
5302        let else_blk = node(g.next(), NodeKind::Return { value: None });
5303        let guard_node = node(
5304            g.next(),
5305            NodeKind::Guard {
5306                let_pattern: None,
5307                condition: Box::new(bool_lit(&g, false)),
5308                else_block: Box::new(else_blk),
5309            },
5310        );
5311        assert_eq!(
5312            interp.eval_expr(&guard_node).await,
5313            Err(RuntimeError::Return(Box::new(Value::Void)))
5314        );
5315    }
5316
5317    #[tokio::test]
5318    async fn let_binding_with_tuple_destructuring() {
5319        // let (a, b) = (1, 2)
5320        let mut interp = Interpreter::new();
5321        let g = gen();
5322        let tuple_pat = node(
5323            g.next(),
5324            NodeKind::TuplePat {
5325                elems: vec![bind_pat(&g, "a"), bind_pat(&g, "b")],
5326            },
5327        );
5328        let tuple_val = node(
5329            g.next(),
5330            NodeKind::TupleLiteral {
5331                elems: vec![int_lit(&g, 1), int_lit(&g, 2)],
5332            },
5333        );
5334        let stmt = let_stmt(&g, tuple_pat, tuple_val);
5335        assert_eq!(interp.exec_stmt(&stmt).await, Ok(None));
5336        assert_eq!(interp.env.get("a"), Some(&Value::Int(1)));
5337        assert_eq!(interp.env.get("b"), Some(&Value::Int(2)));
5338    }
5339
5340    // ── Effect handler runtime tests ─────────────────────────────────────
5341
5342    #[tokio::test]
5343    async fn effect_op_with_no_handler_errors() {
5344        let mut interp = Interpreter::new();
5345        let g = gen();
5346        let effect_op = node(
5347            g.next(),
5348            NodeKind::EffectOp {
5349                effect: type_path("Log"),
5350                operation: ident("log"),
5351                args: vec![],
5352            },
5353        );
5354        let result = interp.eval_expr(&effect_op).await;
5355        assert!(matches!(result, Err(RuntimeError::NoEffectHandler { .. })));
5356        if let Err(RuntimeError::NoEffectHandler { effect }) = result {
5357            assert_eq!(effect, "Log");
5358        }
5359    }
5360
5361    #[tokio::test]
5362    async fn effect_op_dispatches_to_single_fn_handler() {
5363        let mut interp = Interpreter::new();
5364        let g = gen();
5365
5366        // Register a handler function that returns Int(42)
5367        let handler_body = int_lit(&g, 42);
5368        interp.register_fn("my_log", vec!["msg".to_string()], handler_body);
5369
5370        // Set module-level handler
5371        let handler_val = interp.env.get("my_log").cloned().unwrap();
5372        interp
5373            .effect_handlers
5374            .set_module_handler("Log", handler_val);
5375
5376        // Call the effect operation
5377        let effect_op = node(
5378            g.next(),
5379            NodeKind::EffectOp {
5380                effect: type_path("Log"),
5381                operation: ident("log"),
5382                args: vec![AirArg {
5383                    label: None,
5384                    value: str_lit(&g, "hello"),
5385                }],
5386            },
5387        );
5388        let result = interp.eval_expr(&effect_op).await;
5389        assert_eq!(result, Ok(Value::Int(42)));
5390    }
5391
5392    #[tokio::test]
5393    async fn effect_op_dispatches_to_record_handler() {
5394        let mut interp = Interpreter::new();
5395        let g = gen();
5396
5397        // Register the operation function
5398        let op_body = int_lit(&g, 99);
5399        interp.register_fn("_log_op", vec!["msg".to_string()], op_body);
5400        let op_fn = interp.env.get("_log_op").cloned().unwrap();
5401
5402        // Create a record handler with the operation as a field
5403        let mut fields = BTreeMap::new();
5404        fields.insert("log".to_string(), op_fn);
5405        let handler_record = Value::Record(RecordValue {
5406            type_name: "ConsoleLog".to_string(),
5407            fields,
5408        });
5409        interp
5410            .effect_handlers
5411            .set_module_handler("Log", handler_record);
5412
5413        let effect_op = node(
5414            g.next(),
5415            NodeKind::EffectOp {
5416                effect: type_path("Log"),
5417                operation: ident("log"),
5418                args: vec![AirArg {
5419                    label: None,
5420                    value: str_lit(&g, "test"),
5421                }],
5422            },
5423        );
5424        let result = interp.eval_expr(&effect_op).await;
5425        assert_eq!(result, Ok(Value::Int(99)));
5426    }
5427
5428    #[tokio::test]
5429    async fn handling_block_pushes_and_pops_handler() {
5430        let mut interp = Interpreter::new();
5431        let g = gen();
5432
5433        // Register a handler function
5434        let handler_body = int_lit(&g, 7);
5435        interp.register_fn("test_handler", vec!["msg".to_string()], handler_body);
5436
5437        // Create handling block with an effect op call in the body
5438        let effect_op = node(
5439            g.next(),
5440            NodeKind::EffectOp {
5441                effect: type_path("Log"),
5442                operation: ident("log"),
5443                args: vec![AirArg {
5444                    label: None,
5445                    value: str_lit(&g, "inside"),
5446                }],
5447            },
5448        );
5449
5450        let handling = node(
5451            g.next(),
5452            NodeKind::HandlingBlock {
5453                handlers: vec![AirHandlerPair {
5454                    effect: type_path("Log"),
5455                    handler: Box::new(var(&g, "test_handler")),
5456                }],
5457                body: Box::new(effect_op),
5458            },
5459        );
5460
5461        // The handling block should succeed
5462        let result = interp.eval_expr(&handling).await;
5463        assert_eq!(result, Ok(Value::Int(7)));
5464
5465        // After the handling block, the handler should be popped
5466        assert!(interp.effect_handlers.resolve("Log").is_none());
5467    }
5468
5469    #[tokio::test]
5470    async fn handling_block_pops_on_error() {
5471        let mut interp = Interpreter::new();
5472        let g = gen();
5473
5474        // Handling block whose body accesses an undefined variable
5475        let bad_body = var(&g, "nonexistent");
5476        let handler_body = int_lit(&g, 1);
5477        interp.register_fn("h", vec![], handler_body);
5478
5479        let handling = node(
5480            g.next(),
5481            NodeKind::HandlingBlock {
5482                handlers: vec![AirHandlerPair {
5483                    effect: type_path("Log"),
5484                    handler: Box::new(var(&g, "h")),
5485                }],
5486                body: Box::new(bad_body),
5487            },
5488        );
5489
5490        let result = interp.eval_expr(&handling).await;
5491        assert!(result.is_err());
5492        // Handler stack should still be cleaned up
5493        assert!(interp.effect_handlers.resolve("Log").is_none());
5494    }
5495
5496    #[tokio::test]
5497    async fn nested_handling_blocks_innermost_wins() {
5498        let mut interp = Interpreter::new();
5499        let g = gen();
5500
5501        // Outer handler returns 1
5502        let outer_body = int_lit(&g, 1);
5503        interp.register_fn("outer_h", vec!["m".to_string()], outer_body);
5504
5505        // Inner handler returns 2
5506        let inner_body = int_lit(&g, 2);
5507        interp.register_fn("inner_h", vec!["m".to_string()], inner_body);
5508
5509        let effect_op = node(
5510            g.next(),
5511            NodeKind::EffectOp {
5512                effect: type_path("Log"),
5513                operation: ident("log"),
5514                args: vec![AirArg {
5515                    label: None,
5516                    value: str_lit(&g, "test"),
5517                }],
5518            },
5519        );
5520
5521        let inner_handling = node(
5522            g.next(),
5523            NodeKind::HandlingBlock {
5524                handlers: vec![AirHandlerPair {
5525                    effect: type_path("Log"),
5526                    handler: Box::new(var(&g, "inner_h")),
5527                }],
5528                body: Box::new(effect_op),
5529            },
5530        );
5531
5532        let outer_handling = node(
5533            g.next(),
5534            NodeKind::HandlingBlock {
5535                handlers: vec![AirHandlerPair {
5536                    effect: type_path("Log"),
5537                    handler: Box::new(var(&g, "outer_h")),
5538                }],
5539                body: Box::new(inner_handling),
5540            },
5541        );
5542
5543        let result = interp.eval_expr(&outer_handling).await;
5544        assert_eq!(result, Ok(Value::Int(2)));
5545    }
5546
5547    #[tokio::test]
5548    async fn three_layer_resolution_local_over_module_over_project() {
5549        let mut interp = Interpreter::new();
5550        let g = gen();
5551
5552        // Project handler returns 1
5553        let proj_body = int_lit(&g, 1);
5554        interp.register_fn("proj_h", vec!["m".to_string()], proj_body);
5555        let proj_val = interp.env.get("proj_h").cloned().unwrap();
5556        interp.effect_handlers.set_project_handler("Log", proj_val);
5557
5558        // Module handler returns 2
5559        let mod_body = int_lit(&g, 2);
5560        interp.register_fn("mod_h", vec!["m".to_string()], mod_body);
5561        let mod_val = interp.env.get("mod_h").cloned().unwrap();
5562        interp.effect_handlers.set_module_handler("Log", mod_val);
5563
5564        // Local handler returns 3
5565        let local_body = int_lit(&g, 3);
5566        interp.register_fn("local_h", vec!["m".to_string()], local_body);
5567
5568        let effect_op = node(
5569            g.next(),
5570            NodeKind::EffectOp {
5571                effect: type_path("Log"),
5572                operation: ident("log"),
5573                args: vec![AirArg {
5574                    label: None,
5575                    value: str_lit(&g, "test"),
5576                }],
5577            },
5578        );
5579
5580        let handling = node(
5581            g.next(),
5582            NodeKind::HandlingBlock {
5583                handlers: vec![AirHandlerPair {
5584                    effect: type_path("Log"),
5585                    handler: Box::new(var(&g, "local_h")),
5586                }],
5587                body: Box::new(effect_op),
5588            },
5589        );
5590
5591        // Local wins over module and project
5592        let result = interp.eval_expr(&handling).await;
5593        assert_eq!(result, Ok(Value::Int(3)));
5594    }
5595
5596    #[tokio::test]
5597    async fn module_handle_registers_handler() {
5598        let mut interp = Interpreter::new();
5599        let g = gen();
5600
5601        // Register a handler function
5602        let handler_body = int_lit(&g, 55);
5603        interp.register_fn("console_log", vec!["m".to_string()], handler_body);
5604
5605        // Execute ModuleHandle node
5606        let module_handle = node(
5607            g.next(),
5608            NodeKind::ModuleHandle {
5609                effect: type_path("Log"),
5610                handler: Box::new(var(&g, "console_log")),
5611            },
5612        );
5613        let result = interp.eval_expr(&module_handle).await;
5614        assert_eq!(result, Ok(Value::Void));
5615
5616        // Now an effect op should resolve to the module handler
5617        let effect_op = node(
5618            g.next(),
5619            NodeKind::EffectOp {
5620                effect: type_path("Log"),
5621                operation: ident("log"),
5622                args: vec![AirArg {
5623                    label: None,
5624                    value: str_lit(&g, "test"),
5625                }],
5626            },
5627        );
5628        let result = interp.eval_expr(&effect_op).await;
5629        assert_eq!(result, Ok(Value::Int(55)));
5630    }
5631
5632    #[tokio::test]
5633    async fn effect_decl_evaluates_to_void() {
5634        let mut interp = Interpreter::new();
5635        let g = gen();
5636        let effect_decl = node(
5637            g.next(),
5638            NodeKind::EffectDecl {
5639                annotations: vec![],
5640                visibility: bock_ast::Visibility::Private,
5641                name: ident("Log"),
5642                generic_params: vec![],
5643                components: vec![],
5644                operations: vec![],
5645            },
5646        );
5647        let result = interp.eval_expr(&effect_decl).await;
5648        assert_eq!(result, Ok(Value::Void));
5649    }
5650
5651    #[tokio::test]
5652    async fn effect_ref_evaluates_to_void() {
5653        let mut interp = Interpreter::new();
5654        let g = gen();
5655        let effect_ref = node(
5656            g.next(),
5657            NodeKind::EffectRef {
5658                path: type_path("Log"),
5659            },
5660        );
5661        let result = interp.eval_expr(&effect_ref).await;
5662        assert_eq!(result, Ok(Value::Void));
5663    }
5664
5665    #[tokio::test]
5666    async fn no_handler_error_message_is_clear() {
5667        let err = RuntimeError::NoEffectHandler {
5668            effect: "Log".to_string(),
5669        };
5670        let msg = err.to_string();
5671        assert!(msg.contains("Log"));
5672        assert!(msg.contains("handling"));
5673        assert!(msg.contains("handler"));
5674    }
5675
5676    #[tokio::test]
5677    async fn register_effect_dispatches_through_call() {
5678        let mut interp = Interpreter::new();
5679        let g = gen();
5680
5681        // Create an effect with a "log" operation
5682        let empty_body = node(
5683            g.next(),
5684            NodeKind::Block {
5685                stmts: vec![],
5686                tail: None,
5687            },
5688        );
5689        let log_op = node(
5690            g.next(),
5691            NodeKind::FnDecl {
5692                annotations: vec![],
5693                visibility: bock_ast::Visibility::Public,
5694                is_async: false,
5695                name: ident("log"),
5696                generic_params: vec![],
5697                params: vec![],
5698                return_type: None,
5699                effect_clause: vec![],
5700                where_clause: vec![],
5701                body: Box::new(empty_body),
5702            },
5703        );
5704        interp.register_effect("Logger", &[log_op]);
5705
5706        // Register a handler function
5707        let handler_body = int_lit(&g, 77);
5708        interp.register_fn("my_handler", vec!["msg".to_string()], handler_body);
5709        let handler_val = interp.env.get("my_handler").cloned().unwrap();
5710        interp
5711            .effect_handlers
5712            .set_module_handler("Logger", handler_val);
5713
5714        // Call `log("test")` as a regular Call node (how the lowerer emits it)
5715        let call_node = node(
5716            g.next(),
5717            NodeKind::Call {
5718                callee: Box::new(var(&g, "log")),
5719                args: vec![AirArg {
5720                    label: None,
5721                    value: str_lit(&g, "test"),
5722                }],
5723                type_args: vec![],
5724            },
5725        );
5726        let result = interp.eval_expr(&call_node).await;
5727        assert_eq!(result, Ok(Value::Int(77)));
5728    }
5729
5730    // ── M-074: BinOp::Is (runtime type check) ──────────────────────────
5731
5732    #[tokio::test]
5733    async fn is_operator_int() {
5734        let mut interp = Interpreter::new();
5735        let g = gen();
5736        // 42 is Int → true
5737        let n = node(
5738            g.next(),
5739            NodeKind::BinaryOp {
5740                op: BinOp::Is,
5741                left: Box::new(int_lit(&g, 42)),
5742                right: Box::new(str_lit(&g, "Int")),
5743            },
5744        );
5745        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Bool(true)));
5746    }
5747
5748    #[tokio::test]
5749    async fn is_operator_wrong_type() {
5750        let mut interp = Interpreter::new();
5751        let g = gen();
5752        // 42 is String → false
5753        let n = node(
5754            g.next(),
5755            NodeKind::BinaryOp {
5756                op: BinOp::Is,
5757                left: Box::new(int_lit(&g, 42)),
5758                right: Box::new(str_lit(&g, "String")),
5759            },
5760        );
5761        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Bool(false)));
5762    }
5763
5764    #[tokio::test]
5765    async fn is_operator_string() {
5766        let mut interp = Interpreter::new();
5767        let g = gen();
5768        let n = node(
5769            g.next(),
5770            NodeKind::BinaryOp {
5771                op: BinOp::Is,
5772                left: Box::new(str_lit(&g, "hello")),
5773                right: Box::new(str_lit(&g, "String")),
5774            },
5775        );
5776        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Bool(true)));
5777    }
5778
5779    #[tokio::test]
5780    async fn is_operator_bool() {
5781        let mut interp = Interpreter::new();
5782        let g = gen();
5783        let n = node(
5784            g.next(),
5785            NodeKind::BinaryOp {
5786                op: BinOp::Is,
5787                left: Box::new(bool_lit(&g, true)),
5788                right: Box::new(str_lit(&g, "Bool")),
5789            },
5790        );
5791        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Bool(true)));
5792    }
5793
5794    #[tokio::test]
5795    async fn is_operator_list() {
5796        let mut interp = Interpreter::new();
5797        let g = gen();
5798        let list_node = node(
5799            g.next(),
5800            NodeKind::ListLiteral {
5801                elems: vec![int_lit(&g, 1)],
5802            },
5803        );
5804        let n = node(
5805            g.next(),
5806            NodeKind::BinaryOp {
5807                op: BinOp::Is,
5808                left: Box::new(list_node),
5809                right: Box::new(str_lit(&g, "List")),
5810            },
5811        );
5812        assert_eq!(interp.eval_expr(&n).await, Ok(Value::Bool(true)));
5813    }
5814
5815    // ── M-075: Match exhaustiveness (covered by warning output) ─────────
5816
5817    #[tokio::test]
5818    async fn match_on_enum_with_wildcard_succeeds() {
5819        let mut interp = Interpreter::new();
5820        let g = gen();
5821        // Define an enum value
5822        interp.env.define(
5823            "color",
5824            Value::Enum(crate::value::EnumValue {
5825                type_name: "Color".to_string(),
5826                variant: "Red".to_string(),
5827                payload: None,
5828            }),
5829        );
5830        // Match with wildcard arm
5831        let match_expr = node(
5832            g.next(),
5833            NodeKind::Match {
5834                scrutinee: Box::new(var(&g, "color")),
5835                arms: vec![node(
5836                    g.next(),
5837                    NodeKind::MatchArm {
5838                        pattern: Box::new(node(g.next(), NodeKind::WildcardPat)),
5839                        guard: None,
5840                        body: Box::new(int_lit(&g, 99)),
5841                    },
5842                )],
5843            },
5844        );
5845        assert_eq!(interp.eval_expr(&match_expr).await, Ok(Value::Int(99)));
5846    }
5847
5848    // ── M-077: Compound assignment on field/index targets ───────────────
5849
5850    #[tokio::test]
5851    async fn compound_assign_field() {
5852        let mut interp = Interpreter::new();
5853        let g = gen();
5854        // obj = Point { x: 10, y: 20 }
5855        let mut fields = BTreeMap::new();
5856        fields.insert("x".to_string(), Value::Int(10));
5857        fields.insert("y".to_string(), Value::Int(20));
5858        interp.env.define(
5859            "obj",
5860            Value::Record(RecordValue {
5861                type_name: "Point".to_string(),
5862                fields,
5863            }),
5864        );
5865        // obj.x += 5
5866        let assign = node(
5867            g.next(),
5868            NodeKind::Assign {
5869                op: AssignOp::AddAssign,
5870                target: Box::new(node(
5871                    g.next(),
5872                    NodeKind::FieldAccess {
5873                        object: Box::new(var(&g, "obj")),
5874                        field: ident("x"),
5875                    },
5876                )),
5877                value: Box::new(int_lit(&g, 5)),
5878            },
5879        );
5880        assert_eq!(interp.eval_expr(&assign).await, Ok(Value::Void));
5881        // Check that obj.x is now 15
5882        let obj = interp.env.get("obj").unwrap().clone();
5883        if let Value::Record(rv) = obj {
5884            assert_eq!(rv.fields.get("x"), Some(&Value::Int(15)));
5885            assert_eq!(rv.fields.get("y"), Some(&Value::Int(20)));
5886        } else {
5887            panic!("expected Record");
5888        }
5889    }
5890
5891    #[tokio::test]
5892    async fn compound_assign_index() {
5893        let mut interp = Interpreter::new();
5894        let g = gen();
5895        // list = [10, 20, 30]
5896        interp.env.define(
5897            "list",
5898            Value::List(vec![Value::Int(10), Value::Int(20), Value::Int(30)]),
5899        );
5900        // list[1] += 5
5901        let assign = node(
5902            g.next(),
5903            NodeKind::Assign {
5904                op: AssignOp::AddAssign,
5905                target: Box::new(node(
5906                    g.next(),
5907                    NodeKind::Index {
5908                        object: Box::new(var(&g, "list")),
5909                        index: Box::new(int_lit(&g, 1)),
5910                    },
5911                )),
5912                value: Box::new(int_lit(&g, 5)),
5913            },
5914        );
5915        assert_eq!(interp.eval_expr(&assign).await, Ok(Value::Void));
5916        let list = interp.env.get("list").unwrap().clone();
5917        assert_eq!(
5918            list,
5919            Value::List(vec![Value::Int(10), Value::Int(25), Value::Int(30)])
5920        );
5921    }
5922
5923    #[tokio::test]
5924    async fn assign_field_simple() {
5925        let mut interp = Interpreter::new();
5926        let g = gen();
5927        let mut fields = BTreeMap::new();
5928        fields.insert("name".to_string(), Value::String(BockString::new("old")));
5929        interp.env.define(
5930            "obj",
5931            Value::Record(RecordValue {
5932                type_name: "Item".to_string(),
5933                fields,
5934            }),
5935        );
5936        // obj.name = "new"
5937        let assign = node(
5938            g.next(),
5939            NodeKind::Assign {
5940                op: AssignOp::Assign,
5941                target: Box::new(node(
5942                    g.next(),
5943                    NodeKind::FieldAccess {
5944                        object: Box::new(var(&g, "obj")),
5945                        field: ident("name"),
5946                    },
5947                )),
5948                value: Box::new(str_lit(&g, "new")),
5949            },
5950        );
5951        assert_eq!(interp.eval_expr(&assign).await, Ok(Value::Void));
5952        let obj = interp.env.get("obj").unwrap().clone();
5953        if let Value::Record(rv) = obj {
5954            assert_eq!(
5955                rv.fields.get("name"),
5956                Some(&Value::String(BockString::new("new")))
5957            );
5958        } else {
5959            panic!("expected Record");
5960        }
5961    }
5962
5963    // ── M-078: Descending ranges ────────────────────────────────────────
5964
5965    #[tokio::test]
5966    async fn descending_range_exclusive() {
5967        // 5..1 with default step should produce [5, 4, 3, 2]
5968        let result = range_to_vec(5, 1, false, 1);
5969        assert_eq!(
5970            result,
5971            vec![Value::Int(5), Value::Int(4), Value::Int(3), Value::Int(2)]
5972        );
5973    }
5974
5975    #[tokio::test]
5976    async fn descending_range_inclusive() {
5977        // 5..=1 with default step should produce [5, 4, 3, 2, 1]
5978        let result = range_to_vec(5, 1, true, 1);
5979        assert_eq!(
5980            result,
5981            vec![
5982                Value::Int(5),
5983                Value::Int(4),
5984                Value::Int(3),
5985                Value::Int(2),
5986                Value::Int(1),
5987            ]
5988        );
5989    }
5990
5991    #[tokio::test]
5992    async fn descending_range_explicit_negative_step() {
5993        // 10..0 step -2 should produce [10, 8, 6, 4, 2]
5994        let result = range_to_vec(10, 0, false, -2);
5995        assert_eq!(
5996            result,
5997            vec![
5998                Value::Int(10),
5999                Value::Int(8),
6000                Value::Int(6),
6001                Value::Int(4),
6002                Value::Int(2),
6003            ]
6004        );
6005    }
6006
6007    #[tokio::test]
6008    async fn ascending_range_still_works() {
6009        let result = range_to_vec(1, 5, false, 1);
6010        assert_eq!(
6011            result,
6012            vec![Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)]
6013        );
6014    }
6015
6016    // ── M-079: for..in over Map ─────────────────────────────────────────
6017
6018    #[tokio::test]
6019    async fn for_in_map() {
6020        let mut interp = Interpreter::new();
6021        let g = gen();
6022        // Build a map {1: "a", 2: "b"}
6023        let mut map = BTreeMap::new();
6024        map.insert(Value::Int(1), Value::String(BockString::new("a")));
6025        map.insert(Value::Int(2), Value::String(BockString::new("b")));
6026        interp.env.define("m", Value::Map(map));
6027        interp.env.define("result", Value::List(vec![]));
6028
6029        // for (k, v) in m { result.push(k) }
6030        // (DQ18/DQ30: `push` mutates the receiver place in place and returns
6031        // `Void`, so the loop body is a bare statement — no reassignment.)
6032        let for_expr = node(
6033            g.next(),
6034            NodeKind::For {
6035                pattern: Box::new(node(
6036                    g.next(),
6037                    NodeKind::TuplePat {
6038                        elems: vec![
6039                            node(
6040                                g.next(),
6041                                NodeKind::BindPat {
6042                                    name: ident("k"),
6043                                    is_mut: false,
6044                                },
6045                            ),
6046                            node(
6047                                g.next(),
6048                                NodeKind::BindPat {
6049                                    name: ident("v"),
6050                                    is_mut: false,
6051                                },
6052                            ),
6053                        ],
6054                    },
6055                )),
6056                iterable: Box::new(var(&g, "m")),
6057                body: Box::new(node(
6058                    g.next(),
6059                    NodeKind::MethodCall {
6060                        receiver: Box::new(var(&g, "result")),
6061                        method: ident("push"),
6062                        args: vec![AirArg {
6063                            label: None,
6064                            value: var(&g, "k"),
6065                        }],
6066                        type_args: vec![],
6067                    },
6068                )),
6069            },
6070        );
6071        assert_eq!(interp.eval_expr(&for_expr).await, Ok(Value::Void));
6072        let result = interp.env.get("result").unwrap().clone();
6073        // BTreeMap iterates in key order, so keys are [1, 2]
6074        assert_eq!(result, Value::List(vec![Value::Int(1), Value::Int(2)]));
6075    }
6076
6077    // ── M-076: for..in over lazy iterators ──────────────────────────────
6078
6079    #[tokio::test]
6080    async fn for_in_lazy_map_iterator() {
6081        use crate::value::{IteratorKind, IteratorValue};
6082
6083        let mut interp = Interpreter::new();
6084        let g = gen();
6085
6086        // Create a function that doubles its argument
6087        let double_body = node(
6088            g.next(),
6089            NodeKind::BinaryOp {
6090                op: BinOp::Mul,
6091                left: Box::new(var(&g, "x")),
6092                right: Box::new(int_lit(&g, 2)),
6093            },
6094        );
6095        interp.register_fn("double", vec!["x".to_string()], double_body);
6096        let double_fn = match interp.env.get("double").unwrap().clone() {
6097            Value::Function(fv) => fv,
6098            _ => panic!("expected function"),
6099        };
6100
6101        // Create a lazy map iterator over [1, 2, 3] with the double function
6102        let source = IteratorKind::List {
6103            items: vec![Value::Int(1), Value::Int(2), Value::Int(3)],
6104            pos: 0,
6105        };
6106        let map_iter = IteratorKind::Map {
6107            source: std::sync::Arc::new(std::sync::Mutex::new(source)),
6108            func: double_fn,
6109        };
6110        let iter_val = IteratorValue::new(map_iter);
6111        interp.env.define("it", Value::Iterator(iter_val));
6112        interp.env.define("result", Value::List(vec![]));
6113
6114        // for x in it { result.push(x) }   (push mutates in place; DQ18/DQ30)
6115        let for_expr = node(
6116            g.next(),
6117            NodeKind::For {
6118                pattern: Box::new(node(
6119                    g.next(),
6120                    NodeKind::BindPat {
6121                        name: ident("item"),
6122                        is_mut: false,
6123                    },
6124                )),
6125                iterable: Box::new(var(&g, "it")),
6126                body: Box::new(node(
6127                    g.next(),
6128                    NodeKind::MethodCall {
6129                        receiver: Box::new(var(&g, "result")),
6130                        method: ident("push"),
6131                        args: vec![AirArg {
6132                            label: None,
6133                            value: var(&g, "item"),
6134                        }],
6135                        type_args: vec![],
6136                    },
6137                )),
6138            },
6139        );
6140        assert_eq!(interp.eval_expr(&for_expr).await, Ok(Value::Void));
6141        let result = interp.env.get("result").unwrap().clone();
6142        assert_eq!(
6143            result,
6144            Value::List(vec![Value::Int(2), Value::Int(4), Value::Int(6)])
6145        );
6146    }
6147
6148    #[tokio::test]
6149    async fn for_in_lazy_filter_iterator() {
6150        use crate::value::{IteratorKind, IteratorValue};
6151
6152        let mut interp = Interpreter::new();
6153        let g = gen();
6154
6155        // Create a predicate function: x > 2
6156        let pred_body = node(
6157            g.next(),
6158            NodeKind::BinaryOp {
6159                op: BinOp::Gt,
6160                left: Box::new(var(&g, "x")),
6161                right: Box::new(int_lit(&g, 2)),
6162            },
6163        );
6164        interp.register_fn("gt2", vec!["x".to_string()], pred_body);
6165        let gt2_fn = match interp.env.get("gt2").unwrap().clone() {
6166            Value::Function(fv) => fv,
6167            _ => panic!("expected function"),
6168        };
6169
6170        // Create a lazy filter iterator over [1, 2, 3, 4, 5]
6171        let source = IteratorKind::List {
6172            items: vec![
6173                Value::Int(1),
6174                Value::Int(2),
6175                Value::Int(3),
6176                Value::Int(4),
6177                Value::Int(5),
6178            ],
6179            pos: 0,
6180        };
6181        let filter_iter = IteratorKind::Filter {
6182            source: std::sync::Arc::new(std::sync::Mutex::new(source)),
6183            pred: gt2_fn,
6184        };
6185        let iter_val = IteratorValue::new(filter_iter);
6186        interp.env.define("it", Value::Iterator(iter_val));
6187        interp.env.define("result", Value::List(vec![]));
6188
6189        // for item in it { result.push(item) }   (push mutates in place; DQ18/DQ30)
6190        let for_expr = node(
6191            g.next(),
6192            NodeKind::For {
6193                pattern: Box::new(node(
6194                    g.next(),
6195                    NodeKind::BindPat {
6196                        name: ident("item"),
6197                        is_mut: false,
6198                    },
6199                )),
6200                iterable: Box::new(var(&g, "it")),
6201                body: Box::new(node(
6202                    g.next(),
6203                    NodeKind::MethodCall {
6204                        receiver: Box::new(var(&g, "result")),
6205                        method: ident("push"),
6206                        args: vec![AirArg {
6207                            label: None,
6208                            value: var(&g, "item"),
6209                        }],
6210                        type_args: vec![],
6211                    },
6212                )),
6213            },
6214        );
6215        assert_eq!(interp.eval_expr(&for_expr).await, Ok(Value::Void));
6216        let result = interp.env.get("result").unwrap().clone();
6217        assert_eq!(
6218            result,
6219            Value::List(vec![Value::Int(3), Value::Int(4), Value::Int(5)])
6220        );
6221    }
6222
6223    // ── Method-body global visibility (codegen-correctness defect 5) ──────────
6224
6225    #[tokio::test]
6226    async fn method_body_sees_program_globals() {
6227        // A method body must see program globals (here the prelude `None`
6228        // constructor). Before the fix, method bodies ran in a bare
6229        // `Environment::new()`, so `None`/`Some`/`Ok`/`Err` and top-level fns
6230        // were invisible and resolving `None` failed.
6231        let mut interp = Interpreter::new();
6232        let g = gen();
6233
6234        // Method `get_none(self) -> { None }`, registered on type `Holder`.
6235        let body = node(
6236            g.next(),
6237            NodeKind::Block {
6238                stmts: vec![],
6239                tail: Some(Box::new(var(&g, "None"))),
6240            },
6241        );
6242        interp.method_table.insert(
6243            "Holder".to_string(),
6244            HashMap::from([(
6245                "get_none".to_string(),
6246                MethodEntry {
6247                    params: vec!["self".to_string()],
6248                    self_is_mut: false,
6249                    body,
6250                },
6251            )]),
6252        );
6253
6254        let receiver = Value::Record(RecordValue {
6255            type_name: "Holder".to_string(),
6256            fields: std::collections::BTreeMap::new(),
6257        });
6258
6259        let outcome = interp
6260            .try_call_impl_method(&receiver, "get_none", vec![])
6261            .await
6262            .expect("method dispatch should not error")
6263            .expect("method should be found");
6264        assert_eq!(outcome.value, Value::Optional(None));
6265        assert!(outcome.updated_self.is_none());
6266    }
6267
6268    #[tokio::test]
6269    async fn method_body_global_invisible_without_seeding() {
6270        // Guard: a method env seeded *without* globals (the old behavior) does
6271        // not resolve `None`. This pins the regression — proving the seeding in
6272        // `try_call_impl_method` is what makes the test above pass.
6273        let mut interp = Interpreter::new();
6274        let g = gen();
6275        let bare = Environment::new();
6276        // Replacing the env with a bare one drops the globals.
6277        interp.env = bare;
6278        assert!(interp.env.get("None").is_none());
6279        let _ = g;
6280    }
6281}