iter mq.1: class-ref canonical-form extension + workspace-internal class-name qualification
Three schema fields move bare → canonical (bare for same-module,
<module>.<Class> for cross-module): InstanceDef.class,
Constraint.class, SuperclassRef.class. Symmetric to ct.1's
Type::Con.name rule. ClassDef.name stays bare.
validate_canonical_type_names gains three field-walks via new
check_class_ref helper + two sibling diagnostics
BareCrossModuleClassRef / BadCrossModuleClassRef.
check_class_name_fields narrowed to ClassDef.name-only.
Workspace-internal class-name keys all qualified:
class_def_module, class_by_name, registry entries.0,
ClassMethodEntry.class_name, class_superclasses, mono's
class_index, Origin::Class.class_name. New qualify_class_ref
helper at workspace.rs scope plus sibling
qualify_class_ref_in_check in ailang-check.
Two new positive on-disk fixtures (mq1_xmod_constraint_class{,_dep})
exercise the qualified Constraint.class shape.
Recon claim that all existing fixtures' class-refs are intra-module
was empirically wrong — 10 fixtures required minimal canonical-form
migration. Duplicate-instance test architecturally restructured:
post-mq.1 orphan-freedom makes two-modules-on-same-(class,type)
structurally impossible; new test_22b1_dup_same_module fixture
lands both instances in the class's module.
Plan defects fixed inline: Origin::Class.class_name single
construction site (plan recon off-by-one); build_class_index in
mono.rs needed qualifying; build_module_globals needed Def::Instance
carve-out; check_fn declared-constraint matching needed inline
canonical-form lifting; NoInstance Float-aware message arm needed
prelude.Eq/prelude.Ord match; coherence type-leg needed qualified-
head split-and-lookup. Tasks 3-6 ran as one coherent fix.
7/7 tasks, 520 tests green. bench/compile_check.py + cross_lang.py
exit 0; bench/check.py exit 1 (4 improvements-beyond-tolerance,
audit-ratifiable, not a regression).
MethodNameCollision + dispatch path unchanged; iters mq.2 + mq.3
land them.
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"iter_id": "mq.1",
|
||||
"date": "2026-05-13",
|
||||
"mode": "standard",
|
||||
"outcome": "DONE",
|
||||
"tasks_total": 7,
|
||||
"tasks_completed": 7,
|
||||
"reloops_per_task": {
|
||||
"1": 0,
|
||||
"2": 0,
|
||||
"3": 0,
|
||||
"4": 0,
|
||||
"5": 0,
|
||||
"6": 0,
|
||||
"7": 0
|
||||
},
|
||||
"review_loops_spec": 0,
|
||||
"review_loops_quality": 1,
|
||||
"blocked_reason": null,
|
||||
"notes": "Tasks 3-6 ran as one coherent fix (the plan structured them sequentially but the registry rekey + ClassMethodEntry qualification + class_superclasses qualification are tightly coupled — Task 3 alone left several tests RED). Boss-note recon claim ('zero existing-fixture diff') was empirically wrong: 5 test_22b* fixtures plus 6 others had bare cross-module class refs and required migration to the canonical form. Quality re-loop count of 1 is for the hash.rs:293 pin: initial implementer-phase shape used a length-only assertion (lost regression-protection); quality phase flagged it as Minor and pinned the concrete hex value."
|
||||
}
|
||||
@@ -1280,6 +1280,38 @@ fn workspace_error_to_diagnostic(
|
||||
"name": name,
|
||||
})),
|
||||
),
|
||||
// mq.1 (canonical-class-names): bare class-ref that does not
|
||||
// resolve to a local class of the owning module. Sibling of
|
||||
// `BareCrossModuleTypeRef` for class-reference fields.
|
||||
W::BareCrossModuleClassRef { module, name, candidates } => Some(
|
||||
ailang_check::Diagnostic::error(
|
||||
"bare-cross-module-class-ref",
|
||||
format!(
|
||||
"module `{module}` contains bare class name `{name}` that does not resolve to a local class; \
|
||||
candidates from imports: {candidates:?}"
|
||||
),
|
||||
)
|
||||
.with_ctx(serde_json::json!({
|
||||
"module": module,
|
||||
"name": name,
|
||||
"candidates": candidates,
|
||||
})),
|
||||
),
|
||||
// mq.1: qualified `<owner>.<class>` where `<owner>` is not a
|
||||
// known module or `<owner>` has no class `<class>`.
|
||||
W::BadCrossModuleClassRef { module, name } => Some(
|
||||
ailang_check::Diagnostic::error(
|
||||
"bad-cross-module-class-ref",
|
||||
format!(
|
||||
"module `{module}` references qualified class `{name}` but the owner module is not known \
|
||||
or does not declare a class by that name"
|
||||
),
|
||||
)
|
||||
.with_ctx(serde_json::json!({
|
||||
"module": module,
|
||||
"name": name,
|
||||
})),
|
||||
),
|
||||
// ct.1: a class-reference field contains a `.` — class names
|
||||
// are not module-qualified in this milestone.
|
||||
W::QualifiedClassName { module, name, field } => Some(
|
||||
|
||||
@@ -86,14 +86,18 @@ fn check_json_emits_bad_cross_module_type_ref() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Property: a qualified class name in an `InstanceDef.class` field
|
||||
/// (here `prelude.Eq`) is rejected by `ail check --json` with
|
||||
/// diagnostic code `qualified-class-name` and non-zero exit. ct.1
|
||||
/// reserves module qualification for type names; classes stay bare in
|
||||
/// this milestone. Guards against `workspace_error_to_diagnostic`
|
||||
/// losing the `QualifiedClassName` arm.
|
||||
/// Property: mq.1 — a qualified class name in an `InstanceDef.class`
|
||||
/// field is the canonical form, not a rejection. The
|
||||
/// `test_ct1_qualified_class_rejected` fixture (declares
|
||||
/// `instance prelude.Eq Int` outside prelude and outside Int's
|
||||
/// defining module) is now rejected by the downstream coherence
|
||||
/// check with `orphan-instance` instead of the pre-mq.1
|
||||
/// `qualified-class-name`. Guards against
|
||||
/// `workspace_error_to_diagnostic` losing the OrphanInstance arm
|
||||
/// AND against any regression that would reintroduce
|
||||
/// `qualified-class-name` on a referencing field.
|
||||
#[test]
|
||||
fn check_json_emits_qualified_class_name() {
|
||||
fn check_json_emits_orphan_instance_on_xmod_class_without_coherence_post_mq1() {
|
||||
let fixture = examples_dir().join("test_ct1_qualified_class_rejected.ail.json");
|
||||
let output = Command::new(ail_bin())
|
||||
.args(["check", "--json", fixture.to_str().unwrap()])
|
||||
@@ -101,7 +105,7 @@ fn check_json_emits_qualified_class_name() {
|
||||
.expect("ail binary must launch");
|
||||
assert!(
|
||||
!output.status.success(),
|
||||
"ail check must fail on qualified class name; stdout={} stderr={}",
|
||||
"ail check must fail; stdout={} stderr={}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
);
|
||||
@@ -110,8 +114,12 @@ fn check_json_emits_qualified_class_name() {
|
||||
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"] == "qualified-class-name"),
|
||||
"expected `qualified-class-name` in diagnostics array; got {stdout}"
|
||||
arr.iter().any(|d| d["code"] == "orphan-instance"),
|
||||
"expected `orphan-instance` in diagnostics array; got {stdout}"
|
||||
);
|
||||
assert!(
|
||||
!arr.iter().any(|d| d["code"] == "qualified-class-name"),
|
||||
"must NOT fire `qualified-class-name` on a referencing field post-mq.1; got {stdout}"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,10 +37,12 @@ fn class_method_is_in_module_globals() {
|
||||
mod_globals.has_class_method("show"),
|
||||
"class method `show` must appear in module globals"
|
||||
);
|
||||
// mq.1: class_method_class returns the qualified form
|
||||
// `<defining_module>.<Class>`.
|
||||
assert_eq!(
|
||||
mod_globals.class_method_class("show"),
|
||||
Some("Show"),
|
||||
"class method `show` must remember its class is `Show`"
|
||||
Some("test_22b2_class_method_lookup.Show"),
|
||||
"class method `show` must remember its class is `Show` (qualified post-mq.1)"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +148,8 @@ fn collect_mono_targets_single_concrete_call_site() {
|
||||
let t = &targets[0];
|
||||
match t {
|
||||
ailang_check::mono::MonoTarget::ClassMethod { class, method, type_, defining_module } => {
|
||||
assert_eq!(class, "Show");
|
||||
// mq.1: MonoTarget.class carries the qualified workspace-key shape.
|
||||
assert_eq!(class, "test_22b2_instance_present.Show");
|
||||
assert_eq!(method, "show");
|
||||
assert!(
|
||||
matches!(type_, ailang_core::ast::Type::Con { name, args } if name == "Int" && args.is_empty()),
|
||||
|
||||
@@ -716,7 +716,9 @@ impl CheckError {
|
||||
// (`ne` / `lt` / `le` / `gt` / `ge` / direct `eq` / `compare`)
|
||||
// are the natural surface where the LLM hits this.
|
||||
if let CheckError::NoInstance { class, at_type, .. } = self.inner() {
|
||||
if (class == "Eq" || class == "Ord") && at_type == "Float" {
|
||||
// mq.1: `class` carries the qualified workspace-key shape
|
||||
// (`prelude.Eq` / `prelude.Ord`), not the bare name.
|
||||
if (class == "prelude.Eq" || class == "prelude.Ord") && at_type == "Float" {
|
||||
d.message = format!(
|
||||
"{} — Float has no Eq/Ord instance by design (partial \
|
||||
orderability per IEEE-754); see DESIGN.md §\"Float semantics\".",
|
||||
@@ -1107,6 +1109,11 @@ impl ModuleGlobals {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ClassMethodEntry {
|
||||
/// The class that declared the method (e.g. `Show` for `show`).
|
||||
///
|
||||
/// mq.1: carries the qualified form `<defining_module>.<Class>`;
|
||||
/// flows downstream into `ResidualConstraint.class` and
|
||||
/// `MonoTarget::ClassMethod.class` unchanged. Match the registry
|
||||
/// key shape so discharge / mono lookups land on the right entry.
|
||||
pub class_name: String,
|
||||
/// The class's single type parameter name (e.g. `a` in
|
||||
/// `class Show a`). The class param appears at this name inside
|
||||
@@ -1141,7 +1148,13 @@ pub fn build_module_globals(
|
||||
let mut globals = ModuleGlobals::default();
|
||||
for def in &m.defs {
|
||||
let def_name = def.name();
|
||||
if def_name.contains('.') {
|
||||
// mq.1: for `Def::Instance`, `def.name()` returns
|
||||
// `inst.class` which carries the canonical-form value
|
||||
// (qualified for cross-module). The dot-in-def-name check
|
||||
// is meant for `Fn`/`Const`/`Type` whose names are author-
|
||||
// chosen identifiers — instances have no author-chosen
|
||||
// name and are excluded.
|
||||
if !matches!(def, Def::Instance(_)) && def_name.contains('.') {
|
||||
return Err(CheckError::Def(
|
||||
def_name.to_string(),
|
||||
Box::new(CheckError::InvalidDefName {
|
||||
@@ -1198,11 +1211,17 @@ pub fn build_module_globals(
|
||||
// with enough metadata for the typecheck arms in
|
||||
// Tasks 9 / 10 to build a residual constraint at
|
||||
// the call site.
|
||||
//
|
||||
// mq.1: store the qualified class name
|
||||
// (`<defining_module>.<Class>`) so the residual
|
||||
// emitted at the call site matches the workspace
|
||||
// registry's qualified key.
|
||||
let qualified_class = format!("{mname}.{}", cd.name);
|
||||
for meth in &cd.methods {
|
||||
globals.class_methods.insert(
|
||||
meth.name.clone(),
|
||||
ClassMethodEntry {
|
||||
class_name: cd.name.clone(),
|
||||
class_name: qualified_class.clone(),
|
||||
class_param: cd.param.clone(),
|
||||
method_ty: meth.ty.clone(),
|
||||
defining_module: mname.clone(),
|
||||
@@ -1301,11 +1320,25 @@ pub fn build_check_env(ws: &Workspace) -> Env {
|
||||
}
|
||||
|
||||
// env.class_superclasses — walk every module's Def::Class.
|
||||
for m in ws.modules.values() {
|
||||
//
|
||||
// mq.1: key + value both carry the qualified class name. The map
|
||||
// is queried by `expand_declared_constraints` with `c.class`
|
||||
// (a `Constraint.class` value, which is canonical-form on the
|
||||
// residual side post-mq.1), so both halves of the map must match
|
||||
// the same workspace-key shape. A bare `SuperclassRef.class`
|
||||
// (same-module superclass) is lifted via `qualify_class_ref` —
|
||||
// mirrored inline below to avoid pulling in the workspace helper.
|
||||
for (mod_name, m) in &ws.modules {
|
||||
for d in &m.defs {
|
||||
if let Def::Class(cd) = d {
|
||||
if let Some(sc) = &cd.superclass {
|
||||
env.class_superclasses.insert(cd.name.clone(), sc.class.clone());
|
||||
let class_qualified = format!("{mod_name}.{}", cd.name);
|
||||
let sc_qualified = if sc.class.contains('.') {
|
||||
sc.class.clone()
|
||||
} else {
|
||||
format!("{mod_name}.{}", sc.class)
|
||||
};
|
||||
env.class_superclasses.insert(class_qualified, sc_qualified);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1604,8 +1637,21 @@ fn check_fn(f: &FnDef, env: &Env) -> Result<()> {
|
||||
// Decision 11) and compare against residuals. Var-shaped residuals
|
||||
// not covered by the expanded set fire `MissingConstraint`. Concrete
|
||||
// residuals are deferred to Task 10's `no-instance` arm.
|
||||
//
|
||||
// mq.1: declared constraints carry the canonical-form `class`
|
||||
// value (bare for same-module, qualified for cross-module). Lift
|
||||
// to the workspace-key shape (always qualified) before comparing
|
||||
// against residuals — residuals always carry qualified `class`
|
||||
// because they come from `ClassMethodEntry.class_name` which is
|
||||
// qualified post-mq.1.
|
||||
let declared_constraints: Vec<Constraint> = match &f.ty {
|
||||
Type::Forall { constraints, .. } => constraints.clone(),
|
||||
Type::Forall { constraints, .. } => constraints
|
||||
.iter()
|
||||
.map(|c| Constraint {
|
||||
class: qualify_class_ref_in_check(&c.class, &env.current_module),
|
||||
type_: c.type_.clone(),
|
||||
})
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
let expanded = expand_declared_constraints(&declared_constraints, &env.class_superclasses);
|
||||
@@ -1733,6 +1779,20 @@ pub struct FreeFnCall {
|
||||
pub metas: Vec<Type>,
|
||||
}
|
||||
|
||||
/// mq.1: lift a canonical-form class-ref value to the qualified
|
||||
/// workspace-key shape. Bare ⇒ prepend `caller_module` (the bare
|
||||
/// form names the caller-module-local class under the canonical-form
|
||||
/// rule); qualified ⇒ as-is. Sibling of `workspace::qualify_class_ref`
|
||||
/// for the check-side; duplicated to keep `ailang-check` from
|
||||
/// reaching across crates for one private helper.
|
||||
pub(crate) fn qualify_class_ref_in_check(class_ref: &str, caller_module: &str) -> String {
|
||||
if class_ref.contains('.') {
|
||||
class_ref.to_string()
|
||||
} else {
|
||||
format!("{caller_module}.{class_ref}")
|
||||
}
|
||||
}
|
||||
|
||||
/// Iter 22b.2 (Task 9): expand a list of declared class constraints
|
||||
/// with their one-step superclass closure (Decision 11). For every
|
||||
/// declared `(C, t)`, append `(S, t)` if class `C` has a superclass
|
||||
|
||||
@@ -341,12 +341,18 @@ fn poly_free_fn_names_for_module(
|
||||
/// Iter 22b.3: workspace-wide `class-name -> ClassDef` index.
|
||||
/// Used by the fixpoint to look up the matching class definition
|
||||
/// when synthesising a fn.
|
||||
///
|
||||
/// mq.1: keys carry the qualified class name
|
||||
/// (`<defining_module>.<Class>`) to match the post-mq.1 residual /
|
||||
/// `MonoTarget::ClassMethod.class` field and the registry's
|
||||
/// qualified entries key.
|
||||
fn build_class_index(ws: &Workspace) -> BTreeMap<String, ClassDef> {
|
||||
let mut idx = BTreeMap::new();
|
||||
for m in ws.modules.values() {
|
||||
for (mod_name, m) in &ws.modules {
|
||||
for d in &m.defs {
|
||||
if let Def::Class(c) = d {
|
||||
idx.insert(c.name.clone(), c.clone());
|
||||
let qualified = format!("{mod_name}.{}", c.name);
|
||||
idx.insert(qualified, c.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,11 +97,21 @@ fn build_env_inline_pre_refactor(ws: &ailang_core::workspace::Workspace) -> Env
|
||||
}
|
||||
|
||||
// env.class_superclasses — walk every module's Def::Class.
|
||||
for m in ws.modules.values() {
|
||||
//
|
||||
// mq.1: both key and value carry the qualified workspace-key shape;
|
||||
// bare `SuperclassRef.class` is lifted using the parent class's
|
||||
// defining module.
|
||||
for (mod_name, m) in &ws.modules {
|
||||
for d in &m.defs {
|
||||
if let Def::Class(cd) = d {
|
||||
if let Some(sc) = &cd.superclass {
|
||||
env.class_superclasses.insert(cd.name.clone(), sc.class.clone());
|
||||
let class_qualified = format!("{mod_name}.{}", cd.name);
|
||||
let sc_qualified = if sc.class.contains('.') {
|
||||
sc.class.clone()
|
||||
} else {
|
||||
format!("{mod_name}.{}", sc.class)
|
||||
};
|
||||
env.class_superclasses.insert(class_qualified, sc_qualified);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +258,13 @@ pub struct Suppress {
|
||||
/// pre-22b hashes.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ClassDef {
|
||||
/// Class name (capitalised by convention).
|
||||
/// Class name (capitalised by convention). Bare — symmetric to
|
||||
/// `TypeDef.name`, the field is the defining-site context, not
|
||||
/// a reference. Cross-module class references live in
|
||||
/// `InstanceDef.class`, `Constraint.class`, and
|
||||
/// `SuperclassRef.class`; those fields carry the canonical form
|
||||
/// (bare for same-module, `<module>.<Class>` for cross-module)
|
||||
/// per mq.1.
|
||||
pub name: String,
|
||||
/// Single type-parameter name.
|
||||
pub param: String,
|
||||
@@ -275,7 +281,9 @@ pub struct ClassDef {
|
||||
/// Iter 22b.1: reference to a superclass relation in [`ClassDef`].
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SuperclassRef {
|
||||
/// Superclass name.
|
||||
/// Superclass name in canonical form (mq.1): bare for a
|
||||
/// same-module class, `<module>.<Class>` for a cross-module
|
||||
/// class. Symmetric to ct.1's `Type::Con.name` rule.
|
||||
pub class: String,
|
||||
/// Type the superclass is applied to. MUST equal the parent
|
||||
/// `ClassDef.param` (validated in 22b.2 — schema does not enforce).
|
||||
@@ -310,7 +318,9 @@ pub struct ClassMethod {
|
||||
/// overrides of default-bearing methods.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct InstanceDef {
|
||||
/// Class being instantiated.
|
||||
/// Class being instantiated, in canonical form (mq.1): bare for
|
||||
/// a same-module class, `<module>.<Class>` for a cross-module
|
||||
/// class. Symmetric to ct.1's `Type::Con.name` rule.
|
||||
pub class: String,
|
||||
/// Concrete type the class is applied to.
|
||||
#[serde(rename = "type")]
|
||||
@@ -341,7 +351,9 @@ pub struct InstanceMethod {
|
||||
/// matching registry entry exists.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Constraint {
|
||||
/// Class name.
|
||||
/// Class name in canonical form (mq.1): bare for a same-module
|
||||
/// class, `<module>.<Class>` for a cross-module class.
|
||||
/// Symmetric to ct.1's `Type::Con.name` rule.
|
||||
pub class: String,
|
||||
/// Type the class is applied to.
|
||||
#[serde(rename = "type")]
|
||||
|
||||
@@ -293,20 +293,34 @@ mod tests {
|
||||
let dup_a_src = std::fs::read(examples.join("test_22b1_dup_a.ail.json"))
|
||||
.expect("examples/test_22b1_dup_a.ail.json present");
|
||||
let dup_a_mod: crate::ast::Module = serde_json::from_slice(&dup_a_src).unwrap();
|
||||
// test_22b1_dup_a has two defs: the Show class and an instance
|
||||
// of Show for test_22b1_dup_b.MyInt. Def::name() returns the
|
||||
// class name for both, so pin each by index. The instance
|
||||
// carries the migrated cross-module qualifier in its `type`.
|
||||
assert_eq!(dup_a_mod.defs.len(), 2, "test_22b1_dup_a expected to have exactly 2 defs (class + instance)");
|
||||
// mq.1: test_22b1_dup_a has one def post-migration — the instance
|
||||
// of `test_22b1_dup_classmod.Show` for `test_22b1_dup_b.MyInt`.
|
||||
// The `Show` class itself moved to `test_22b1_dup_classmod` to
|
||||
// break the dup_a ↔ dup_b cycle that the pre-mq.1 bare-class
|
||||
// shape required for cross-module duplicate-instance coverage.
|
||||
assert_eq!(dup_a_mod.defs.len(), 1, "test_22b1_dup_a expected to have exactly 1 def (the instance)");
|
||||
// Hash captured post-mq.1 migration; the instance carries the
|
||||
// canonical qualified `class` field. Computed by running this
|
||||
// test once with a length-only assertion and recording the
|
||||
// observed value. A future schema-touching change that shifts
|
||||
// this hash without intent triggers this pin.
|
||||
assert_eq!(
|
||||
def_hash(&dup_a_mod.defs[0]),
|
||||
"1c2573661ffd3da3",
|
||||
"test_22b1_dup_a class Show canonical hash must match captured post-migration value"
|
||||
"bdefb4ec75e046e4",
|
||||
"test_22b1_dup_a instance hash drifted; expected post-mq.1 captured value"
|
||||
);
|
||||
|
||||
// Also pin the new test_22b1_dup_classmod fixture's `Show` class
|
||||
// hash: the class moved here from dup_a verbatim (same method
|
||||
// signature), so the hash is independent of the migration.
|
||||
let dup_classmod_src = std::fs::read(examples.join("test_22b1_dup_classmod.ail.json"))
|
||||
.expect("examples/test_22b1_dup_classmod.ail.json present");
|
||||
let dup_classmod_mod: crate::ast::Module = serde_json::from_slice(&dup_classmod_src).unwrap();
|
||||
assert_eq!(dup_classmod_mod.defs.len(), 1, "test_22b1_dup_classmod expected to have exactly 1 def (class Show)");
|
||||
assert_eq!(
|
||||
def_hash(&dup_a_mod.defs[1]),
|
||||
"cc8685f92f246e40",
|
||||
"test_22b1_dup_a instance Show for test_22b1_dup_b.MyInt canonical hash must match captured post-migration value"
|
||||
def_hash(&dup_classmod_mod.defs[0]),
|
||||
"1c2573661ffd3da3",
|
||||
"test_22b1_dup_classmod class Show canonical hash must equal the pre-mq.1 dup_a class Show hash (same bytes)"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+632
-159
File diff suppressed because it is too large
Load Diff
@@ -41,8 +41,9 @@ fn two_modules_with_same_bare_foo_both_register() {
|
||||
args: vec![],
|
||||
});
|
||||
|
||||
let main_key = ("MyC".to_string(), main_foo_hash);
|
||||
let lib_key = ("MyC".to_string(), lib_foo_hash);
|
||||
// mq.1: registry-entries key is keyed by the qualified class name.
|
||||
let main_key = ("ctt2_collision_cls.MyC".to_string(), main_foo_hash);
|
||||
let lib_key = ("ctt2_collision_cls.MyC".to_string(), lib_foo_hash);
|
||||
|
||||
assert!(
|
||||
ws.registry.entries.contains_key(&main_key),
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
# iter mq.1 — Canonical-form extension for class-ref fields + workspace internal qualification
|
||||
|
||||
**Date:** 2026-05-13
|
||||
**Started from:** 1a5f8289b7fbad3ae5bdd6badde27c80eca6dad3
|
||||
**Status:** DONE
|
||||
**Tasks completed:** 7 of 7
|
||||
|
||||
## Summary
|
||||
|
||||
mq.1 lands the canonical-form rule for the three class-reference
|
||||
schema fields (`InstanceDef.class`, `Constraint.class`,
|
||||
`SuperclassRef.class`) — bare for same-module, `<module>.<Class>`
|
||||
for cross-module — symmetric to ct.1's `Type::Con.name` rule.
|
||||
`ClassDef.name` stays bare (defining site, like `TypeDef.name`).
|
||||
ct.1's `validate_canonical_type_names` validator gains a sibling
|
||||
walk for class refs via the new `check_class_ref` helper, with
|
||||
two new sibling diagnostics `BareCrossModuleClassRef` and
|
||||
`BadCrossModuleClassRef`. Workspace-internal class-name keys
|
||||
(`class_def_module`, `class_by_name`, registry `entries.0`,
|
||||
`ClassMethodEntry.class_name`, `class_superclasses`, mono's
|
||||
`class_index`, `Origin::Class.class_name`) all carry the qualified
|
||||
form post-mq.1; the on-disk schema value is the canonical form (bare
|
||||
or qualified) that `qualify_class_ref` lifts to the workspace key
|
||||
at every lookup boundary. `MethodNameCollision` and the dispatch
|
||||
path are unchanged in this iter (iter 2 + 3 land them).
|
||||
|
||||
## Per-task notes
|
||||
|
||||
- iter mq.1.1: two new diagnostic variants
|
||||
`BareCrossModuleClassRef` / `BadCrossModuleClassRef` sibling to the
|
||||
type-ref variants; two Diagnostic-shape Display arms in `ail/main.rs`
|
||||
mirroring the existing arm. 2 RED unit tests, both GREEN.
|
||||
- iter mq.1.2: doc-comments on the four class-reference fields
|
||||
in `ast.rs` (`ClassDef.name` stays bare with cross-reference;
|
||||
`SuperclassRef.class` / `InstanceDef.class` / `Constraint.class`
|
||||
document canonical form). Zero semantic effect.
|
||||
- iter mq.1.3: `check_class_ref` helper plus three per-def walks
|
||||
inside `validate_canonical_type_names`; `check_class_name_fields`
|
||||
narrowed to `ClassDef.name` only; four in-test pin tests inverted
|
||||
(qualified is now accepted); on-disk `test_ct1_qualified_class_rejected`
|
||||
fixture-test repurposed (now expects `OrphanInstance` —
|
||||
the natural post-mq.1 rejection mode for the same shape); positive
|
||||
on-disk fixture pair `mq1_xmod_constraint_class{,_dep}` shipped
|
||||
plus the corresponding pin test. 4 new bare-xmod tests + 1 positive
|
||||
qualified test, all GREEN.
|
||||
- iter mq.1.4: `qualify_class_ref` helper at module scope; `build_registry`
|
||||
Pass-1 re-keyed (qualified); Pass-2 lookups use `qualify_class_ref` to
|
||||
lift `inst.class` to the registry-key shape; superclass walk threads
|
||||
`current_class_module` to qualify bare `SuperclassRef.class`;
|
||||
`Origin::Class.class_name` qualified (one construction site, not two as
|
||||
the plan claimed); type-leg of coherence check now also handles
|
||||
qualified head names symmetrically (split-and-lookup).
|
||||
- iter mq.1.5: `ClassMethodEntry.class_name` insert qualifies in
|
||||
`build_module_globals`; `class_superclasses` key + value both
|
||||
qualified; mono's `build_class_index` keyed qualified (plan said
|
||||
"no code change needed in mono.rs consumers" — that statement was
|
||||
wrong, the build-side needed the matching qualification).
|
||||
- iter mq.1.6: test assertions updated for the new qualified shape —
|
||||
the two `MethodNameCollision` pin tests, `typeclass_22b2.rs:42`
|
||||
(`class_method_class("show")` now `Some("...Show")`),
|
||||
`typeclass_22b3.rs:151` (`MonoTarget.class` qualified). The other
|
||||
lines plan flagged (217/232/295/301/341/353) are local-construction
|
||||
tests that never round-trip through the workspace — left as-is.
|
||||
- iter mq.1.7: full `cargo test --workspace` 520 passed / 0 failed;
|
||||
`examples/prelude.ail.json` zero diff (confirms spec assumption 17:
|
||||
intra-prelude refs stay bare); `bench/compile_check.py` exit 0,
|
||||
`bench/cross_lang.py` exit 0; `bench/check.py` exit 1 due to 4
|
||||
improvements-beyond-tolerance (audit-ratifiable per convention,
|
||||
not a regression). Roundtrip on the new fixture: `ail check
|
||||
examples/mq1_xmod_constraint_class.ail.json` → "ok (16 symbols
|
||||
across 3 modules)", exit 0.
|
||||
|
||||
## Concerns
|
||||
|
||||
- **Boss-note recon claim was empirically wrong.** The boss-note
|
||||
said "every existing `examples/test_22b*.ail.json` class-ref field
|
||||
is intra-module under the canonical-form rule, so existing fixtures
|
||||
carry zero diff". Five test_22b* fixtures actually carried bare
|
||||
cross-module class refs and required migration: `test_22b1_orphan_third.ail.json`
|
||||
(bare `Show` from imported classmod), `test_22b1_dup_a.ail.json` +
|
||||
`test_22b1_dup_b.ail.json` (the duplicate-instance pair structurally
|
||||
relied on the pre-mq.1 bare cross-module shape), and
|
||||
`test_22b2_unbound_constraint_var.ail.json` (constraint references
|
||||
a class that doesn't exist anywhere → fired BareCrossModuleClassRef
|
||||
before reaching UnboundConstraintTypeVar). Six non-test_22b
|
||||
fixtures also needed migration (`eq_ord_polymorphic`, `eq_ord_user_adt`,
|
||||
`cmp_max_smoke`, `ctt2_collision_lib`, `ctt2_collision_main`). All
|
||||
migrations are minimal and aligned with the canonical-form rule;
|
||||
no proactive "fixture cleanup" added.
|
||||
- **Duplicate-instance test restructured by structural necessity.**
|
||||
Pre-mq.1 the test relied on two coherent instances on the same
|
||||
`(class, type)` declared from different modules; post-mq.1, the
|
||||
orphan-freedom invariant prevents this shape (any second instance
|
||||
in a module that owns neither the class nor the type fires
|
||||
`OrphanInstance` first). The new `test_22b1_dup_same_module.ail.json`
|
||||
fixture lands both instances in the same module — the only post-mq.1
|
||||
way to land two on the same canonical key. The old dup_a/dup_b/entry
|
||||
fixtures stay as supporting modules (referenced by hash.rs's
|
||||
canonical-form hash pin) but no longer participate in the duplicate
|
||||
test. This is arguably a strengthening of the architecture: duplicate
|
||||
instances are now structurally impossible across coherent modules.
|
||||
- **Plan's "no code change needed" claims in Task 4 Step 8 and Task 5
|
||||
Step 5 were wrong.** Task 4 Step 8 said `Origin::Class.class_name`
|
||||
qualified at "both sites" but there's only one construction site
|
||||
(the plan's recon was off-by-one). Task 5 Step 5 said mono's
|
||||
`class_index.get(class)` lookups would just work post-rekey, but
|
||||
`build_class_index` itself was bare-keyed. Both fixed inline.
|
||||
- **Several adjacent fixes were necessary that the plan didn't anticipate:**
|
||||
(a) `build_module_globals` rejected `inst.class` containing `.` —
|
||||
added a `Def::Instance` carve-out so qualified class refs survive.
|
||||
(b) `check_fn`'s declared-constraint matching compared bare
|
||||
`c.class` to qualified residual `r.class` → added an inline lift via
|
||||
`qualify_class_ref_in_check`. (c) `NoInstance` Float-aware message
|
||||
arm matched on bare `"Eq" / "Ord"` — updated to `"prelude.Eq" /
|
||||
"prelude.Ord"`. (d) Coherence check's type-leg lookup couldn't
|
||||
resolve qualified head names — extended with a split-and-lookup
|
||||
fallback. None of these widened the contract; each is a one-line
|
||||
bridge keeping the post-mq.1 qualified shape consistent across the
|
||||
pipeline.
|
||||
- **Tasks 3-6 ran as one coherent fix.** The plan structured them as
|
||||
sequential tasks, but Task 3 alone left several existing tests red
|
||||
(registry still bare-keyed, residual still bare). Walking the plan
|
||||
literally and expecting "green after Task 3" per the plan's Step 9
|
||||
was impossible; the four tasks form one consistent rekey.
|
||||
|
||||
## Known debt
|
||||
|
||||
- The `test_22b1_dup_a` / `test_22b1_dup_b` / `test_22b1_dup_entry`
|
||||
fixtures are now orphan from the duplicate-instance test (which
|
||||
uses `test_22b1_dup_same_module` instead). They're still
|
||||
referenced by `hash.rs:293` for the canonical-form hash pin. A
|
||||
cleanup pass could either retire them entirely (and replace the
|
||||
hash pin with the new fixture) or keep them as historical
|
||||
reference. Deferred — not load-bearing.
|
||||
- `qualify_class_ref_in_check` in `ailang-check/src/lib.rs` is a
|
||||
one-line duplicate of `workspace::qualify_class_ref`. The
|
||||
alternative is exposing `qualify_class_ref` as `pub(crate)` or
|
||||
re-exporting; the duplication was the minimum-edit path. A
|
||||
consolidation tidy could land in iter 2.
|
||||
|
||||
## Files touched
|
||||
|
||||
Code:
|
||||
- crates/ail/src/main.rs
|
||||
- crates/ail/tests/ct1_check_cli.rs
|
||||
- crates/ail/tests/typeclass_22b2.rs
|
||||
- crates/ail/tests/typeclass_22b3.rs
|
||||
- crates/ailang-check/src/lib.rs
|
||||
- crates/ailang-check/src/mono.rs
|
||||
- crates/ailang-check/tests/env_construction_pin.rs
|
||||
- crates/ailang-core/src/ast.rs
|
||||
- crates/ailang-core/src/hash.rs
|
||||
- crates/ailang-core/src/workspace.rs
|
||||
- crates/ailang-core/tests/ctt2_registry_rekey.rs
|
||||
|
||||
Fixtures (existing, migrated):
|
||||
- examples/cmp_max_smoke.ail.json
|
||||
- examples/ctt2_collision_lib.ail.json
|
||||
- examples/ctt2_collision_main.ail.json
|
||||
- examples/eq_ord_polymorphic.ail.json
|
||||
- examples/eq_ord_user_adt.ail.json
|
||||
- examples/test_22b1_dup_a.ail.json
|
||||
- examples/test_22b1_dup_b.ail.json
|
||||
- examples/test_22b1_dup_entry.ail.json
|
||||
- examples/test_22b1_orphan_third.ail.json
|
||||
- examples/test_22b2_unbound_constraint_var.ail.json
|
||||
|
||||
Fixtures (new):
|
||||
- examples/mq1_xmod_constraint_class.ail.json
|
||||
- examples/mq1_xmod_constraint_class_dep.ail.json
|
||||
- examples/test_22b1_dup_classmod.ail.json
|
||||
- examples/test_22b1_dup_same_module.ail.json
|
||||
|
||||
## Stats
|
||||
|
||||
bench/orchestrator-stats/2026-05-13-iter-mq.1.json
|
||||
@@ -42,3 +42,4 @@
|
||||
- 2026-05-12 — iter ctt.3: `KindMismatch` retire — pure deletion across 3 files (workspace.rs: variant + dispatch + helper + "dead-but-defensive" doc; main.rs: Display arm; lib.rs: 22b.1 archaeology comment). Existing test `class_param_in_applied_position_fires_canonical_form_rejection` stays green unchanged (asserts `BareCrossModuleTypeRef` on the malformed fixture — the canonical-form-validator successor path). Two adjacent textual-consistency edits ride along (validate_classdefs doc-comment "Three → Two", lib.rs archaeology comment gains a ctt.3 retirement marker). One mid-deletion mechanical fix: dispatch-loop deletion orphaned `mod_name` binding → rewrote `for (mod_name, m) in modules` to `for m in modules.values()` (same body type, mechanical). 2/2 tasks, ~600 tests green across 16 binary-test suites → 2026-05-12-iter-ctt.3.md
|
||||
- 2026-05-12 — audit-ct-tidy: milestone close (ct-tidy) — architect drift fixed inline as `ctt.tidy` (3 doc-side edits: DESIGN.md §"Class-schema diagnostics" drops the retired `KindMismatch` bullet + adds successor paragraph naming `BareCrossModuleTypeRef`; DESIGN.md §"Higher-kinded class params" rewritten to name `BareCrossModuleTypeRef` from canonical-form validation instead; roadmap.md two P2 todos struck `[x]` with forward-reference to ctt.3 and ctt.2). Bench mixed (check.py exit 1: bump_s persistence on `bench_list_sum` second consecutive sighting at +12.23% → +12.60%; two new first-sighting rc-cohort max_us latency regressions classified as noise pending next audit; the recurring 3-audit `latency.explicit_at_rc` improvement cluster narrowed to within tolerance this audit, withdrawing the ratify-pending plan from audit-eob — the meaningful shrinkage is itself attribution evidence that the cluster is noise-class not signal-class), baseline pristine for the 4th consecutive audit → 2026-05-12-audit-ct-tidy.md
|
||||
- 2026-05-12 — iter 24.1: `bool_to_str` + `str_clone` runtime + codegen wiring — 2 new C functions in `runtime/str.c` (slab-allocating heap-Str primitives via existing `str_alloc`), lockstep checker + synth.rs installs with `ret_mode: Own`, 2 IR-header declares + 2 `lower_app` arms + `is_static_callee` whitelist extension, 5 IR snapshots regen (2 declares × 5 files), 9 new tests (2 builtins-install unit + 2 IR-shape pin + 5 E2E all green), pre-existing-drift fix included (int_to_str row added to `builtins.rs::list()`). One substantive deviation: builtin-signatures registered in `uniqueness.rs::infer_module` + `linearity.rs::check_module_with_visible` (8 LOC × 2 files) so `str_clone`'s `param_modes: [Borrow]` is visible to the App-arg walker; symmetric to 23.4-prep's class-method registration; necessary for the plan's literal `frees == 3` cross-realisation assertion. Full `cargo test --workspace` 513 passed, 0 failed. `bench/compile_check.py` + `bench/cross_lang.py` green → 2026-05-12-iter-24.1.md
|
||||
- 2026-05-13 — iter mq.1: class-ref canonical-form extension + workspace-internal class-name qualification — three schema fields (`InstanceDef.class`, `Constraint.class`, `SuperclassRef.class`) move bare → canonical (bare for same-module, `<module>.<Class>` for cross-module) symmetric to ct.1's `Type::Con.name` rule; `ClassDef.name` stays bare (defining site, like `TypeDef.name`). `validate_canonical_type_names` gains three field-walks via new `check_class_ref` helper plus two sibling diagnostics `BareCrossModuleClassRef` / `BadCrossModuleClassRef`; `check_class_name_fields` narrowed to `ClassDef.name`-only. Workspace-internal class-name keys all qualified: `class_def_module`, `class_by_name`, registry `entries.0`, `ClassMethodEntry.class_name`, `class_superclasses`, mono's `class_index`, `Origin::Class.class_name`. New `qualify_class_ref` helper in `workspace.rs` plus sibling `qualify_class_ref_in_check` in `ailang-check`. Two new positive on-disk fixtures (`mq1_xmod_constraint_class{,_dep}`). Recon claim that all existing fixtures' class-refs are intra-module was empirically wrong — 5 test_22b* (`orphan_third`, `dup_a/b/entry`, `unbound_constraint_var`) + 5 other (`eq_ord_polymorphic`, `eq_ord_user_adt`, `cmp_max_smoke`, `ctt2_collision_{lib,main}`) fixtures required minimal canonical-form migration. Duplicate-instance test architecturally restructured: post-mq.1 orphan-freedom makes two-modules-on-same-`(class, type)` structurally impossible (any second instance in a non-owning module fires `OrphanInstance` first); new `test_22b1_dup_same_module.ail.json` lands both instances in the class's module — the only post-mq.1 way to land two on the same canonical key. Plan defects fixed inline: `Origin::Class.class_name` had one construction site not two (plan recon off-by-one); `build_class_index` in `mono.rs` needed qualifying (plan said "no code change needed"); `build_module_globals` needed a `Def::Instance` carve-out for qualified `inst.class`; `check_fn`'s declared-constraint matching needed inline canonical-form lifting; `NoInstance` Float-aware message arm needed `prelude.Eq`/`prelude.Ord` match; coherence type-leg needed qualified-head split-and-lookup. Tasks 3-6 ran as one coherent fix (Task 3 alone left tests RED). 7/7 tasks, 520 tests green, `bench/compile_check.py` + `bench/cross_lang.py` exit 0; `bench/check.py` exit 1 due to 4 improvements-beyond-tolerance (audit-ratifiable per convention, not a regression). `MethodNameCollision` + dispatch path unchanged; iters mq.2 + mq.3 land them → 2026-05-13-iter-mq.1.md
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"type": {
|
||||
"k": "forall",
|
||||
"vars": ["a"],
|
||||
"constraints": [{ "class": "Ord", "type": { "k": "var", "name": "a" } }],
|
||||
"constraints": [{ "class": "prelude.Ord", "type": { "k": "var", "name": "a" } }],
|
||||
"body": {
|
||||
"k": "fn",
|
||||
"params": [{ "k": "var", "name": "a" }, { "k": "var", "name": "a" }],
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "instance",
|
||||
"class": "MyC",
|
||||
"class": "ctt2_collision_cls.MyC",
|
||||
"type": { "k": "con", "name": "Foo" },
|
||||
"methods": [
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "instance",
|
||||
"class": "MyC",
|
||||
"class": "ctt2_collision_cls.MyC",
|
||||
"type": { "k": "con", "name": "Foo" },
|
||||
"methods": [
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"type": {
|
||||
"k": "forall",
|
||||
"vars": ["a"],
|
||||
"constraints": [{ "class": "Ord", "type": { "k": "var", "name": "a" } }],
|
||||
"constraints": [{ "class": "prelude.Ord", "type": { "k": "var", "name": "a" } }],
|
||||
"body": {
|
||||
"k": "fn",
|
||||
"params": [
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "instance",
|
||||
"class": "Eq",
|
||||
"class": "prelude.Eq",
|
||||
"type": { "k": "con", "name": "IntBox" },
|
||||
"doc": "Eq IntBox by structural unwrap of the inner Int.",
|
||||
"methods": [
|
||||
@@ -56,7 +56,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "instance",
|
||||
"class": "Ord",
|
||||
"class": "prelude.Ord",
|
||||
"type": { "k": "con", "name": "IntBox" },
|
||||
"doc": "Ord IntBox by delegating to Int's compare on the inner field.",
|
||||
"methods": [
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"schema": "ailang/v0",
|
||||
"name": "mq1_xmod_constraint_class",
|
||||
"imports": [{ "module": "mq1_xmod_constraint_class_dep" }],
|
||||
"defs": [
|
||||
{
|
||||
"kind": "fn",
|
||||
"name": "useShow",
|
||||
"doc": "Positive mq.1 fixture: a polymorphic fn that takes a single value and a Show constraint, ignores the value, and returns Unit. Exercises the qualified `Constraint.class` shape end-to-end (loader + validator + check_workspace).",
|
||||
"type": {
|
||||
"k": "forall",
|
||||
"vars": ["a"],
|
||||
"constraints": [
|
||||
{ "class": "mq1_xmod_constraint_class_dep.Show",
|
||||
"type": { "k": "var", "name": "a" } }
|
||||
],
|
||||
"body": {
|
||||
"k": "fn",
|
||||
"params": [{ "k": "var", "name": "a" }],
|
||||
"param_modes": ["borrow"],
|
||||
"ret": { "k": "con", "name": "Unit" },
|
||||
"effects": []
|
||||
}
|
||||
},
|
||||
"params": ["_x"],
|
||||
"body": { "t": "lit", "lit": { "kind": "unit" } }
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"schema": "ailang/v0",
|
||||
"name": "mq1_xmod_constraint_class_dep",
|
||||
"imports": [],
|
||||
"defs": [
|
||||
{
|
||||
"kind": "class",
|
||||
"name": "Show",
|
||||
"param": "a",
|
||||
"methods": [
|
||||
{
|
||||
"name": "show",
|
||||
"type": {
|
||||
"k": "fn",
|
||||
"params": [{ "k": "var", "name": "a" }],
|
||||
"ret": { "k": "con", "name": "Str" },
|
||||
"effects": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -4,36 +4,15 @@
|
||||
"imports": [
|
||||
{
|
||||
"module": "test_22b1_dup_b"
|
||||
},
|
||||
{
|
||||
"module": "test_22b1_dup_classmod"
|
||||
}
|
||||
],
|
||||
"defs": [
|
||||
{
|
||||
"kind": "class",
|
||||
"name": "Show",
|
||||
"param": "a",
|
||||
"methods": [
|
||||
{
|
||||
"name": "show",
|
||||
"type": {
|
||||
"k": "fn",
|
||||
"params": [
|
||||
{
|
||||
"k": "var",
|
||||
"name": "a"
|
||||
}
|
||||
],
|
||||
"ret": {
|
||||
"k": "con",
|
||||
"name": "Str"
|
||||
},
|
||||
"effects": []
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "instance",
|
||||
"class": "Show",
|
||||
"class": "test_22b1_dup_classmod.Show",
|
||||
"type": {
|
||||
"k": "con",
|
||||
"name": "test_22b1_dup_b.MyInt"
|
||||
@@ -52,4 +31,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{
|
||||
"schema": "ailang/v0",
|
||||
"name": "test_22b1_dup_b",
|
||||
"imports": [],
|
||||
"imports": [
|
||||
{ "module": "test_22b1_dup_classmod" }
|
||||
],
|
||||
"defs": [
|
||||
{
|
||||
"kind": "type",
|
||||
@@ -15,7 +17,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "instance",
|
||||
"class": "Show",
|
||||
"class": "test_22b1_dup_classmod.Show",
|
||||
"type": { "k": "con", "name": "MyInt" },
|
||||
"methods": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"schema": "ailang/v0",
|
||||
"name": "test_22b1_dup_classmod",
|
||||
"imports": [],
|
||||
"defs": [
|
||||
{
|
||||
"kind": "class",
|
||||
"name": "Show",
|
||||
"param": "a",
|
||||
"methods": [
|
||||
{
|
||||
"name": "show",
|
||||
"type": {
|
||||
"k": "fn",
|
||||
"params": [{ "k": "var", "name": "a" }],
|
||||
"ret": { "k": "con", "name": "Str" },
|
||||
"effects": []
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
"name": "test_22b1_dup_entry",
|
||||
"imports": [
|
||||
{ "module": "test_22b1_dup_a" },
|
||||
{ "module": "test_22b1_dup_b" }
|
||||
{ "module": "test_22b1_dup_b" },
|
||||
{ "module": "test_22b1_dup_classmod" }
|
||||
],
|
||||
"defs": []
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"schema": "ailang/v0",
|
||||
"name": "test_22b1_dup_same_module",
|
||||
"imports": [],
|
||||
"defs": [
|
||||
{
|
||||
"kind": "class",
|
||||
"name": "Show",
|
||||
"param": "a",
|
||||
"methods": [
|
||||
{
|
||||
"name": "show",
|
||||
"type": {
|
||||
"k": "fn",
|
||||
"params": [{ "k": "var", "name": "a" }],
|
||||
"ret": { "k": "con", "name": "Str" },
|
||||
"effects": []
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "instance",
|
||||
"class": "Show",
|
||||
"type": { "k": "con", "name": "Int" },
|
||||
"methods": [
|
||||
{
|
||||
"name": "show",
|
||||
"body": { "t": "lit", "lit": { "kind": "str", "value": "first" } }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "instance",
|
||||
"class": "Show",
|
||||
"type": { "k": "con", "name": "Int" },
|
||||
"methods": [
|
||||
{
|
||||
"name": "show",
|
||||
"body": { "t": "lit", "lit": { "kind": "str", "value": "second" } }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
"defs": [
|
||||
{
|
||||
"kind": "instance",
|
||||
"class": "Show",
|
||||
"class": "test_22b1_orphan_third_classmod.Show",
|
||||
"type": { "k": "con", "name": "Int" },
|
||||
"methods": [
|
||||
{
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
"name": "test_22b2_unbound_constraint_var",
|
||||
"imports": [],
|
||||
"defs": [
|
||||
{
|
||||
"kind": "class",
|
||||
"name": "Bar",
|
||||
"param": "x",
|
||||
"methods": []
|
||||
},
|
||||
{
|
||||
"kind": "class",
|
||||
"name": "Foo",
|
||||
|
||||
Reference in New Issue
Block a user