iter 22b.1.1: ClassDef/InstanceDef AST + downstream match arms

Adds Def::Class(ClassDef) and Def::Instance(InstanceDef) variants per
Decision 11 §"Form-A schema". All optional fields (superclass, doc,
default body) carry skip_serializing_if so canonical-JSON bytes of
pre-22b fixtures stay bit-identical.

Downstream match-on-Def sites are extended with explicit Class/Instance
arms. Behaviour for 22b.1 is placeholder (skip in typecheck/codegen,
one-line summary in pretty/manifest, placeholder marker in prose/print)
with TODO comments naming the deferred sub-iters (22b.2 typecheck,
22b.3 codegen, 22b.4 prose-projection arms).
This commit is contained in:
2026-05-09 12:31:23 +02:00
parent 522500a6f6
commit f25d7b6cd6
12 changed files with 264 additions and 4 deletions
+115 -4
View File
@@ -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<String>`).
///
/// `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<SuperclassRef>,
/// Methods of the class, in declaration order.
pub methods: Vec<ClassMethod>,
/// Optional source-level documentation string.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub doc: Option<String>,
}
/// 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<Term>,
}
/// 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<InstanceMethod>,
/// Optional source-level documentation string.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub doc: Option<String>,
}
/// 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
+17
View File
@@ -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.
+5
View File
@@ -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,