# `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` | |