From d7706dc3ed57c44c19e8d421df23e5d91848d8b2 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 10 May 2026 02:03:09 +0200 Subject: [PATCH] test: red for env-construction drift-shape pin --- .../tests/env_construction_pin.rs | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 crates/ailang-check/tests/env_construction_pin.rs diff --git a/crates/ailang-check/tests/env_construction_pin.rs b/crates/ailang-check/tests/env_construction_pin.rs new file mode 100644 index 0000000..de3f9b2 --- /dev/null +++ b/crates/ailang-check/tests/env_construction_pin.rs @@ -0,0 +1,196 @@ +//! Drift-shape pin for env construction (env-construction unify +//! milestone). Asserts that `build_check_env(&ws)` agrees with an +//! in-test reproduction of the pre-refactor inline seeding on every +//! workspace-flat field. Future-you adds a new workspace-flat env +//! field without updating `build_check_env` -> this test fails before +//! the bug ships. +//! +//! Fixture: a two-module workspace covering one `Def::Class`, one +//! `Def::Instance`, one user ADT, and one cross-module import (the +//! same shape that `test_mono_imports_*` use). + +use ailang_check::{build_check_env, build_module_globals, build_module_types, Env}; +use ailang_core::ast::Def; +use ailang_core::load_workspace; +use std::collections::BTreeMap; +use std::path::Path; + +fn examples_dir() -> std::path::PathBuf { + let manifest = env!("CARGO_MANIFEST_DIR"); + Path::new(manifest).parent().unwrap().parent().unwrap().join("examples") +} + +/// In-test reproduction of the pre-refactor `check_in_workspace` +/// workspace-flat seeding (lib.rs as of commit a9c685d). Kept as a +/// frozen reference: any future divergence between this body and +/// `build_check_env` makes the assertion below fail. +fn build_env_inline_pre_refactor(ws: &ailang_core::workspace::Workspace) -> Env { + let mut env = Env::default(); + ailang_check::builtins::install(&mut env); + + // env.types + env.ctor_index — workspace-flat from every Def::Type. + for m in ws.modules.values() { + for d in &m.defs { + if let Def::Type(td) = d { + for c in &td.ctors { + env.ctor_index.insert( + c.name.clone(), + ailang_check::CtorRef { type_name: td.name.clone() }, + ); + } + env.types.insert(td.name.clone(), td.clone()); + } + } + } + + // env.module_globals — per-module .fns projection. + let mg = build_module_globals(ws).expect("build_module_globals"); + env.module_globals = mg + .iter() + .map(|(k, v)| (k.clone(), v.fns.clone())) + .collect(); + + // env.module_types — workspace-wide. + env.module_types = build_module_types(ws); + + // env.module_imports — per-module alias map. + for m in ws.modules.values() { + let mut import_map: BTreeMap = BTreeMap::new(); + for imp in &m.imports { + let key = imp.alias.clone().unwrap_or_else(|| imp.module.clone()); + import_map.insert(key, imp.module.clone()); + } + env.module_imports.insert(m.name.clone(), import_map); + } + + // env.class_methods — workspace-merged from each module's + // ModuleGlobals.class_methods. + for g in mg.values() { + for (n, e) in &g.class_methods { + env.class_methods.insert(n.clone(), e.clone()); + } + } + + // env.class_superclasses — walk every module's Def::Class. + for m in ws.modules.values() { + for d in &m.defs { + if let Def::Class(cd) = d { + if let Some(sc) = &cd.superclass { + env.class_superclasses.insert(cd.name.clone(), sc.class.clone()); + } + } + } + } + + // env.workspace_registry — clone of ws.registry. + env.workspace_registry = ws.registry.clone(); + + env +} + +#[test] +fn build_check_env_matches_inline_seeding() { + let entry = examples_dir().join("test_mono_imports_main.ail.json"); + let ws = load_workspace(&entry).expect("load test_mono_imports_main"); + + let env_helper = build_check_env(&ws); + let env_inline = build_env_inline_pre_refactor(&ws); + + // env.types — IndexMap: compare key-set + name. + assert_eq!( + env_helper.types.keys().collect::>(), + env_inline.types.keys().collect::>(), + "env.types key-set differs" + ); + for k in env_helper.types.keys() { + assert_eq!( + env_helper.types[k].name, + env_inline.types[k].name, + "env.types[{k}] type-def name differs" + ); + } + + // env.ctor_index — IndexMap: compare key-set + + // type_name. + assert_eq!( + env_helper.ctor_index.keys().collect::>(), + env_inline.ctor_index.keys().collect::>(), + "env.ctor_index key-set differs" + ); + for k in env_helper.ctor_index.keys() { + assert_eq!( + env_helper.ctor_index[k].type_name, + env_inline.ctor_index[k].type_name, + "env.ctor_index[{k}].type_name differs" + ); + } + + // env.module_globals — BTreeMap>. + assert_eq!( + env_helper.module_globals.keys().collect::>(), + env_inline.module_globals.keys().collect::>(), + "env.module_globals key-set differs" + ); + for k in env_helper.module_globals.keys() { + assert_eq!( + env_helper.module_globals[k].keys().collect::>(), + env_inline.module_globals[k].keys().collect::>(), + "env.module_globals[{k}] inner key-set differs" + ); + } + + // env.module_types — same shape as module_globals. + assert_eq!( + env_helper.module_types.keys().collect::>(), + env_inline.module_types.keys().collect::>(), + "env.module_types key-set differs" + ); + for k in env_helper.module_types.keys() { + assert_eq!( + env_helper.module_types[k].keys().collect::>(), + env_inline.module_types[k].keys().collect::>(), + "env.module_types[{k}] inner key-set differs" + ); + } + + // env.module_imports — BTreeMap>: + // direct equality. + assert_eq!( + env_helper.module_imports, env_inline.module_imports, + "env.module_imports differs" + ); + + // env.class_methods — BTreeMap: + // compare key-set (entry shape has no PartialEq). + assert_eq!( + env_helper.class_methods.keys().collect::>(), + env_inline.class_methods.keys().collect::>(), + "env.class_methods key-set differs" + ); + + // env.class_superclasses — direct equality. + assert_eq!( + env_helper.class_superclasses, env_inline.class_superclasses, + "env.class_superclasses differs" + ); + + // env.effect_ops — IndexMap: compare key-set. + assert_eq!( + env_helper.effect_ops.keys().collect::>(), + env_inline.effect_ops.keys().collect::>(), + "env.effect_ops key-set differs" + ); + + // env.workspace_registry — entries key-set must match. + assert_eq!( + env_helper.workspace_registry.entries.keys().collect::>(), + env_inline.workspace_registry.entries.keys().collect::>(), + "env.workspace_registry.entries key-set differs" + ); + + // Per-call overlay fields must NOT be seeded by the helper. + assert_eq!(env_helper.current_module, "", "current_module is overlay"); + assert!(env_helper.globals.is_empty(), "globals is overlay"); + assert!(env_helper.imports.is_empty(), "imports is overlay"); + assert!(env_helper.rigid_vars.is_empty(), "rigid_vars is overlay"); +}