Vocabulary concepts and the generic type metatype
When a vocabulary metatype is in scope, its name introduces a concept:
// A declaration is terminated by exactly ONE of: its `{ }` body, its `{ }`
// cover, or — when it has NEITHER — a REQUIRED ';' (the same item-termination
// rule as Rust: `struct Foo;` vs `struct Foo { … }`). A missing terminator on
// the bodyless form is `OE0011 MissingTerminator`.
concept-decl ::= attribute* 'pub'? <metatype-name> Ident generic-params?
supertype? refinement-clause? ( cover | body | ';' )
supertype ::= specialization? instantiation?
specialization ::= '<:' TypeExpr (',' TypeExpr)*
instantiation ::= ':' TypeExpr (',' TypeExpr)*
cover ::= '{' variant (',' variant)* ','? '}' // bare-ident members; self-terminating
body ::= '{' (field-decl | instance-value) (',' …)* ','? '}' // self-terminating — no ';'
instance-value ::= Ident '=' expr // see body modes below
A cover and a field/instance body share the { … } surface; the member shape distinguishes them. A cover’s members are bare concept names (a brace whose first entry is an Ident followed by , or }), matching enum’s { Circle, Square } and the complete Parent { A, B } group axiom; a body’s first entry carries a : (a field name: Type), :: (a per-target axis override), or = (an instance-value).
The leading identifier (type, or a vocabulary name like kind, subkind, role, phase, category, …) is contextually a concept-introducing keyword — resolved per Name resolution against the pub metatype declarations visible in scope: declared in this package, or imported from a vocabulary package. type is not ambient — it is the std::core baseline, brought into scope by use std::core::{type} or a [package].prelude entry (RFD 0038). An introducer that resolves to no visible pub metatype is refused with OE0605 UnknownMetatype at ox check and ox build.
use std::core::{type}; // the no-commitment baseline, brought into scope (RFD 0038)
pub type Document { title: String, body: String }
// Vocabulary-classified concepts: the vocabulary must be IN SCOPE.
// Here it is declared in-package (the external-vocabulary-package
// pattern — a real UFO package would ship these declarations and be
// imported instead; nothing UFO-related ships with Argon):
pub metatype kind = { };
pub metatype subkind = { };
pub metatype category = { };
pub kind Person { name: String, age: Nat }
pub subkind Adult <: Person iff { self.age >= 18 }; // defined class: auto-classified (Refinement)
pub category Animal;
// Concept with a cover — the fields attach to each variant via subkind hierarchy.
pub kind Vehicle { Car, Truck, Motorcycle }
pub subkind Car <: Vehicle { license_plate: String }
pub subkind Truck <: Vehicle { license_plate: String, cargo_kg: Nat }
pub subkind Motorcycle <: Vehicle { license_plate: String, weight: Real }
Supertype clauses. <: T declares specialization (intra-classification subtyping): Adult <: Person says every adult is a person. : T declares iof-instantiation: pub kind USD : Currency says USD is an instance of the higher-order type Currency. Both clauses are general substrate features — neither commits to any specific higher-order theory. Higher-order theories like MLT (the unprivileged mlt package, Higher-order modeling, Stdlib (selected)) interpret the order arithmetic over :; the substrate itself only enforces that iof is well-founded and acyclic.
Multiple parents on either axis. Both clauses admit a comma-separated list, and both can co-occur on a single declaration:
pub subkind Penguin
<: FlyingAnimal, AquaticAnimal // specializes two kinds at order n
: Vertebrate_Species, Endangered_Species // instance of two categories at order n+1
{
name: String,
body_plan = "tetrapod",
iucn_status = IucnStatus::Vulnerable,
}
When both clauses are present the canonical order is <: then :, mirroring the order arithmetic (specialization parents at the concept’s own order, instantiation parents one above). The order is a style convention, not a rule: both clauses are independent (parents are an unordered set on each axis), so either source order parses and means the same thing — writing : before <: is accepted with the OW0010 style warning (never an error), and oxfmt normalizes it to the canonical order. All parents in a single <: clause must be at the same order; all parents in a single : clause must be at one order above. Unprivileged theory packages (e.g. mlt) enforce additional cross-parent constraints over the iof DAG.
Diamond resolution. When multiple parents (on either axis) declare a field with the same name, the elaborator rejects the declaration with OE0208 AmbiguousFieldFromMultipleParents. The modeler resolves the ambiguity by qualifying the field with its parent:
pub category Vertebrate_Species { habitat: String }
pub category Endangered_Species { habitat: HabitatProtectionZone }
pub subkind Penguin : Vertebrate_Species, Endangered_Species {
Vertebrate_Species::habitat = "Antarctic coast",
Endangered_Species::habitat = HabitatProtectionZone::IUCN_II,
}
Field access from instances follows the same qualification when ambiguous: bar.habitat is rejected with OE0208; the modeler writes bar.Vertebrate_Species::habitat to disambiguate. The substrate offers no automatic merge, override-by-position, or last-wins behavior — every collision is resolved explicitly. (This is consistent with how Argon’s trait system handles method-name collisions; see Trait atom.)
Body modes when : T is present. When a concept is declared as an iof-instance of another (pub kind USD : Currency { … }), the body admits two statement kinds:
field = value— sets the value of a field defined on the targetT. This is the kind-as-instance facet:USDis filling in fields thatCurrencydeclared on its instances.field: Type— declares an instance-level field that the kind itself will carry forward to its own instances. This is the kind-as-kind facet: everyUSDbill will have its ownserial_number.
pub category Currency {
#[intrinsic] iso_code: String,
#[intrinsic] decimal_places: Nat,
symbol: String,
}
pub kind USD : Currency {
iso_code = "USD", // value for the Currency-defined field
decimal_places = 2,
symbol = "$",
serial_number: String, // instance-level field on bills
denomination: Nat,
}
let my_twenty = USD { serial_number: "L12345678A", denomination: 20 };
#[intrinsic] on a field declares it must be set at kind level by every iof-instance. Cross-level binding diagnostics: OE1901 InstantiationFieldNotInTarget (a = binding names a field absent from the target metatype), OE1902 InstantiationFieldShadows (a : field shadows an inherited one), OE1908 IntrinsicPropertyMissing (a required #[intrinsic] field is left unbound), and OE0208 AmbiguousFieldFromMultipleParents (a field declared by multiple parents, unqualified). Theory-specific cross-level constraints (e.g., MLT’s categorization / partition / subordination / powertype rules) are not compiler diagnostics: a higher-order theory ships as an unprivileged package (e.g. mlt, Higher-order modeling) and authors those constraints as its own rules/checks over the neutral substrate; see appendix C.
Field access through the iof chain. For an instance bar: SomeKind where SomeKind : SomeCat and SomeCat declares field x, the access bar.x is resolved by walking up the iof chain:
- Instance fields first. If
barhasxdeclared as an instance-level field (viax: TypeinSomeKind’s body), returnbar’s own value. - Kind-level values next. Otherwise, look at
meta(bar) = SomeKind. IfSomeKindsetx = valueat its kind level, return that value. - Walk further up. If
SomeKinditself does not bindx, continue:meta(meta(bar)), etc., until eitherxis found or the chain bottoms out at an entity with no further:parent. - Not found. Error:
OE0101 UnresolvedName(or a more specific field-not-found variant).
Three equivalent surface forms reach the same value when x lives at the kind level:
| Form | Meaning |
|---|---|
bar.x | auto-walk; returns the first x found going up the iof chain |
meta(bar).x | explicit step to bar’s kind; field access on the kind |
SomeKind.x | direct field access on the type entity itself |
Shadowing across levels. If x is bound at both an instance level and a higher kind level — different types, or the same type with different values — the elaborator rejects the declaration with OE1902 InstantiationFieldShadows. Modelers either rename, or accept the shadow explicitly by writing meta(bar).x / SomeKind.x when they need the kind-level value. There is no implicit override.