diff --git a/crates/ail/tests/typeclass_22b2.rs b/crates/ail/tests/typeclass_22b2.rs index 9004967..0d696e6 100644 --- a/crates/ail/tests/typeclass_22b2.rs +++ b/crates/ail/tests/typeclass_22b2.rs @@ -4,6 +4,7 @@ //! (class methods register into module globals) and is extended by //! Tasks 9 (`missing-constraint`) and 10 (`no-instance`). +use ailang_core::ast::Type; use std::path::{Path, PathBuf}; fn examples_dir() -> PathBuf { @@ -42,3 +43,87 @@ fn class_method_is_in_module_globals() { "class method `show` must remember its class is `Show`" ); } + +/// Property protected: the registered `ClassMethodEntry` carries the +/// full metadata Tasks 9 / 10 need — the defining module (so the +/// typecheck arms can find the matching `Registry` entries) and the +/// declared method signature (so a residual class constraint can be +/// built by instantiating the class param at the call site). The +/// fixture declares `class Show a { show: a -> Str }`, so the +/// recovered `method_ty` must be exactly `Type::Var "a" -> Type::Con +/// "Str"` with empty effects. +#[test] +fn class_method_entry_carries_full_metadata() { + let entry = examples_dir().join("test_22b2_class_method_lookup.ail.json"); + let ws = ailang_core::workspace::load_workspace(&entry) + .expect("workspace must load"); + let globals = ailang_check::build_module_globals(&ws) + .expect("globals build"); + let mod_globals = globals + .get("test_22b2_class_method_lookup") + .expect("module globals present"); + let cm_entry = mod_globals + .class_method("show") + .expect("class method `show` must be registered"); + + assert_eq!( + cm_entry.defining_module, "test_22b2_class_method_lookup", + "defining_module must point at the module that declared the class" + ); + assert_eq!( + cm_entry.class_param, "a", + "class_param must be the class's single type parameter name" + ); + + match &cm_entry.method_ty { + Type::Fn { + params, + ret, + effects, + .. + } => { + assert_eq!( + params.len(), + 1, + "method `show` must have exactly one parameter" + ); + match ¶ms[0] { + Type::Var { name } => assert_eq!( + name, "a", + "param type must be the class param `a`, not `{}`", + name + ), + other => panic!( + "param type must be `Type::Var \"a\"`, got {:?}", + other + ), + } + match ret.as_ref() { + Type::Con { name, args } => { + assert_eq!( + name, "Str", + "return type must be `Str`, got `{}`", + name + ); + assert!( + args.is_empty(), + "return type `Str` must take no args" + ); + } + other => panic!( + "return type must be `Type::Con \"Str\"`, got {:?}", + other + ), + } + assert!( + effects.is_empty(), + "method `show` must have empty effects, got {:?}", + effects + ); + } + other => panic!( + "method_ty must be `Type::Fn`, got {:?}", + other + ), + } +} diff --git a/crates/ailang-check/src/lib.rs b/crates/ailang-check/src/lib.rs index 6f7476f..20389ac 100644 --- a/crates/ailang-check/src/lib.rs +++ b/crates/ailang-check/src/lib.rs @@ -857,7 +857,11 @@ pub struct ModuleGlobals { /// Class-method names, by method name. Populated in /// [`build_module_globals`] from `Def::Class` entries; consulted by /// the 22b.2 `missing-constraint` / `no-instance` arms (Tasks 9 / 10). - pub class_methods: BTreeMap, + /// `IndexMap` matches the sibling [`Self::fns`] channel and preserves + /// definition order — Tasks 9 / 10 may surface diagnostics in source + /// order, and downstream consumers should not depend on a sorted + /// iteration order they did not ask for. + pub class_methods: IndexMap, } impl ModuleGlobals { @@ -873,6 +877,15 @@ impl ModuleGlobals { .get(name) .map(|e| e.class_name.as_str()) } + + /// The full [`ClassMethodEntry`] for method `name`, or `None` if + /// `name` is not a class method in this module. Tasks 9 / 10 use + /// this to read `method_ty` and `defining_module` together; the + /// narrower `has_class_method` / `class_method_class` accessors + /// stay for callers that only need a yes/no or the class name. + pub fn class_method(&self, name: &str) -> Option<&ClassMethodEntry> { + self.class_methods.get(name) + } } /// Iter 22b.2: per-class-method metadata stored in @@ -935,24 +948,31 @@ pub fn build_module_globals( // missing-method / method-name-collision) is enforced // earlier at `workspace::build_registry`. match def { - Def::Fn(_) | Def::Const(_) | Def::Type(_) => { + Def::Fn(f) => { if globals.fns.contains_key(def_name) { return Err(CheckError::Def( def_name.to_string(), Box::new(CheckError::DuplicateDef(def_name.to_string())), )); } - } - Def::Class(_) | Def::Instance(_) => {} - } - match def { - Def::Fn(f) => { globals.fns.insert(def_name.to_string(), f.ty.clone()); } Def::Const(c) => { + if globals.fns.contains_key(def_name) { + return Err(CheckError::Def( + def_name.to_string(), + Box::new(CheckError::DuplicateDef(def_name.to_string())), + )); + } globals.fns.insert(def_name.to_string(), c.ty.clone()); } Def::Type(_) => { + if globals.fns.contains_key(def_name) { + return Err(CheckError::Def( + def_name.to_string(), + Box::new(CheckError::DuplicateDef(def_name.to_string())), + )); + } globals.fns.insert( def_name.to_string(), Type::Con {