//! Pin for the iter-24.3 FreeFnCall constraint-residual-push //! invariant (design/contracts/0013-typeclasses.md, "Cross-module //! references in synthesised bodies" invariant 3). //! //! Property protected: bare-name references to polymorphic free fns //! that resolve through the auto-injected-prelude path AT THE //! DOT-QUALIFIED SYNTH BRANCH push residuals for the fn's declared //! constraints. The discharge loop then fires `NoInstance` at //! typecheck if no instance ships for the unified concrete type. //! //! Failure mode this pin catches: a future refactor changes the //! prelude auto-injection resolution path so that bare-name `print` //! reaches synth via a different branch (e.g. locals, env.module_globals //! direct hit) that does NOT push residuals. Without this pin, the //! regression surfaces as `unknown variable: show` from codegen for //! the negative case — confusing diagnostic, hard to bisect. use ailang_check::check_workspace; use ailang_surface::load_workspace; use std::path::PathBuf; fn fixture_path() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("../../examples") .join("show_no_instance.ail") } #[test] fn bare_name_polyfn_fires_typecheck_no_instance_not_codegen_unknown_var() { // The fixture calls `print f` bare-name (no `prelude.` qualifier) // where `f : Int -> Int`. The auto-injected-prelude resolution // must route this through the dot-qualified synth branch so that // the `Show a` declared constraint of `print` produces a residual, // and the discharge loop fires `no-instance`. let ws = load_workspace(&fixture_path()).expect("workspace loads"); let diags = check_workspace(&ws); // Exactly one `no-instance` diagnostic — proves: // (a) the bare-name `print` resolved (was not "unknown variable") // (b) the resolution reached the dot-qualified synth branch // which pushes the declared-constraint residual // (c) the discharge loop ran with the residual and fired the // NoInstance because no `Show (Int -> Int)` instance exists. let no_inst: Vec<_> = diags.iter().filter(|d| d.code == "no-instance").collect(); assert_eq!( no_inst.len(), 1, "expected exactly one 'no-instance' diagnostic — got {} (all diags: {diags:?})", no_inst.len() ); // No "unknown variable" or other codegen-grade errors at typecheck: // if the residual push did NOT fire, the typecheck would pass // silently and the error would only surface at codegen. let unknown_vars: Vec<_> = diags .iter() .filter(|d| { d.code == "unknown-variable" || d.message.contains("unknown variable") || d.code == "internal" }) .collect(); assert!( unknown_vars.is_empty(), "expected zero 'unknown variable' or 'internal' diagnostics at typecheck — \ got {} (all diags: {diags:?}). If this fires, the bare-name `print` \ resolution bypassed the dot-qualified synth branch and the constraint \ residual was never pushed.", unknown_vars.len() ); }