Type Reflection¶
Accepted
Accepted for the V1 public Type reflection API surface and V1 reflection/deferred boundaries; advanced reflection work is deferred from V1.
Catalyst exposes type metadata through comptime-only Type values and related reflection values. This document records the reflection API shape used by structural constraints, semantic contracts, diagnostics, and tooling.
Type Reflection API¶
Primitive type reflection uses methods on comptime Type values when the reflected type is the receiver:
T.is_concrete() Type.Predicate
T.is_constraint() Type.Predicate
T.is_sized() Type.Predicate
T.is_structural() Type.Predicate
T.is_contract() Type.Predicate
T.is_aggregate() Type.Predicate
T.has_fields() Type.Predicate
T.is_dyn() Type.Predicate
T.is_optional() Type.Predicate
T.is_enum() Type.Predicate
T.is_enum_union() Type.Predicate
Primitive is_* type predicates are total classifiers on every Type value. They return Type.Predicate when a true result grants sema a reusable fact; the predicate is still boolean-testable in ordinary conditions. Richer metadata APIs remain fallible and report structured wrong-kind errors.
T.is_dyn() is true only for the concrete unsized erased type form dyn C. It is false for sized pointer and owner types around a dyn payload, such as *dyn C, *const dyn C, and Box(dyn C).
T.is_optional() is true only for optional type forms such as ?T. T.is_enum() is true only for plain tag-only enum types. T.is_enum_union() is true only for enum-union types.
Dyn type metadata exposes the erased visible contract surface:
T.dyn_contract() Type!Type.ReflectionError
dyn_contract() is valid only when T.is_dyn() is true. It returns the fully applied semantic contract constraint surface C from dyn C.
Constraint classifiers distinguish pure constraint families from mixed intersections. is_contract() is true for semantic contract constraints, including normalized intersections made only of semantic contract constraints. is_structural() is true for structural constraints, including normalized intersections made only of structural constraints. A mixed structural/semantic intersection is a constraint, so is_constraint() is true, but both is_contract() and is_structural() are false. Concrete runtime types, including concrete unsized dyn C, are not constraints.
Classifier examples:
| Type expression | is_concrete() |
is_constraint() |
is_sized() |
is_structural() |
is_contract() |
is_aggregate() |
has_fields() |
is_dyn() |
|---|---|---|---|---|---|---|---|---|
i32 |
||||||||
satisfies(.{ ... }) |
||||||||
Sequence(f32) |
||||||||
satisfies(.{ ... }) & Sequence(f32) |
||||||||
struct { ... } |
||||||||
[]f32 |
||||||||
dyn Iterator(i32) |
||||||||
*dyn Iterator(i32) |
||||||||
Box(dyn Iterator(i32)) |
Table 1. = true; = false.
Reflection should return ordinary deterministic comptime values, not opaque compiler handles unless there is no practical alternative.
Reflection metadata declarations below describe public members exposed through Type. They use ordinary const Name = struct { ... } or const Name = enum { ... } bindings because qualified declarations such as struct Type.Name { ... } are not Catalyst source syntax.
Reflection APIs use ordinary Catalyst error returns. Simple kind and property checks return bool when they do not grant reusable facts; fact-bearing checks return Type.Predicate. Metadata APIs report structured errors instead of silently returning empty data for wrong-kind inputs:
if T.has_fields() {
const fields = try T.fields()
}
T.fields() []const Type.Field!Type.ReflectionError
has_fields() is the guard for fields(). It is true for concrete aggregate types with source fields and for selected built-in field-bearing types such as slices. is_aggregate() remains narrower: it is true for concrete aggregate declarations, not for built-in descriptor types or structural constraints. Pointer field-access sugar does not make pointer types field-bearing; *Point.has_fields() is false even though ptr.x may access Point.x.
Concrete declaration reflection is separate from constraint requirement reflection:
T.fields()
T.inherent_functions()
Shape.required_fields()
Shape.required_functions()
Structural requirement metadata is documented in Structural Satisfaction Reflection.
Optional and enum reflection uses focused APIs:
T.optional_payload() Type!Type.ReflectionError
T.enum_cases() []const Type.EnumCase!Type.ReflectionError
T.enum_union_members() []const Type!Type.ReflectionError
optional_payload() unwraps one optional layer. enum_cases() is valid only for plain enum types and returns declaration-order case metadata. enum_union_members() is valid only for enum-union types and returns member enum types in normalized deterministic order. See Optional Types and Enums.
Field metadata includes field access:
const FieldAccess = enum {
read,
readwrite,
}
T.fields() and related source-visible member reflection include builtin or synthetic members, such as slice .ptr and .len, and mark their origin in the returned metadata.
const DeclOrigin = enum {
source,
builtin,
synthetic,
}
const Field = struct {
name: []const u8
ty: Type
visibility: Visibility
access: Type.FieldAccess
default: ?Type.DefaultExpression
docs: ?[]const u8
attributes: []const Type.Attribute
source: ?SourceLocation
origin: Type.DeclOrigin
}
default is declaration metadata for the expression used when aggregate construction omits the field. It uses the same Type.DefaultExpression metadata shape as function parameter defaults: source location and optional normalized source text. default = null means the field must be explicitly initialized by aggregate construction.
Fields are mutable by default. const fields are initialized during construction and copied during value copy, but cannot be assigned through field access after initialization. Mutable and const fields may have comptime-only default values. Field defaults are construction metadata, not runtime layout, ABI shape, assignment compatibility, or structural/contract satisfaction.
Type.Value and public Type.Expr are deferred from V1. V1 reflection exposes source spans, optional normalized source text, declaration metadata, and semantic facts through focused APIs rather than a general reflected value or expression tree model. Attribute arguments and default expressions are provenance metadata in V1; they are not reusable reflected values.
Type.Field.access is shallow field-slot access. A normal mutable field is .readwrite; a const field is .read. This does not describe deep or referent mutability of the field value. For example, const data: []f32 has field access .read, while the field type []f32 still describes mutable element access through the slice value.
Source-visible builtin fields follow the same metadata model. Slice .ptr and .len are mutable descriptor fields with origin = .builtin for both []T and []const T. Their Field.access is .readwrite at the type metadata level; whether a particular slice expression can assign to the descriptor fields is ordinary expression mutability. Arrays expose no fields in V1; array length is type metadata and is available through reflection APIs and the array's Sequence(T) conformance.
Namespace member enumeration is not part of public V1 type reflection. Namespace values remain minimal comptime lookup values for module/include/import mechanics and compiler-defined namespace member syntax.