diff --git a/src/ast/compiler/type_checker.rs b/src/ast/compiler/type_checker.rs index 8c6183e..87927bd 100644 --- a/src/ast/compiler/type_checker.rs +++ b/src/ast/compiler/type_checker.rs @@ -906,7 +906,10 @@ impl TypeChecker { NodeKind::Identifier { symbol, binding } => { if let IdentifierBinding::Reference(addr) = binding { - let ty = ctx.get_type(*addr); + // Apply the current HM substitution so that TypeVars resolved in + // nested scopes (e.g. inside a `while` body) are visible here even + // when ctx.set_type could not propagate back through an upvalue address. + let ty = Self::apply_subst(ctx.get_type(*addr), &self.subst.borrow()); ( NodeKind::Identifier { symbol: symbol.clone(), @@ -915,7 +918,7 @@ impl TypeChecker { ty, ) } else if let IdentifierBinding::Declaration { addr, kind: decl_kind } = binding { - let ty = ctx.get_type(*addr); + let ty = Self::apply_subst(ctx.get_type(*addr), &self.subst.borrow()); ( NodeKind::Identifier { symbol: symbol.clone(), diff --git a/tests/rtl_series.rs b/tests/rtl_series.rs index 241a12e..3252255 100644 --- a/tests/rtl_series.rs +++ b/tests/rtl_series.rs @@ -40,6 +40,31 @@ fn test_series_infer_type_from_nested_closure() { } } +/// Regression test: TypeVar on a series used as an upvalue inside a `while` loop +/// (zero-parameter lambda created by the macro) must be resolved via the global +/// substitution even when `ctx.set_type` silently ignores the upvalue address. +#[test] +fn test_series_typevar_resolved_through_while_upvalue() { + let env = Environment::new(); + let source = r#" + (do + (def ticks (series 10)) + (def i 0) + (while (< i 3) + (do + (push ticks {:price (* i 1.0)}) + (assign i (+ i 1)))) + (def p (.price ticks)) + (p 0) + ) + "#; + let result = env.run_script(source); + match result { + Ok(Value::Float(v)) if (v - 2.0).abs() < 1e-9 => {} + other => panic!("Expected Float(2.0), got {:?}", other), + } +} + #[test] fn test_record_structural_equality_order() { let env = Environment::new();