//! 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, Env}; use ailang_core::ast::Def; use ailang_surface::load_workspace; use ailang_test_support::examples_dir; use std::collections::BTreeMap; /// Independent reproduction of every workspace-flat env seeding /// step. Pre-refactor, workspace-flat seeding was split between /// `check_in_workspace` (per-module `types`/`ctor_index`) and /// `mono::build_workspace_env` (workspace-flat `types`/`ctor_index` /// plus everything else); this reproduction captures the *union* /// — exactly the surface `build_check_env` is responsible for. /// 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 ADT index, inlined here as // a frozen reproduction (test does not share helper code with // production). let mut module_types: BTreeMap> = BTreeMap::new(); for (mname, m) in &ws.modules { let mut tys: indexmap::IndexMap = indexmap::IndexMap::new(); for d in &m.defs { if let Def::Type(td) = d { tys.insert(td.name.clone(), td.clone()); } } module_types.insert(mname.clone(), tys); } env.module_types = module_types; // env.module_imports — per-module alias map. Iter 23.1 added // the implicit `prelude` import. bugfix-prepass-resolver- // asymmetry (F3) generalised it: every workspace module flagged // `kernel: true` is implicitly imported into every non-self // module (mirrors the loader filter at // `crates/ailang-surface/src/loader.rs`). The literal // `"prelude"` injection stays for back-compat with unit-test // workspaces that construct a `kernel: false` prelude. let kernel_modules: Vec = ws .modules .values() .filter(|m| m.kernel) .map(|m| m.name.clone()) .collect(); 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()); } if m.name != "prelude" { import_map .entry("prelude".to_string()) .or_insert_with(|| "prelude".to_string()); } for k in &kernel_modules { if &m.name != k { import_map.entry(k.clone()).or_insert_with(|| k.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. // // both key and value carry the qualified workspace-key shape; // bare `SuperclassRef.class` is lifted using the parent class's // defining module. for (mod_name, m) in &ws.modules { for d in &m.defs { if let Def::Class(cd) = d { if let Some(sc) = &cd.superclass { let class_qualified = format!("{mod_name}.{}", cd.name); let sc_qualified = if sc.class.contains('.') { sc.class.clone() } else { format!("{mod_name}.{}", sc.class) }; env.class_superclasses.insert(class_qualified, sc_qualified); } } } } // 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"); 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"); // env.globals — partially workspace-flat (builtins operators // installed by `builtins::install`) and partially overlay // (per-module fns seeded by callers). The helper carries only // the workspace-flat half; key-set must match the inline // reproduction at this point. assert_eq!( env_helper.globals.keys().collect::>(), env_inline.globals.keys().collect::>(), "env.globals key-set differs (workspace-flat half from builtins)" ); assert!(env_helper.imports.is_empty(), "imports is overlay"); assert!(env_helper.rigid_vars.is_empty(), "rigid_vars is overlay"); }