use lex_ast::canonicalize_program;
use lex_bytecode::{compile_program, Value, Vm};
use lex_syntax::parse_source;
fn run(src: &str, fn_name: &str, args: Vec<Value>) -> Value {
let p = parse_source(src).expect("parse");
let stages = canonicalize_program(&p);
if let Err(errs) = lex_types::check_program(&stages) {
panic!("type errors:\n{errs:#?}");
}
let prog = compile_program(&stages);
let mut vm = Vm::new(&prog);
vm.call(fn_name, args).unwrap_or_else(|e| panic!("call {fn_name}: {e}"))
}
const FN_NAME_SHADOWS_PARAM_IN_LAMBDA: &str = r#"
import "std.list" as list
type Field = { name :: Str, kind :: Str }
type ModelSchema = { title :: Str, fields :: List[Field] }
# Inner fn whose param `schema` collides with the top-level fn `schema`
# below. Body uses a closure that references the param — the lambda
# must capture the local, not materialize the top-level fn as a value.
# The field access `schema.title` is what triggered the original
# "GetField on non-record: Closure" panic.
fn count_escapes(schema :: ModelSchema) -> Int {
list.fold(schema.fields, 0, fn(acc :: Int, _f :: Field) -> Int {
match schema.title { "U" => acc + 1, _ => acc }
})
}
# Top-level fn whose name is also the param name above.
fn schema() -> ModelSchema {
{ title: "U", fields: [{ name: "x", kind: "Str" }] }
}
fn run_it() -> Int { count_escapes(schema()) }
"#;
#[test]
fn issue_339_lambda_captures_local_shadowing_top_level_fn() {
assert_eq!(run(FN_NAME_SHADOWS_PARAM_IN_LAMBDA, "run_it", vec![]), Value::Int(1));
}
#[test]
fn issue_339_lambda_capture_without_collision_still_works() {
let src = r#"
import "std.list" as list
fn count(xs :: List[Int]) -> Int {
list.fold(xs, 0, fn(acc :: Int, x :: Int) -> Int { acc + x })
}
fn run_it() -> Int { count([1, 2, 3, 4]) }
"#;
assert_eq!(run(src, "run_it", vec![]), Value::Int(10));
}
#[test]
fn issue_339_top_level_fn_referenced_in_lambda_still_resolves() {
let src = r#"
import "std.list" as list
fn double(x :: Int) -> Int { x * 2 }
# Lambda body references `double` — no local with that name, so it
# must resolve to the top-level fn.
fn run_it() -> List[Int] {
list.map([1, 2, 3], fn(x :: Int) -> Int { double(x) })
}
"#;
assert_eq!(
run(src, "run_it", vec![]),
Value::List(vec![Value::Int(2), Value::Int(4), Value::Int(6)].into()),
);
}