# `nupp.derive`
`nupp.derive` holds the three bundled derive providers and the recipe API a
package uses to publish its own. `@derive` names a provider on a record or
struct declaration. The provider decides which targets it admits and adds the
closed set of checked members or data that it returns.
The bundled recipes render by appending into a `string.buffer`, so a `lua51`
target needs a `text.buffer` provider selected. Without one, a `@derive` naming a
provider reports `NUPP3012` on the annotation that named it. The browser backend
selects one, so a page needs nothing further.
```nupp:playground
@derive(nupp.derive.Debug, nupp.derive.JSON)
local record User
@json(name = "user_id")
id: integer
name: string = "anonymous"
tags: {string} = {}
end
local user = new User()
local out = string.buffer.new()
local writer = nupp.codec.json.writer(out)
user:writeJSON(writer)
writer:close()
print(user:debug(), out:tostring())
```
Applying a provider is a declaration-augmentation phase, not a text macro: it
cannot add imports, top-level declarations, modules, records, interfaces, or
independently nameable types. The bundled providers are:
- [`nupp.derive.Debug`](#debug): `debug(self): string` and `nupp.Debug`
conformance.
- [`nupp.derive.JSON`](#json): `writeJSON(writer)`, a static `fromJSON`,
`fieldCodec`, and
`nupp.codec.json.JSONEncodable` conformance.
- [`nupp.derive.Serde`](#serde): one format-neutral schema and physical binding
for a record or struct, with no generated format methods.
[`nupp.events.Event`](#event) is applied the same way and marks a record or a
fixed-layout struct as an event a [](nupp.events) source constructs and
delivers.
Generated members participate in normal member lookup, generic inference, and
interface checking. A written member of the same name is a compile-time
conflict. Stacked `@derive` applications combine, but a provider cannot be
requested twice. See [annotations.md](annotations.md#derive) for where `@derive`
sits among the built-in annotations.
## Debug
`debug(self): string` renders a record or fixed-layout struct the way the
declaration reads, so what comes back names the declaration and its fields in
declaration order. The derive records a format-neutral schema and physical
binding; the generic formatter prepares and caches its traversal on first use.
```nupp
@derive(nupp.derive.Debug)
local record Point
x: integer
y: integer
end
local p = new Point(x = 3, y = -1)
print(p:debug())
```
```text
Point { x = 3, y = -1 }
```
Strings are quoted, map keys are sorted by byte order so two runs agree, nested
records render through their own `debug`, and a runtime table that reaches
itself renders as ``.
```nupp
@derive(nupp.derive.Debug)
local record Tag
name: string
end
@derive(nupp.derive.Debug)
local record Post
title: string
views: integer
tags: {Tag}
scores: {[string]: integer}
end
local post = new Post(
title = "hello",
views = 12,
tags = {new Tag(name = "a"), new Tag(name = "b")},
scores = {zeta = 1, alpha = 2}
)
print(post:debug())
```
```text
Post { title = "hello", views = 12, tags = {Tag { name = "a" }, Tag { name = "b" }}, scores = {["alpha"] = 2, ["zeta"] = 1} }
```
### Field visibility
A `@debug` field annotation decides what a field contributes. `redact` keeps the
name and replaces the value, which is what a secret wants; `skip` removes the
field from the output entirely.
```nupp
@derive(nupp.derive.Debug)
local record Credentials
user: string
@debug(redact = true)
password: string
@debug(skip = true)
cache: any
end
local c = new Credentials(user = "ada", password = "hunter2", cache = {1, 2})
print(c:debug())
```
```text
Credentials { user = "ada", password = }
```
`Debug` and `Serde` on the same declaration share one schema recipe. `Debug`
alone keeps that binding internal and does not make the declaration
`nupp.serde.Serializable`. Code that already retains a public binding can
prepare the same formatter explicitly and append without constructing the final
string:
```nupp
@derive(nupp.derive.Debug, nupp.derive.Serde)
local struct Vec2
x: float
y: float
end
local prepared = nupp.serde.prepareDebug(nupp.serde.of(Vec2))
local output = string.buffer.new()
prepared:write(new Vec2(1.25, 2.5), output)
assert(output:tostring() == "Vec2 { x = 1.25, y = 2.5 }")
```
## Serde
`Serde` derives one logical `nupp.serde.Schema` and one
`nupp.serde.Binding`. It applies to records and fixed-layout structs,
and generates no `writeJSON`, `fromJSON`, XML, or CBOR methods. A codec prepares
the binding separately and caches its format-specific data.
```nupp
@derive(nupp.derive.Serde)
local record User
id: uint32
name: string?
end
local binding = nupp.serde.of(User)
local prepared = nupp.serde.json():prepare(binding)
local text = prepared:encode(new User(id = 7, name = "ada"))
local restored, problem = prepared:decode(text)
assert(problem == nil)
assert(restored and restored.id == 7)
```
The same type witness works for a struct:
```nupp
@derive(nupp.derive.Serde)
local struct Vec3
x: float
y: float
z: float
end
local binding: nupp.serde.Binding = nupp.serde.of(Vec3)
```
Derived fields currently admit booleans, strings, finite numbers, integers
through 32 bits, optionals, arrays, string-keyed maps, and other declarations
that also derive `Serde`. Pointer-bearing struct fields are rejected because a
pointer does not describe its extent or ownership. See [Schema-driven
serde](../learn/runtime/data/serde.md) for dynamic schemas, profiles, extensions, and the
prepared JSON path.
## Event
`nupp.events.Event` records an event's name and representation and asks the
compiler for the declaration's initializer, so a source can construct the event
into storage it already holds. It generates no members; what it adds is the
`nupp.events.Emittable` contract that every `Type` an event source takes is
bounded by.
```nupp
local events = require("nupp.events")
@derive(events.Event)
@event(name = "combat.Damage")
local record Damage
amount: number
source: integer
kind: string = "physical"
end
local bus: events.MessageBus = events.newMessageBus()
bus:observe(7, Damage, |event| -> print(event.kind))
bus:emit(7, Damage, amount = 10, source = 3)
```
`@event(name = "...")` sets the name `events.name(Damage)` answers; the
declaration's own name is the default. The name is what something outside the
program pins, such as a debug protocol, which is why it is written rather than
derived from a path that a refactor would move.
The derive admits a concrete record or a fixed-layout struct with one
construction contract. It refuses a declaration with several constructors, a
constructor that lets `self` escape or moves an owned parameter into a field,
an affine field, and a generic owner, because none of those can run against
storage that is reused: the next lease would find the reference, the moved
obligation, or the shared identity already there.
## JSON
`JSON` generates `writeJSON(writer)`, a static `fromJSON`, a `fieldCodec`, and
`nupp.codec.json.JSONEncodable` conformance. Encoding writes through the checked
buffer-backed writer; it does not allocate a complete result string. Record and
shape fields follow declaration order and string map keys sort by byte order, so
the same value always produces the same bytes. Encoded field names and literal
values are cached lazily on the derived schema.
If encoding fails, bytes appended before the failure remain in the buffer;
reset or discard it when the surrounding operation needs atomic output.
```nupp
@derive(nupp.derive.JSON, nupp.derive.Debug)
local record User
@json(name = "user_id")
id: integer
name: string
end
local user = new User(id = 7, name = "ada")
local out = string.buffer.new()
local writer = nupp.codec.json.writer(out)
user:writeJSON(writer)
writer:close()
print(out:tostring())
local decoded = User.fromJSON('{"user_id": 7, "name": "ada"}')
print(decoded and decoded:debug())
```
```text
{"user_id":7,"name":"ada"}
User { id = 7, name = "ada" }
```
### Decoding errors
`fromJSON` returns `T?, string?`, and the error names the path that failed
rather than saying the document was bad:
```nupp
@derive(nupp.derive.JSON)
local record User
@json(name = "user_id")
id: integer
name: string
end
print(select(2, User.fromJSON('{"user_id": 7, "name": "ada", "nmae": 1}')))
print(select(2, User.fromJSON('{"user_id": "seven", "name": "ada"}')))
print(select(2, User.fromJSON('{"user_id": 1e300, "name": "ada"}')))
print(select(2, User.fromJSON('{"name": "ada"}')))
```
```text
$: unknown field "nmae"
$.user_id: expected finite number
$.user_id: expected integer in range
$.user_id: required field is absent
```
### Options
A record decides what happens to keys it does not know.
| Option | Effect |
| --- | --- |
| `@json(unknown = "reject")` | rejects unknown keys, and is the default |
| `@json(unknown = "ignore")` | ignores unknown keys |
A field decides how it appears on the wire.
| Option | Effect |
| --- | --- |
| `name = "wire_name"` | renames the key |
| `omit = true` | removes the field both ways, and requires a default |
| `omitEmpty = true` | omits nil, false, empty strings and empty tables, encoding only |
`omitEmpty` is encoding only, which is the part worth knowing: a field left out
of the output is still required coming back in.
```nupp
@derive(nupp.derive.JSON)
local record User
id: integer
@json(omitEmpty = true)
tags: {string}
end
local user = new User(id = 7, tags = {})
local out = string.buffer.new()
local writer = nupp.codec.json.writer(out)
user:writeJSON(writer)
writer:close()
local text = out:tostring()
print(text)
print(select(2, User.fromJSON(text)))
```
```text
{"id":7}
$.tags: required field is absent
```
Use `omit` with an explicit field default when a field should disappear from
both directions:
```nupp
@json(omit = true)
secret: string = "redacted"
```
### Schemas
Booleans, strings, finite numbers, exactly representable integer widths,
optionals, arrays, tuples, string-keyed maps, finite shapes, and records
deriving JSON are all supported. `int64` and `uint64` are rejected, because a
JSON number cannot round-trip their full range, and the erased `integer` type is
checked against the safe interval at run time.
Strings must be valid UTF-8, and a cycle or excessive nesting fails with the
JSON path that reached it. Decoding uses Nupp's strict SIMD-accelerated codec and
preserves null with `nupp.codec.json.NULL` while it validates the raw value.
The JSON field codec is allocated lazily as a runtime reflection extension. Use
`nupp.codec.json.writeRecord`, `writeAs(User, value, writer)`, and
`decodeAs(User, text)` when a type-witness API fits better than generated
members. The allocating `encodeRecord` and `encodeAs` wrappers remain available
when a complete string is specifically required. See
[reflection.md](../learn/language/reflection.md#runtime-reflection) for the witness
and allocation model, and [](nupp.codec.json) for the rest of the codec.
## Package providers
A package may export a derive provider as a `comptime function`. Its exact
signature names the one existing interface it implements:
```nupp
comptime function M.derive(info: nupp.derive.Info): nupp.derive.Result
-- inspect info and return a closed recipe
end
```
In a declared module, write `export comptime function derive(...)` instead of
qualifying the function through a module table. Exported annotation declarations
may accompany the provider and remain compile-time metadata, not runtime values.
A consumer applies the resolved exported symbol, not a runtime function value:
```nupp
local inspect = require("inspect")
@derive(inspect.derive)
local record Credentials
username: string
password: string
end
```
Applying the provider also claims `M.Inspect`. An equal written
`is inspect.Inspect` is redundant and coalesced. Interface defaults are
inherited normally and associated requirements are checked normally. A provider
can fill a bodyless callable requirement or declare a new function member with a
closed comptime-built signature. Generic, variadic, overloaded, and effectful
provider declarations are not part of the first recipe version.
::: deepdive
`Debug` and `JSON` are ordinary exported `comptime function` declarations
implemented in `src/nupp/derive.nupp`, and the compiler has no provider-name or
operation switch for them. Both travel through the same sealed comptime worker,
immutable `Info`, versioned result envelope, cache and recipe lowering a package
provider uses, and their schema configuration (`@debug` and `@json`) is part of
the semantic annotations visible through `Info` rather than a second planner.
That is also the boundary against source generation.
[Comptime](../learn/language/comptime.md) evaluates closed value-producing programs
after normal type checking, and derives run as part of declaration checking and
may attach only validated member recipes. Neither becomes a way to emit
arbitrary source.
:::
### Provider inputs
Every provider on an owner receives the same immutable pre-merge `Info` view. It
contains the owner and interface type handles, ordered stored fields with
read/write handles, semantic identities, and opaque diagnostic references. It
contains no tokens, locations, comments, AST, CST, mutable compiler objects, or
previous provider output. `nupp.derive.claims(T, I)` asks whether a nominal type
writes or requests contract `I`, which lets mutually recursive derives plan
without depending on provider execution order.
`Info.name` names the declaration. `Info.qualifiedName` combines its module and
declaration path, giving providers a default identity without a source filename.
Packages that persist this identity should offer an explicit override: renaming
a declaration or moving it to another module changes its qualified name.
A generic owner is planned once, not per instantiation. A type parameter exposes
its bound, or `unknown`, so providers cannot specialize for future concrete
arguments.
Providers run through the bounded comptime worker. Their sealed source and
reachable comptime helper closure travel in the module interface; they do not
remain runtime functions. A provider failure may return
`nupp.derive.error(message, reference, code)` to point at the owner or
contributing field without observing a filename or source position. The code is
optional and defaults to the generic provider diagnostic
[`NUPP2810`](diagnostics.md).
### Initializers
A provider may ask for the owner's initializer with `initializer = true` beside
`methods`, `statics`, and `data`. The compiler then mints the declaration's
constructor body, or its field list when it declares none, as a hidden member
taking the instance first: `initializer(storage, ...)` fills storage the caller
already holds and answers it, and `new` allocates and calls the same body. The
runtime reaches it through `nupp.derive.initializer(Type)`. A field-list
initializer applies a field default where its argument is nil, which is the
one place that can, since a positional call never passes through the checker's
default filling.
The compiler refuses the request on a declaration whose body could notice the
reuse: several constructors, a constructor that lets `self` escape or moves a
`takes` parameter into a field, an affine field, or a generic owner, each
reported as `NUPP2810` on the application.
### Filesystem inputs
A provider that generates a recipe from a schema or other immutable project file
reads it with `nupp.derive.file`:
```nupp
comptime function M.derive(info: nupp.derive.Info): nupp.derive.Result
local schema = nupp.derive.file("schemas/inspect.txt")
return nupp.derive.implement {
methods = {
inspect = nupp.derive.forward {
helper = nupp.derive.helper(M, "renderSchema"),
arguments = {nupp.derive.constant(schema)},
},
},
}
end
```
The path must be a string literal and remain within the consumer project root.
The compiler reads it before the isolated worker starts, fingerprints its bytes
with the provider input, and records it in the incremental dependency graph.
Changing the file invalidates only provider consumers, and watch mode observes
the canonical path and refuses to patch over changed generated state without a
restart. Missing files are diagnostics.
Providers have no general host I/O, so network resources, environment variables,
clocks, mutable tables, and hidden filesystem reads cannot silently enter a
cache or a [hot-reload](../learn/projects/hot-reload.md) guarantee.
## Closed forwarding recipes
`nupp.derive.implement` returns instance methods and static functions. A bare
`Forward` fills an interface requirement and inherits its signature. A
`nupp.derive.member` supplies a function type built with `nupp.types` and its
parameter names, allowing a provider to add a member that is not declared by the
result interface.
```nupp
return nupp.derive.implement {
methods = {
inspect = nupp.derive.forward {
helper = nupp.derive.helper(M, "renderRecord"),
arguments = {
nupp.derive.constant(names),
nupp.derive.array(values),
},
},
},
}
```
Both forms lower through `forward.v1`, which names one ordinary runtime helper
and supplies a closed argument list:
- `receiver()` passes the generated method receiver.
- `argument(name)` passes a named interface method parameter.
- `entry()` passes the derived type's private runtime schema entry.
- `field(fieldInfo)` directly reads one admitted stored field.
- `constant(value)` embeds a bounded quotable value.
- `array(arguments)` constructs a fresh array from argument recipes.
There are no nested calls, operators, branches, assignments, loops, arbitrary
member accesses, or source fragments in a forwarding recipe. Table-shaped
constants and arrays are fresh for each call, so mutation by one invocation
cannot affect the next.
The first version refuses overloaded requirements, interface defaults,
properties, setters, and metamethods. Those require separate versioned recipe
capabilities rather than silently widening `forward.v1`.
### Runtime helpers
Runtime behavior stays in ordinary exported Nupp functions. Helpers are type
checked at their declarations, and the generated call is checked again against
the interface-owned argument and result packs, ownership, effects, and
suspension contract. `forward.v1` refuses generic runtime helpers; a later
recipe version can admit them once symbolic helper identity and caching are
specified. A helper module becomes an ordinary runtime dependency of the
consumer even when the comptime provider itself would otherwise erase.
::: deepdive
Keeping behavior in the language makes arbitrary runtime control flow,
optimization, effects, diagnostics, and future generic helpers available without
turning them into a macro IR. A macro IR would have to grow its own version of
each of those, and every one would then be a second implementation to keep
agreeing with the first.
The generated wrapper is a semantic node the compiler may inline or sink when
ordinary optimization proves that safe. Such optimization is not part of the
provider contract, so a recipe cannot depend on it happening.
:::
::: seealso
- [annotations.md](annotations.md#built-in-annotations) for `@derive`, `@json`,
and `@debug` beside the rest of the built-ins
- [comptime.md](../learn/language/comptime.md) for the evaluation model a provider
runs in
- [reflection.md](../learn/language/reflection.md#runtime-reflection) for the type
witnesses generated members are built on
- [diagnostics.md](diagnostics.md) for the codes a provider failure reports
:::
Generates checked members on a declaration while that declaration is checked.
`@derive(Provider, ...)` names providers on a `record` or `struct`. Each one reads
an immutable semantic view of the declaration and returns a closed recipe; the
compiler generates members from that recipe and checks them the way it checks
written ones. This is a declaration-augmentation phase rather than a text macro. A
provider adds no imports, top-level declarations, modules, or independently
nameable types, and it observes no tokens, source positions, comments, or another
provider's output.
Three providers ship with the compiler and cross the same boundary a package
provider does: `Debug`, `JSON`, and `Serde`.
#### Derive the shipped providers
```nupp
@derive(nupp.derive.Debug, nupp.derive.JSON)
local record Settings
host: string = "localhost"
port: integer = 8080
end
local settings = new Settings()
assert(settings:debug() == 'Settings { host = "localhost", port = 8080 }')
```
#### Write a package provider
A provider is an exported, nongeneric `comptime function` whose signature names the
one interface it implements. `Info` describes the owner, `implement` returns the
recipe, and a consumer applies the resolved export rather than a runtime function
value. Applying it also claims that interface.
```nupp:static
local M = {}
interface M.Named
named: function(self): string
end
function M.nameValue(value: string): string
return "name=" .. value
end
comptime function M.derive(info: nupp.derive.Info): nupp.derive.Result
local field = info.fields[1]
if not field or field.name ~= "name" then
return nupp.derive.error("Named needs a first field named name", info.reference)
end
return nupp.derive.implement{
methods = {
named = nupp.derive.forward{
helper = nupp.derive.helper(M, "nameValue"),
arguments = {nupp.derive.field(field)},
},
},
}
end
@derive(M.derive)
record M.User
name: string
end
local user = new M.User(name = "ada")
assert(user:named() == "name=ada")
return M
```
#### What a recipe may say
The interface owns every generated signature, so a recipe chooses only which runtime
function stands behind a requirement and which values reach it. `forward` names one
ordinary exported Nupp function and a closed argument list built from `receiver`,
`argument`, `field`, `constant`, and `array`. There are no nested calls, operators,
branches, loops, assignments, or provider-chosen signatures, so behavior stays in
ordinary type-checked Nupp rather than in a macro IR, and the generated call is
checked against the helper's declaration the way any other call is.
`forward.v1` fills only bodyless, unoverloaded callable requirements, and cannot
replace an interface default. A provider that refuses a declaration returns `error`
rather than raising.
Providers run inside the bounded comptime worker, once per owner rather than once
per generic instantiation, and a recipe is memoized on the `Info` fingerprint.
`claims` asks whether a type already writes or requests an interface, which lets
mutually recursive derives plan without depending on evaluation order.
## Types
### `Argument` _record_
```nupp
record Argument
end
```
One value a generated call passes.
Built by `receiver`, `entry`, `argument`, `field`, `constant`, or `array`.
The compiler owns it: a provider may hold one and place it in an argument
list, and nothing else.
### `DebugOptions` _interface_
```nupp
interface DebugOptions
skip: boolean?
redact: boolean?
end
```
The checked shape of `@debug`, written on a field.
#### Fields
##### `skip`
```nupp
skip: boolean?
```
Removes the field from the rendered output entirely.
##### `redact`
```nupp
redact: boolean?
```
Keeps the field's name and renders its value as ``.
### `Entry` _record_
```nupp
record derive.Entry
key: string?
mt: any
schema: {
data: {[string]: any},
[string]: any
}
codec: nupp.reflect.FieldCodec
decoder: any
end
```
One derived type, as the registry holds it.
#### Fields
##### `key`
```nupp
key: string?
```
The recipe fingerprint the entry is filed under.
##### `mt`
```nupp
mt: any
```
The type's runtime table, which is its instances' metatable.
##### `schema`
```nupp
schema: {
data: {[string]: any},
[string]: any
}
```
The recipe's data, plus the JSON field list and unknown-field policy that
`jsonCodec` folds onto it the first time the type is encoded.
##### `codec`
```nupp
codec: nupp.reflect.FieldCodec
```
##### `decoder`
```nupp
decoder: any
```
The JSON decoder, present once the codec has been built.
### `EventOptions` _interface_
```nupp
interface EventOptions
name: string?
end
```
The checked shape of `@event`, written on a declaration deriving
`nupp.events.Event`.
#### Fields
##### `name`
```nupp
name: string?
```
The name the event registers under. The declaration's own name when
omitted; written when the name is an external surface something pins.
### `Field` _record_
```nupp
record Field
readonly name: string
readonly readable: boolean
readonly writable: boolean
readonly readType: any?
readonly writeType: any?
readonly hasDefault: boolean
readonly defaultValue: any
readonly annotations: {nupp.reflect.Annotation}
readonly reference: Reference
end
```
One written stored field in the immutable derive input projection.
Fields arrive in declaration order. A generated member never reaches one by
name; pass this value to `field` to read it.
#### Fields
##### `name`
```nupp
name: string
```
The name as written.
##### `readable`
```nupp
readable: boolean
```
Whether the field can be read, and so whether `field` admits it.
##### `writable`
```nupp
writable: boolean
```
Whether the field can be written.
##### `readType`
```nupp
readType: any?
```
A transported handle for the read type, or nil for a write-only field.
##### `writeType`
```nupp
writeType: any?
```
A transported handle for the write type, or nil for a read-only field.
##### `hasDefault`
```nupp
hasDefault: boolean
```
Whether omission during construction supplies a declaration-owned default.
##### `defaultValue`
```nupp
defaultValue: any
```
The source-free constant default value. Read only when `hasDefault` is true.
##### `annotations`
```nupp
annotations: {nupp.reflect.Annotation}
```
The field's typed annotations in source order, `@json` and `@debug` among
them.
##### `reference`
```nupp
reference: Reference
```
Points this provider's `error` at this field.
### `Forward` _record_
```nupp
record Forward
end
```
One requirement's implementation, from `forward`, to be placed in `implement`
under the name of the requirement it fills.
### `Info` _record_
```nupp
record Info
readonly schema: integer
readonly kind: string
readonly name: string
readonly qualifiedName: string
readonly visibility: string
readonly providerIdentity: string
readonly ownerIdentity: string
readonly ownerType: any
readonly interfaceType: any
readonly fields: {Field}
readonly annotations: {nupp.reflect.Annotation}
readonly hasConstructor: boolean
readonly reference: Reference
readonly fingerprint: string
end
```
The immutable semantic view passed to a `comptime function` provider.
One `Info` describes one owner for one provider, and is built before any
provider's output is merged, so two providers on the same declaration see the
same thing whichever order they run in. A generic owner is projected once rather
than per instantiation, and a type parameter exposes its bound, or `unknown`, so
a provider cannot specialize for arguments a later instantiation supplies.
#### Fields
##### `schema`
```nupp
schema: integer
```
The projection's schema version, currently 1.
##### `kind`
```nupp
kind: string
```
What the owner was declared as: `record`, `struct`, or `interface`.
##### `name`
```nupp
name: string
```
The owner's declared name.
##### `qualifiedName`
```nupp
qualifiedName: string
```
The declaration's module-qualified name, independent of filesystem paths.
##### `visibility`
```nupp
visibility: string
```
How the declaration is bound: `local`, `module`, `global`, or `nested`.
##### `providerIdentity`
```nupp
providerIdentity: string
```
The stable identity of the provider being run.
##### `ownerIdentity`
```nupp
ownerIdentity: string
```
The stable identity of the owning declaration.
##### `ownerType`
```nupp
ownerType: any
```
A transported handle for the owner's type, accepted by `claims`.
##### `interfaceType`
```nupp
interfaceType: any
```
A transported handle for the one interface this provider implements.
##### `fields`
```nupp
fields: {Field}
```
The written stored fields, in declaration order.
##### `annotations`
```nupp
annotations: {nupp.reflect.Annotation}
```
The owner's own typed annotations, in source order.
##### `hasConstructor`
```nupp
hasConstructor: boolean
```
Whether the declaration writes a constructor.
##### `reference`
```nupp
reference: Reference
```
Points this provider's `error` at the declaration as a whole.
##### `fingerprint`
```nupp
fingerprint: string
```
A digest of everything above. Two owners with one fingerprint share one
evaluation, so a provider must decide from `Info` and nothing else.
### `JSONContract` _interface_
```nupp
interface derive.JSONContract is JSONEncodable
end
```
### `JSONOptions` _interface_
```nupp
interface JSONOptions
unknown: ("reject" | "ignore")?
name: string?
omit: boolean?
omitEmpty: boolean?
end
```
Semantic configuration visible to providers as `Info` annotations.
Each of these is the checked shape of one field annotation. A provider reads
what was written from `Field.annotations` or `Info.annotations`; the shipped
providers read the same projection rather than consulting a second planner.
The checked shape of `@json`, written on a record or on one of its fields.
#### Fields
##### `unknown`
```nupp
unknown: ("reject" | "ignore")?
```
What decoding does with a key the record does not declare. Written on the
record, and `reject` when omitted.
##### `name`
```nupp
name: string?
```
The key this field encodes to and decodes from.
##### `omit`
```nupp
omit: boolean?
```
Removes the field from both directions. Requires a default.
##### `omitEmpty`
```nupp
omitEmpty: boolean?
```
Omits nil, false, empty strings, and empty tables when encoding. Encoding
only: a field left out of the output is still required coming back in.
### `Member` _record_
```nupp
record Member
end
```
A generated member with a comptime-built function signature and forward
recipe. Use this when the provider declares a member that is not already an
interface requirement.
### `Provider` _record_
```nupp
record Provider
end
```
A comptime-only provider symbol accepted by `@derive`.
A shipped provider is one of the three values below; a package provider is the
`comptime function` a module exports. Neither survives into the program.
### `Reference` _record_
```nupp
record Reference
end
```
An opaque reference which a provider may attach to a diagnostic.
Obtained from `Info.reference` for the declaration or `Field.reference` for one
field, and accepted only by `error`. It carries no filename, line, or column: a
provider says which part of the declaration is at fault and the compiler decides
where to point.
### `Result` _record_
```nupp
record Result
end
```
What a provider returns: the recipe implementing interface `I`.
`I` is the interface the provider implements, or `any` when every generated
member supplies its own signature. Writing an interface in the return type is
how `@derive` knows which contract applying the provider claims. Produce one
with `implement`, or refuse the declaration with `error`. The compiler owns it
and there is nothing on it to read: what it carries crosses the comptime
boundary as a versioned recipe the compiler validates.
#### Type parameters
| Name | Description |
| --- | --- |
| `I` | the interface this provider implements, or `any` |
### `RuntimeHelper` _record_
```nupp
record RuntimeHelper
end
```
An ordinary exported Nupp function a generated member calls, from `helper`.
The module owning it becomes a runtime dependency of the consumer, even when the
comptime provider itself erases.
## Functions
### `argument` _function_
```nupp
local argument: function(name: string): Argument
```
Passes one parameter of the generated method, under the name the interface
requirement declares for it.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `name` | `string` | the parameter's declared name |
#### Returns
| Type | Description |
| --- | --- |
| `Argument` | the argument recipe |
### `array` _function_
```nupp
local array: function(arguments: {Argument}): Argument
```
Builds a fresh array from argument recipes, once per call.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `arguments` | `{Argument}` | the element recipes, in order |
#### Returns
| Type | Description |
| --- | --- |
| `Argument` | the argument recipe |
### `claims` _function_
```nupp
local claims: function(subject: any, interface: any): boolean
```
Asks whether a type writes or requests an interface.
The answer does not depend on whether that type's own providers have run, so
mutually recursive derives can plan against one another.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `subject` | `any` | a type handle, such as a `Field.readType` |
| `interface` | `any` | a type handle, such as `Info.interfaceType` |
#### Returns
| Type | Description |
| --- | --- |
| `boolean` | whether `subject` claims `interface` |
### `constant` _function_
```nupp
local constant: function(value: any): Argument
```
Embeds a bounded quotable value.
A table-shaped constant is rebuilt for every call, so mutation by one invocation
cannot reach the next.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `value` | `any` | the value to embed |
#### Returns
| Type | Description |
| --- | --- |
| `Argument` | the argument recipe |
### `derive.Debug` _comptime function_
```nupp
comptime function derive.Debug(info: nupp.derive.Info): nupp.derive.Result
```
Generates `debug(self): string` and `nupp.Debug` conformance for a record or
struct. The method lazily prepares a schema-driven declaration-order
formatter and shares its schema recipe with `Serde` when both are derived.
`@debug(redact = true)` keeps a field's name and hides its value, which is what
a
secret wants; `@debug(skip = true)` removes the field from the output entirely.
```nupp
@derive(nupp.derive.Debug)
local record Credentials
user: string
@debug(redact = true)
password: string
end
local credentials = new Credentials(user = "ada", password = "hunter2")
assert(credentials:debug() == 'Credentials { user = "ada", password = }')
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `info` | `nupp.derive.Info` | |
#### Returns
| Type | Description |
| --- | --- |
| `nupp.derive.Result\` | |
### `derive.debug` _function_
```nupp
function derive.debug(value: any, entry: derive.Entry): string
```
Renders a derived record the way `@derive(Debug)` promises.
Nothing calls this by hand. `@derive(Debug)` writes the `debug` member that
does, with the type's own registry entry already bound.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `value` | `any` | the record to render |
| `entry` | `derive.Entry` | the type's registry entry |
#### Returns
| Type | Description |
| --- | --- |
| `string` | the rendered text |
### `derive.fieldCodec` _function_
```nupp
function derive.fieldCodec(entry: derive.Entry): nupp.reflect.FieldCodec
```
The field codec a derived type's `fieldCodec` static answers.
Built on first use and memoized as a reflection extension, so every route to it
answers the same codec.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `entry` | `derive.Entry` | the type's registry entry |
#### Returns
| Type | Description |
| --- | --- |
| `nupp.reflect.FieldCodec\` | the codec, built if this is the first ask |
### `derive.fromJSON` _function_
```nupp
function derive.fromJSON(text: string, entry: derive.Entry): T?, string?
```
Decodes a JSON document into a derived record.
#### Type parameters
| Name | Description |
| --- | --- |
| `T` | |
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `text` | `string` | the document to decode |
| `entry` | `derive.Entry` | the type's registry entry |
#### Returns
| Type | Description |
| --- | --- |
| `T?` | the record, or nil when the document did not fit it |
| `string?` | what was wrong with the document, when something was |
### `derive.install` _function_
```nupp
function derive.install(namespace: any): nil
```
Fills a program's derive namespace in and wires JSON to reflection.
The second half is conditional because reflection is its own feature. A program
that derives only Debug has no registry of descriptors, and the JSON codec is
memoized as a reflection extension, so what it does not get is JSON.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `namespace` | `any` | the program's `nupp` table |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `derive.JSON` _comptime function_
```nupp
comptime function derive.JSON(info: nupp.derive.Info): nupp.derive.Result
```
Generates `writeJSON(writer)`, a static `fromJSON(text): T?, string?`, a
`fieldCodec`, and `nupp.codec.json.JSONEncodable` conformance.
Encoding is deterministic: fields follow declaration order and string map keys
sort by byte order, so one value always produces the same bytes. A decode error
names the path that failed rather than saying the document was bad.
```nupp
@derive(nupp.derive.JSON)
local record User
@json(name = "user_id")
id: integer
name: string
end
local user = new User(id = 7, name = "ada")
local out = string.buffer.new()
local writer = nupp.codec.json.writer(out)
user:writeJSON(writer)
writer:close()
assert(out:tostring() == '{"user_id":7,"name":"ada"}')
local decoded, failure = User.fromJSON('{"name": "ada"}')
assert(decoded == nil and failure == '$.user_id: required field is absent')
```
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `info` | `nupp.derive.Info` | |
#### Returns
| Type | Description |
| --- | --- |
| `nupp.derive.Result\` | |
### `derive.Serde` _comptime function_
```nupp
comptime function derive.Serde(info: nupp.derive.Info): nupp.derive.Result
```
Derives one format-neutral schema and physical binding blueprint.
Unlike `JSON`, this provider admits both records and fixed-layout structs and
generates no per-format traversal methods.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `info` | `nupp.derive.Info` | |
#### Returns
| Type | Description |
| --- | --- |
| `nupp.derive.Result\` | |
### `derive.writeJSON` _function_
```nupp
function derive.writeJSON(value: T, entry: derive.Entry, exclusive out: Writer): nil
```
Writes a derived record as one JSON value.
The field list, the renaming and the omission rules all come from the recipe
`@derive(JSON)` folded, so the document is what the declaration asked for rather
than what the runtime table happens to hold.
#### Type parameters
| Name | Description |
| --- | --- |
| `T` | |
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `value` | `T` | the record to encode |
| `entry` | `derive.Entry` | the type's registry entry |
| `exclusive out` | `Writer` | the checked destination |
#### Returns
| Type | Description |
| --- | --- |
| `nil` | |
### `entry` _function_
```nupp
local entry: function(): Argument
```
Passes the record's registered derive entry.
#### Returns
| Type | Description |
| --- | --- |
| `Argument` | the argument recipe |
### `error` _function_
```nupp
local error: function(message: string, reference: Reference?, code: string?): any
```
Refuses the declaration with a diagnostic instead of returning a recipe.
The reference decides what the diagnostic points at: `Info.reference` for the
declaration, or the `Field.reference` of the field that made the recipe
impossible. Returning this is how a provider fails; raising is a provider fault.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `message` | `string` | what the consumer is told |
| `reference` | `Reference?` | which part of the declaration is at fault |
| `code` | `string?` | the diagnostic code, defaulting to `NUPP2810` |
#### Returns
| Type | Description |
| --- | --- |
| `any` | the refusal, as this provider's `Result` |
### `field` _function_
```nupp
local field: function(field: Field): Argument
```
Reads one stored field of the receiver directly. The field must be readable.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `field` | `Field` | one of `Info.fields` |
#### Returns
| Type | Description |
| --- | --- |
| `Argument` | the argument recipe |
### `file` _function_
```nupp
local file: function(path: string): string
```
Reads one immutable filesystem input admitted to this provider invocation.
The path must be a string literal in the provider's checked source. It resolves
from the consumer project root, becomes an incremental query dependency, and is
included in the provider recipe fingerprint. Watch mode therefore observes the
file and rechecks only modules that consume the provider.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `path` | `string` | project-relative path written as a literal |
#### Returns
| Type | Description |
| --- | --- |
| `string` | the file's bytes |
### `forward` _function_
```nupp
local forward: function(specification: any): Forward
```
Implements one requirement by calling one runtime helper.
`helper` names the function and `arguments` is the closed list it receives, in
order. The interface owns the resulting signature, and the generated call is
checked against the helper's declaration for types, ownership, effects, and its
suspension contract.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `specification` | `any` | a table with `helper` and `arguments` |
#### Returns
| Type | Description |
| --- | --- |
| `Forward` | the implementation, to place in `implement` |
### `helper` _function_
```nupp
local helper: function(module: any, name: string): RuntimeHelper
```
Names an ordinary exported Nupp function to forward to.
Runtime behavior stays in the language: helpers are checked at their own
declarations, and a generic helper is refused by `forward.v1`.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `module` | `any` | the module value holding the function |
| `name` | `string` | the name it is exported under |
#### Returns
| Type | Description |
| --- | --- |
| `RuntimeHelper` | the helper handle |
### `implement` _function_
```nupp
local implement: function(specification: any): any
```
Returns forwarding implementations. `methods` fills instance members and
`statics` fills static members.
Both are keyed by member name. A `Forward` fills a bodyless, unoverloaded
callable requirement on the provider's interface; a `Member` supplies a new
member's function signature and forwarding recipe. Generating a member that
another provider on the same owner already generates is refused.
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `specification` | `any` | a table with optional `methods` and `statics` maps |
#### Returns
| Type | Description |
| --- | --- |
| `any` | the recipe, as this provider's `Result` |
### `member` _function_
```nupp
local member: function(specification: any): Member
```
Declares a generated member's function signature and forwarding recipe.
a `forward` recipe
#### Arguments
| Name | Type | Description |
| --- | --- | --- |
| `specification` | `any` | a table with `signature`, optional parameter names, and |
#### Returns
| Type | Description |
| --- | --- |
| `Member` | the member recipe, to place in `implement` |
### `receiver` _function_
```nupp
local receiver: function(): Argument
```
Passes the generated method's receiver.
#### Returns
| Type | Description |
| --- | --- |
| `Argument` | the argument recipe |