# Annotations

An annotation is typed, type-erased metadata attached to a declaration or a
statement. The built-in `@unsafe` also supports expressions. User-defined
annotations are declared as records or structs, whose fields are the
annotation's values and whose definition says where applications may attach.

```nupp
@annotation(targets = {"function"})
record retry
    attempts: integer
end
```

An application names the definition and supplies its fields:

```nupp:fragment
@retry(attempts = 3)
local function fetch(url: string): string
    return url
end
```

Unknown annotations, invalid targets, missing values, and values of the wrong
type are errors. An annotation never becomes a silently erased comment.

## Defining an annotation

Apply the built-in `@annotation` annotation to a record or struct. Its `targets`
value is a non-empty array of [semantic target names](#attachment-targets):

```nupp:playground
@annotation(targets = {"record", "struct"})
record serializable
    format: string
    version: integer?
end
```

That definition admits an application on either kind of declaration:

```nupp:fragment
@serializable(format = "json")
local record User
    id: uint64
end

@serializable(format = "binary", version = 2)
local struct Header
    tag: uint32
end
```

The record's fields are the annotation's members, so ordinary Nupp types apply
and an optional field may be omitted. Annotation values are literal compile-time
constants: strings, numbers, booleans, nil, and literal tables composed from
those values. A field marked [`@ref`](#type-reference-members) accepts a type
reference instead.

A zero-field definition creates a marker annotation:

```nupp
@annotation(targets = {"record"})
record internal
end

@internal
local record CacheEntry
end
```

An annotation definition is registered project-wide under its own name, which is
why it is written without a visibility: applications name an unqualified
`@name`, so the name has to be unique across the project and there is no table
to reach it through. It is the one declaration exempt from `NUPP2119`; see
[modules.md](../learn/language/modules.md#exports-and-privacy) for the visibility rule
every other declaration holds to. Definitions and applications may live in
different files, no runtime import is required, and the definition record itself
and every application are erased from generated Lua.

::: deepdive
The model follows Smithy traits: a trait is declared with the same shape as the
data it carries, its definition restricts where it may be applied, and applying
one is checked rather than parsed out of a comment. That is what makes an
annotation a type-system object instead of a string, so an editor can navigate
a `@ref` member to the type it names and
[comptime](#compile-time-reflection) can read applications back with their
values still typed.
:::

## Single-value applications

Apply `@annotationValue` to one field to designate it as the annotation's
single-value member:

```nupp
@annotation(targets = {"record", "field"})
record documentation
    @annotationValue text: string
end
```

The designated field may then be supplied positionally:

```nupp:fragment
@documentation("A user")
local record User
    @documentation("The stable user ID")
    id: uint64
end
```

That is the same metadata as writing `@documentation(text = "A user")`. There
may be at most one `@annotationValue` field, and an annotation without one
requires named members.

The formatter treats the positional form as canonical: whenever an application
contains only the designated named member, it rewrites it to the single-value
form. That works for definitions in the same file and for definitions resolved
elsewhere in the project. See [fmt.md](../learn/tooling/formatter.md#formatting-rules) for
the rest of what the formatter rewrites.

## Type-reference members

Apply `@ref` to an annotation-definition field when its value names a type
rather than an ordinary compile-time constant:

```nupp
@annotation(targets = {"record"})
record relatesTo
    @annotationValue @ref target: any
end
```

An application then writes the type's name where a literal would go:

```nupp:fragment
local record User
end

@relatesTo(User)
local record UserEvent
end
```

The value must be a bare or module-qualified type name. It is resolved in the
type namespace and checked against the member's declared type; `any` accepts any
valid type reference. Editors navigate from the reference to the type
declaration through the [language server](../learn/tooling/language-server.md). `@ref` is valid
only on a field inside an `@annotation` record or struct.

## Attachment targets

Targets are semantic categories rather than parser node names. A definition must
name at least one target and may name several; the list is a union, and an
unrecognized target makes the definition invalid.

- `statement`: every annotatable statement.
- `declaration`: value, function, type, and C declarations.
- `binding`, `local-binding`: local/const value bindings.
- `function`: local, const, and named functions with bodies.
- `local-function`, `named-function`: only the indicated function form.
- `type-declaration`: alias, record, interface, struct, and C struct
  declarations.
- `alias`, `record`, `interface`, `struct`: only that type declaration.
- `field`: a record, interface, or struct field.
- `c-declaration`: `cdef function` and `cdef struct`.
- `c-function`: only `cdef function`.
- `block`, `loop`, `conditional`, `assignment`, `call`: the corresponding
  statement family.

A statement annotation decorates exactly the immediately following statement,
and stacked annotations all target the innermost non-annotation statement:

```nupp
@allow("unused-binding")
@allow("discarded-result")
local pending = 1
```

They do not insert `do ... end` and do not extend to later sibling statements.
The grammar's terminal `return` is not an ordinary statement, so it cannot
currently be annotated directly.

## Unsafe operations

`@unsafe` authorizes operations requiring unsafe permission in one statement or
expression. Ordinary type, ownership, borrow, lifetime and cleanup checks still
apply. It takes no arguments and cannot be redefined.

On statements it supports `do`, `if`, numeric and generic `for`, `while`,
`repeat`, local/const value bindings, assignments and calls. Permission covers
conditions, loop setup, assignment destinations and initializers, as well as
bodies. An annotated binding keeps its ordinary scope.

These helpers assume the caller supplies the elements they access.

```nupp
local function readAndClear(exclusive pointer: int32*): integer
    @unsafe local first = pointer[0]
    @unsafe if first > 0 then
        pointer[0] = 0
    end
    return first
end
```

In expression position the annotation covers the following operand, including
unary operators, calls, indexing, member access and casts. Binary operators
remain outside it; parentheses include a larger expression. Both
`@unsafe(expression)` and `@unsafe (expression)` annotate that parenthesized
operand. Annotated calls retain their normal multiple results. Unary/power
precedence is unchanged: `@unsafe -x ^ y` still means `-(x ^ y)`, while
permission ends before `^`.

```nupp
local function combine(borrows pointer: int32*): integer
    local first = @unsafe pointer[0]
    local sum = @unsafe (pointer[0] + pointer[1])
    local chosen = @unsafe do
        if first > 0 then
            yield pointer[0]
        end
        yield 0
    end
    return sum + chosen
end
```

For example, `@unsafe pointer[0] + other[0]` authorizes only the first read,
and `(@unsafe pointer)[0]` does not authorize indexing. A `switch`
expression includes its selector, guards and arms, retaining lazy execution.

Permission ends with the annotated construct and resets in every nested
function body, including an immediately called literal. Function and type
declarations, function literals, fields, foreign declarations and whole files
are unsupported targets. Annotate operations inside functions; write
`return @unsafe expression` or `yield @unsafe expression` for terminal values.
`@aot` continues to reject unsafe constructs, and comptime gains no operations.

Ownership transfers remain explicit: `@unsafe nupp.release(owner)` consumes an
affine obligation without cleanup, and `@unsafe nupp.adopt<Owner>(raw)` creates
one with the target's cleanup policy. These compiler-recognized calls require
`@unsafe` even inside an annotated block.

The former contextual forms are rejected with a fix that inserts `@` at the
old marker. Comments, strings, grouping and ordinary identifiers are preserved.

| Rejected form | Replacement |
| --- | --- |
| `unsafe do ... end` | `@unsafe do ... end` |

Names such as `unsafe`, `adopt` and `release` remain ordinary identifiers
outside these annotation forms.

## File-level directives

A file-level inner annotation is written `@!name` and applies to the file it
opens rather than to any statement. There are two, and both are compiler
directives rather than user-defined annotation definitions:

```nupp
@!internal
module app.internals
```

`@!nofmt` disables formatting for one file. `@!internal` hides one file from
public generated documentation and, when it is placed on `init.nupp`, hides the
entire module namespace beneath it. Checked imports are restricted to the same
package namespace; see [internal modules](../learn/language/modules.md#internal-modules). See
[doc.md](../learn/tooling/documentation.md#public-surface) for what the documentation generator
publishes.

## Compile-time reflection

Checked user-defined annotations on records, interfaces, structs, and their
fields are available through `nupp.reflect(T)` inside
[comptime](../learn/language/comptime.md). Applications retain source order, and
arguments follow the annotation definition's member order:

```nupp:fragment
return comptime do
    local info = nupp.reflect(User)
    local recordMetadata = info.annotations
    local fieldMetadata = info.fields[1].annotations
    return {recordMetadata = #recordMetadata, fieldMetadata = #fieldMetadata}
end
```

Each argument has a `name` and a `kind`. Literal metadata uses `kind = "value"`
and `value`; an explicitly supplied nil uses `kind = "nil"`. A member declared
with `@ref` uses `kind = "type"` and `type`, whose integer value indexes the
same `info.types` graph as field types, so reflection carries semantic type
identity rather than a possibly aliased source name. See
[reflection.md](../learn/language/reflection.md#comptime-reflection) for the rest of
the `Info` surface.

Annotation names, argument values, and referenced types participate in the
descriptor fingerprint and comptime cache key, so changing serialization
metadata invalidates a reflected materialization even when no field type
changes. Reflection remains read-only, and annotations remain absent from
generated runtime Lua unless a comptime result or materializer deliberately
turns them into a runtime value.

## Built-in annotations

These definitions are compiled into the registry, and a project cannot redefine
any of their names.

| Annotation | Arguments | Attaches to |
| --- | --- | --- |
| `@nosuspend` | none | callable type, block, loop; see [non-suspending regions](../learn/runtime/concurrency/suspension.md#non-suspending-regions) |
| `@sendable` | none | callable type reproducible in another isolated Lua state |
| `@comptime` | none | callable type, function, type declaration used only at compile time |
| `@noalloc` | none | block; see [allocation and raising regions](../learn/language/effects.md#allocation-and-raising-regions) |
| `@noraise` | none | block; see [allocation and raising regions](../learn/language/effects.md#allocation-and-raising-regions) |
| `@readonly` | none | field, indexer; see [property capabilities](../learn/language/types/properties.md#declaring-a-capability) |
| `@writeonly` | none | field, indexer |
| `@private` | none | record field; see [private fields](../learn/language/types/records-and-structs.md#private-fields) |
| `@unsafe` | None | Supported statements and operands; see [unsafe operations](#unsafe-operations) |
| `@annotation` | `targets = {"..."}` | record, struct |
| `@annotationValue` | none | annotation definition field |
| `@ref` | none | annotation definition field |
| `@allow` | lint names or codes | statement |
| `@override` | none | function |
| `@partition` | two result field names | sealed interface field |
| `@effects` | named effect members | function, c-function, local-binding |
| `@relax` | guarantee names | function |
| `@derive` | qualified comptime providers | record, struct |
| `@cli` | command and field options for the `nupp.cli` derives | record, field |
| `@debug` | `skip` or `redact` | field in a derived record |
| `@event` | the registered event name | record, struct deriving `nupp.events.Event` |
| `@deprecated` | optional reason and replacement | declaration, field, c-declaration |
| `@syntax` | one syntax name | local binding |
| `@jit` | none | function |
| `@aot` | `target = "cpu"` or `target = "gpu"` | function |

### `@allow`

`@allow(LINT, ...)` suppresses the named [lints](lints.md) while its statement
is checked, taking either a lint name or its code:

```nupp
@allow("unused-binding")
local pending = 1
```

Bare `@allow` and `@allow()` suppress every lint in that statement. It reaches a
lint at any level, including one a build would fail on, because a lint is a
judgement a project may disagree with. It does not reach a type error, which is
not a judgement: naming one reports `NUPP2108` and the error stands.

### `@override`

`@override` marks a method that replaces an inherited interface method. It is
required on every method override in a child interface, and on a concrete
implementor that replaces an inherited default. It is equally an error when no
matching inherited parameter pack exists:

```nupp
local interface Greeter
    name: string

    function greet(self): string
        return "hello, " .. self.name
    end
end

local record Shouter is Greeter
    name: string

    @override
    function greet(self): string
        return "HELLO, " .. self.name
    end
end
```

That catches both the misspelling that silently defines a new method instead of
overriding, and the interface that later adds a default which would otherwise
silently shadow an implementor's method. See
[overloads.md](../learn/language/types/overloads.md#default-implementations-and-override)
for per-entry replacement, and
[interfaces.md](../learn/language/types/interfaces.md#default-implementations) for how
interface defaults behave.

### `@partition`

`@partition(left, right)` is an audited ownership assertion on a method of a
sealed interface. It states that the two named fields of the method's first
result carry sibling-disjoint regions:

```nupp
local record Split
    left: integer
    right: integer
end

local sealed interface Splitter
    @partition(left, right)
    split: function(self: Splitter): Split
end
```

That preserves a private implementation's `nupp.partition` proof through the
public interface signature. It may not be attached to an unsealed or externally
implementable contract, and `nupp ownership-audit` lists every use. See
[ownership.md](../learn/runtime/ownership/borrowing.md#public-capability-contracts) for the
contracts a public signature is allowed to carry.

### `@derive`

`@derive` accepts `nupp.derive.Debug` or an exported comptime provider and adds
checked members
or format-neutral materialization data without exposing source or AST macros:

```nupp
@derive(nupp.derive.Debug)
local record Point
    x: integer
    y: integer
end
```

`Debug` admits records and structs. A package provider chooses its supported
targets. `@debug` configures the renderer; serialization mappings belong to an
explicit [binding](../learn/runtime/data/serde.md). See
[derives.md](derives.md) for provider contracts and generated methods.

### `@deprecated`

`@deprecated`, `@deprecated("reason")`, or
`@deprecated(reason = "...", replacement = "new.name")` keeps an API available
while marking it for migration:

```nupp
local function current(): integer
    return 2
end

@deprecated(reason = "kept for compatibility", replacement = "current")
local function legacy(): integer
    return current()
end
```

It may decorate functions, methods, bindings, type declarations, C declarations,
and record/interface fields. A use reports the suppressible
[`deprecated`](lints.md#deprecated) lint; completion and semantic tokens carry
the LSP deprecated tag and modifier; hover shows the reason and replacement; and
generated documentation retains the annotation. It emits no Lua.

### `@syntax`

`@syntax("name")` is editor metadata for a local or const binding. It accepts
any literal syntax name and does not change the binding's type:

```nupp
@syntax("json")
local defaults = [[
{"retries": 3}
]]
```

The bundled VS Code extension recognizes JSON, GLSL, Lua, Nupp, and PEG when the
initializer is a long string; other names remain available to tools that
understand them. See [editors.md](../learn/tooling/editors.md#visual-studio-code) for
what the extension provides.

### `@jit`

`@jit` is an absence-of-known-blockers contract for the selected LuaJIT trace
profile:

```nupp
@jit
local function total(values: {number}): number
    local sum = 0
    for _, value in ipairs(values) do
        sum = sum + value
    end
    return sum
end
```

The visible body and statically resolved checked callees must avoid catalogued
recorder blockers; a call path to one reports `NUPP2707`. Variadic FFI and
callback boundaries remain conservative contract errors, and `jit.off(function)`
is an explicit boundary, so it is also an error when reached from an `@jit`
body.

The annotation does not promise that the function runs, becomes hot, receives
stable runtime types, or stays compiled for every input. Compile-time-only
helpers use the `@comptime` annotation. See
[jit-trace-checking.md](../learn/performance/jit-trace-checking.md) for
every current blocker, risk, expected stop, warning, call-path error, bytecode
verdict, editor query, and runtime reason.

### `@aot`

`@aot` reserves a required whole-function ahead-of-time compilation contract
on a top-level local function declaration:

```nupp
@aot
local function clamp(value: number, low: number, high: number): number
    if value < low then return low end
    if value > high then return high end
    return value
end
```

A target's `aot` build policy says what happens with it: `off`, the default,
does nothing; `require` compiles it into the project's own shared library and
fails the build when it cannot. Browser targets use `emit-wasm` to package
pointer kernels as Wasm modules, or `require-wasm` to replace their bodies with
checked calls into those modules. See
[wasm-aot.md](../learn/performance/ahead-of-time/wasm.md) for that host and its limits.

A closure, table, interpolated string, vararg, `goto`, dynamic call, or unsafe
operation inside the body reports `NUPP2903` at the construct. Stacking it with
`@jit` reports `NUPP2901`, and annotating a constructor or inline requirement
reports `NUPP2902`, since neither is a whole function to compile.

What lowering refuses about an admitted construct, such as a `number` used as a
branch condition or a call no intrinsic admits, reports `NUPP2905` through
`NUPP2908`. `nupp check` lowers for a target whose policy is anything but
`off`, so it reports these where the build would, and a target under `off`,
which never lowers, reports none of them.

Code that runs in vector lanes uses `nupp.simd`. See
[simd-programming.md](../learn/performance/ahead-of-time/simd-programming.md) for the
vector loop and scalar continuation patterns, and
[build-and-artifacts.md](../learn/performance/ahead-of-time/build-and-artifacts.md)
for a full kernel, the build policy, and what the backend does not do yet.

`target = "gpu"` records a GPU execution family in the verified IR and maps one
whole-span loop iteration to one GPU invocation. With the native `aot =
"require"` policy, the compiler emits canonical SPIR-V for the Rust WGPU
provider and replaces the declaration with a typed kernel specification. Its
`compile(context)` method owns the shader and entrypoint, `bind(...)` accepts
resident `gpu.Buffer<T>` values in the span parameters' order and types, and
`dispatch(...)` accepts the scalar parameters and packs their uniform block. The
kernel returned by `compile` borrows its context, and the binding returned by
`bind` borrows its kernel; both are closeable.

With `aot = "require-wasm"`, the same declaration emits WGSL for a browser
WebGPU application. Browser GPU storage uses `nupp.mem.span.Span` and
`nupp.mem.span.WriteSpan`, with the selected physical representation. The first browser
profile admits complete-span map kernels over `int32` and `uint32` storage and
scalar uniforms; it does not yet admit floating-point values, structs,
cursor-indexed storage, or workgroup phases. The generated binding retains the
same `compile`, `bind`, and `dispatch` shape and crosses the browser Worker
boundary through bounded Wasm-memory transfer leases. See
[gpu.md](../learn/performance/ahead-of-time/gpu.md#browser-gpu-kernels) for a complete
browser target and source example.

`aot = "off"` retains the ordinary function value. `nupp aot --emit
spirv FILE` writes the native WGPU module; WGPU performs platform shader
translation internally.

The GPU subset has two entry shapes. A complete-span map assigns one invocation
to every element. A structured workgroup entry ends in
`gpu.workgroups(groups, size, controller)`: `controller` allocates statically
sized scratch with `phases:scratch` and divides execution into immediate
`phases:run` callbacks. On CPU each callback runs for local indices zero through
`size - 1` before the next callback begins; on GPU the boundary is a workgroup
execution-and-memory barrier.

Generated workgroups admit at most 256 lanes and 16 KiB of fixed-width scratch.
Every scratch write is exactly `shared[localIndex]`, so writes are structurally
disjoint. A later phase may read an index whose full uint32 range is statically
proved inside the allocation; a phase may only read the scratch it also writes
at its own local index. `phases:reduceSum(shared)` is the compiler-owned
exception: it expands into a fixed, left-before-right power-of-two tree whose
stage order is identical on CPU and GPU. There is no unordered or atomic
reduction.
`phases:inclusiveScan(values, temporary)` similarly expands into a
deterministic power-of-two inclusive prefix tree. Both arrays contain one
`uint32` per lane; the second is compiler-checked ping-pong scratch and the
result is left in `values`. Stable block compaction can use the resulting
prefix as each selected lane's destination without atomics.

Both shapes accept any number of proved read and write spans and up to 128 bytes
of fixed-width scalar uniforms in a local function declaration. Uploads and
downloads remain explicit, so several generated bindings can share resident
buffers without an intermediate CPU copy.

`Context:tensor(element, shape)` allocates dense row-major storage.
`buffer:layout()` returns an element-independent layout.
The `nupp.gpu.layout` module (imported as `layout`) owns the
`layout.subview`,
`layout.transpose`, `layout.broadcast`, and `layout.asStrided`
operations, which are allocation-free;
`buffer:view(layout)` applies it while preserving the buffer's element type.
Each view retains logical dimensions separately from its bounded physical
extent. Host transfers and dispatch-indexed span accesses require dense layout;
cursor-indexed kernels may consume other inputs by passing `dimensions()` and
`strides()` as scalar uniforms. Writable views additionally need disjoint
coordinates and one complete span extent, so broadcast and overlapping writes
are refused.

Binary16 and bfloat16 storage conversions are explicit through
`nupp.math.f32.fromF16Bits`, `toF16Bits`, `fromBF16Bits`, and `toBF16Bits`.
The narrow integer names remain physical span and buffer element types, so a
kernel can define its own dequantization while accumulation uses explicit
binary32. These portable paths require no optional device feature. Native half
arithmetic and cooperative matrix instructions are separate capability-selected
optimizations rather than silent changes to these operations.

## Contracts in type position

Three contracts a reader might look for on this page are written in type
position instead, because each is part of what a declaration returns rather than
metadata about it.

An owning result is written `affine(T, cleanup)`, where `cleanup` is one exact
const-function identity, and `affine(T)` is the explicit transfer-only form:

```nupp
local record File
    handle: integer
end

local function close(takes file: File): nil
    file.handle = -1
end

local function open(handle: integer): affine(File, close)
    return new File(handle = handle)
end
```

Automatic lexical cleanup works over every public `affine(...)` type. The
cleanup belongs to that affine type, so different policies over the same runtime
representation remain distinct static types.

A C output states its contract the same way. A borrowed output is written
`out view: T* borrows (source)`, an owned output is written
`out value: affine(T, cleanup)*`, and `Success<T, N>` or `Failure<T, N>` on the
return says which status means the outputs hold values:

```nupp:fragment
cdef function lookup(borrows key: const char*,
    out value: voidptr* borrows (key)): int32
```

Both forms allocate and position the C output holder while presenting an
ordinary Lua multiple return.

A declaration that never comes back, because it raises, exits, or loops forever,
says so with `never` as its return type rather than with an annotation.

::: seealso
- [ownership.md](../learn/runtime/ownership/borrowing.md) for the complete affine contract
  reference
- [c-interop.md](../learn/runtime/c-interop/index.md) for what a C boundary adds to it
- [primitives.md](../learn/language/types/primitives.md#never-the-bottom-type) for
  `never` as the bottom type
:::

## Effect contracts

`@effects` is a complete, pessimistic contract. A function with a visible Nupp
body is analyzed and the checker rejects a contract that omits one of its
effects, while a bodyless declaration is a trust boundary: the annotation
records what the implementation promises, just as its type signature records
what values it accepts and returns.

```nupp
@effects(reads = {"value"}, returns = {"1=value"})
local function identity(value: table): table
    return value
end
```

```nupp
@effects(writes = {"buffer[*]"}, shapes = {"buffer"})
cdef function fill(buffer: uint8*, count: uint64): integer
```

The list members are `reads`, `writes`, `shapes`, `metatables`, `escapes`,
`calls`, and `returns`. The boolean members are `allocates`, `suspends`, `raises`,
and `external`. Every member defaults to empty or false, so `@effects()` means
the function has no observable effects; it does not mean "infer these later."

See [effects.md](../learn/language/effects.md) for the path vocabulary, every member's
meaning, inference and fixed-point propagation, return aliases, unknown-call
behavior, trusted declarations, optimizer interaction, current limitations, and
complete examples.

## Const declaration bindings

`const` on a bodyless declaration says that the binding keeps the same runtime
value. It is a shallow identity promise, not deep immutability: a const module
binding does not freeze the module's fields.

```nupp [clock.d.nupp]
-- clock.d.nupp: a declaration for an implementation outside this source
const monotonicNow: function(): number

return {monotonicNow = monotonicNow}
```

In visible Nupp, `const` is checked from the binding itself. In `.d.nupp` files
and similar bodyless declaration surfaces, it is the trusted statement that the
host implementation will not replace the binding. Reassigning a const binding is
an error. The standard declaration of `ipairs` is const, and that identity fact
is one part of the proof for numeric array-loop lowering.

## Relaxing observable guarantees

A guarantee is a property of a running program that a reader could notice, and
Nupp keeps every one of them by default. `@relax` says a function may give one
up, which is what lets a rewrite that would otherwise change observable behavior
apply there.

```nupp
@relax("frames", "error-site")
local function dispatch(handler: function()): nil
    handler()
end
```

That says two things about `dispatch`: a traceback through it need not show the
frames it would have shown, and an error raised inside it need not report the
position it would have reported. Nothing else about it changes.

### Relaxable guarantees

Each name is one property, and giving it up permits a specific class of rewrite.

| Guarantee | What holding it promises | What giving it up permits |
| --- | --- | --- |
| `function-identity` | two closures built at one site are distinct values, so `a == b` returns false | caching a closure and handing the same one back |
| `load-order` | modules initialize in the order the requires run | hoisting an import, or binding a callee statically |
| `error-site` | an error reports the position that raised it | hoisting a check or a chain out of a loop |
| `frames` | a traceback shows the frames the source describes | inlining a call away |
| `gc-timing` | a value becomes collectable when it goes unreachable | keeping one alive longer, or dropping it sooner |
| `table-order` | `pairs` visits a table's keys in one order per run | rebuilding a table so iteration reaches keys differently |

`@relax` accepts two further numeric names which are not in that table because
they are not observable in the same sense. `fp-contract` permits a multiply and
an add to fuse into one rounding, so the function returns something different
rather than reaching the same answer differently:

```nupp
@relax("fp-contract")
@aot
local function scale(a: number, b: number, c: number): number
    return a * b + c
end
```

See [numeric semantics](../learn/performance/ahead-of-time/numeric-semantics.md)
for the effect of contraction on observable results.

`fp-transcendentals` lets explicit fixed-width transcendental operations use
the platform's native implementation instead of Nupp's IR-owned operation
sequence. It is useful for throughput-oriented inference and simulation where
the caller validates an error bound rather than an element-exact answer:

```nupp
local span = nupp.mem.span

@relax("fp-transcendentals")
@aot(target = "gpu")
local function activate(
    exclusive output: span.WriteSpan<float>,
    borrows input: span.Span<float>
): nil
    assert(#output == #input, "length mismatch")
    for i = 1, #output do
        output[i] = nupp.math.f32.exp(nupp.math.f32.narrow(input[i]))
    end
end
```

The grant is per function. It does not enable reassociation, unordered
reductions, flush-to-zero, or contraction; those guarantees remain unchanged
unless separately relaxed.

### Grants do not request rewrites

`@relax` widens what a pass is allowed to do; it never asks for anything. A pass
still has to name the guarantee it would change and check that the site granted
it, and a pass that changes nothing observable needs no grant at all.

The numeric `ipairs` rewrite is one of those. It preserves the language's
observable behavior under its own proof, so it applies whether or not anything
was relaxed.

## Compiler-registered definitions

Compiler integrations add definitions directly through the extensible
`nupp.compiler.annotations` registry. Source declarations are the normal
language-facing mechanism, and direct registration remains useful for built-ins
and compiler extensions.

::: seealso
- [lints.md](lints.md) for the lints `@allow` reaches and how a project moves
  their levels
- [derives.md](derives.md) for what `@derive` and `@debug` generate
- [effects.md](../learn/language/effects.md) for the `@effects` vocabulary in full
- [diagnostics.md](diagnostics.md) for every code an annotation can report
:::
