ADR-0061: Generic Pointer Types
Status
Implemented
Summary
Replace Gruel's bespoke pointer keyword syntax (ptr const T, ptr mut T) with comptime-generic builtin types Ptr(T) and MutPtr(T). The internal type representation is unchanged — Ptr(T) lowers to today's TypeKind::PtrConst and MutPtr(T) to TypeKind::PtrMut. This is purely a surface-syntax change, paired with new infrastructure (a closed BuiltinTypeConstructor registry) that ADR-0062 will reuse for Ref/MutRef.
Context
Gruel currently has three syntactic forms for "a type parameterized by another type":
- Pointer keyword syntax (ADR-0028):
ptr const T,ptr mut T. Special-cased in the parser (TypeExpr::PointerConst/PointerMut) and the type system (TypeKind::PtrConst/PtrMut). - Borrow parameter modes (ADR-0013):
borrow x: T,inout x: T. Not types — parameter modes only. Out of scope for this ADR; addressed by ADR-0062. - Comptime generic functions (ADR-0025):
fn Vec(comptime T: type) -> type { struct { ... } }. The mature, user-facing form for everything except pointers and borrows.
The friction is presentational: a programmer writing generic code touches multiple syntactic forms for what feels like the same concept. ADR-0028 already noted as a design goal that pointers should "work with existing type system features (comptime generics, borrow modes)" — this ADR finishes that thought for pointers.
Decision
Replace ptr const T / ptr mut T with Ptr(T) / MutPtr(T).
// Before
checked {
let p: ptr mut i32 = @int_to_ptr(0x1000);
let next: ptr mut i32 = @ptr_offset(p, 1);
}
// After
checked {
let p: MutPtr(i32) = @int_to_ptr(0x1000);
let next: MutPtr(i32) = @ptr_offset(p, 1);
}
Implementation shape
Introduce a BuiltinTypeConstructor registry alongside BUILTIN_TYPES in gruel-builtins. Each entry has:
name: &'static str— the type-constructor name (e.g."Ptr","MutPtr").arity: usize— number of comptime type arguments.lower: fn(&[TypeId]) -> TypeKind— function mapping type arguments to an existingTypeKind.
The parser sees Ptr(i32) as an ordinary call in type position. Sema resolves the callee against the constructor registry and produces TypeKind::PtrConst(intern(i32)). The internal IR is unchanged — only the surface syntax differs.
Alternative considered: define Ptr / MutPtr as user-visible functions in a prelude (fn Ptr(comptime T: type) -> type { @intrinsic_ptr_const(T) }). More uniform, but requires a "comptime intrinsic returning a type" mechanism. Deferred to future work.
What does not change
- All pointer intrinsics (
@ptr_read,@ptr_write,@ptr_offset,@raw,@raw_mut,@null_ptr,@is_null,@int_to_ptr,@ptr_to_int,@ptr_copy,@syscall). checked-block enforcement.- Borrow modes (
borrow/inout) — see ADR-0062 for that. - Internal
TypeKind::PtrConst/TypeKind::PtrMutrepresentation.
Migration
Cut over once feature-complete:
- Implement the new syntax behind the
generic_pointer_typespreview flag. - Codemod the entire test suite, scratch programs, and ADR examples.
- Once everything compiles on the new syntax, remove the old syntax in the same commit that stabilizes the feature.
Implementation Phases
- Phase 1: Builtin type-constructor infrastructure — extend
gruel-builtinswithBuiltinTypeConstructor { name, arity, lower }. Inject into the global namespace alongsideBUILTIN_TYPES. No behavior change yet (registry is empty/unused). - Phase 2: Parser/sema for
Ptr(T)/MutPtr(T)— accept call-style type expressions in type position when the callee resolves to a builtin constructor. LowerPtr(T)to existingTypeKind::PtrConst,MutPtr(T)toTypeKind::PtrMut. Gate behind thegeneric_pointer_typespreview flag. - Phase 3: Diagnostics — error/info messages display
Ptr(T)/MutPtr(T)instead ofptr const T/ptr mut T. The canonical format is unconditional (not gated on the preview flag), since the old surface form is being removed in phase 6. Intrinsic registry descriptions and examples updated in lockstep. - Phase 4: Codemod — convert spec tests, UI tests, scratch programs to the new syntax. Each affected case picks up
preview = "generic_pointer_types"andpreview_should_pass = true; phase 6 removes both. ADR-0028 examples are left as-is for historical accuracy. - Phase 5: Spec rewrite — update
docs/spec/src/09-unchecked-code/{01-syntax,02-intrinsics}.mdto documentPtr(T)/MutPtr(T). ADR-0028's surface-syntax sections are now historical (noted inline in the spec); the rest of ADR-0028 remains accurate so the ADR itself is left unchanged. - Phase 6: Remove old syntax and stabilize — drop
TypeExpr::PointerConst/PointerMut, theptrkeyword from the lexer/parser, allrequire_preview()calls forgeneric_pointer_types, and thePreviewFeature::GenericPointerTypesenum variant. Update ADR status toimplemented.
Consequences
Positive
- Uniform surface form for pointers and other generic types (
Vec(T),Option(T), etc.). - Reusable infrastructure: the
BuiltinTypeConstructorregistry is a building block for ADR-0062 (Ref/MutRef). - One less keyword (
ptr).
Negative
- Test churn: every spec test, UI test, and example mentioning pointers is rewritten.
- ADR-0028 partially superseded: its surface-syntax sections become historical.
Neutral
- No semantic change: pointers work exactly as before.
- No codegen change: internal IR identical.
Resolved Questions
- Should the builtin constructor registry be open to user code? No — closed for now, matching today's "synthetic structs are compiler-only" posture. Opening it is future work and would be its own ADR.
- Migration approach? Parallel grammars during phases 1–5, cut over in phase 6.
Open Questions
None.
Future Work
- Non-null pointer types
NonNullPtr(T)(per ADR-0028 future-work). - User-defined type constructors — let user code register types parameterized like the built-in
Ptr(e.g., for representation tricks). Closed today; opening would be a separate ADR.
References
- ADR-0020: Built-in Types as Structs
- ADR-0025: Comptime
- ADR-0028: Unchecked Code and Raw Pointers
- ADR-0042: Comptime Metaprogramming
- ADR-0062: Reference Types Replacing Borrow Modes (companion ADR)
- Swift UnsafePointer