//! mq.2.5: pin tests on `resolve_method_dispatch` — the new //! dispatch-resolution helper that synth's `Term::Var` arm consults //! per the spec's 5-step rule. //! //! Six cases: //! 1. Unique candidate. //! 2. Multi + explicit qualifier matching one. //! 3. Multi + explicit qualifier matching none → UnknownClass. //! 4. Multi + type-driven filter narrows to one. //! 5. Multi + constraint-driven filter narrows to one (rigid var case). //! 6. Multi + true ambiguity → AmbiguousMethodResolution. use ailang_check::{resolve_method_dispatch, MethodDispatchOutcome}; use ailang_core::ast::{Constraint, Type}; use ailang_core::canonical; use std::collections::{BTreeMap, BTreeSet}; fn registry_with(entries: &[(&str, &str)]) -> BTreeMap<(String, String), ()> { // Returns a synthetic registry: keys are (qualified_class, type_hash) // values are unit (instance presence flag). let mut reg = BTreeMap::new(); for (cls, ty_name) in entries { let t = Type::Con { name: ty_name.to_string(), args: vec![] }; let h = canonical::type_hash(&t); reg.insert((cls.to_string(), h), ()); } reg } fn candidates_of(classes: &[&str]) -> BTreeSet { classes.iter().map(|s| s.to_string()).collect() } /// Case 1: unique candidate → returns that class. #[test] fn case1_unique_candidate_resolves() { let candidates = candidates_of(&["prelude.Eq"]); let registry = registry_with(&[("prelude.Eq", "Int")]); let arg_ty = Type::Con { name: "Int".to_string(), args: vec![] }; let result = resolve_method_dispatch( /*method*/ "eq", /*qualifier_prefix*/ None, /*candidates*/ &candidates, /*concrete_arg_type*/ Some(&arg_ty), /*declared_constraints*/ &[], /*registry*/ ®istry, ); assert_eq!(result, MethodDispatchOutcome::Resolved("prelude.Eq".to_string())); } /// Case 2: multi + explicit qualifier matching one → returns that class. #[test] fn case2_qualifier_matches_one() { let candidates = candidates_of(&["prelude.Show", "userlib.Show"]); let registry = registry_with(&[ ("prelude.Show", "Int"), ("userlib.Show", "Int"), ]); let arg_ty = Type::Con { name: "Int".to_string(), args: vec![] }; let result = resolve_method_dispatch( "show", Some("userlib.Show"), &candidates, Some(&arg_ty), &[], ®istry, ); assert_eq!(result, MethodDispatchOutcome::Resolved("userlib.Show".to_string())); } /// Case 3: multi + explicit qualifier matching none → UnknownClass. #[test] fn case3_qualifier_matches_none_unknown_class() { let candidates = candidates_of(&["prelude.Show", "userlib.Show"]); let registry = registry_with(&[("prelude.Show", "Int")]); let arg_ty = Type::Con { name: "Int".to_string(), args: vec![] }; let result = resolve_method_dispatch( "show", Some("nonexistent.Show"), &candidates, Some(&arg_ty), &[], ®istry, ); assert_eq!( result, MethodDispatchOutcome::UnknownClass("nonexistent.Show".to_string()), ); } /// Case 4: multi + type-driven filter narrows to one → returns that class. #[test] fn case4_type_driven_filter_narrows_to_one() { let candidates = candidates_of(&["prelude.Show", "userlib.Show"]); let registry = registry_with(&[("prelude.Show", "Int")]); // only prelude.Show has Show Int let arg_ty = Type::Con { name: "Int".to_string(), args: vec![] }; let result = resolve_method_dispatch( "show", None, &candidates, Some(&arg_ty), &[], ®istry, ); assert_eq!(result, MethodDispatchOutcome::Resolved("prelude.Show".to_string())); } /// Case 5: multi + constraint-driven filter narrows to one (rigid var) → returns that class. #[test] fn case5_constraint_driven_filter_narrows_to_one() { let candidates = candidates_of(&["prelude.Show", "userlib.Show"]); let registry = registry_with(&[]); let rigid_a = Type::Var { name: "a".to_string() }; let declared = vec![Constraint { class: "prelude.Show".to_string(), type_: Type::Var { name: "a".to_string() }, }]; let result = resolve_method_dispatch( "show", None, &candidates, Some(&rigid_a), // rigid var, can't drive registry lookup &declared, ®istry, ); assert_eq!(result, MethodDispatchOutcome::Resolved("prelude.Show".to_string())); } /// Case 6: multi + true ambiguity → AmbiguousMethodResolution. #[test] fn case6_true_ambiguity() { let candidates = candidates_of(&["prelude.Show", "userlib.Show"]); let registry = registry_with(&[ ("prelude.Show", "Int"), ("userlib.Show", "Int"), ]); let arg_ty = Type::Con { name: "Int".to_string(), args: vec![] }; let result = resolve_method_dispatch( "show", None, &candidates, Some(&arg_ty), &[], ®istry, ); assert_eq!( result, MethodDispatchOutcome::Ambiguous { method: "show".to_string(), at_type: "Int".to_string(), candidates: vec!["prelude.Show".to_string(), "userlib.Show".to_string()], }, ); }