# `nupp.reflect` Semantic type reflection and reflection-driven field codecs. Call the namespace with a concrete type inside `comptime` to obtain an immutable, target-independent descriptor. `fieldCodec` materializes the stored fields of a reflected record into a keyed runtime codec. The root's common facts are projected onto `Info`, so the usual inspection does not need to walk the graph: ```nupp local record User id: integer name: string = "anonymous" end const UserSummary: string = comptime do local info = nupp.reflect(User) assert(info.kind == "record") assert(info.fields[2].hasDefault) return info.fields[1].name .. ":" .. info.fields[1].kind end assert(UserSummary == "id:integer") ``` Use `root`, `types`, and the integer edges between them when a generator needs the complete semantic type rather than the root projection. The graph remains finite even when the declaration is recursive: ```nupp local record Node value: string next: Node? end const NextKind: string = comptime do local info = nupp.reflect(Node) local edge = info.fields[2].type as integer return info.types[edge].kind end assert(NextKind == "union") ``` ## Types ### `Annotation` _record_ ```nupp record Annotation readonly name: string readonly arguments: {AnnotationArgument} end ``` One typed annotation application, retaining source order. #### Fields ##### `name` ```nupp name: string ``` ##### `arguments` ```nupp arguments: {AnnotationArgument} ``` ### `AnnotationArgument` _record_ ```nupp record AnnotationArgument readonly name: string readonly kind: "value" | "nil" | "type" readonly value: any? readonly type: integer? end ``` One checked member supplied to a reflected typed annotation. `value` is present for `kind = "value"`; `type` is an index into the owning `Info.types` graph for `kind = "type"`. `kind = "nil"` preserves an explicitly supplied nil without pretending an absent table field contains it. ```nupp @annotation(targets = {"field"}) local record serialized name: string? @ref codec: any end local record StringCodec end local record User @serialized(name = "user_id", codec = StringCodec) id: integer end const Serialization: string = comptime do local info = nupp.reflect(User) local arguments = info.fields[1].annotations[1].arguments assert(arguments[1].kind == "value") assert(arguments[2].kind == "type") local codec = info.types[arguments[2].type as integer] return (arguments[1].value as string) .. ":" .. (codec.name as string) end assert(Serialization == "user_id:StringCodec") ``` #### Fields ##### `name` ```nupp name: string ``` ##### `kind` ```nupp kind: "value" | "nil" | "type" ``` ##### `value` ```nupp value: any? ``` ##### `type` ```nupp type: integer? ``` ### `Entry` _record_ ```nupp record Entry readonly name: string? readonly type: integer? readonly read: integer? readonly write: integer? readonly readable: boolean? readonly writable: boolean? readonly hasDefault: boolean? readonly defaultValue: any readonly mode: string? readonly bound: integer? readonly answer: integer? readonly default: boolean? readonly annotations: {Annotation}? readonly [string]: any end ``` One named or positional edge in the indexed semantic type graph. #### Fields ##### `name` ```nupp name: string? ``` ##### `type` ```nupp type: integer? ``` ##### `read` ```nupp read: integer? ``` ##### `write` ```nupp write: integer? ``` ##### `readable` ```nupp readable: boolean? ``` ##### `writable` ```nupp writable: boolean? ``` ##### `hasDefault` ```nupp hasDefault: boolean? ``` ##### `defaultValue` ```nupp defaultValue: any ``` ##### `mode` ```nupp mode: string? ``` ##### `bound` ```nupp bound: integer? ``` ##### `answer` ```nupp answer: integer? ``` ##### `default` ```nupp default: boolean? ``` ##### `annotations` ```nupp annotations: {Annotation}? ``` ### `ExtensionKey` _interface_ ```nupp interface ExtensionKey readonly id: integer readonly name: string? end ``` A typed identity for data derived lazily from a reflection descriptor, schema, or binding. It has the shape of `nupp.store.Key`, declared here because the prelude is read before any module is. `_valueType` is never present; it mentions `T` in both positions so that keys are invariant. #### Type parameters | Name | Description | | --- | --- | | `T` | | #### Fields ##### `id` ```nupp id: integer ``` ##### `name` ```nupp name: string? ``` ### `Field` _record_ ```nupp record Field readonly name: string readonly kind: string readonly typeName: string? readonly type: integer? readonly readable: boolean readonly writable: boolean readonly hasDefault: boolean readonly defaultValue: any readonly annotations: {Annotation} end ``` One stored field projected directly from the reflected root type. The projection preserves declaration order, access capabilities, defaults, and annotations without requiring a lookup in `Info.types`. ```nupp local record Options format: string = "compact" retries: integer end const DefaultFormat: string = comptime do local field = nupp.reflect(Options).fields[1] assert(field.name == "format" and field.hasDefault) assert(field.readable and field.writable) return field.defaultValue as string end assert(DefaultFormat == "compact") ``` #### Fields ##### `name` ```nupp name: string ``` ##### `kind` ```nupp kind: string ``` ##### `typeName` ```nupp typeName: string? ``` ##### `type` ```nupp type: integer? ``` ##### `readable` ```nupp readable: boolean ``` ##### `writable` ```nupp writable: boolean ``` ##### `hasDefault` ```nupp hasDefault: boolean ``` ##### `defaultValue` ```nupp defaultValue: any ``` ##### `annotations` ```nupp annotations: {Annotation} ``` ### `FieldCodec` _record_ ```nupp record FieldCodec encode: function(self, value: T): {[string]: any} decode: function(self, value: {[string]: any}): (T?, string?) fingerprint: string end ``` A keyed runtime codec materialized from a reflected record. `encode` copies present declared fields into a plain keyed table. The stable fingerprint records the field names in declaration order. ```nupp local record Position x: number y: number end const PositionCodec: nupp.reflect.FieldCodec = comptime do return nupp.reflect.fieldCodec(nupp.reflect(Position)) end local encoded = PositionCodec:encode(new Position(x = 3, y = 4)) assert(encoded.x == 3 and encoded.y == 4) assert(PositionCodec.fingerprint == "t:x,y") ``` #### Type parameters | Name | Description | | --- | --- | | `T` | | #### Methods ##### `encode` ```nupp encode: function(self, value: T): {[string]: any} ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | | `value` | `T` | | ###### Returns | Type | Description | | --- | --- | | `{\[string\]: any}` | | ##### `decode` ```nupp decode: function(self, value: {[string]: any}): (T?, string?) ``` ###### Arguments | Name | Type | Description | | --- | --- | --- | | `?` | `self` | | | `value` | `{\[string\]: any}` | | ###### Returns | Type | Description | | --- | --- | | `T?` | | | `string?` | | #### Fields ##### `fingerprint` ```nupp fingerprint: string ``` ### `FieldCodecBlueprint` _record_ ```nupp record FieldCodecBlueprint end ``` An opaque field-codec recipe returned inside `comptime`. ### `Info` _record_ ```nupp record Info readonly schema: integer readonly root: integer readonly types: {Node} readonly kind: string readonly name: string readonly qualifiedName: string readonly fields: {Field} readonly annotations: {Annotation} readonly soa: SoAInfo readonly fingerprint: string end ``` An immutable compile-time description of one resolved semantic type. `Info` can cross typed comptime helper boundaries. Its fingerprint is suitable for cache keys because it changes with the canonical type graph, defaults, and checked annotations rather than with compiler-local identities. ```nupp local record Pair left: string right: integer end local comptime function describe(info: nupp.reflect.Info): string return info.fingerprint .. ":" .. tostring(#info.fields) end const PairDescription: string = comptime do return describe(nupp.reflect(Pair)) end assert(PairDescription:match(":2$") ~= nil) ``` #### Fields ##### `schema` ```nupp schema: integer ``` ##### `root` ```nupp root: integer ``` ##### `types` ```nupp types: {Node} ``` ##### `kind` ```nupp kind: string ``` ##### `name` ```nupp name: string ``` ##### `qualifiedName` ```nupp qualifiedName: string ``` ##### `fields` ```nupp fields: {Field} ``` ##### `annotations` ```nupp annotations: {Annotation} ``` ##### `soa` ```nupp soa: SoAInfo ``` ##### `fingerprint` ```nupp fingerprint: string ``` ### `Node` _record_ ```nupp record Node readonly kind: string readonly name: string? readonly nominal: boolean? readonly annotations: {Annotation}? readonly fields: {Entry}? readonly staticFields: {Entry}? readonly metamethods: {Entry}? readonly nestedTypes: {Entry}? readonly associatedTypes: {Entry}? readonly members: {integer}? readonly parameters: {Entry}? readonly returns: {integer}? readonly typeParameters: {integer}? readonly typeBounds: {integer}? readonly packParameters: {integer}? readonly constParameters: {integer}? readonly parameterKinds: {string}? readonly typeArguments: {integer}? readonly packArguments: {integer}? readonly constArguments: {integer}? readonly supertypes: {integer}? readonly element: integer? readonly body: integer? readonly of: integer? readonly origin: integer? readonly noReturn: boolean? readonly noYield: boolean? readonly [string]: any end ``` One node in an `Info` semantic type graph. Fields, parameters, results, union members, generic arguments, and wrappers point to other nodes by their integer index. Resolve only the edges relevant to the node's `kind`. ```nupp local type Handler = function(message: string, attempts: integer): boolean const HandlerShape: string = comptime do local info = nupp.reflect(Handler) local handler = info.types[info.root] assert(handler.kind == "func") local first = (handler.parameters as {nupp.reflect.Entry})[1] local input = info.types[first.type as integer] local output = info.types[(handler.returns as {integer})[1]] return (first.name as string) .. ":" .. input.kind .. "->" .. output.kind end assert(HandlerShape == "message:string->boolean") ``` #### Fields ##### `kind` ```nupp kind: string ``` ##### `name` ```nupp name: string? ``` ##### `nominal` ```nupp nominal: boolean? ``` ##### `annotations` ```nupp annotations: {Annotation}? ``` ##### `fields` ```nupp fields: {Entry}? ``` ##### `staticFields` ```nupp staticFields: {Entry}? ``` ##### `metamethods` ```nupp metamethods: {Entry}? ``` ##### `nestedTypes` ```nupp nestedTypes: {Entry}? ``` ##### `associatedTypes` ```nupp associatedTypes: {Entry}? ``` ##### `members` ```nupp members: {integer}? ``` ##### `parameters` ```nupp parameters: {Entry}? ``` ##### `returns` ```nupp returns: {integer}? ``` ##### `typeParameters` ```nupp typeParameters: {integer}? ``` ##### `typeBounds` ```nupp typeBounds: {integer}? ``` ##### `packParameters` ```nupp packParameters: {integer}? ``` ##### `constParameters` ```nupp constParameters: {integer}? ``` ##### `parameterKinds` ```nupp parameterKinds: {string}? ``` ##### `typeArguments` ```nupp typeArguments: {integer}? ``` ##### `packArguments` ```nupp packArguments: {integer}? ``` ##### `constArguments` ```nupp constArguments: {integer}? ``` ##### `supertypes` ```nupp supertypes: {integer}? ``` ##### `element` ```nupp element: integer? ``` ##### `body` ```nupp body: integer? ``` ##### `of` ```nupp of: integer? ``` ##### `origin` ```nupp origin: integer? ``` ##### `noReturn` ```nupp noReturn: boolean? ``` ##### `noYield` ```nupp noYield: boolean? ``` ### `SoAField` _record_ ```nupp record SoAField readonly name: string readonly identity: string readonly ordinal: integer readonly type: integer? readonly ctype: string? readonly eligible: boolean end ``` One top-level stored struct field available as a SoA column. #### Fields ##### `name` ```nupp name: string ``` ##### `identity` ```nupp identity: string ``` ##### `ordinal` ```nupp ordinal: integer ``` ##### `type` ```nupp type: integer? ``` ##### `ctype` ```nupp ctype: string? ``` ##### `eligible` ```nupp eligible: boolean ``` ### `SoAInfo` _record_ ```nupp record SoAInfo readonly schema: integer readonly eligible: boolean readonly reason: string? readonly fields: {SoAField} end ``` Target-independent semantic inputs to SoA storage derivation. This says whether the declaration can be split into columns and identifies those columns. It deliberately does not contain target sizes or offsets; use `nupp.mem.soa.layoutof` for those. ```nupp local struct Particle x: float y: float end const ParticleColumns: string = comptime do local soa = nupp.reflect(Particle).soa assert(soa.eligible and #soa.fields == 2) assert(soa.fields[1].ordinal == 1) return soa.fields[1].name .. "," .. soa.fields[2].name end assert(ParticleColumns == "x,y") ``` #### Fields ##### `schema` ```nupp schema: integer ``` ##### `eligible` ```nupp eligible: boolean ``` ##### `reason` ```nupp reason: string? ``` ##### `fields` ```nupp fields: {SoAField} ``` ## Functions ### `extensionKey` _function_ ```nupp local extensionKey: function(build: function(host: any): T): ExtensionKey ``` Creates an identity for one lazily derived metadata value. A descriptor, schema, or binding builds a key's value once, then answers the same value from its private cache. Key identities and their slot numbers are process-local and must not be persisted. #### Type parameters | Name | Description | | --- | --- | | `T` | | #### Arguments | Name | Type | Description | | --- | --- | --- | | `build` | `function(host: any): T` | | #### Returns | Type | Description | | --- | --- | | `ExtensionKey\` | | ### `fieldCodec` _comptime function_ ```nupp local fieldCodec: comptime function(info: Info): FieldCodecBlueprint ``` Builds a keyed field-codec recipe from a reflected record. The opaque recipe must directly initialize a matching `FieldCodec` declaration. It may pass through typed comptime helpers on the way to that materialization boundary. ```nupp local record Message id: integer text: string end local comptime function keyed( info: nupp.reflect.Info ): nupp.reflect.FieldCodecBlueprint return nupp.reflect.fieldCodec(info) end const MessageCodec: nupp.reflect.FieldCodec = comptime do return keyed(nupp.reflect(Message)) end local encoded = MessageCodec:encode(new Message(id = 1, text = "hello")) assert(encoded.id == 1 and encoded.text == "hello") ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `info` | `Info` | | #### Returns | Type | Description | | --- | --- | | `FieldCodecBlueprint` | |