Skip to main content

gruel_builtins/
lib.rs

1//! Built-in registries for the Gruel compiler.
2//!
3//! After ADR-0081 retired the `STRING_TYPE` registry, this crate hosts
4//! three smaller registries the compiler still keys off of:
5//!
6//! - **Built-in type constructors** ([`BUILTIN_TYPE_CONSTRUCTORS`]):
7//!   `Ptr`, `MutPtr`, `Ref`, `MutRef`, `Slice`, `MutSlice`, `Vec` — written
8//!   in source as `Name(arg, ...)` in type position and lowered directly to
9//!   `TypeKind` variants by sema.
10//! - **Lang items** ([`LangInterfaceItem`], [`LangEnumItem`]): the closed
11//!   set of `@lang("…")` strings the compiler recognises and binds to
12//!   prelude interface or enum declarations (`Drop`, `Clone`, `Handle`,
13//!   `Eq`, `Ord`, `Ordering`).
14//! - **Built-in interface and enum names** ([`BUILTIN_INTERFACE_NAMES`],
15//!   [`BUILTIN_ENUM_NAMES`]): breadcrumbs the doc generator and other
16//!   crates use to refer to prelude declarations without re-typing the
17//!   strings.
18//!
19//! All four prelude-resident built-in enums (`Arch`, `Os`, `TypeKind`,
20//! `Ownership`) and the three interfaces (`Drop`, `Clone`, `Handle`)
21//! live in the prelude — see `prelude/target.gruel`,
22//! `prelude/type_info.gruel`, and `prelude/interfaces.gruel`. The
23//! compiler caches their `EnumId` / `InterfaceId`s after declaration
24//! resolution; see `Sema::cache_builtin_enum_ids` in `gruel-air`.
25
26// ============================================================================
27// Built-in Type Constructors (parameterized types)
28// ============================================================================
29
30/// Identifier for a built-in parameterized type.
31///
32/// Each variant corresponds to a closed, compiler-recognized type constructor
33/// (e.g. `Ptr(T)`, `MutPtr(T)`). The actual lowering to a `TypeKind` happens
34/// in sema (`gruel-air`), which dispatches on this tag — `gruel-builtins`
35/// has no dependency on the type system.
36///
37/// New constructors are added by extending this enum, adding an entry to
38/// [`BUILTIN_TYPE_CONSTRUCTORS`], and adding a corresponding sema lowering
39/// arm. Exhaustive matches in sema force you to add the lowering arm when
40/// adding a variant — that's intentional, so the enum is not marked
41/// `#[non_exhaustive]`.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub enum BuiltinTypeConstructorKind {
44    /// Immutable raw pointer (ADR-0061): `Ptr(T)` lowers to `TypeKind::PtrConst`.
45    Ptr,
46    /// Mutable raw pointer (ADR-0061): `MutPtr(T)` lowers to `TypeKind::PtrMut`.
47    MutPtr,
48    /// Immutable reference (ADR-0062): `Ref(T)` lowers to `TypeKind::Ref`.
49    Ref,
50    /// Mutable reference (ADR-0062): `MutRef(T)` lowers to `TypeKind::MutRef`.
51    MutRef,
52    /// Immutable slice (ADR-0064): `Slice(T)` lowers to `TypeKind::Slice`.
53    Slice,
54    /// Mutable slice (ADR-0064): `MutSlice(T)` lowers to `TypeKind::MutSlice`.
55    MutSlice,
56    /// Owned vector (ADR-0066): `Vec(T)` lowers to `TypeKind::Vec`.
57    Vec,
58}
59
60/// Definition of a built-in parameterized type constructor.
61///
62/// Built-in type constructors share a single surface form with user-defined
63/// comptime-generic functions that return `type` (e.g. `fn Vec(comptime T: type) -> type`):
64/// both are written `Name(arg1, arg2, ...)` in type position. The difference is
65/// that built-in constructors are hard-wired in the compiler — sema resolves
66/// the name against this registry and lowers directly to a `TypeKind` without
67/// running the comptime interpreter.
68///
69/// See ADR-0061 (`Ptr`/`MutPtr`) and ADR-0062 (`Ref`/`MutRef`) for usage.
70#[derive(Debug, Clone, Copy)]
71pub struct BuiltinTypeConstructor {
72    /// Constructor name as it appears in source code (e.g., "Ptr").
73    pub name: &'static str,
74    /// Number of comptime type arguments this constructor accepts.
75    pub arity: usize,
76    /// Which built-in lowering to use.
77    pub kind: BuiltinTypeConstructorKind,
78}
79
80/// `Ptr(T)` — immutable raw pointer (ADR-0061).
81pub static PTR_CONSTRUCTOR: BuiltinTypeConstructor = BuiltinTypeConstructor {
82    name: "Ptr",
83    arity: 1,
84    kind: BuiltinTypeConstructorKind::Ptr,
85};
86
87/// `MutPtr(T)` — mutable raw pointer (ADR-0061).
88pub static MUT_PTR_CONSTRUCTOR: BuiltinTypeConstructor = BuiltinTypeConstructor {
89    name: "MutPtr",
90    arity: 1,
91    kind: BuiltinTypeConstructorKind::MutPtr,
92};
93
94/// `Ref(T)` — immutable reference (ADR-0062).
95pub static REF_CONSTRUCTOR: BuiltinTypeConstructor = BuiltinTypeConstructor {
96    name: "Ref",
97    arity: 1,
98    kind: BuiltinTypeConstructorKind::Ref,
99};
100
101/// `MutRef(T)` — mutable reference (ADR-0062).
102pub static MUT_REF_CONSTRUCTOR: BuiltinTypeConstructor = BuiltinTypeConstructor {
103    name: "MutRef",
104    arity: 1,
105    kind: BuiltinTypeConstructorKind::MutRef,
106};
107
108/// `Slice(T)` — immutable slice (ADR-0064).
109pub static SLICE_CONSTRUCTOR: BuiltinTypeConstructor = BuiltinTypeConstructor {
110    name: "Slice",
111    arity: 1,
112    kind: BuiltinTypeConstructorKind::Slice,
113};
114
115/// `MutSlice(T)` — mutable slice (ADR-0064).
116pub static MUT_SLICE_CONSTRUCTOR: BuiltinTypeConstructor = BuiltinTypeConstructor {
117    name: "MutSlice",
118    arity: 1,
119    kind: BuiltinTypeConstructorKind::MutSlice,
120};
121
122/// `Vec(T)` — owned, growable vector (ADR-0066).
123pub static VEC_CONSTRUCTOR: BuiltinTypeConstructor = BuiltinTypeConstructor {
124    name: "Vec",
125    arity: 1,
126    kind: BuiltinTypeConstructorKind::Vec,
127};
128
129/// All built-in type constructors.
130///
131/// The compiler iterates over this slice when resolving type-call expressions
132/// and when reserving names so user code cannot shadow them.
133pub static BUILTIN_TYPE_CONSTRUCTORS: &[&BuiltinTypeConstructor] = &[
134    &PTR_CONSTRUCTOR,
135    &MUT_PTR_CONSTRUCTOR,
136    &REF_CONSTRUCTOR,
137    &MUT_REF_CONSTRUCTOR,
138    &SLICE_CONSTRUCTOR,
139    &MUT_SLICE_CONSTRUCTOR,
140    &VEC_CONSTRUCTOR,
141];
142
143/// Look up a built-in type constructor by name.
144pub fn get_builtin_type_constructor(name: &str) -> Option<&'static BuiltinTypeConstructor> {
145    BUILTIN_TYPE_CONSTRUCTORS
146        .iter()
147        .find(|c| c.name == name)
148        .copied()
149}
150
151/// Check if a name is reserved for a built-in type constructor.
152pub fn is_reserved_type_constructor_name(name: &str) -> bool {
153    BUILTIN_TYPE_CONSTRUCTORS.iter().any(|c| c.name == name)
154}
155
156// ============================================================================
157// Built-in Markers (ADR-0083: `@mark(...)` directive)
158// ============================================================================
159//
160// Markers are declaration-time-only attributes carried by `@mark(...)` on
161// struct/enum (and anonymous literal) heads. They sit alongside `@derive`,
162// `@lang`, and `@allow` in the directive list. Today's marker set is
163// closed and small (`copy`, `affine`, `linear` — all of them postures);
164// future ADRs may add more without parser surgery — adding a row here is
165// the extension point.
166//
167// Three markers cover the posture trichotomy: `copy` asserts the type
168// must structurally infer Copy; `affine` suppresses Copy inference (the
169// type is always Affine, even if its members would otherwise make it
170// Copy); `linear` overrides inference to declare the type Linear
171// regardless of member postures.
172
173/// What a marker conveys to sema. Markers fall into independent
174/// "namespaces" — at most one posture marker may attach to a type, and
175/// (separately) at most one thread-safety marker. Future markers (e.g.
176/// capability tags, layout hints) plug in by adding a new variant
177/// without disturbing the existing ones.
178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
179pub enum MarkerKind {
180    Posture(Posture),
181    /// ADR-0084: thread-safety classification overrides. `unsend` is the
182    /// always-safe downgrade; `checked_send` and `checked_sync` are
183    /// user-asserted upgrades the compiler cannot verify on its own.
184    ThreadSafety(ThreadSafety),
185    /// ADR-0085: ABI markers — C ABI on fns, C layout on structs.
186    Abi(Abi),
187    /// ADR-0088: marks a fn as unchecked — caller must wrap every call
188    /// in a `checked { }` block. Replaces the legacy `unchecked` keyword
189    /// (ADR-0028) and extends the unchecked surface to methods, interface
190    /// method signatures, and FFI imports under a single uniform spelling.
191    Unchecked,
192}
193
194/// ABI marker variants (ADR-0085). C is the only ABI in v1; future
195/// values (`system`, `stdcall`, `vectorcall`, eventually `rust`) extend
196/// this enum.
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198pub enum Abi {
199    /// C ABI: C calling convention on fns, C layout on structs.
200    C,
201}
202
203/// Posture trichotomy carried by `MarkerKind::Posture` (ADR-0080 / ADR-0083).
204#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
205pub enum Posture {
206    /// The type is Copy: bitwise duplicable, never moves on assignment.
207    Copy,
208    /// The type is Affine: the default — moves on use, no implicit duplicate.
209    /// `@mark(affine)` exists so users can suppress structural Copy inference
210    /// when a type's members are all Copy but its semantics demand
211    /// move-on-use.
212    Affine,
213    /// The type is Linear: must be explicitly consumed; cannot be silently
214    /// dropped.
215    Linear,
216}
217
218/// Thread-safety trichotomy (ADR-0084).
219///
220/// Carried by `MarkerKind::ThreadSafety` and stored on every type-bearing
221/// `StructDef` / `EnumDef`. Inference takes the structural minimum over
222/// members; primitives are intrinsically `Sync` and raw pointers are
223/// intrinsically `Unsend`.
224///
225/// The variant order matters: it makes the derived `Ord` impl yield the
226/// chain `Unsend < Send < Sync`, which is what the structural-minimum
227/// inference rule expects.
228#[derive(
229    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
230)]
231pub enum ThreadSafety {
232    /// Cannot cross thread boundaries.
233    Unsend,
234    /// Safe to move across threads (transferring ownership).
235    Send,
236    /// Safe to share across threads. Subsumes `Send`.
237    Sync,
238}
239
240impl Default for ThreadSafety {
241    /// `Sync` is the identity for the structural-minimum operation —
242    /// `min(any, Sync) = any`. Defaulting to `Sync` means an
243    /// uninitialized field has no impact when the inference pass folds
244    /// over members.
245    fn default() -> Self {
246        ThreadSafety::Sync
247    }
248}
249
250/// Item kinds a marker is applicable to.
251///
252/// Markers may permit structs, enums, or both. Today both posture markers
253/// allow either; the field is forward-looking for future markers (e.g. a
254/// "no-niche" marker that only makes sense on structs).
255#[derive(Debug, Clone, Copy, PartialEq, Eq)]
256pub struct ItemKinds(u8);
257
258impl ItemKinds {
259    pub const STRUCT: ItemKinds = ItemKinds(0b001);
260    pub const ENUM: ItemKinds = ItemKinds(0b010);
261    pub const FUNCTION: ItemKinds = ItemKinds(0b100);
262    pub const STRUCT_OR_ENUM: ItemKinds = ItemKinds(0b011);
263    pub const FN_OR_STRUCT: ItemKinds = ItemKinds(0b101);
264    /// ADR-0086: `@mark(c)` applies to fns, structs, and enums.
265    pub const FN_STRUCT_OR_ENUM: ItemKinds = ItemKinds(0b111);
266
267    pub fn includes_struct(self) -> bool {
268        (self.0 & Self::STRUCT.0) != 0
269    }
270
271    pub fn includes_enum(self) -> bool {
272        (self.0 & Self::ENUM.0) != 0
273    }
274
275    pub fn includes_function(self) -> bool {
276        (self.0 & Self::FUNCTION.0) != 0
277    }
278}
279
280/// Definition of a built-in marker.
281///
282/// Markers are looked up by name (see [`get_builtin_marker`]) when sema
283/// processes `@mark(...)` arguments. The closed list documents what
284/// *exists*; user-defined markers are explicitly out of scope (ADR-0083).
285#[derive(Debug, Clone, Copy)]
286pub struct BuiltinMarker {
287    /// Marker name as it appears inside `@mark(...)` (e.g. "copy").
288    pub name: &'static str,
289    /// What the marker does — a posture today, possibly more later.
290    pub kind: MarkerKind,
291    /// Item kinds the marker is applicable to.
292    pub applicable_to: ItemKinds,
293}
294
295/// All built-in markers recognized by the compiler.
296///
297/// Sema iterates this slice when looking up `@mark(name)` arguments and
298/// when generating "did you mean?" suggestions for typo'd marker names.
299pub static BUILTIN_MARKERS: &[BuiltinMarker] = &[
300    BuiltinMarker {
301        name: "copy",
302        kind: MarkerKind::Posture(Posture::Copy),
303        applicable_to: ItemKinds::STRUCT_OR_ENUM,
304    },
305    BuiltinMarker {
306        name: "affine",
307        kind: MarkerKind::Posture(Posture::Affine),
308        applicable_to: ItemKinds::STRUCT_OR_ENUM,
309    },
310    BuiltinMarker {
311        name: "linear",
312        kind: MarkerKind::Posture(Posture::Linear),
313        applicable_to: ItemKinds::STRUCT_OR_ENUM,
314    },
315    // ADR-0084: thread-safety overrides. `unsend` is an always-safe
316    // downgrade. `checked_send` / `checked_sync` are user-asserted
317    // upgrades the compiler cannot verify; the `checked_` prefix names
318    // them as such (analogous to Rust's `unsafe impl Send`).
319    BuiltinMarker {
320        name: "unsend",
321        kind: MarkerKind::ThreadSafety(ThreadSafety::Unsend),
322        applicable_to: ItemKinds::STRUCT_OR_ENUM,
323    },
324    BuiltinMarker {
325        name: "checked_send",
326        kind: MarkerKind::ThreadSafety(ThreadSafety::Send),
327        applicable_to: ItemKinds::STRUCT_OR_ENUM,
328    },
329    BuiltinMarker {
330        name: "checked_sync",
331        kind: MarkerKind::ThreadSafety(ThreadSafety::Sync),
332        applicable_to: ItemKinds::STRUCT_OR_ENUM,
333    },
334    // ADR-0085: C FFI. Applied to fns selects the C calling convention;
335    // applied to structs selects C layout (field order, alignment,
336    // niches disabled). ADR-0086 widens to enums — `@mark(c) enum` uses
337    // c_int as its discriminant type. The `c_ffi_extras` preview gate
338    // (fired in sema) is what guards the new enum applicability.
339    BuiltinMarker {
340        name: "c",
341        kind: MarkerKind::Abi(Abi::C),
342        applicable_to: ItemKinds::FN_STRUCT_OR_ENUM,
343    },
344    // ADR-0088: declares a fn (top-level, method, interface method, or
345    // FFI import) as unchecked — every caller must wrap the call in
346    // `checked { }`. Legal positions are fn-like; the registry knows
347    // the marker as "function-applicable", and sema applies a finer
348    // position check (e.g. forbids `@mark(unchecked) fn __drop`).
349    BuiltinMarker {
350        name: "unchecked",
351        kind: MarkerKind::Unchecked,
352        applicable_to: ItemKinds::FUNCTION,
353    },
354];
355
356/// Look up a built-in marker by name.
357pub fn get_builtin_marker(name: &str) -> Option<&'static BuiltinMarker> {
358    BUILTIN_MARKERS.iter().find(|m| m.name == name)
359}
360
361/// All recognized marker names (for diagnostic suggestions).
362pub fn all_marker_names() -> Vec<&'static str> {
363    BUILTIN_MARKERS.iter().map(|m| m.name).collect()
364}
365
366// ============================================================================
367// Built-in Enums (Arch, Os, TypeKind, Ownership)
368// ============================================================================
369//
370// ADR-0078 Phase 3: the platform-reflection enums (`Arch`, `Os`) live
371// in `prelude/target.gruel`; the type-reflection enums (`TypeKind`,
372// `Ownership`) live in `prelude/type_info.gruel`. The intrinsics that
373// produce values of those types (`@target_arch`, `@target_os`,
374// `@type_info`, `@ownership`) cache their `EnumId`s after declaration
375// resolution via `Sema::cache_builtin_enum_ids`. Variant order in the
376// prelude files matches the order returned by the compiler-side
377// `arch_variant_index` / `os_variant_index` mappers; see
378// `crates/gruel-air/src/sema/analysis.rs`.
379
380/// Names of the prelude-resident built-in enums. Kept here only so
381/// other crates have a single source of truth when they need to refer to
382/// the names (e.g. for documentation generation).
383pub static BUILTIN_ENUM_NAMES: &[&str] = &["Arch", "Os", "TypeKind", "Ownership", "ThreadSafety"];
384
385// ============================================================================
386// Built-in Interfaces (Drop, Clone, Handle)
387// ============================================================================
388//
389// ADR-0078 Phase 2: the interface declarations live in
390// `prelude/interfaces.gruel`. The compiler still recognizes them by
391// interned name (the hardcoded behaviors — drop glue, @derive(Clone)
392// synthesis, Handle linearity carve-out — key off these names).
393// ADR-0080 retired `Copy` from the interface set: posture is declared
394// on the type and queried via `@ownership(T)`, not via interface
395// conformance.
396
397/// Names of the three compiler-recognized built-in interfaces. Kept
398/// here only so the doc generator can point at `prelude/interfaces.gruel`
399/// for canonical declarations. Do not use this for anything load-bearing
400/// — the compiler resolves these names through the prelude scope.
401pub static BUILTIN_INTERFACE_NAMES: &[&str] = &["Drop", "Clone", "Handle"];
402
403// ============================================================================
404// Lang items (ADR-0079)
405// ============================================================================
406//
407// `@lang("name")` directives in the prelude bind the compiler's built-in
408// behaviors (drop glue, copy/clone synthesis, operator desugaring, …) to
409// specific interface or enum declarations. The closed list here is the
410// only set of names the compiler recognizes — unknown lang-item names
411// produce a compile error at the directive site. Stdlib renames the
412// underlying type freely (e.g. `Clone` → `Dup`) so long as the renamed
413// declaration carries the matching `@lang(...)` tag.
414
415/// Lang-item name applied to an interface declaration.
416///
417/// ADR-0080 retired `Copy` from this enum: posture is declared on the
418/// type with the `copy` keyword, queried via `@ownership(T)`, and never
419/// dispatched, so it is no longer an interface.
420#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
421pub enum LangInterfaceItem {
422    /// `Drop` — values may carry custom destructors.
423    Drop,
424    /// `Clone` — values support a `clone(self)` method producing an
425    /// owned duplicate.
426    Clone,
427    /// `Handle` — wraps a non-copyable resource that's still allowed to
428    /// move out of `let` bindings (linear-type carve-out).
429    Handle,
430    /// `Eq` — drives `==` operator desugaring.
431    OpEq,
432    /// `Ord` — drives `<`/`<=`/`>`/`>=` operator desugaring.
433    OpCmp,
434}
435
436/// Lang-item name applied to an enum declaration.
437#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
438pub enum LangEnumItem {
439    /// `Ordering` — return type of `Ord::cmp`; variants drive ordering
440    /// operator desugaring.
441    Ordering,
442}
443
444/// Lang-item name applied to a function declaration. Used for
445/// type-constructor functions whose result has compiler-recognized
446/// behavior (indexing, slice-borrow, drop synthesis, etc.).
447#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
448pub enum LangFnItem {
449    /// `Vec(comptime T: type) -> type` (ADR-0082). Instances of the
450    /// returned struct are recognized as the canonical owned-buffer
451    /// vector for `v[i]` indexing, `&v[..]` slice borrows, and drop
452    /// synthesis.
453    Vec,
454}
455
456impl LangFnItem {
457    pub fn name(self) -> &'static str {
458        match self {
459            LangFnItem::Vec => "vec",
460        }
461    }
462
463    pub fn from_str(s: &str) -> Option<Self> {
464        Some(match s {
465            "vec" => LangFnItem::Vec,
466            _ => return None,
467        })
468    }
469
470    pub fn all() -> &'static [LangFnItem] {
471        &[LangFnItem::Vec]
472    }
473}
474
475impl LangInterfaceItem {
476    /// The string the prelude uses inside `@lang("…")` for this item.
477    pub fn name(self) -> &'static str {
478        match self {
479            LangInterfaceItem::Drop => "drop",
480            LangInterfaceItem::Clone => "clone",
481            LangInterfaceItem::Handle => "handle",
482            LangInterfaceItem::OpEq => "op_eq",
483            LangInterfaceItem::OpCmp => "op_cmp",
484        }
485    }
486
487    pub fn from_str(s: &str) -> Option<Self> {
488        Some(match s {
489            "drop" => LangInterfaceItem::Drop,
490            "clone" => LangInterfaceItem::Clone,
491            "handle" => LangInterfaceItem::Handle,
492            "op_eq" => LangInterfaceItem::OpEq,
493            "op_cmp" => LangInterfaceItem::OpCmp,
494            _ => return None,
495        })
496    }
497
498    pub fn all() -> &'static [LangInterfaceItem] {
499        use LangInterfaceItem::*;
500        &[Drop, Clone, Handle, OpEq, OpCmp]
501    }
502}
503
504impl LangEnumItem {
505    pub fn name(self) -> &'static str {
506        match self {
507            LangEnumItem::Ordering => "ordering",
508        }
509    }
510
511    pub fn from_str(s: &str) -> Option<Self> {
512        Some(match s {
513            "ordering" => LangEnumItem::Ordering,
514            _ => return None,
515        })
516    }
517
518    pub fn all() -> &'static [LangEnumItem] {
519        &[LangEnumItem::Ordering]
520    }
521}
522
523/// Classification of a lang-item name string. Returns `None` for
524/// unrecognized strings.
525pub enum LangItemKind {
526    Interface(LangInterfaceItem),
527    Enum(LangEnumItem),
528    Fn(LangFnItem),
529}
530
531impl LangItemKind {
532    pub fn from_str(s: &str) -> Option<Self> {
533        if let Some(i) = LangInterfaceItem::from_str(s) {
534            Some(LangItemKind::Interface(i))
535        } else if let Some(e) = LangEnumItem::from_str(s) {
536            Some(LangItemKind::Enum(e))
537        } else {
538            LangFnItem::from_str(s).map(LangItemKind::Fn)
539        }
540    }
541}
542
543/// Closed list of lang-item names recognized by the compiler. Driving
544/// data for diagnostics — the actual lookup goes through
545/// `LangInterfaceItem::from_str` / `LangEnumItem::from_str` /
546/// `LangFnItem::from_str`.
547pub fn all_lang_item_names() -> Vec<&'static str> {
548    let mut names: Vec<&'static str> = LangInterfaceItem::all()
549        .iter()
550        .map(|i| i.name())
551        .chain(LangEnumItem::all().iter().map(|e| e.name()))
552        .chain(LangFnItem::all().iter().map(|f| f.name()))
553        .collect();
554    names.sort();
555    names
556}
557
558// ============================================================================
559// Reference doc generation
560// ============================================================================
561
562impl BuiltinTypeConstructorKind {
563    fn description(self) -> &'static str {
564        match self {
565            BuiltinTypeConstructorKind::Ptr => "immutable raw pointer (ADR-0061)",
566            BuiltinTypeConstructorKind::MutPtr => "mutable raw pointer (ADR-0061)",
567            BuiltinTypeConstructorKind::Ref => "immutable reference (ADR-0062)",
568            BuiltinTypeConstructorKind::MutRef => "mutable reference (ADR-0062)",
569            BuiltinTypeConstructorKind::Slice => "immutable slice (ADR-0064)",
570            BuiltinTypeConstructorKind::MutSlice => "mutable slice (ADR-0064)",
571            BuiltinTypeConstructorKind::Vec => "owned, growable vector (ADR-0066)",
572        }
573    }
574}
575
576/// Render the reference page for built-in type constructors, enums, and
577/// interfaces.
578///
579/// The output is a self-contained markdown page generated from the registries
580/// in this crate. It is the source of truth for the checked-in reference page
581/// at `docs/generated/builtins-reference.md`; `make check-builtins-docs` runs
582/// it and fails CI if the committed file differs from the generated output.
583pub fn render_reference_markdown() -> String {
584    let mut out = String::new();
585    out.push_str("<!-- AUTO-GENERATED by `cargo run -p gruel-builtins-docs`. Do not edit by hand; edit the registries in `crates/gruel-builtins/src/lib.rs` and regenerate. -->\n\n");
586    out.push_str("# Built-in Types Reference\n\n");
587    out.push_str("This page documents every built-in type constructor, enum, and interface the Gruel compiler hard-codes by name. ADR-0081 retired the `BUILTIN_TYPES` registry; built-in *types* (currently just `String`) live in the prelude alongside `Option` / `Result`. The constructors, enums, and interfaces here are still hard-wired because their semantics aren't expressible as ordinary Gruel code.\n\n");
588
589    // ---- Quick reference ----
590    out.push_str("## Quick Reference\n\n");
591
592    out.push_str("### Type Constructors\n\n");
593    out.push_str("| Name | Arity | Description |\n");
594    out.push_str("|---|---|---|\n");
595    for c in BUILTIN_TYPE_CONSTRUCTORS {
596        out.push_str(&format!(
597            "| `{}` | {} | {} |\n",
598            c.name,
599            c.arity,
600            c.kind.description(),
601        ));
602    }
603    out.push('\n');
604
605    out.push_str("### Enums\n\n");
606    out.push_str("Platform-reflection enums (`Arch`, `Os`) live in `prelude/target.gruel`; type-reflection enums (`TypeKind`, `Ownership`) live in `prelude/type_info.gruel`. The corresponding intrinsics produce values of these types by name lookup.\n\n");
607    out.push_str("| Name | Variants |\n");
608    out.push_str("|---|---|\n");
609    out.push_str("| `Arch` | `X86_64`, `Aarch64`, `X86`, `Arm`, `Riscv32`, `Riscv64`, `Wasm32`, `Wasm64` |\n");
610    out.push_str("| `Os` | `Linux`, `Macos`, `Windows`, `Freestanding`, `Wasi` |\n");
611    out.push_str("| `TypeKind` | `Struct`, `Enum`, `Int`, `Bool`, `Unit`, `Never`, `Array` |\n");
612    out.push_str("| `Ownership` | `Copy`, `Affine`, `Linear` |\n");
613    out.push_str("| `ThreadSafety` | `Unsend`, `Send`, `Sync` |\n");
614    out.push('\n');
615
616    out.push_str("### Interfaces\n\n");
617    out.push_str("Compiler-recognized interfaces are declared in `prelude/interfaces.gruel`. The compiler keys off these names for hardcoded behaviors (drop glue, `@derive(Clone)` synthesis, `Handle` linearity carve-out). ADR-0080 retired `Copy` from this set: posture is declared on the type with the `copy` keyword and queried via `@ownership(T)`.\n\n");
618    out.push_str("| Name | Method | Conformance |\n");
619    out.push_str("|---|---|---|\n");
620    out.push_str("| `Drop` | `fn __drop(self)` | method presence |\n");
621    out.push_str("| `Clone` | `fn clone(self: Ref(Self)) -> Self` | `@derive(Clone)` |\n");
622    out.push_str("| `Handle` | `fn handle(self: Ref(Self)) -> Self` | method presence |\n");
623    out.push('\n');
624
625    out.push_str("### Markers\n\n");
626    out.push_str("Marker names recognized inside the `@mark(...)` directive (ADR-0083). Markers attach declaration-time metadata to a struct/enum head; future markers plug in by adding a row to the `BUILTIN_MARKERS` registry.\n\n");
627    out.push_str("| Name | Kind | Applies to |\n");
628    out.push_str("|---|---|---|\n");
629    for m in BUILTIN_MARKERS {
630        let kind_str = match m.kind {
631            MarkerKind::Posture(Posture::Copy) => "Posture(Copy)",
632            MarkerKind::Posture(Posture::Affine) => "Posture(Affine)",
633            MarkerKind::Posture(Posture::Linear) => "Posture(Linear)",
634            MarkerKind::ThreadSafety(ThreadSafety::Unsend) => "ThreadSafety(Unsend)",
635            MarkerKind::ThreadSafety(ThreadSafety::Send) => "ThreadSafety(Send)",
636            MarkerKind::ThreadSafety(ThreadSafety::Sync) => "ThreadSafety(Sync)",
637            MarkerKind::Abi(Abi::C) => "Abi(C)",
638            MarkerKind::Unchecked => "Unchecked",
639        };
640        let apply_str = match (
641            m.applicable_to.includes_struct(),
642            m.applicable_to.includes_enum(),
643            m.applicable_to.includes_function(),
644        ) {
645            (true, true, false) => "struct or enum",
646            (true, false, false) => "struct only",
647            (false, true, false) => "enum only",
648            (true, false, true) => "fn or struct",
649            (false, false, true) => "fn only",
650            _ => "(none)",
651        };
652        out.push_str(&format!(
653            "| `{}` | {} | {} |\n",
654            m.name, kind_str, apply_str
655        ));
656    }
657    out.push('\n');
658
659    // ---- Type constructors in detail ----
660    out.push_str("## Type Constructors\n\n");
661    out.push_str("Built-in type constructors are written `Name(arg1, arg2, ...)` in type position. Sema resolves the name against the registry and lowers directly to a `TypeKind` without running the comptime interpreter.\n\n");
662    for c in BUILTIN_TYPE_CONSTRUCTORS {
663        let args = (0..c.arity)
664            .map(|i| {
665                if c.arity == 1 {
666                    "T".to_string()
667                } else {
668                    format!("T{}", i + 1)
669                }
670            })
671            .collect::<Vec<_>>()
672            .join(", ");
673        out.push_str(&format!("### `{}({})`\n\n", c.name, args));
674        out.push_str(&format!("{}.\n\n", c.kind.description()));
675    }
676
677    // ---- Enums in detail ----
678    //
679    // ADR-0078 Phase 3: `Arch`/`Os` live in `prelude/target.gruel`,
680    // `TypeKind`/`Ownership` in `prelude/type_info.gruel`. Variant
681    // order in this section matches the prelude files.
682    out.push_str("## Enums\n\n");
683    out.push_str("Platform-reflection (`Arch`, `Os`) and type-reflection (`TypeKind`, `Ownership`) enums. Declarations live in `prelude/target.gruel` and `prelude/type_info.gruel` respectively; the corresponding intrinsics (`@target_arch`, `@target_os`, `@type_info`, `@ownership`) materialize values of these types.\n\n");
684
685    for (name, variants) in [
686        (
687            "Arch",
688            &[
689                "X86_64", "Aarch64", "X86", "Arm", "Riscv32", "Riscv64", "Wasm32", "Wasm64",
690            ][..],
691        ),
692        (
693            "Os",
694            &["Linux", "Macos", "Windows", "Freestanding", "Wasi"][..],
695        ),
696        (
697            "TypeKind",
698            &["Struct", "Enum", "Int", "Bool", "Unit", "Never", "Array"][..],
699        ),
700        ("Ownership", &["Copy", "Affine", "Linear"][..]),
701        ("ThreadSafety", &["Unsend", "Send", "Sync"][..]),
702    ] {
703        out.push_str(&format!("### `{}`\n\n", name));
704        out.push_str("| Index | Variant |\n");
705        out.push_str("|---|---|\n");
706        for (i, v) in variants.iter().enumerate() {
707            out.push_str(&format!("| {} | `{}::{}` |\n", i, name, v));
708        }
709        out.push('\n');
710    }
711
712    // ---- Interfaces in detail ----
713    //
714    // ADR-0078 Phase 2: declarations live in `prelude/interfaces.gruel`.
715    // Names listed here as a directory; canonical signatures and method
716    // bodies are in the prelude file.
717    out.push_str("## Interfaces\n\n");
718    out.push_str("Compiler-recognized interfaces. Declarations live in `prelude/interfaces.gruel`; the compiler keys off the interface names for hardcoded behaviors. Conformance is structural — a type satisfies the interface when it provides matching methods.\n\n");
719
720    out.push_str("### `Drop`\n\n");
721    out.push_str("Types with custom cleanup logic that runs when the value goes out of scope (ADR-0059).\n\n");
722    out.push_str("**Required methods:**\n\n");
723    out.push_str("- `fn __drop(self)`\n\n");
724    out.push_str("**Conformance:** structural (no derive). Defining `fn __drop(self)` on a struct or enum makes it conform — there is no `@derive(Drop)` directive.\n\n");
725
726    out.push_str("### `Clone`\n\n");
727    out.push_str("Types that may be explicitly duplicated via `.clone()`. All `Copy` types auto-conform (ADR-0065).\n\n");
728    out.push_str("**Required methods:**\n\n");
729    out.push_str("- `fn clone(self: Ref(Self)) -> Self`\n\n");
730    out.push_str("**Conformance derive:** `@derive(Clone)` (compiler-recognized; no user `derive` declaration required). Synthesizes a `clone` method that recursively calls `clone` on every field (struct) or variant payload (enum). Synthesis fails if any field is not `Clone`. Rejected on `linear` types.\n\n");
731
732    out.push_str("### `Handle`\n\n");
733    out.push_str("Types that may be explicitly duplicated via `.handle()`, typically because the duplication has visible cost (refcount bumps, transaction forks). Unlike `Clone`, `Handle` is permitted on `linear` types (ADR-0075).\n\n");
734    out.push_str("**Required methods:**\n\n");
735    out.push_str("- `fn handle(self: Ref(Self)) -> Self`\n\n");
736    out.push_str("**Conformance:** structural (no derive). Defining `fn handle(self: Ref(Self)) -> Self` on a struct or enum makes it conform — there is no `@derive(Handle)` directive.\n\n");
737
738    // ---- Markers in detail ----
739    out.push_str("## Markers\n\n");
740    out.push_str("Markers are declaration-time-only attributes on struct/enum heads, written inside `@mark(...)` (ADR-0083). The marker set is closed; user-defined markers are out of scope. New markers must go through an ADR.\n\n");
741    for m in BUILTIN_MARKERS {
742        out.push_str(&format!("### `@mark({})`\n\n", m.name));
743        match m.kind {
744            MarkerKind::Posture(Posture::Copy) => out.push_str(
745                "Asserts the type is Copy. Under uniform structural inference, a struct/enum of all-Copy fields would already be Copy without the directive — `@mark(copy)` exists so the user can document intent and turn a silent posture downgrade (adding a non-Copy field later) into a declaration-site error.\n\n",
746            ),
747            MarkerKind::Posture(Posture::Affine) => out.push_str(
748                "Suppresses Copy inference. A type whose members would otherwise infer Copy is forced to remain Affine, so move-on-use semantics are preserved even when bitwise duplication is safe. Has no effect on Linear inference: a Linear member still propagates upward.\n\n",
749            ),
750            MarkerKind::Posture(Posture::Linear) => out.push_str(
751                "Forces the type to be Linear regardless of member postures. Use when the type has linear semantics that are not visible from its fields (e.g. an `i32` handle that is actually a kernel resource ID).\n\n",
752            ),
753            MarkerKind::ThreadSafety(ThreadSafety::Unsend) => out.push_str(
754                "Downgrades the type's thread-safety classification to `Unsend`, even if its members would structurally permit `Send` or `Sync`. Always safe — the marker only restricts. Use when the type has thread-affine state that isn't visible from its fields (e.g. a handle to a thread-local resource).\n\n",
755            ),
756            MarkerKind::ThreadSafety(ThreadSafety::Send) => out.push_str(
757                "Asserts the type is `Send`, even if a member's type would structurally pull it down to `Unsend` (e.g. a raw pointer field). The compiler cannot verify this — the `checked_` prefix flags it as a user assertion (analogous to Rust's `unsafe impl Send`). Mis-applying breaks data-race freedom; the user takes responsibility.\n\n",
758            ),
759            MarkerKind::ThreadSafety(ThreadSafety::Sync) => out.push_str(
760                "Asserts the type is `Sync`, even if its structural minimum would be `Send` or `Unsend`. The compiler cannot verify this — the `checked_` prefix flags it as a user assertion (analogous to Rust's `unsafe impl Sync`). Mis-applying breaks data-race freedom; the user takes responsibility.\n\n",
761            ),
762            MarkerKind::Abi(Abi::C) => out.push_str(
763                "Selects the C ABI / C layout (ADR-0085). On a function, uses the platform C calling convention and suppresses Gruel name mangling. On a struct, switches to C field layout (declaration order, natural alignment, no reordering, niches disabled), making the type eligible to cross the FFI boundary by value.\n\n",
764            ),
765            MarkerKind::Unchecked => out.push_str(
766                "Declares a fn (top-level, method, interface method, or FFI import) as unchecked (ADR-0088). Every caller must wrap the call in a `checked { }` block; the body's soundness rests on a caller-asserted precondition the type system can't verify. Replaces the legacy `unchecked` keyword and extends the surface to methods, interface method signatures, and FFI imports under a single uniform spelling.\n\n",
767            ),
768        }
769    }
770
771    out
772}
773
774#[cfg(test)]
775mod tests {
776    use super::*;
777
778    #[test]
779    fn test_builtin_enum_names() {
780        assert_eq!(
781            BUILTIN_ENUM_NAMES,
782            &["Arch", "Os", "TypeKind", "Ownership", "ThreadSafety"]
783        );
784    }
785
786    #[test]
787    fn test_builtin_type_constructors_registry() {
788        // ADR-0061: Ptr / MutPtr. ADR-0062: Ref / MutRef. ADR-0064: Slice /
789        // MutSlice. ADR-0066: Vec.
790        assert_eq!(BUILTIN_TYPE_CONSTRUCTORS.len(), 7);
791    }
792
793    #[test]
794    fn test_get_builtin_type_constructor() {
795        let ptr = get_builtin_type_constructor("Ptr").unwrap();
796        assert_eq!(ptr.name, "Ptr");
797        assert_eq!(ptr.arity, 1);
798        assert_eq!(ptr.kind, BuiltinTypeConstructorKind::Ptr);
799
800        let mut_ptr = get_builtin_type_constructor("MutPtr").unwrap();
801        assert_eq!(mut_ptr.name, "MutPtr");
802        assert_eq!(mut_ptr.arity, 1);
803        assert_eq!(mut_ptr.kind, BuiltinTypeConstructorKind::MutPtr);
804
805        assert!(get_builtin_type_constructor("MyConstructor").is_none());
806    }
807
808    #[test]
809    fn test_is_reserved_type_constructor_name() {
810        assert!(is_reserved_type_constructor_name("Ptr"));
811        assert!(is_reserved_type_constructor_name("MutPtr"));
812        assert!(!is_reserved_type_constructor_name("MyConstructor"));
813    }
814}