iter 22b.2.8: fix — fold double-match, IndexMap parity, broader contract test
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
//! (class methods register into module globals) and is extended by
|
//! (class methods register into module globals) and is extended by
|
||||||
//! Tasks 9 (`missing-constraint`) and 10 (`no-instance`).
|
//! Tasks 9 (`missing-constraint`) and 10 (`no-instance`).
|
||||||
|
|
||||||
|
use ailang_core::ast::Type;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
fn examples_dir() -> 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`"
|
"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
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -857,7 +857,11 @@ pub struct ModuleGlobals {
|
|||||||
/// Class-method names, by method name. Populated in
|
/// Class-method names, by method name. Populated in
|
||||||
/// [`build_module_globals`] from `Def::Class` entries; consulted by
|
/// [`build_module_globals`] from `Def::Class` entries; consulted by
|
||||||
/// the 22b.2 `missing-constraint` / `no-instance` arms (Tasks 9 / 10).
|
/// the 22b.2 `missing-constraint` / `no-instance` arms (Tasks 9 / 10).
|
||||||
pub class_methods: BTreeMap<String, ClassMethodEntry>,
|
/// `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<String, ClassMethodEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModuleGlobals {
|
impl ModuleGlobals {
|
||||||
@@ -873,6 +877,15 @@ impl ModuleGlobals {
|
|||||||
.get(name)
|
.get(name)
|
||||||
.map(|e| e.class_name.as_str())
|
.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
|
/// 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
|
// missing-method / method-name-collision) is enforced
|
||||||
// earlier at `workspace::build_registry`.
|
// earlier at `workspace::build_registry`.
|
||||||
match def {
|
match def {
|
||||||
Def::Fn(_) | Def::Const(_) | Def::Type(_) => {
|
Def::Fn(f) => {
|
||||||
if globals.fns.contains_key(def_name) {
|
if globals.fns.contains_key(def_name) {
|
||||||
return Err(CheckError::Def(
|
return Err(CheckError::Def(
|
||||||
def_name.to_string(),
|
def_name.to_string(),
|
||||||
Box::new(CheckError::DuplicateDef(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());
|
globals.fns.insert(def_name.to_string(), f.ty.clone());
|
||||||
}
|
}
|
||||||
Def::Const(c) => {
|
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());
|
globals.fns.insert(def_name.to_string(), c.ty.clone());
|
||||||
}
|
}
|
||||||
Def::Type(_) => {
|
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(
|
globals.fns.insert(
|
||||||
def_name.to_string(),
|
def_name.to_string(),
|
||||||
Type::Con {
|
Type::Con {
|
||||||
|
|||||||
Reference in New Issue
Block a user