diff --git a/crates/ail/src/main.rs b/crates/ail/src/main.rs index 12a515c..7945683 100644 --- a/crates/ail/src/main.rs +++ b/crates/ail/src/main.rs @@ -1007,6 +1007,14 @@ fn collect_refs(def: &ailang_core::Def) -> std::collections::BTreeSet { } } } + // Iter 22b.1: `ail deps` does not yet trace references inside + // class/instance defs. Walking method signatures and bodies + // (default bodies, instance method bodies) lands in 22b.2 along + // with the typecheck arms — until then a class/instance def + // contributes no entries to the dependency graph. Tools that + // call `collect_refs` (`ail deps`, the cross-module-fan-out + // diagnostics) therefore treat them as leaves. + ailang_core::Def::Class(_) | ailang_core::Def::Instance(_) => {} } out } @@ -1416,6 +1424,25 @@ fn def_summary(d: &ailang_core::Def) -> (&'static str, String, Vec) { .join(" | "); ("type", s, vec![]) } + // Iter 22b.1: a one-line `class C a` / `instance C T` summary + // for `ail manifest`. The `effects` slot is empty for both — + // class methods carry their own effect annotations on each + // method signature, but the class itself does not (it is a + // collection of method signatures, not a single function). + ailang_core::Def::Class(c) => ( + "class", + format!("{} {}", c.name, c.param), + vec![], + ), + ailang_core::Def::Instance(i) => ( + "instance", + format!( + "{} {}", + i.class, + ailang_core::pretty::type_to_string(&i.type_) + ), + vec![], + ), } } diff --git a/crates/ailang-check/src/lib.rs b/crates/ailang-check/src/lib.rs index 8047feb..2d6eff2 100644 --- a/crates/ailang-check/src/lib.rs +++ b/crates/ailang-check/src/lib.rs @@ -751,6 +751,18 @@ pub fn check(m: &Module) -> Result { name: def.name().to_string(), args: vec![], }, + // Iter 22b.1: class/instance defs are not yet checked. + // The symbols map is keyed by definition name; class/ + // instance contribute their class name, but with no + // concrete pre-monomorphisation type we represent them + // as a placeholder Con. Tools that consume this map + // (`ail diff`, `ail manifest`) read kind separately + // via `def_kind`, so the placeholder type does not + // mislead them. + Def::Class(_) | Def::Instance(_) => Type::Con { + name: def.name().to_string(), + args: vec![], + }, }; symbols.insert(def.name().to_string(), (ty, h)); } @@ -840,6 +852,10 @@ fn build_module_globals( name: def_name.to_string(), args: vec![], }, + // Iter 22b.1: class/instance defs do not contribute + // monomorphic globals to the workspace symbol table + // before 22b.3 monomorphisation runs. Skip them here. + Def::Class(_) | Def::Instance(_) => continue, }; globals.insert(def_name.to_string(), ty); } @@ -944,6 +960,14 @@ fn check_def(def: &Def, env: &Env) -> Result<()> { Def::Fn(f) => check_fn(f, env), Def::Const(c) => check_const(c, env), Def::Type(td) => check_type_def(td, env), + // Iter 22b.1: schema-only landing for class/instance defs. + // Class-schema validation (KindMismatch, InvalidSuperclassParam, + // ConstraintReferencesUnboundTypeVar) and instance-body + // typechecking (with class-method substitution) land in 22b.2. + // Workspace-load coherence checks (Orphan / Duplicate / + // MissingMethod) already fire from `workspace::build_registry`, + // so a malformed instance never reaches this point. + Def::Class(_) | Def::Instance(_) => Ok(()), } } diff --git a/crates/ailang-check/src/lift.rs b/crates/ailang-check/src/lift.rs index 7a37cd4..2c0d86b 100644 --- a/crates/ailang-check/src/lift.rs +++ b/crates/ailang-check/src/lift.rs @@ -99,6 +99,11 @@ pub fn lift_letrecs(m: &Module) -> Result { name: def.name().to_string(), args: vec![], }, + // Iter 22b.1: skip class/instance defs in the global-type + // collection. Class methods become globally typed names + // only after 22b.3 monomorphisation; until then there is + // no concrete `Type` to register here. + Def::Class(_) | Def::Instance(_) => continue, }; env.globals.insert(def.name().to_string(), ty); } @@ -186,6 +191,10 @@ pub fn lift_letrecs(m: &Module) -> Result { c.value = lifter.lift_in_term(&c.value, &mut locals, &c.name)?; } Def::Type(_) => {} + // Iter 22b.1: class/instance defs do not enter the lift + // pass yet. 22b.2 will lift method-body lambdas in instance + // methods; for 22b.1 this is a passthrough. + Def::Class(_) | Def::Instance(_) => {} } } diff --git a/crates/ailang-check/src/linearity.rs b/crates/ailang-check/src/linearity.rs index 13cd9f3..d304a3f 100644 --- a/crates/ailang-check/src/linearity.rs +++ b/crates/ailang-check/src/linearity.rs @@ -205,6 +205,11 @@ pub(crate) fn check_module(m: &Module) -> Vec { ctors.insert(name.clone(), fields.clone()); } } + // Iter 22b.1: class/instance defs are skipped here. Once + // 22b.3 monomorphisation materialises class methods as + // ordinary `FnDef`s, the lift's globals map will pick + // them up automatically. + Def::Class(_) | Def::Instance(_) => {} } } diff --git a/crates/ailang-check/src/uniqueness.rs b/crates/ailang-check/src/uniqueness.rs index fd219a9..22863cc 100644 --- a/crates/ailang-check/src/uniqueness.rs +++ b/crates/ailang-check/src/uniqueness.rs @@ -118,6 +118,12 @@ pub fn infer_module(m: &Module) -> UniquenessTable { globals.insert(c.name.clone(), strip_forall(&c.ty).clone()); } Def::Type(_) => {} + // Iter 22b.1: class/instance defs are skipped here. Class + // method signatures contribute global names once + // monomorphisation (22b.3) materialises them; the + // pre-monomorphisation form has no concrete `Type` to + // hand to the uniqueness pass. + Def::Class(_) | Def::Instance(_) => {} } } diff --git a/crates/ailang-codegen/src/lib.rs b/crates/ailang-codegen/src/lib.rs index 5b21d9d..afe7435 100644 --- a/crates/ailang-codegen/src/lib.rs +++ b/crates/ailang-codegen/src/lib.rs @@ -765,6 +765,13 @@ impl<'a> Emitter<'a> { // logical type. Heap boxes are allocated ad hoc via // GC_malloc (Boehm conservative collector, Iter 14f). } + // Iter 22b.1: class/instance defs do not emit IR yet. + // 22b.3 monomorphisation will rewrite class-method + // calls into calls against synthesised monomorphic + // FnDefs; once that pass runs, class/instance bodies + // never reach the emit path on their own — they only + // appear inlined into the synthesised fns. + Def::Class(_) | Def::Instance(_) => {} } } // Drain the monomorphisation queue. Specialised fns may diff --git a/crates/ailang-core/src/ast.rs b/crates/ailang-core/src/ast.rs index 94a4260..6356c5c 100644 --- a/crates/ailang-core/src/ast.rs +++ b/crates/ailang-core/src/ast.rs @@ -57,9 +57,16 @@ pub struct Import { /// A top-level definition — the unit that gets a content hash. /// -/// Discriminated by the JSON `kind` tag: `"fn"`, `"const"`, or -/// `"type"`. Use [`def_name`] / [`def_kind`] (or the [`Def::name`] -/// method) to inspect generically without matching every variant. +/// Discriminated by the JSON `kind` tag: `"fn"`, `"const"`, `"type"`, +/// `"class"`, or `"instance"`. Use [`def_name`] / [`def_kind`] (or the +/// [`Def::name`] method) to inspect generically without matching every +/// variant. +/// +/// Iter 22b.1 (Decision 11): adds `Class` and `Instance`. Both are +/// additive — pre-22b fixtures never produce these tags, so their +/// canonical-JSON bytes (and therefore their content hashes) are +/// unchanged. The `Def`-level match exhaustiveness in downstream +/// crates is the only caller that has to acknowledge the variants. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "lowercase")] pub enum Def { @@ -69,15 +76,26 @@ pub enum Def { Const(ConstDef), /// Type (ADT) definition; see [`TypeDef`]. Type(TypeDef), + /// Iter 22b.1: typeclass declaration; see [`ClassDef`]. + Class(ClassDef), + /// Iter 22b.1: instance declaration; see [`InstanceDef`]. + Instance(InstanceDef), } impl Def { /// Source-level name of the definition, regardless of kind. + /// + /// For `Def::Instance` the "name" is the class being instantiated; + /// instances are not separately named at the source level. The + /// workspace registry keys instances by `(class, type-hash)`, so + /// the class name alone is the closest analogue of a "name". pub fn name(&self) -> &str { match self { Def::Fn(f) => &f.name, Def::Const(c) => &c.name, Def::Type(t) => &t.name, + Def::Class(c) => &c.name, + Def::Instance(i) => &i.class, } } } @@ -92,7 +110,7 @@ pub fn def_name(def: &Def) -> &str { } /// External helper: discriminator tag of a definition (`"fn"`, -/// `"const"`, `"type"`). +/// `"const"`, `"type"`, `"class"`, `"instance"`). /// /// Identical to the `kind` field in the JSON representation. Useful /// for tools that group or filter definitions by kind. @@ -101,6 +119,8 @@ pub fn def_kind(def: &Def) -> &'static str { Def::Fn(_) => "fn", Def::Const(_) => "const", Def::Type(_) => "type", + Def::Class(_) => "class", + Def::Instance(_) => "instance", } } @@ -220,6 +240,97 @@ pub struct Suppress { pub because: String, } +/// Iter 22b.1: a typeclass declaration (Decision 11). +/// +/// Single-parameter, multi-method, optional-default, optional-superclass +/// typeclass. The `param` is a single string — multi-param classes are +/// rejected by Decision 11 axis 1, and the schema enforces it by shape +/// (`param: String`, not `Vec`). +/// +/// `superclass`, when present, MUST have its `type` field equal to +/// `param`. The check is enforced in 22b.2 via the +/// `InvalidSuperclassParam` diagnostic; the schema does not encode it. +/// +/// All optional fields are omitted from canonical JSON when absent / +/// empty, so future schema evolution can land here without disturbing +/// pre-22b hashes. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClassDef { + /// Class name (capitalised by convention). + pub name: String, + /// Single type-parameter name. + pub param: String, + /// Optional single-superclass relation. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub superclass: Option, + /// Methods of the class, in declaration order. + pub methods: Vec, + /// Optional source-level documentation string. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub doc: Option, +} + +/// Iter 22b.1: reference to a superclass relation in [`ClassDef`]. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SuperclassRef { + /// Superclass name. + pub class: String, + /// Type the superclass is applied to. MUST equal the parent + /// `ClassDef.param` (validated in 22b.2 — schema does not enforce). + #[serde(rename = "type")] + pub type_: String, +} + +/// Iter 22b.1: one method declared in a [`ClassDef`]. +/// +/// `default` is `None` when the method is abstract-required (every +/// instance must specify it); `Some(body)` when the method has a +/// default body (instances may inherit or override). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ClassMethod { + /// Method name. + pub name: String, + /// Full method signature including any mode annotations. The + /// class parameter appears as a [`Type::Var`] inside this signature. + #[serde(rename = "type")] + pub ty: Type, + /// Default body. `None` ⇒ abstract-required; `Some(body)` ⇒ + /// default-with-fallback. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub default: Option, +} + +/// Iter 22b.1: an instance declaration (Decision 11). +/// +/// `class` is the name of the class being instantiated. `type_` is the +/// concrete type expression the class is applied to — never the class +/// param. `methods` contains bodies for the required methods plus any +/// overrides of default-bearing methods. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InstanceDef { + /// Class being instantiated. + pub class: String, + /// Concrete type the class is applied to. + #[serde(rename = "type")] + pub type_: Type, + /// Method bodies in declaration order. + pub methods: Vec, + /// Optional source-level documentation string. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub doc: Option, +} + +/// Iter 22b.1: one method body in an [`InstanceDef`]. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct InstanceMethod { + /// Method name (must match a method in the corresponding class). + pub name: String, + /// Method body. The class's declared method type with the class + /// param substituted to the instance type is the body's expected + /// type — checked in 22b.2. + pub body: Term, +} + /// A constant (top-level binding to a value). /// /// Differs from a zero-arg [`FnDef`] in that it is evaluated once and diff --git a/crates/ailang-core/src/desugar.rs b/crates/ailang-core/src/desugar.rs index 7f3485b..9077cfa 100644 --- a/crates/ailang-core/src/desugar.rs +++ b/crates/ailang-core/src/desugar.rs @@ -169,6 +169,18 @@ pub fn desugar_module(m: &Module) -> Module { Def::Type(td) => { used.insert(td.name.clone()); } + // Iter 22b.1: class/instance defs are passthrough for the + // desugar pass — their bodies (default methods, instance + // method bodies) will be desugared once 22b.2 wires the + // class/instance arms into typecheck. For 22b.1 the names + // still go into `used` so the synthetic-name generator + // does not collide. + Def::Class(c) => { + used.insert(c.name.clone()); + } + Def::Instance(i) => { + used.insert(i.class.clone()); + } } } // Iter 16b.1: pre-collect every top-level def name. The LetRec @@ -248,6 +260,11 @@ pub fn desugar_module(m: &Module) -> Module { c.value = d.desugar_term(&c.value, &scope); } Def::Type(_) => {} + // Iter 22b.1: class/instance defs are not desugared yet. + // Their bodies (default methods, instance method bodies) + // will be processed once 22b.2 lands the class/instance + // arms in typecheck and 22b.3 in codegen. + Def::Class(_) | Def::Instance(_) => {} } } // Iter 16b.1: append every lifted fn to the desugared module. diff --git a/crates/ailang-core/src/pretty.rs b/crates/ailang-core/src/pretty.rs index 680c458..61bdbc8 100644 --- a/crates/ailang-core/src/pretty.rs +++ b/crates/ailang-core/src/pretty.rs @@ -63,6 +63,11 @@ pub fn manifest(m: &Module) -> String { }; ("type", body) } + // Iter 22b.1: one-line summary `class C a` / `instance C T`. + // Full pretty rendering of method signatures and bodies + // lands in 22b.4 alongside the prose projection arms. + Def::Class(c) => ("class", format!("{} {}", c.name, c.param)), + Def::Instance(i) => ("instance", format!("{} {}", i.class, type_to_string(&i.type_))), }; writeln!( s, diff --git a/crates/ailang-core/tests/spec_drift.rs b/crates/ailang-core/tests/spec_drift.rs index cc8f258..7635e35 100644 --- a/crates/ailang-core/tests/spec_drift.rs +++ b/crates/ailang-core/tests/spec_drift.rs @@ -272,6 +272,14 @@ fn spec_mentions_every_def_kind() { Def::Fn(_) => "fn", Def::Const(_) => "const", Def::Type(_) => "type", + // Iter 22b.1: class/instance Def variants exist but are + // not yet anchored in the prose-spec block. Once 22b.4 + // adds prose projection for them, the FORM_A_SPEC text + // gains `(class ` / `(instance ` anchors and this match + // will be exercised. For 22b.1 the exemplars list above + // does not produce these variants. + Def::Class(_) => "class", + Def::Instance(_) => "instance", }; assert!( FORM_A_SPEC.contains(anchor), diff --git a/crates/ailang-prose/src/lib.rs b/crates/ailang-prose/src/lib.rs index 9a0c338..11bd649 100644 --- a/crates/ailang-prose/src/lib.rs +++ b/crates/ailang-prose/src/lib.rs @@ -77,6 +77,28 @@ fn write_def(out: &mut String, def: &Def, level: usize) { Def::Type(td) => write_type_def(out, td, level), Def::Fn(fd) => write_fn_def(out, fd, level), Def::Const(cd) => write_const_def(out, cd, level), + // Iter 22b.1: ClassDef / InstanceDef render as a single-line + // placeholder so prose round-trip survives a module that + // contains them. The full Form-B projection (mirror of the + // ClassDef / InstanceDef Form-A schema, with method bodies + // re-indented and superclass / default markers spelled out) + // is deferred to 22b.4 alongside the prose-roundtrip parser + // arms. The placeholder is informational only — the prose + // round-trip mediator does not yet read these forms back. + Def::Class(c) => { + indent(out, level); + out.push_str("// (class "); + out.push_str(&c.name); + out.push(' '); + out.push_str(&c.param); + out.push_str(") -- 22b.4 will render full Form-B\n"); + } + Def::Instance(i) => { + indent(out, level); + out.push_str("// (instance "); + out.push_str(&i.class); + out.push_str(") -- 22b.4 will render full Form-B\n"); + } } } diff --git a/crates/ailang-surface/src/print.rs b/crates/ailang-surface/src/print.rs index f516f9d..647f28e 100644 --- a/crates/ailang-surface/src/print.rs +++ b/crates/ailang-surface/src/print.rs @@ -94,6 +94,25 @@ fn write_def(out: &mut String, def: &Def, level: usize) { Def::Type(td) => write_type_def(out, td, level), Def::Fn(fd) => write_fn_def(out, fd, level), Def::Const(cd) => write_const_def(out, cd, level), + // Iter 22b.1: surface (Form-B) printer arms for ClassDef / + // InstanceDef are deferred to 22b.4 (along with the parse-side + // arms in `surface/src/parse.rs`). For 22b.1 we emit a + // single-line placeholder so a module containing these defs + // still round-trips through `ail render` in print-only mode. + Def::Class(c) => { + indent(out, level); + out.push_str("(class "); + out.push_str(&c.name); + out.push(' '); + out.push_str(&c.param); + out.push(')'); + } + Def::Instance(i) => { + indent(out, level); + out.push_str("(instance "); + out.push_str(&i.class); + out.push(')'); + } } }