//! Bug regression: the workspace-monomorphisation pass must seed //! `env.globals` for the current module before re-running `synth` on //! a fn body, otherwise self-recursive top-level fns are rejected with //! `unknown identifier` whenever the workspace also contains a //! `Def::Class` (which makes the mono pass actually traverse bodies //! instead of identity-cloning the workspace). //! //! Surfaced by the 2026-05-10 mono-vs-vdisp micro-benchmark: the //! original bench fixture had a self-recursive `loop` fn alongside a //! `Foo` class, and `ail build` failed at the mono pass with //! `monomorphise_workspace: unknown identifier: \`loop\``. The bench //! worked around it by also wrapping the loop in a class method, //! masking but not fixing the defect. use std::path::Path; use std::process::Command; fn ail_bin() -> &'static str { env!("CARGO_BIN_EXE_ail") } /// Property: a workspace that mixes a `Def::Class` / `Def::Instance` /// with a self-recursive top-level `Def::Fn` (one whose body refers /// to its own name) must build and run end-to-end. The mono pass /// re-runs `synth` on every fn body to recover residual class /// constraints; that env must agree with the env the main typecheck /// path used, including `env.globals` for the current module. /// /// Pre-fix: `ail build` fails at the mono pass with /// `monomorphise_workspace: unknown identifier: \`loop\`` because /// `mono::build_workspace_env` populates `env.module_globals` but /// never seeds `env.globals` from the current module's fns — /// `synth`'s `Term::Var` lookup at `lib.rs:1678` falls through to /// the qualified-cross-module branch and finally to `UnknownIdent`. /// The analogous gap for ADTs / ctors was fixed in commit 5c5180f /// (iter 22c); this is the sibling gap for plain top-level fns. #[test] fn mono_pass_handles_recursive_fn_in_class_workspace() { let manifest_dir = env!("CARGO_MANIFEST_DIR"); let workspace = Path::new(manifest_dir).parent().unwrap().parent().unwrap(); let src = workspace .join("examples") .join("test_mono_recursive_fn_bug.ail.json"); let tmp = std::env::temp_dir().join(format!( "ailang_mono_recursive_fn_{}", std::process::id() )); std::fs::create_dir_all(&tmp).unwrap(); let out = tmp.join("bin"); let build = Command::new(ail_bin()) .args(["build", src.to_str().unwrap(), "-o"]) .arg(&out) .output() .expect("ail build failed to run"); assert!( build.status.success(), "ail build failed: stderr={}", String::from_utf8_lossy(&build.stderr) ); let run = Command::new(&out).output().expect("execute binary"); assert!( run.status.success(), "binary {} exited non-zero", out.display() ); // loop(0, 5) = 0 + 5 + 4 + 3 + 2 + 1 = 15 assert_eq!( String::from_utf8(run.stdout).expect("stdout utf8").trim(), "15" ); }