From 7289bbc9d4b3677c0bb9a0bffa2465ee43b1b936 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 10 May 2026 22:34:30 +0200 Subject: [PATCH] iter 23.2.3-prep: reconcile workspace tests with auto-loaded prelude class Eq The auto-loaded prelude (iter 23.2 work-in-progress) injects `class Eq` plus three primitive instances (Eq Int, Eq Bool, Eq Str) into every workspace. Five workspace::tests assertions broke against this new baseline: - iter22b1_missing_method_fires_diagnostic - instance_without_superclass_instance_fires - instance_overriding_nonexistent_method_fires Their fixtures redeclare `class Eq` (and `class Ord` in one case), which now collides with the prelude. Renamed each fixture's `Eq`/`eq` -> `TEq`/`teq` and `Ord`/`lt` -> `TOrd`/`tlt`, matching the scheme already used in the two earlier-renamed test_22b2 fixtures. Method `ne` stays (does not collide). Assertion strings in workspace.rs updated to match. - iter22b1_workspace_with_no_classes_has_empty_registry - iter22b1_instance_in_class_module_loads_clean Their asserted registry counts assumed an otherwise-empty registry. Rewritten to filter by `defining_module != "prelude"`: the property is "no NON-prelude entries" / "exactly one fixture-owned entry", not raw count. Doc comments updated alongside each assertion change so the rationale for TEq/TOrd (collision avoidance) is local. No production code touched. --- crates/ailang-core/src/workspace.rs | 57 +++++++++++++------ examples/test_22b1_missing_method.ail.json | 6 +- ..._22b2_missing_superclass_instance.ail.json | 14 ++--- .../test_22b2_overriding_nonexistent.ail.json | 8 +-- 4 files changed, 53 insertions(+), 32 deletions(-) diff --git a/crates/ailang-core/src/workspace.rs b/crates/ailang-core/src/workspace.rs index 841e6e7..36f0eef 100644 --- a/crates/ailang-core/src/workspace.rs +++ b/crates/ailang-core/src/workspace.rs @@ -976,10 +976,13 @@ mod tests { } } - /// Iter 22b.1: a workspace whose modules contain no - /// `Def::Instance` defs has an empty registry. This is the - /// happy-path baseline for the registry-build pass — every - /// pre-22b workspace falls into this case. + /// Iter 22b.1: a workspace whose own modules contain no + /// `Def::Instance` defs produces no non-prelude registry entries. + /// This is the happy-path baseline for the registry-build pass — + /// every pre-22b workspace falls into this case. With the + /// auto-loaded prelude (iter 23.2) the registry is no longer + /// strictly empty, so the invariant is now "no entries whose + /// `defining_module` is anything other than `prelude`". #[test] fn iter22b1_workspace_with_no_classes_has_empty_registry() { let manifest_dir = env!("CARGO_MANIFEST_DIR"); @@ -989,8 +992,14 @@ mod tests { let ws = load_workspace(&entry).expect("sum.ail.json loads"); assert!( - ws.registry.entries.is_empty(), - "pre-22b fixture has no class/instance defs, registry must be empty" + ws.registry.entries.values().all(|e| e.defining_module == "prelude"), + "pre-22b fixture has no class/instance defs of its own; \ + all registry entries must come from the auto-injected prelude. \ + got non-prelude entries: {:?}", + ws.registry.entries.values() + .filter(|e| e.defining_module != "prelude") + .map(|e| &e.defining_module) + .collect::>() ); } @@ -1005,13 +1014,19 @@ mod tests { } /// Iter 22b.1: a coherent instance (in the class's module) - /// loads cleanly and produces one registry entry. + /// loads cleanly and produces one registry entry from the + /// fixture itself. With the auto-loaded prelude (iter 23.2) + /// the registry also contains the prelude's own entries; the + /// invariant is filtered to fixture-only entries. #[test] fn iter22b1_instance_in_class_module_loads_clean() { let entry = examples_dir().join("test_22b1_orphan_class.ail.json"); let ws = load_workspace(&entry).expect("coherent instance loads"); - assert_eq!(ws.registry.entries.len(), 1); - let (key, entry) = ws.registry.entries.iter().next().unwrap(); + let fixture_entries: Vec<_> = ws.registry.entries.iter() + .filter(|(_, e)| e.defining_module != "prelude") + .collect(); + assert_eq!(fixture_entries.len(), 1); + let (key, entry) = fixture_entries[0]; assert_eq!(&key.0, "Show"); assert_eq!(entry.defining_module, "test_22b1_orphan_class"); assert_eq!(entry.instance.class, "Show"); @@ -1079,8 +1094,10 @@ mod tests { /// Iter 22b.1: an instance that omits a required (non-default) /// method of its class fires `MissingMethod`. The fixture's - /// `class Eq` declares `eq` and `ne` as both non-default; the - /// instance only specifies `ne`, leaving `eq` missing. + /// `class TEq` declares `teq` and `ne` as both non-default; the + /// instance only specifies `ne`, leaving `teq` missing. + /// (Class is `TEq` rather than `Eq` to avoid colliding with the + /// auto-loaded prelude's `class Eq`.) #[test] fn iter22b1_missing_method_fires_diagnostic() { let entry = examples_dir().join("test_22b1_missing_method.ail.json"); @@ -1091,9 +1108,9 @@ mod tests { type_repr, method, } => { - assert_eq!(class, "Eq"); + assert_eq!(class, "TEq"); assert_eq!(type_repr, "Int"); - assert_eq!(method, "eq"); + assert_eq!(method, "teq"); } other => panic!("expected MissingMethod, got {other:?}"), } @@ -1154,6 +1171,8 @@ mod tests { /// when the instance provides a body the class did not ask for. /// Decision 11 forbids ad-hoc additions to a class's method set /// at the instance site. + /// (Class is `TEq` rather than `Eq` to avoid colliding with the + /// auto-loaded prelude's `class Eq`.) #[test] fn instance_overriding_nonexistent_method_fires() { let entry = std::path::PathBuf::from( @@ -1165,7 +1184,7 @@ mod tests { WorkspaceLoadError::OverridingNonExistentMethod { class, type_repr, method, } => { - assert_eq!(class, "Eq"); + assert_eq!(class, "TEq"); assert_eq!(type_repr, "Int"); assert_eq!(method, "ne"); } @@ -1269,11 +1288,13 @@ mod tests { /// Iter 22b.2: an instance `C T` whose class `C` declares a /// superclass `S` requires that `instance S T` also exist in the - /// workspace. The fixture declares `class Eq a`, `class Ord a - /// extends Eq a`, and `instance Ord Int` — but no `instance Eq + /// workspace. The fixture declares `class TEq a`, `class TOrd a + /// extends TEq a`, and `instance TOrd Int` — but no `instance TEq /// Int` — so registry build must fire /// `MissingSuperclassInstance`. Decision 11 single-superclass /// model requires `instance S T` whenever `instance C T` exists. + /// (Classes are `TEq` / `TOrd` rather than `Eq` / `Ord` to avoid + /// colliding with the auto-loaded prelude's `class Eq`.) #[test] fn instance_without_superclass_instance_fires() { let entry = examples_dir().join("test_22b2_missing_superclass_instance.ail.json"); @@ -1283,8 +1304,8 @@ mod tests { WorkspaceLoadError::MissingSuperclassInstance { class, superclass, type_repr, } => { - assert_eq!(class, "Ord"); - assert_eq!(superclass, "Eq"); + assert_eq!(class, "TOrd"); + assert_eq!(superclass, "TEq"); assert_eq!(type_repr, "Int"); } other => panic!("expected MissingSuperclassInstance, got {other:?}"), diff --git a/examples/test_22b1_missing_method.ail.json b/examples/test_22b1_missing_method.ail.json index ebb4ff7..83bbd97 100644 --- a/examples/test_22b1_missing_method.ail.json +++ b/examples/test_22b1_missing_method.ail.json @@ -5,11 +5,11 @@ "defs": [ { "kind": "class", - "name": "Eq", + "name": "TEq", "param": "a", "methods": [ { - "name": "eq", + "name": "teq", "type": { "k": "fn", "params": [ @@ -36,7 +36,7 @@ }, { "kind": "instance", - "class": "Eq", + "class": "TEq", "type": { "k": "con", "name": "Int" }, "methods": [ { diff --git a/examples/test_22b2_missing_superclass_instance.ail.json b/examples/test_22b2_missing_superclass_instance.ail.json index cfa87ff..beda5b5 100644 --- a/examples/test_22b2_missing_superclass_instance.ail.json +++ b/examples/test_22b2_missing_superclass_instance.ail.json @@ -4,9 +4,9 @@ "imports": [], "defs": [ { - "kind": "class", "name": "Eq", "param": "a", + "kind": "class", "name": "TEq", "param": "a", "methods": [{ - "name": "eq", + "name": "teq", "type": { "k": "fn", "params": [{ "k": "var", "name": "a" }, { "k": "var", "name": "a" }], @@ -15,10 +15,10 @@ }] }, { - "kind": "class", "name": "Ord", "param": "a", - "superclass": { "class": "Eq", "type": "a" }, + "kind": "class", "name": "TOrd", "param": "a", + "superclass": { "class": "TEq", "type": "a" }, "methods": [{ - "name": "lt", + "name": "tlt", "type": { "k": "fn", "params": [{ "k": "var", "name": "a" }, { "k": "var", "name": "a" }], @@ -28,10 +28,10 @@ }, { "kind": "instance", - "class": "Ord", + "class": "TOrd", "type": { "k": "con", "name": "Int" }, "methods": [{ - "name": "lt", + "name": "tlt", "body": { "t": "lit", "lit": { "kind": "bool", "value": true } } }] } diff --git a/examples/test_22b2_overriding_nonexistent.ail.json b/examples/test_22b2_overriding_nonexistent.ail.json index 2a6dd5f..e194055 100644 --- a/examples/test_22b2_overriding_nonexistent.ail.json +++ b/examples/test_22b2_overriding_nonexistent.ail.json @@ -5,11 +5,11 @@ "defs": [ { "kind": "class", - "name": "Eq", + "name": "TEq", "param": "a", "methods": [ { - "name": "eq", + "name": "teq", "type": { "k": "fn", "params": [{ "k": "var", "name": "a" }, { "k": "var", "name": "a" }], @@ -21,11 +21,11 @@ }, { "kind": "instance", - "class": "Eq", + "class": "TEq", "type": { "k": "con", "name": "Int" }, "methods": [ { - "name": "eq", + "name": "teq", "body": { "t": "lit", "lit": { "kind": "bool", "value": true } } }, {