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:

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:

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")

Module contents

Types

TypeKindDescription
AnnotationrecordOne typed annotation application, retaining source order.
AnnotationArgumentrecordOne checked member supplied to a reflected typed annotation.
EntryrecordOne named or positional edge in the indexed semantic type graph.
ExtensionKeyinterfaceA typed identity for data derived lazily from a reflection descriptor, schema, or binding.
FieldrecordOne stored field projected directly from the reflected root type.
FieldCodecrecordA keyed runtime codec materialized from a reflected record.
FieldCodecBlueprintrecordAn opaque field-codec recipe returned inside comptime.
InforecordAn immutable compile-time description of one resolved semantic type.
NoderecordOne node in an Info semantic type graph.
SoAFieldrecordOne top-level stored struct field available as a SoA column.
SoAInforecordTarget-independent semantic inputs to SoA storage derivation.

Functions

FunctionKindDescription
extensionKeyfunctionCreates an identity for one lazily derived metadata value.
fieldCodeccomptime functionBuilds a keyed field-codec recipe from a reflected record.

Types#

Annotationrecord#

record Annotation
    readonly name: string
    readonly arguments: {AnnotationArgument}
end

One typed annotation application, retaining source order.

Fields

name#
name: string

AnnotationArgumentrecord#

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.

@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#
name: string
kind#
kind: "value" | "nil" | "type"
value#
type#
type: integer?

Entryrecord#

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#
name: string?
type#
type: integer?
read#
read: integer?
write#
write: integer?
readable#
readable: boolean?
writable#
writable: boolean?
hasDefault#
hasDefault: boolean?
defaultValue#
defaultValue: any
mode#
mode: string?
bound#
bound: integer?
answer#
answer: integer?
default#
default: boolean?
annotations#
annotations: {Annotation}?

ExtensionKeyinterface#

interface ExtensionKey<T>
    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<T>, 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

NameDescription
T

Fields

id#
id: integer
name#
name: string?

Fieldrecord#

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.

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#
name: string
kind#
kind: string
typeName#
typeName: string?
type#
type: integer?
readable#
readable: boolean
writable#
writable: boolean
hasDefault#
hasDefault: boolean
defaultValue#
defaultValue: any
annotations#
annotations: {Annotation}

FieldCodecrecord#

record FieldCodec<T>
    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.

local record Position
    x: number
    y: number
end

const PositionCodec: nupp.reflect.FieldCodec<Position> = 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

NameDescription
T

Methods

encode#
encode: function(self, value: T): {[string]: any}
Arguments
NameTypeDescription
?self
valueT
Returns
TypeDescription
{[string]: any}
decode#
decode: function(self, value: {[string]: any}): (T?, string?)
Arguments
NameTypeDescription
?self
value{[string]: any}
Returns
TypeDescription
T?
string?

Fields

fingerprint#
fingerprint: string

FieldCodecBlueprintrecord#

record FieldCodecBlueprint
end

An opaque field-codec recipe returned inside comptime.

Inforecord#

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.

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#
schema: integer
root#
root: integer
types#
types: {Node}
kind#
kind: string
name#
name: string
qualifiedName#
qualifiedName: string
fields#
annotations#
annotations: {Annotation}
soa#
soa: SoAInfo
fingerprint#
fingerprint: string

Noderecord#

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.

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#
kind: string
name#
name: string?
nominal#
nominal: boolean?
annotations#
annotations: {Annotation}?
fields#
staticFields#
staticFields: {Entry}?
metamethods#
metamethods: {Entry}?
nestedTypes#
nestedTypes: {Entry}?
associatedTypes#
associatedTypes: {Entry}?
members#
members: {integer}?
parameters#
returns#
returns: {integer}?
typeParameters#
typeParameters: {integer}?
typeBounds#
typeBounds: {integer}?
packParameters#
packParameters: {integer}?
constParameters#
constParameters: {integer}?
parameterKinds#
parameterKinds: {string}?
typeArguments#
typeArguments: {integer}?
packArguments#
packArguments: {integer}?
constArguments#
constArguments: {integer}?
supertypes#
supertypes: {integer}?
element#
element: integer?
body#
body: integer?
of#
of: integer?
origin#
origin: integer?
noReturn#
noReturn: boolean?
noYield#
noYield: boolean?

SoAFieldrecord#

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#
name: string
identity#
identity: string
ordinal#
ordinal: integer
type#
type: integer?
ctype#
ctype: string?
eligible#
eligible: boolean

SoAInforecord#

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.

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#
schema: integer
eligible#
eligible: boolean
reason#
reason: string?
fields#

Functions#

extensionKeyfunction#

local extensionKey: function<T>(build: function(host: any): T): ExtensionKey<T>

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

NameDescription
T

Arguments

NameTypeDescription
buildfunction(host: any): T

Returns

TypeDescription
ExtensionKey<T>

fieldCodeccomptime function#

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<Record> declaration. It may pass through typed comptime helpers on the way to that materialization boundary.

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<Message> = 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

NameTypeDescription
infoInfo

Returns

TypeDescription
FieldCodecBlueprint