//! End-to-end coverage of the post-`MethodNameCollision`- //! retirement multi-candidate dispatch path. Three positive fixtures //! exercise the three trajectories from the milestone spec //! §"Data flow": //! //! - Trajectory C — ambiguous bare-method call with both candidate //! classes shipping `Show Int`. Typecheck rejects with //! `ambiguous-method-resolution`. //! - Trajectory E — explicit qualifier `..` //! resolves cleanly to the named class. //! - Class-fn shadow — bare-method call where both a class method and //! a free fn of the same name exist. Fn wins per lookup precedence; //! `class-method-shadowed-by-fn` warning fires so the LLM-author //! sees the shadow. //! //! Each test invokes `ail check --json ` and asserts on the //! diagnostic stream's `code` field. Stdout / runtime behaviour is //! out of scope here — the dispatch path is a check-time concern. use ailang_test_support::examples_dir; use std::path::PathBuf; use std::process::Command; fn ail_bin() -> PathBuf { PathBuf::from(env!("CARGO_BIN_EXE_ail")) } fn run_ail_check_json(entry: &str) -> std::process::Output { Command::new(ail_bin()) .args(["check", "--json", examples_dir().join(entry).to_str().unwrap()]) .output() .expect("ail binary must launch") } /// Multi-class trajectory C: bare `show 42` in a workspace with two /// `Show` classes (modA and modB) each shipping `Show Int` is /// genuinely ambiguous. `ail check` rejects with /// `ambiguous-method-resolution` and names both candidate classes. #[test] fn mq3_two_show_ambiguous_fires_ambiguous_method_resolution() { let out = run_ail_check_json("mq3_two_show_ambiguous.ail"); assert!( !out.status.success(), "expected check to reject ambiguous dispatch; stdout={} stderr={}", String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr), ); let stdout = String::from_utf8(out.stdout).expect("stdout is utf-8"); let diags: serde_json::Value = serde_json::from_str(&stdout).expect("--json mode emits a JSON array on stdout"); let arr = diags.as_array().expect("diagnostics is a JSON array"); assert!( arr.iter().any(|d| d["code"] == "ambiguous-method-resolution"), "expected `ambiguous-method-resolution` diagnostic; got {stdout}", ); // Both candidate classes named in the diagnostic so the author // knows which qualifiers to choose from. assert!( stdout.contains("mq3_two_show_ambiguous_a.Show"), "expected candidate A in diagnostic; got {stdout}", ); assert!( stdout.contains("mq3_two_show_ambiguous_b.Show"), "expected candidate B in diagnostic; got {stdout}", ); } /// Multi-class trajectory E: explicit qualifier /// `mq3_two_show_ambiguous_a.Show.show 42` disambiguates the same /// workspace as the ambiguous fixture. Typecheck succeeds. #[test] fn mq3_two_show_qualified_resolves_clean() { let out = run_ail_check_json("mq3_two_show_qualified.ail"); assert!( out.status.success(), "expected check to succeed with explicit qualifier; stdout={} stderr={}", String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr), ); } /// Class-fn shadow: bare `myeq 1 2` in a workspace with /// `class MyEq { myeq }` in one module and `fn myeq` in another /// resolves to the fn per lookup precedence. Typecheck succeeds AND /// the structured warning `class-method-shadowed-by-fn` fires so the /// LLM-author sees the shadow. #[test] fn mq3_class_eq_vs_fn_eq_fn_wins_with_warning() { let out = run_ail_check_json("mq3_class_eq_vs_fn_eq.ail"); assert!( out.status.success(), "expected check to succeed (fn wins); stdout={} stderr={}", String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr), ); let stdout = String::from_utf8(out.stdout).expect("stdout is utf-8"); let diags: serde_json::Value = serde_json::from_str(&stdout).expect("--json mode emits a JSON array on stdout"); let arr = diags.as_array().expect("diagnostics is a JSON array"); assert!( arr.iter().any(|d| d["code"] == "class-method-shadowed-by-fn"), "expected `class-method-shadowed-by-fn` warning; got {stdout}", ); }