# Diagnostics A diagnostic is a stable code, a severity, a source range, and a message, and every one the compiler reports carries all four whether a person or a program is reading. This is what one looks like on standard error: ```text src/main.nupp:8:13: error: NUPP2004: no field "horizonal" in Point 8 | print(point.horizonal) | ^~~~~~~~~ help: use the suggested field spelling ``` ## Text report The first line keeps the conventional compiler form, so a build tool that already reads `file:line:column: severity: message` needs no adapter for Nupp. Under it come the quoted source line and a caret run marking the primary range, then one `note` line per related location, then `help`. Lines, columns, and offsets are 1-based byte positions. They are `0` when the file could not be read at all. ### Lint names A lint carries its name after the code, so the thing a project would configure is visible in the line it printed: ```text src/main.nupp:4:5: warning: NUPP2107 exhaustiveness: every branch returns, so this handles "blue" | "green" | "red" and leaves "red" unhandled help: add branches for "red" or add an else clause ``` That name is what an `@allow` suppression or a `nupp.lua` entry writes. See [lints.md](lints.md) for the built-in lints, their categories, and their default levels. ### Color Written to a terminal the same report is colored: the severity and the caret run share a color, the code and lint name are dimmed, the file position and the message have their own, and so does the rail beside the quoted source. Written anywhere else it is exactly the text above, byte for byte. `--color=always` and `--color=never` answer without looking at the stream. Failing an explicit flag, `NO_COLOR` refuses escapes, `CLICOLOR_FORCE` demands them, and failing both the stream is asked whether it is a terminal that understands them, which `TERM=dumb` answers no for. ::: deepdive Color never changes the bytes. A styled report is the plain report with escapes wrapped around spans of it, so a tool that reads compiler output has nothing to strip and no second format to learn, and a person who pipes a failing build into a file gets the same thing they saw. That constraint is why `NO_COLOR` is one-way. The convention gives a user a way to refuse escapes and no way to ask for them, so the variable that forces them on is a separate one, and a terminal that has announced itself as incapable through `TERM=dumb` is still asked last rather than first. ::: ## Diagnostic fields Every diagnostic carries `severity`, `message`, and `range`, plus `fixes`, `notes`, and `related`, which are always present and may be empty. The rest appear when there is something to say: - `file` and `code`, on anything with a source position and a code of its own. - `help`: a concrete repair direction, in one sentence. - `related`: labeled secondary ranges, including ranges in other files. - `fixes`: titled edit sets a tool can apply without reading the prose. - `notes`: context that points at no source, such as the trace classification a [JIT trace check](../learn/performance/jit-trace-checking.md) attaches to its finding. - `lint`: the lint name, when the code names one. - `docs`: the reference section covering the code, as a path and an anchor. The language server converts the same data to UTF-16 LSP ranges, `relatedInformation`, diagnostic `data`, and code actions, so an editor and a terminal disagree about presentation and about nothing else. ## Explaining a code `nupp explain` prints the rule behind a code, a program that reports it, the same program corrected, related codes, and a reference into these documents: ```bash nupp explain NUPP2119 ``` Every code with an entry can be looked up this way, which is faster than searching for the number. The [diagnostic index](#diagnostic-index) below is the same content rendered as a page. ## Code families The leading digit is the family, and every code falls in one, so a code with no entry of its own can still be resolved this far. | Codes | Meaning | | --- | --- | | `NUPP0001` | Source input could not be read. | | `NUPP1001` | Invalid or unterminated lexical input. | | `NUPP1002` | A required token is missing. | | `NUPP1003` | A required name is missing. | | `NUPP1004` | A required expression is missing. | | `NUPP1005` | Another syntax or recovery constraint failed. | | `NUPP1006` | Typed Nupp syntax appeared in plain Lua. | | `NUPP1007` | A docblock names a parameter that does not exist. | | `NUPP1008` | An annotated Lua type was recovered with reduced precision. | | `NUPP2xxx` | Type, declaration, lint, FFI, or ownership diagnostics. | | `NUPP3xxx` | Code generation cannot represent a checked construct. | | `NUPP4001` | Formatting could not safely produce the requested result. | | `NUPP5xxx` | A development-time change requires a restart. | | `OPT-n` | An optimization pass reporting what it did or declined to do. | `OPT-n` is the one family that does not describe a problem. A pass reports one to say that it rewrote something, or that it looked at something and could not. The severity is always `note`, so a remark is reported and stepped over and never fails a build. Remarks are off unless `--remarks` is passed, and they come from `nupp build` and `nupp run` rather than `nupp check`, which does not optimize. The code is stable across a pass being renamed, split, or merged, so it can be cited in a bug report or passed to `-Zno-opt`. See the [performance guide](../learn/performance/index.md) for what each pass reports. ::: deepdive An optimizer is the one part of the compiler whose silence is ambiguous. A pass that declined to inline a call and a pass that was never reached both produce a program with the call still in it, and nothing in the output says which happened. `OPT-n` is the answer a language owes its user when a declared intention did not reach the generated code. Giving remarks codes rather than free text is what makes them usable twice. The same identifier that names a remark in a bug report turns the pass off through `-Zno-opt`, and it survives the pass being renamed or split, so neither use breaks when the optimizer is rearranged. ::: ## Codes by area A code is explained in full by the [diagnostic index](#diagnostic-index) below, and in context by the page that owns the rule. This is the map from an area to that page: | Area | Page | Codes | | --- | --- | --- | | Affine types | [affine-types.md](../learn/runtime/ownership/affine-types.md) | `NUPP2606` | | Ahead-of-time compilation | [ahead-of-time.md](../learn/performance/ahead-of-time/index.md) | `NUPP2901`, `NUPP2902`, `NUPP2903` | | Annotations | [annotations.md](annotations.md) | `NUPP2108`, `NUPP2112`, `NUPP2113`, `NUPP2119`, `NUPP2707`, `NUPP2901`, `NUPP2902`, `NUPP2903` | | Associated types | [associated-types.md](../learn/language/types/associated-types.md) | `NUPP2127`, `NUPP2128`, `NUPP2129`, `NUPP2134`, `NUPP2135`, `NUPP2511` | | C interop | [c-interop.md](../learn/runtime/c-interop/index.md) | `NUPP2201`, `NUPP2402`, `NUPP2403`, `NUPP2904` | | Checked spans | [](nupp.mem.span) | `NUPP2001`, `NUPP2004`, `NUPP2602`, `NUPP2604` | | Comptime | [comptime.md](../learn/language/comptime.md) | `NUPP2410` through `NUPP2416`, `NUPP2419`, `NUPP2420`, `NUPP2421` | | Comptime types | [type-level-computation.md](../learn/language/types/comptime-types.md) | `NUPP2001` | | Derives | [derives.md](derives.md) | `NUPP2810` | | Effect contracts | [effects.md](../learn/language/effects.md) | `NUPP2112`, `NUPP2710`, `NUPP2711` | | Files | [](nupp.io.files) | `NUPP2701` | | Formatter | [fmt.md](../learn/tooling/formatter.md) | `NUPP4001` | | Generics | [generics.md](../learn/language/types/generics.md) | `NUPP2003`, `NUPP2116`, `NUPP2122` | | Gradual typing | [strictness.md](../learn/language/gradual-typing.md) | `NUPP1006`, `NUPP1008`, `NUPP2105`, `NUPP2106` | | Hot reload | [hot-reload.md](../learn/projects/hot-reload.md) | `NUPP5001` | | Interfaces | [interfaces.md](../learn/language/types/interfaces.md) | `NUPP2116`, `NUPP2117`, `NUPP2118`, `NUPP2136`, `NUPP3001` | | Intersections and overloads | [intersections.md](../learn/language/types/intersections.md) | `NUPP2124`, `NUPP2125`, `NUPP2126`, `NUPP2208` | | Lints | [lints.md](lints.md) | `NUPP2107`, `NUPP2120`, `NUPP2501`, `NUPP2502`, `NUPP2504` through `NUPP2515` | | Logging | [](nupp.log) | `NUPP2006` | | LuaJIT trace checking | [jit-trace-checking.md](../learn/performance/jit-trace-checking.md) | `NUPP2502`, `NUPP2505`, `NUPP2514`, `NUPP2515`, `NUPP2707`, `NUPP2904` | | Math | [](nupp.math) | `NUPP2011`, `NUPP2012` | | Metamethods | [metamethods.md](../learn/language/metamethods.md) | `NUPP2003`, `NUPP2005`, `NUPP2006`, `NUPP2007`, `NUPP2116`, `NUPP2117`, `NUPP2118` | | Modules | [modules.md](../learn/language/modules.md) | `NUPP1002`, `NUPP2004`, `NUPP2101`, `NUPP2105`, `NUPP2119` | | Named and plucked arguments | [calls.md](../learn/language/named-arguments.md) | `NUPP2004`, `NUPP2006`, `NUPP2125` | | Narrowing | [narrowing.md](../learn/language/types/narrowing.md) | `NUPP2002`, `NUPP2109`, `NUPP2110` | | Overloads and overrides | [overloads.md](../learn/language/types/overloads.md) | `NUPP2118`, `NUPP2125`, `NUPP2126`, `NUPP2208` | | Ownership | [ownership.md](../learn/runtime/ownership/borrowing.md) | `NUPP2601`, `NUPP2602`, `NUPP2603`, `NUPP2606` through `NUPP2615`, `NUPP2620` | | Primitive types | [primitives.md](../learn/language/types/primitives.md) | `NUPP2001`, `NUPP2002`, `NUPP2004`, `NUPP2006`, `NUPP2106`, `NUPP2115` | | Property capabilities | [properties.md](../learn/language/types/properties.md) | `NUPP2009`, `NUPP2118` | | Records and structs | [records.md](../learn/language/types/records-and-structs.md) | `NUPP2118`, `NUPP2201`, `NUPP2202`, `NUPP2204`, `NUPP2205` | | Reflection | [reflection.md](../learn/language/reflection.md) | `NUPP2414`, `NUPP2415`, `NUPP2416`, `NUPP2418` | | Refinements | [refinements.md](../learn/language/types/refinements.md) | `NUPP2122` | | Structure-of-arrays storage | [structure-of-arrays.md](../learn/runtime/data/structure-of-arrays.md) | `NUPP2009`, `NUPP2403` | | Suspension | [suspension.md](../learn/runtime/concurrency/suspension.md) | `NUPP2603`, `NUPP2701`, `NUPP2702`, `NUPP2706` | | Switch expressions | [switch-expressions.md](../learn/language/switch-expressions.md) | `NUPP2137` through `NUPP2142`, `NUPP3001` | | Target capability profiles | [build.md](../learn/projects/build.md) | `NUPP2904` | | Type packs | [packs.md](../learn/language/types/packs.md) | `NUPP2007`, `NUPP2010`, `NUPP2121`, `NUPP2605` | | Type system | [overview.md](../learn/language/types/index.md) | `NUPP2001`, `NUPP2004`, `NUPP2011`, `NUPP2012`, `NUPP2105`, `NUPP2106` | | Unions | [unions.md](../learn/language/types/unions.md) | `NUPP2001`, `NUPP2107`, `NUPP2138`, `NUPP2139`, `NUPP2140` | A code appears in several rows when several rules can report it. `NUPP2004` is one code for "that field is not there", and which page explains it depends on what was being reached for. ## Repairs A fix is a title and a set of byte-ranged edits into one file, and applying it is all-or-nothing. Only unambiguous rewrites qualify: where a message lists alternatives, each alternative is its own fix rather than a guess between them. The checker offers fixes for misspelled variables, type names, fields, methods, and metamethods; missing module qualifications and `require` statements; declaration visibility; a customary operator replaced with Lua's word; explicit casts for intended lossy narrowing; and the conversion that establishes a fixed-width value. A diagnostic gives `help` rather than an edit when the compiler cannot choose a program on the author's behalf. Enum exhaustiveness cannot invent branch bodies, an ambiguous global cannot decide which public declaration should change visibility, and a resource without cleanup metadata cannot guess which function owns that responsibility. ## Machine-readable output Every command whose result is data rather than a side effect takes `--format json`, with `--json` as its shorthand, and every command that takes it also takes `--schema`, which prints the JSON Schema of what `--json` writes and exits. The schema is declared beside the code that writes it and a test validates real output against it, so the two cannot drift. | Command | `--json` reports | | --- | --- | | `aot` | what every `@aot` function in one file lowered to | | `ast` | the lossless syntax tree | | `bc` | the bytecode of one file, instruction by instruction | | `build` | diagnostics, the target, and every path written | | `check` | diagnostics, and where the check's time went | | `clean` | the paths removed, or that would be | | `coverage` | the aggregate coverage summary | | `doc` | the resolved format, the output, and every path written | | `explain` | a code's rule and worked examples | | `export-c` | the header written and the declarations in it | | `fixpoint` | whether it reproduced, and why not | | `fmt` | unformatted, written, and failed, kept apart | | `import-c` | the module written and any warnings | | `init` | what the template resolved to and what it wrote | | `lints` | every lint, its level here, and its default | | `lsp` | per operation; each has its own schema | | `ownership-audit` | foreign pointer contracts and unsafe assertion sites | | `reference` | the reference, section by section | | `tasks` | the task list, or one task's configuration | | `test` | totals and a record per test, with file and line | `nupp run` is the exception. Its `--json` writes the `--jit-aborts` record as JSON instead of CSV, because the program's own output is the run's output. Read `ok` before `diagnostics`. An empty list means the project is clean only when `ok` is true, since a run that could not use the manifest never reached a file and reports the same empty list. ## Agent workflow 1. Run `nupp check --json --strict`, and read `ok` first. 2. Apply a complete fix from `diagnostics[].fixes` when its title matches the intended repair. Never take individual edits out of one. 3. Read `docs` on a diagnostic, or run `nupp explain --json`, when the message alone does not say what the rule is. For the surrounding prose rather than the rule, `nupp reference --for ` prints the sections that cover it, and `nupp reference --section ` takes the `docs` pointer itself. Either is a few hundred words where the chapter is thousands. 4. Inspect `related` locations before changing cross-file declarations or ownership transfers. 5. Use `nupp lsp inspect`, `nupp lsp definition`, and `nupp lsp references` when more semantic context is needed. 6. Re-run the check after each edit group, then `nupp test --json` before committing, which reports each failing test's name, message, file, and line rather than a wall of progress text. 7. Read `timing.compiledModules` and `timing.slowest` when a check of an unchanged project is slower than expected. `compiledModules = 0` is the authoritative answer that nothing was redone, and `slowest` ranks modules by time actually spent either way, since confirming that a cache entry is still valid costs time too. ## FAQ ### How do I stop the checker reporting a lint I do not want? Set the lint to `off` by name or by category in the `lints` table in `nupp.lua`, or decorate the one statement with `@allow("lint-name")`. See [lints.md](lints.md) for the resolution order, which runs registry default, then category, then name, then the `@allow`. ### Why did a diagnostic give me `help` and no fix? Because more than one program would satisfy the rule and the compiler will not pick one. An unhandled enum member needs a branch body only the author knows, so the message says what is missing and stops. See [Repairs](#repairs) for the repairs that are unambiguous enough to be offered as edits. ### Why is `diagnostics` empty when the command still failed? The run never reached a file. A manifest that could not be used ends a check before anything is parsed, and that answers with the same empty list a clean project does, which is why `ok` is a separate field. See [Machine-readable output](#machine-readable-output). ### Why did `@allow` not silence an error? `@allow` reaches lints and nothing else, so naming a type error in one leaves the error standing and reports `NUPP2108` for the name. See [lints.md](lints.md) for what counts as a lint. ::: seealso - [cli.md](cli.md) for every command, its options, and its exit codes - [lsp.md](../learn/tooling/language-server.md) for the same diagnostics inside an editor ::: ## Diagnostic index Every code the compiler reports specifically, with the rule it enforces and, where one is known, the program that reports it beside the same program corrected. `nupp explain ` prints the same thing in a terminal. A code that resolves only through its family is not here. The families are the leading digit, and each one answers for every code under it. ### Input #### NUPP0001 A source file could not be read. The path named on the command line, or reached from a require, could not be opened. Check the spelling and that the file is readable from the directory the compiler was run in. Reference: [docs/reference/diagnostics.md#code-families](docs/reference/diagnostics.md#code-families). ### Syntax #### NUPP1001 The lexer could not read a token to completion. ```nupp [Reported] local s = 'oops return 1 ``` A string, long string, long comment, interpolation, or number must close or resolve within the input it is given: a quote must find its matching quote before the line ends, a long bracket must find its matching close, and a numeral must match the shape of a number. A byte that starts none of the lexer's tokens is reported the same way. Recovery marks the malformed span as an error token and resumes lexing from the next line. ```nupp [Accepted] local s = 'oops' return s ``` Related: [**NUPP1002**](#nupp1002), [**NUPP1005**](#nupp1005). Reference: [docs/reference/diagnostics.md#code-families](docs/reference/diagnostics.md#code-families). [Open the reported program in the playground](/playground/#source=local%20s%20%3D%20%27oops%0Areturn%201). #### NUPP1002 A required token is missing. ```nupp [Reported] local function f(): integer if true then return 1 end ``` A construct was opened and not closed. The position reported is where the closing token was expected, which is the end of the file when nothing closed it at all. ```nupp [Accepted] local function f(): integer if true then return 1 end return 0 end ``` Related: [**NUPP1004**](#nupp1004). Reference: [docs/reference/diagnostics.md#code-families](docs/reference/diagnostics.md#code-families). [Open the reported program in the playground](/playground/#source=local%20function%20f%28%29%3A%20integer%0A%20%20%20%20if%20true%20then%0A%20%20%20%20%20%20%20%20return%201%0Aend). #### NUPP1003 A required name is missing. ```nupp [Reported] local record Point x: number end local function show(p: Point): number return p. end return show ``` Certain tokens are always followed by a name: '.', 'function', 'for', 'as', '@', 'metamethod', and other positions where a declaration or reference names something. When the next token is not a name, the parser reports it there and inserts a zero-width name placeholder so parsing, and the source round trip, can continue. ```nupp [Accepted] local record Point x: number end local function show(p: Point): number return p.x end return show ``` Related: [**NUPP1002**](#nupp1002), [**NUPP1004**](#nupp1004). Reference: [docs/reference/diagnostics.md#code-families](docs/reference/diagnostics.md#code-families). [Open the reported program in the playground](/playground/#source=local%20record%20Point%0A%20%20%20%20x%3A%20number%0Aend%0A%0Alocal%20function%20show%28p%3A%20Point%29%3A%20number%0A%20%20%20%20return%20p.%0Aend%0A%0Areturn%20show). #### NUPP1004 A required expression is missing. ```nupp [Reported] local record Point x: integer end local bare = new Point return bare ``` An expression position, whether a parenthesized operand, a call argument, or the whole of a value, must actually contain one; a token that cannot begin an expression is reported there and replaced with a zero-width placeholder. `new` is held to the same requirement in its own way: it takes a suffixed expression that must end in a call, since a bare `new T` would be a second spelling of `new T()`. ```nupp [Accepted] local record Point x: integer end local made = new Point(x = 1) return made ``` Related: [**NUPP1002**](#nupp1002), [**NUPP1003**](#nupp1003). Reference: [docs/reference/diagnostics.md#code-families](docs/reference/diagnostics.md#code-families). [Open the reported program in the playground](/playground/#source=local%20record%20Point%0A%20%20%20%20x%3A%20integer%0Aend%0A%0Alocal%20bare%20%3D%20new%20Point%0A%0Areturn%20bare). #### NUPP1005 Another syntax or recovery constraint failed. ```nupp [Reported] return 1 local x = 2 ``` The catch-all syntax code, used for a structural violation that is not a missing token, name, or expression: a `return` that is not the last statement in its block, a `const` path that is not static, and similar constraints the parser enforces directly. Each diagnostic's message names the specific rule that failed. ```nupp [Accepted] local x = 2 return x ``` Related: [**NUPP1002**](#nupp1002), [**NUPP1004**](#nupp1004). Reference: [docs/reference/diagnostics.md#code-families](docs/reference/diagnostics.md#code-families). [Open the reported program in the playground](/playground/#source=return%201%0Alocal%20x%20%3D%202). #### NUPP1006 The typed layer appears in a plain Lua file. A `.lua` file is Lua. The toolchain will require, build and run one unchanged, so typed Nupp syntax cannot be written into it. Rename the file `.nupp` to mean it, or `.g.nupp` to keep the gradual layer with typed syntax available. Type comments are separate: recognized LuaCATS, EmmyLua, and typed LuaDoc comments are always imported. Related: [**NUPP2105**](#nupp2105), [**NUPP2106**](#nupp2106). Reference: [docs/reference/diagnostics.md#code-families](docs/reference/diagnostics.md#code-families). #### NUPP1007 A docblock parameter does not exist. ```nupp [Reported] --- @param valeu the value local function keep(value: integer): integer return value end return keep(1) ``` An @param line documents one parameter of the declaration below it. Spell that parameter's source name exactly, or remove the line; otherwise generated documentation promises an argument the function does not accept. ```nupp [Accepted] --- @param value the value local function keep(value: integer): integer return value end return keep(1) ``` Related: [**NUPP2506**](#nupp2506). Reference: [docs/learn/tooling/documentation.md#doc-comments](docs/learn/tooling/documentation.md#doc-comments). [Open the reported program in the playground](/playground/#source=---%20%40param%20valeu%20the%20value%0Alocal%20function%20keep%28value%3A%20integer%29%3A%20integer%0A%20%20%20%20return%20value%0Aend%0Areturn%20keep%281%29). #### NUPP1008 An annotated Lua type lost precision during import. Type comments in a .lua file are always read. A malformed, ambiguous, unsupported, or ownership-incomplete foreign type recovers at the smallest affected position, usually as any, and checking continues. Related: [**NUPP1006**](#nupp1006), [**NUPP2101**](#nupp2101). Reference: [docs/learn/projects/integrations/luacats.md#recovery](docs/learn/projects/integrations/luacats.md#recovery). #### NUPP1009 A type dependency's declaration file was not imported. A `kind = "types"` dependency is somebody else's source, pinned at a revision this project chose but did not write. A file in that tree the importer cannot read, parse, or check is skipped and named here; the declarations beside it are still imported, so one unreadable corner of an API does not cost the project the rest of it. Pin a revision this compiler can read, or report the construct that stopped it. Related: [**NUPP1008**](#nupp1008), [**NUPP1006**](#nupp1006). Reference: [docs/learn/projects/integrations/luacats.md#recovery](docs/learn/projects/integrations/luacats.md#recovery). ### Types, declarations, and rules #### NUPP2001 A value does not fit the type it is bound to. ```nupp [Reported] local count: integer = "twelve" return count ``` An annotated binding accepts only values of that type. The annotation is the claim; the initializer has to keep it. ```nupp [Accepted] local count: integer = 12 return count ``` Related: [**NUPP2002**](#nupp2002), [**NUPP2006**](#nupp2006). Reference: [docs/reference/diagnostics.md#code-families](docs/reference/diagnostics.md#code-families). [Open the reported program in the playground](/playground/#source=local%20count%3A%20integer%20%3D%20%22twelve%22%0Areturn%20count). #### NUPP2002 A returned value does not fit the declared result. ```nupp [Reported] local function length(): integer return "long" end return length ``` A function's result annotation is a promise to every caller. Each returned value must fit its corresponding result type, and a function that declares several results must return the complete sequence. ```nupp [Accepted] local function length(): integer return 4 end return length ``` Related: [**NUPP2001**](#nupp2001), [**NUPP2010**](#nupp2010). Reference: [docs/learn/language/types/primitives.md#function-declarations](docs/learn/language/types/primitives.md#function-declarations). [Open the reported program in the playground](/playground/#source=local%20function%20length%28%29%3A%20integer%0A%20%20%20%20return%20%22long%22%0Aend%0A%0Areturn%20length). #### NUPP2003 An operator's operand types cannot support what it does. ```nupp [Reported] local lpeg = require("lpeg") local pattern = lpeg.P("a") ^ "two" return pattern ``` Arithmetic, comparison, concatenation, length, and LPeg's pattern algebra each accept a fixed set of operand shapes or a metamethod contract that stands in for one. LPeg's `Pattern` operators are compiler-provided rather than ordinary metamethods: `*`, `+`, and `-` need operands whose capture shapes can be combined, `^` needs an integer exponent, `/` needs a string, integer, function, or table transform, and `%` needs a function accumulator. An operand outside what the operator accepts is reported rather than run to see what happens. ```nupp [Accepted] local lpeg = require("lpeg") local pattern = lpeg.P("a") ^ 2 return pattern ``` Related: [**NUPP2005**](#nupp2005), [**NUPP2006**](#nupp2006). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20lpeg%20%3D%20require%28%22lpeg%22%29%0Alocal%20pattern%20%3D%20lpeg.P%28%22a%22%29%20%5E%20%22two%22%0Areturn%20pattern). #### NUPP2004 The field does not exist on that type. ```nupp [Reported] local record Point x: number y: number end local function show(p: Point): number return p.z end return show ``` A field read has to name a field the receiver's type actually has. When the name is close to a real one the diagnostic carries a fix that spells it correctly. ```nupp [Accepted] local record Point x: number y: number end local function show(p: Point): number return p.x end return show ``` Related: [**NUPP2005**](#nupp2005), [**NUPP2119**](#nupp2119). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20record%20Point%0A%20%20%20%20x%3A%20number%0A%20%20%20%20y%3A%20number%0Aend%0A%0Alocal%20function%20show%28p%3A%20Point%29%3A%20number%0A%20%20%20%20return%20p.z%0Aend%0A%0Areturn%20show). #### NUPP2005 A call's callee has no callable type or `__call` contract. ```nupp [Reported] local count: integer = 5 return count() ``` A call needs a function type, an intersection with at least one function member, or a receiver whose type contracts for `__call`. `any` is exempt, since a gradual value's runtime shape is not yet known; anything else that is called is reported once, and its argument expressions are still checked so one mistake does not hide another. ```nupp [Accepted] local function callable(): integer return 5 end return callable() ``` Related: [**NUPP2003**](#nupp2003), [**NUPP2006**](#nupp2006). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20count%3A%20integer%20%3D%205%0Areturn%20count%28%29). #### NUPP2006 A call's arguments are not arranged in a way it can be given. ```nupp [Reported] local record Vec3 x: number y: number z: number end local function draw(x: number, y: number): nil print(x, y) end local function make(): Vec3 return new Vec3(x = 1, y = 2, z = 3) end draw({x, y} = make()) return draw ``` Named and plucked arguments follow every positional argument, so nothing positional may come after one. A plucked operand must be a name or dotted field path: each name it fills reads one field of that path, and confining it to a path is what lets those reads be unordered and the path be evaluated once. Bind a call or a computed index to a local and pluck from the local. ```nupp [Accepted] local record Vec3 x: number y: number z: number end local function draw(x: number, y: number): nil print(x, y) end local function make(): Vec3 return new Vec3(x = 1, y = 2, z = 3) end local position = make() draw({x, y} = position) return draw ``` Related: [**NUPP2004**](#nupp2004), [**NUPP2125**](#nupp2125). Reference: [docs/learn/language/named-arguments.md](docs/learn/language/named-arguments.md). [Open the reported program in the playground](/playground/#source=local%20record%20Vec3%0A%20%20%20%20x%3A%20number%0A%20%20%20%20y%3A%20number%0A%20%20%20%20z%3A%20number%0Aend%0A%0Alocal%20function%20draw%28x%3A%20number%2C%20y%3A%20number%29%3A%20nil%0A%20%20%20%20print%28x%2C%20y%29%0Aend%0A%0Alocal%20function%20make%28%29%3A%20Vec3%0A%20%20%20%20return%20new%20Vec3%28x%20%3D%201%2C%20y%20%3D%202%2C%20z%20%3D%203%29%0Aend%0A%0Adraw%28%7Bx%2C%20y%7D%20%3D%20make%28%29%29%0A%0Areturn%20draw). #### NUPP2007 A call passes more arguments than its callee accepts. ```nupp [Reported] local function keep(value: integer): integer return value end return keep(1, 2) ``` A function that takes fewer parameters is usable where more are supplied, because ignoring trailing arguments is ordinary Lua; the reverse is an error unless the callee's parameter pack has a variadic tail to absorb the extras. ```nupp [Accepted] local function keep(value: integer): integer return value end return keep(1) ``` Related: [**NUPP2006**](#nupp2006), [**NUPP2010**](#nupp2010). Reference: [docs/learn/language/types/primitives.md#function-declarations](docs/learn/language/types/primitives.md#function-declarations). [Open the reported program in the playground](/playground/#source=local%20function%20keep%28value%3A%20integer%29%3A%20integer%0A%20%20%20%20return%20value%0Aend%0Areturn%20keep%281%2C%202%29). #### NUPP2008 A const binding or const module field is assigned to. ```nupp [Reported] const count = 1 count = 2 return count ``` The `const` modifier on a local or a module field is a promise that nothing after its initializer changes it. An ordinary assignment or compound assignment to that name is refused, whether the binding is a local declared `const`, a builtin the prelude declares constant, or a field a module recorded as const on first assignment. ```nupp [Accepted] local count = 1 count = 2 return count ``` Related: [**NUPP2001**](#nupp2001), [**NUPP2602**](#nupp2602). Reference: [docs/learn/language/types/primitives.md#const](docs/learn/language/types/primitives.md#const). [Open the reported program in the playground](/playground/#source=const%20count%20%3D%201%0Acount%20%3D%202%0Areturn%20count). #### NUPP2009 A property view does not grant the requested access. ```nupp [Reported] local out: {writeonly value: string} = {} local value = out.value return value ``` A read-only property may be read but not assigned, and a write-only property may be assigned but not read. Use a view that declares the needed capability. ```nupp [Accepted] local out: {writeonly value: string} = {} out.value = "ready" return out ``` Related: [**NUPP2004**](#nupp2004), [**NUPP2008**](#nupp2008). Reference: [docs/learn/language/types/properties.md#access-diagnostics](docs/learn/language/types/properties.md#access-diagnostics). [Open the reported program in the playground](/playground/#source=local%20out%3A%20%7Bwriteonly%20value%3A%20string%7D%20%3D%20%7B%7D%0Alocal%20value%20%3D%20out.value%0Areturn%20value). #### NUPP2010 A complete value pack does not fit the required sequence. ```nupp [Reported] local value = select(0, 1, 2) return value ``` Calls, returns, assignments, selection, and coroutine transfers compare complete Lua value sequences. Their fixed slots, optional tails, and correlated alternatives must agree; use parentheses only when intentionally projecting one ordinary value. ```nupp [Accepted] local first, second = select(1, 1, 2) return first, second ``` Related: [**NUPP2002**](#nupp2002), [**NUPP2007**](#nupp2007), [**NUPP2121**](#nupp2121). Reference: [docs/learn/language/types/packs.md#pack-compatibility](docs/learn/language/types/packs.md#pack-compatibility). [Open the reported program in the playground](/playground/#source=local%20value%20%3D%20select%280%2C%201%2C%202%29%0Areturn%20value). #### NUPP2011 A fixed-width value was claimed without being established. ```nupp [Reported] local input: number = 0.1 local value: float = input as float return value ``` `float`, `int32`, and `uint32` are checked refinements of Lua numbers. A fitting literal, a checked refined source, a reified load, or the explicit conversion establishes the claim; an erased `as` does not. ```nupp [Accepted] local input: number = 0.1 local value: float = nupp.math.f32.narrow(input) return value ``` Related: [**NUPP2001**](#nupp2001), [**NUPP2002**](#nupp2002), [**NUPP2006**](#nupp2006), [**NUPP2012**](#nupp2012). Reference: [](nupp.math#fixed-width-arithmetic). [Open the reported program in the playground](/playground/#source=local%20input%3A%20number%20%3D%200.1%0Alocal%20value%3A%20float%20%3D%20input%20as%20float%0Areturn%20value). #### NUPP2012 A physical storage width was used as an ordinary value type. ```nupp [Reported] local byte: uint8 = 1 return byte ``` `int8`, `int16`, `uint8`, and `uint16` describe physical layout. Use them for struct fields, C arrays and pointers, cdefs, or span elements; use `int32` or `uint32` for locals, parameters, results, and record fields. ```nupp [Accepted] local byte: uint32 = 1 return byte ``` Related: [**NUPP2011**](#nupp2011), [**NUPP2201**](#nupp2201), [**NUPP2203**](#nupp2203). Reference: [docs/learn/language/types/primitives.md#numbers](docs/learn/language/types/primitives.md#numbers). [Open the reported program in the playground](/playground/#source=local%20byte%3A%20uint8%20%3D%201%0Areturn%20byte). #### NUPP2101 A type name cannot be resolved. ```nupp [Reported] local count: Count = 1 return count ``` A type name must be a built-in, a declaration visible in this file, or a member reached through its module. Declare the type, require the module that exports it, or correct its spelling. ```nupp [Accepted] local type Count = integer local count: Count = 1 return count ``` Related: [**NUPP2105**](#nupp2105), [**NUPP2120**](#nupp2120). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20count%3A%20Count%20%3D%201%0Areturn%20count). #### NUPP2102 Two project globals declare the same type name. A `global` declaration is reachable project-wide with no `require`, so globals share one flat namespace across every file. When two files each declare a global type of the same name, an unqualified use of that name cannot know which declaration it means, and both declaring locations are reported. This is inherently a whole-project condition: within the file that declares a name, its own declaration is already in scope directly and this check is not reached, so a single isolated file cannot exhibit it on its own. Related: [**NUPP2104**](#nupp2104), [**NUPP2101**](#nupp2101). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). #### NUPP2104 Two project globals declare the same value name. A `global record` or `global struct` declaration puts a value in `_G` as well as a type in the project's global type namespace. When two files each declare a global of the same name whose value side collides, an unqualified use of that name as a value cannot know which declaration it means, and both declaring locations are reported. Like NUPP2102, this is a whole-project condition: a file that declares the name resolves its own declaration directly and never reaches this check, so a single isolated file cannot exhibit it on its own. Related: [**NUPP2102**](#nupp2102), [**NUPP2120**](#nupp2120). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). #### NUPP2105 A name is not any value the checker or project can account for. ```nupp [Reported] local function shout(text: string): string return txet .. "!" end return shout ``` An unknown name is gradual by default: it types as `any` and stays quiet, because it might resolve at runtime. Under `--strict`, or in any file the strict floor already applies to, an unknown name that is not a project module's basename is reported instead, with a spelling fix offered when a visible name is close enough to be the one meant. This applies only under `--strict`, or in a `.nupp` file. ```nupp [Accepted] local function shout(text: string): string return text .. "!" end return shout ``` Related: [**NUPP2120**](#nupp2120), [**NUPP2101**](#nupp2101). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20function%20shout%28text%3A%20string%29%3A%20string%0A%20%20%20%20return%20txet%20..%20%22%21%22%0Aend%0Areturn%20shout). #### NUPP2106 An exported declaration needs a type annotation. ```nupp [Reported] local m = {} function m.double(n) return n * 2 end return m ``` What a module returns is its interface, and an interface is written down rather than inferred. A function attached to the module table needs its parameter and result types. This is a strict rule: it is reported only under --strict, or when the manifest sets strict = true. This applies only under `--strict`, or in a `.nupp` file. ```nupp [Accepted] local m = {} function m.double(n: integer): integer return n * 2 end return m ``` Related: [**NUPP2119**](#nupp2119). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20m%20%3D%20%7B%7D%0A%0Afunction%20m.double%28n%29%0A%20%20%20%20return%20n%20%2A%202%0Aend%0A%0Areturn%20m). #### NUPP2107 A dispatch leaves members of a closed set unhandled. ```nupp [Reported] local type Color = 'red' | 'green' | 'blue' local function name(c: Color): string if c == 'red' then return "red" end return "other" end return name ``` When every branch returns, the dispatch is exhaustive or it is not, and the checker can tell which. Add the missing branches, or an else that says the rest are deliberately alike. ```nupp [Accepted] local type Color = 'red' | 'green' | 'blue' local function name(c: Color): string if c == 'red' then return "red" elseif c == 'green' then return "green" else return "blue" end end return name ``` Reported by the `exhaustiveness` lint, category `correctness`, at `warning` by default. Configure it by name or by category. Related: [**NUPP2004**](#nupp2004). Reference: [docs/reference/lints.md](docs/reference/lints.md). [Open the reported program in the playground](/playground/#source=local%20type%20Color%20%3D%20%27red%27%20%7C%20%27green%27%20%7C%20%27blue%27%0A%0Alocal%20function%20name%28c%3A%20Color%29%3A%20string%0A%20%20%20%20if%20c%20%3D%3D%20%27red%27%20then%20return%20%22red%22%20end%0A%20%20%20%20return%20%22other%22%0Aend%0A%0Areturn%20name). #### NUPP2108 A type error is named as though it were a lint. ```nupp [Reported] @allow(NUPP2001) local count: integer = "one" return count ``` `@allow` suppresses lint judgements, by lint name or code. A type error says the program cannot keep its own type claims, so it cannot be configured or silenced; correct that error instead. ```nupp [Accepted] local count: integer = 1 return count ``` Related: [**NUPP2001**](#nupp2001), [**NUPP2112**](#nupp2112). Reference: [docs/reference/lints.md#local-suppressions](docs/reference/lints.md#local-suppressions). [Open the reported program in the playground](/playground/#source=%40allow%28NUPP2001%29%0Alocal%20count%3A%20integer%20%3D%20%22one%22%0Areturn%20count). #### NUPP2109 A name written to select a parameter or captured value resolves to nothing. ```nupp [Reported] local record R x: integer end local function f(a: R): b is R return true end return f ``` Several forms name a parameter or a value already in scope rather than introduce one: a predicate's `x is T` result names the tested parameter, and a closure's `takes (...)` or `borrows (...)` clause names values it captures. Each such name must resolve to an actual parameter of the enclosing function, or an actual value visible where the closure is written; a name that answers to nothing there is reported rather than silently ignored. ```nupp [Accepted] local record R x: integer end local function f(a: R): a is R return true end return f ``` Related: [**NUPP2110**](#nupp2110). Reference: [docs/learn/language/types/narrowing.md#predicate-functions](docs/learn/language/types/narrowing.md#predicate-functions). [Open the reported program in the playground](/playground/#source=local%20record%20R%0A%20%20%20%20x%3A%20integer%0Aend%0A%0Alocal%20function%20f%28a%3A%20R%29%3A%20b%20is%20R%0A%20%20%20%20return%20true%0Aend%0A%0Areturn%20f). #### NUPP2110 A predicate's tested parameter could never hold the narrowed type. ```nupp [Reported] local record R x: integer end local function f(a: string): a is R return true end return f ``` A predicate's return type `param is T` is trusted rather than verified for what the body proves, but the checker still confirms the shape is coherent: the named parameter's declared type must be able to hold T. When the two types are disjoint the predicate can never truthfully hold, so it is refused rather than compiled as narrowing that could never fire. ```nupp [Accepted] local record R x: integer end local function f(a: R): a is R return true end return f ``` Related: [**NUPP2109**](#nupp2109). Reference: [docs/learn/language/types/narrowing.md#predicate-functions](docs/learn/language/types/narrowing.md#predicate-functions). [Open the reported program in the playground](/playground/#source=local%20record%20R%0A%20%20%20%20x%3A%20integer%0Aend%0A%0Alocal%20function%20f%28a%3A%20string%29%3A%20a%20is%20R%0A%20%20%20%20return%20true%0Aend%0A%0Areturn%20f). #### NUPP2111 An annotation name does not resolve to a definition. ```nupp [Reported] @inline local function f(): nil end return f ``` An `@name` application resolves `name` against a definition: a compiler built-in, or a project declaration reached with `@annotation`. Applying a name nothing defines, or one a project's own resolution reports an error for, is refused rather than treated as an inert comment. ```nupp [Accepted] local function f(): nil end return f ``` Related: [**NUPP2112**](#nupp2112), [**NUPP2114**](#nupp2114). Reference: [docs/reference/annotations.md#defining-an-annotation](docs/reference/annotations.md#defining-an-annotation). [Open the reported program in the playground](/playground/#source=%40inline%0Alocal%20function%20f%28%29%3A%20nil%0Aend%0A%0Areturn%20f). #### NUPP2112 An annotation is applied where its definition forbids it, or with arguments outside its declared shape. ```nupp [Reported] @jit local x = 1 return x ``` A definition's `targets` set is the complete list of constructs an application may decorate, and its argument policy (none, names, effects, warnings, typed) is the complete shape its arguments may take. An application outside either is refused rather than silently accepted. ```nupp [Accepted] @jit local function f(): nil end return f ``` Related: [**NUPP2111**](#nupp2111), [**NUPP2115**](#nupp2115). Reference: [docs/reference/annotations.md#attachment-targets](docs/reference/annotations.md#attachment-targets). [Open the reported program in the playground](/playground/#source=%40jit%0Alocal%20x%20%3D%201%0A%0Areturn%20x). #### NUPP2113 A reserved built-in annotation is applied before its behavior is implemented. A built-in definition may carry a reservation naming the feature it is waiting on, ahead of the feature existing. Applying that name still parses and resolves, since the definition is real, but is refused with what it is reserved for rather than silently accepted and doing nothing. No shipped built-in currently carries a reservation -- the mechanism exists for a name registered ahead of its implementation, as `@jit` itself once was. Related: [**NUPP2111**](#nupp2111), [**NUPP2112**](#nupp2112). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). #### NUPP2114 @annotationValue or @ref is used outside an annotation-definition field, or a definition's own shape is invalid. ```nupp [Reported] local record Plain @annotationValue x: string end ``` @annotationValue and @ref are meaningful only on a field inside a record or struct itself marked @annotation, and @annotation takes exactly one named `targets` argument: a non-empty array of target-name string literals. Any of these written where that context is missing describes a definition that cannot exist. ```nupp [Accepted] @annotation(targets = {"record"}) local record Plain @annotationValue x: string end ``` Related: [**NUPP2115**](#nupp2115), [**NUPP2112**](#nupp2112). Reference: [docs/reference/annotations.md#single-value-applications](docs/reference/annotations.md#single-value-applications). [Open the reported program in the playground](/playground/#source=local%20record%20Plain%0A%20%20%20%20%40annotationValue%0A%20%20%20%20x%3A%20string%0Aend). #### NUPP2115 A typed annotation's argument does not fit its member, or is not a compile-time constant. ```nupp [Reported] @syntax(42) local value = 1 return value ``` A `typed`-argument annotation checks each supplied argument against the member it names: the value must fit the member's declared type, a `@ref` member must resolve to a compatible type reference, no member may be supplied twice, and every required member must be supplied. Only the member marked `@annotationValue` may be supplied positionally. ```nupp [Accepted] @syntax("json") local value = 1 return value ``` Related: [**NUPP2114**](#nupp2114), [**NUPP2112**](#nupp2112). Reference: [docs/reference/annotations.md#built-in-annotations](docs/reference/annotations.md#built-in-annotations). [Open the reported program in the playground](/playground/#source=%40syntax%2842%29%0Alocal%20value%20%3D%201%0A%0Areturn%20value). #### NUPP2116 A type argument, or an associated-type answer, does not fit its declared bound. ```nupp [Reported] local interface Named name: string end local interface Tagged associated type Tag is Named = integer end return Tagged ``` A `T is Bound` constraint, wherever it is written -- a generic type parameter or an associated type's own bound -- is checked where the argument is supplied: at a generic's instantiation, or at the default or answer that fills the associated type. An `any` argument skips the check; any other concrete type must actually satisfy the bound. ```nupp [Accepted] local interface Named name: string end local interface Tagged associated type Tag is Named = Named end return Tagged ``` Related: [**NUPP2117**](#nupp2117), [**NUPP2003**](#nupp2003). Reference: [docs/learn/language/types/generics.md#constraints-use-is](docs/learn/language/types/generics.md#constraints-use-is). [Open the reported program in the playground](/playground/#source=local%20interface%20Named%0A%20%20%20%20name%3A%20string%0Aend%0A%0Alocal%20interface%20Tagged%0A%20%20%20%20associated%20type%20Tag%20is%20Named%20%3D%20integer%0Aend%0A%0Areturn%20Tagged). #### NUPP2117 A declaration names something other than an interface after `is`. ```nupp [Reported] local record Other end local record Bad is Other end return Bad ``` Only an interface may appear after `is` on a record or struct declaration, because that is the only declaration kind able to state a contract for another to inherit. A record, struct, alias, or any other named value is refused there, even when its shape happens to match. ```nupp [Accepted] local interface Other end local record Bad is Other end return Bad ``` Related: [**NUPP2116**](#nupp2116), [**NUPP2136**](#nupp2136). Reference: [docs/learn/language/types/interfaces.md#is-is-a-claim-not-a-proof](docs/learn/language/types/interfaces.md#is-is-a-claim-not-a-proof). [Open the reported program in the playground](/playground/#source=local%20record%20Other%0Aend%0A%0Alocal%20record%20Bad%20is%20Other%0Aend%0A%0Areturn%20Bad). #### NUPP2118 A declaration contains an invalid or conflicting member. ```nupp [Reported] local record Pair first: string first: integer end return Pair ``` A declaration has one member of a given name and capability. Duplicate fields, methods with the same parameter pack, conflicting static and instance members, and unknown metamethods cannot describe one coherent contract. ```nupp [Accepted] local record Pair first: string second: integer end return Pair ``` Related: [**NUPP2004**](#nupp2004), [**NUPP2123**](#nupp2123). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20record%20Pair%0A%20%20%20%20first%3A%20string%0A%20%20%20%20first%3A%20integer%0Aend%0A%0Areturn%20Pair). #### NUPP2119 A declaration does not say where it lives. ```nupp [Reported] record Loose id: integer end return Loose ``` A declaration is file-local (`local`), a member of a table (`record m.R`), or a project global (`global`). Plain Lua would have made it a global silently; Nupp asks instead, because a name that means one thing here and another elsewhere is worth one word to prevent. ```nupp [Accepted] local record Loose id: integer end return Loose ``` Related: [**NUPP2106**](#nupp2106), [**NUPP2120**](#nupp2120). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=record%20Loose%0A%20%20%20%20id%3A%20integer%0Aend%0A%0Areturn%20Loose). #### NUPP2120 A project module is used without being required. ```nupp [Reported] local answer: number = mathutil.double(21) return answer ``` A project file does not put its basename into every other file's scope. Bind the module with `require` before reading its members; the diagnostic names the require call that makes the program valid. ```nupp [Accepted] local mathutil = require("mathutil") local answer: number = mathutil.double(21) return answer ``` Reported by the `missing-require` lint, category `correctness`, at `error` by default. Configure it by name or by category. Related: [**NUPP2101**](#nupp2101), [**NUPP2105**](#nupp2105). Reference: [docs/learn/language/modules.md#naming-a-member-from-another-file](docs/learn/language/modules.md#naming-a-member-from-another-file). [Open the reported program in the playground](/playground/#source=local%20answer%3A%20number%20%3D%20mathutil.double%2821%29%0Areturn%20answer). #### NUPP2121 A type pack is used where only one value type can appear. ```nupp [Reported] local value: (number, string) return value ``` A pack binder must follow every ordinary type binder, and a pack may appear only in a sequence position: function parameters or results, a coroutine protocol, a pack-union arm, or an explicit pack argument. ```nupp [Accepted] local function pair(): (number, string) return 1, "one" end return pair ``` Related: [**NUPP2010**](#nupp2010). Reference: [docs/learn/language/types/packs.md#syntax](docs/learn/language/types/packs.md#syntax). [Open the reported program in the playground](/playground/#source=local%20value%3A%20%28number%2C%20string%29%0Areturn%20value). #### NUPP2122 A refinement cannot be enforced. ```nupp [Reported] local interface Circle kind: string satisfies |self| -> tostring(self.kind) == "circle" end return Circle ``` A `satisfies` declaration names the runtime test that decides whether a value is one of these, and `x is T` compiles to it. That test has to run wherever `is` is written, so it reads the declaration's own fields through `self` and nothing else: comparisons against literals, `type()` tests, `and`, `or`, `not`. A call, arithmetic, or a name from outside the subject cannot be evaluated there. A refinement that always answers the same way is also refused, since always true identifies every value and always false leaves the type uninhabited, and so is one on a struct, which `ffi.istype` already answers exactly. A declaration is also held to the refinements of the interfaces it declares. `record C is Shape` is a claim the checker proves, and Shape's refinement is what `is Shape` runs, so fields that make that test fail would leave the two disagreeing about the same value. Only a provable failure is reported: a refinement whose answer no declaration settles is not evidence of a mistake. ```nupp [Accepted] local interface Circle kind: string satisfies |self| -> self.kind == "circle" end return Circle ``` Related: [**NUPP2116**](#nupp2116). Reference: [docs/learn/language/types/refinements.md](docs/learn/language/types/refinements.md). [Open the reported program in the playground](/playground/#source=local%20interface%20Circle%0A%20%20%20%20kind%3A%20string%0A%0A%20%20%20%20satisfies%20%7Cself%7C%20-%3E%20tostring%28self.kind%29%20%3D%3D%20%22circle%22%0Aend%0A%0Areturn%20Circle). #### NUPP2123 A metatable value does not fit the key it is written under. ```nupp [Reported] local record I64 v: integer metamethod __add: function(self: I64, other: I64): I64 end local x = new I64(v = 1) setmetatable(x, {__add = "not a function"}) return x ``` A metamethod declaration is a contract, and a metatable literal is where the value fulfilling it is written, so the value is held to it, with `self` specialized to the receiver, through a bounded type parameter as readily as through a concrete type. Where the declaration contracts for nothing, LuaJIT still says what it will do with the key: `__mode` is read as a string, `__index` and `__newindex` are a table to defer to or a function to run, and everything else it knows is called. Only a literal is checked. What a function returns cannot be seen from here, so a computed metatable stays gradual. ```nupp [Accepted] local record I64 v: integer metamethod __add: function(self: I64, other: I64): I64 end local x = new I64(v = 1) setmetatable(x, {__add = function(a: I64, b: I64): I64 return new I64(v = a.v + b.v) end}) return x ``` Related: [**NUPP2118**](#nupp2118), [**NUPP2006**](#nupp2006). Reference: [docs/learn/language/metamethods.md](docs/learn/language/metamethods.md). [Open the reported program in the playground](/playground/#source=local%20record%20I64%0A%20%20%20%20v%3A%20integer%0A%20%20%20%20metamethod%20__add%3A%20function%28self%3A%20I64%2C%20other%3A%20I64%29%3A%20I64%0Aend%0A%0Alocal%20x%20%3D%20new%20I64%28v%20%3D%201%29%0Asetmetatable%28x%2C%20%7B__add%20%3D%20%22not%20a%20function%22%7D%29%0A%0Areturn%20x). #### NUPP2124 An intersection is provably uninhabited. ```nupp [Reported] local type Impossible = string & number return Impossible ``` A value of `A & B` must satisfy both contracts. Nupp reports only finite proofs of impossibility, such as distinct runtime categories, literal tags, concrete nominal identities, or incompatible required fields; an intersection it cannot disprove remains legal. ```nupp [Accepted] local type NamedValue = {name: string} & {value: number} return NamedValue ``` Related: [**NUPP2125**](#nupp2125), [**NUPP2126**](#nupp2126). Reference: [docs/learn/language/types/intersections.md#provable-emptiness](docs/learn/language/types/intersections.md#provable-emptiness). [Open the reported program in the playground](/playground/#source=local%20type%20Impossible%20%3D%20string%20%26%20number%0Areturn%20Impossible). #### NUPP2125 No overload accepts a call. ```nupp [Reported] local type F = function(integer): string & function(string): boolean local f: F = nil as any return f(true) ``` A callable intersection probes every function member against the same adjusted argument pack. This diagnostic lists why each candidate was rejected; no candidate's ownership or borrowing effects are applied. ```nupp [Accepted] local type F = function(integer): string & function(string): boolean local f: F = nil as any return f(1) ``` Related: [**NUPP2006**](#nupp2006), [**NUPP2126**](#nupp2126). Reference: [docs/learn/language/types/intersections.md#overload-selection](docs/learn/language/types/intersections.md#overload-selection). [Open the reported program in the playground](/playground/#source=local%20type%20F%20%3D%20function%28integer%29%3A%20string%20%26%20function%28string%29%3A%20boolean%0Alocal%20f%3A%20F%20%3D%20nil%20as%20any%0Areturn%20f%28true%29). #### NUPP2126 Several overloads accept a call. ```nupp [Reported] local type F = function(integer): string & function(number): boolean local f: F = nil as any return f(1) ``` Overloads have no ranking or declaration-order tie breaker. A call must leave exactly one candidate; use distinguishable parameter packs or narrow a gradual argument before calling. ```nupp [Accepted] local type F = function(integer): string & function(string): boolean local f: F = nil as any return f(1) ``` Related: [**NUPP2125**](#nupp2125), [**NUPP2208**](#nupp2208). Reference: [docs/learn/language/types/intersections.md#overload-selection](docs/learn/language/types/intersections.md#overload-selection). [Open the reported program in the playground](/playground/#source=local%20type%20F%20%3D%20function%28integer%29%3A%20string%20%26%20function%28number%29%3A%20boolean%0Alocal%20f%3A%20F%20%3D%20nil%20as%20any%0Areturn%20f%281%29). #### NUPP2127 A declaration does not answer an associated type it is owed. ```nupp [Reported] local interface Reader associated type Item end local record Lines is Reader end return Lines ``` An interface may state a type it does not name, and whatever takes that contract has to name it. A default on the interface answers it, so only a requirement nothing named is reported -- as is one that several contracts default differently, or that an implementor answers otherwise than a `==` equality fixes, since neither leaves one answer to take. ```nupp [Accepted] local interface Reader associated type Item end local record Lines is Reader associated type Item = string end return Lines ``` Related: [**NUPP2128**](#nupp2128), [**NUPP2116**](#nupp2116). Reference: [docs/learn/language/types/associated-types.md](docs/learn/language/types/associated-types.md). [Open the reported program in the playground](/playground/#source=local%20interface%20Reader%0A%20%20%20%20associated%20type%20Item%0Aend%0A%0Alocal%20record%20Lines%20is%20Reader%0Aend%0A%0Areturn%20Lines). #### NUPP2128 An associated type member cannot mean anything where it is written. ```nupp [Reported] local record Box associated type Item end return Box ``` An interface states a requirement, and `=` on one states a default. Anything else answers, so it needs the `=`, may not restate the bound -- what may answer is the contract's to say -- and may not use `==`, which fixes a type on the contract that states it. Answering a name no contract declares means the name is private, which a plain nested `type` alias already is. ```nupp [Accepted] local record Box type Item = string end return Box ``` Related: [**NUPP2127**](#nupp2127), [**NUPP2117**](#nupp2117). Reference: [docs/learn/language/types/associated-types.md](docs/learn/language/types/associated-types.md). [Open the reported program in the playground](/playground/#source=local%20record%20Box%0A%20%20%20%20associated%20type%20Item%0Aend%0A%0Areturn%20Box). #### NUPP2129 An associated type collides with another type member. ```nupp [Reported] local interface Reader type Item = string associated type Item end return Reader ``` A declaration has one type namespace. An associated type and a nested alias or nested declaration share it, so two of a name is one namespace saying two things. Fields are a separate namespace and may share the spelling. ```nupp [Accepted] local interface Reader type Unit = string associated type Item end return Reader ``` Related: [**NUPP2128**](#nupp2128). Reference: [docs/learn/language/types/associated-types.md](docs/learn/language/types/associated-types.md). [Open the reported program in the playground](/playground/#source=local%20interface%20Reader%0A%20%20%20%20type%20Item%20%3D%20string%0A%20%20%20%20associated%20type%20Item%0Aend%0A%0Areturn%20Reader). #### NUPP2130 A type-level reduction cannot resolve the operator it is given. ```nupp [Reported] local function read(value: {name: string}.['missing']): string return value end return read ``` `keyof`, `T.[K]`, `writeof`, mapped shapes, and template construction each reduce a neutral type-level term to a concrete type. A term whose operand is not the shape the operator needs -- chief among them a member projection naming a key the subject does not have -- fails to reduce, and is reported where it was written rather than left as an opaque type. ```nupp [Accepted] local function read(value: {name: string}.['name']): string return value end return read ``` Related: [**NUPP2132**](#nupp2132), [**NUPP2133**](#nupp2133). Reference: [docs/learn/language/types/comptime-types.md#direct-finite-operators](docs/learn/language/types/comptime-types.md#direct-finite-operators). [Open the reported program in the playground](/playground/#source=local%20function%20read%28value%3A%20%7Bname%3A%20string%7D.%5B%27missing%27%5D%29%3A%20string%0A%20%20%20%20return%20value%0Aend%0A%0Areturn%20read). #### NUPP2131 A const type parameter cannot be given the value it needs. ```nupp [Reported] local function same(left: S, right: S): nil end same('x', 'y') ``` A `const` type parameter's value must be inferable from a use that names it directly -- an argument, or another const parameter already bound to the same one -- and every use inferring the same parameter must agree on exactly the same value. A parameter nothing binds, two arguments that disagree, or a value outside the parameter's declared const domain are each refused. ```nupp [Accepted] local function same(left: S, right: S): nil end same('x', 'x') ``` Related: [**NUPP2101**](#nupp2101), [**NUPP2130**](#nupp2130). Reference: [docs/learn/language/types/comptime-types.md#const-parameters](docs/learn/language/types/comptime-types.md#const-parameters). [Open the reported program in the playground](/playground/#source=local%20function%20same%3Cconst%20S%3A%20string%3E%28left%3A%20S%2C%20right%3A%20S%29%3A%20nil%0Aend%0A%0Asame%28%27x%27%2C%20%27y%27%29). #### NUPP2132 A template type's hole cannot reduce to a finite string literal. ```nupp [Reported] local function read(value: `${string}`): string return value end return read ``` A backtick template type's `${...}` holes must each reduce to a finite union of string or numeric literals, so the product of every combination is itself finite. A hole naming an unbounded type such as `string`, or one that is not a valid compile-time constant expression at all, cannot be enumerated and is refused. ```nupp [Accepted] local type Choice = 'a' | 'b' local function read(value: `${Choice}`): string return value end return read ``` Related: [**NUPP2130**](#nupp2130), [**NUPP2133**](#nupp2133). Reference: [docs/learn/language/types/comptime-types.md#direct-finite-operators](docs/learn/language/types/comptime-types.md#direct-finite-operators). [Open the reported program in the playground](/playground/#source=local%20function%20read%28value%3A%20%60%24%7Bstring%7D%60%29%3A%20string%0A%20%20%20%20return%20value%0Aend%0A%0Areturn%20read). #### NUPP2133 A recursive type alias is not supported. ```nupp [Reported] local type Loop = Loop return Loop ``` Type aliases describe finite type expressions and cannot refer to themselves, directly or mutually. Write an ordinary recursive comptime function that returns type or typepack when the computation needs control flow. ```nupp [Accepted] local comptime function DeepElement(T: type): type while nupp.types.kind(T) == 'array' do T = nupp.types.elements(T)[1] end return T end local value: DeepElement({{integer}}) = 1 return value ``` Related: [**NUPP2130**](#nupp2130), [**NUPP2132**](#nupp2132). Reference: [docs/learn/language/types/comptime-types.md#closed-and-generic-calls](docs/learn/language/types/comptime-types.md#closed-and-generic-calls). [Open the reported program in the playground](/playground/#source=local%20type%20Loop%3CT%3E%20%3D%20Loop%3CT%3E%0Areturn%20Loop). #### NUPP2134 A projection names something that cannot be projected. ```nupp [Reported] local function first(x: T): T.Item return nil as any end return first ``` `T.Item` is the associated type `Item` as whatever T answers it with, so it is a type only where some contract states that name. An unbounded binder has no contract to project through, a bounded one may not state the name, and a union states it only when every alternative does. A projection takes no type arguments. ```nupp [Accepted] local interface Reader associated type Item end local function first(x: T): T.Item return nil as any end return first ``` Related: [**NUPP2127**](#nupp2127). Reference: [docs/learn/language/types/associated-types.md](docs/learn/language/types/associated-types.md). [Open the reported program in the playground](/playground/#source=local%20function%20first%3CT%3E%28x%3A%20T%29%3A%20T.Item%0A%20%20%20%20return%20nil%20as%20any%0Aend%0A%0Areturn%20first). #### NUPP2135 An associated type answers through itself. ```nupp [Reported] local interface Holds associated type Value end local record Direct is Holds associated type Value = Direct.Value end return Direct ``` An answer has to be a type that does not reach the member it answers. The graph is read once every answer and default is installed, so naming a member declared later in the body is ordinary and only a closed loop is reported. A cyclic default stays latent on the interface that states it and surfaces on the first concrete implementor. ```nupp [Accepted] local interface Holds associated type Value end local record Direct is Holds associated type Value = string end return Direct ``` Related: [**NUPP2127**](#nupp2127). Reference: [docs/learn/language/types/associated-types.md](docs/learn/language/types/associated-types.md). [Open the reported program in the playground](/playground/#source=local%20interface%20Holds%0A%20%20%20%20associated%20type%20Value%0Aend%0A%0Alocal%20record%20Direct%20is%20Holds%0A%20%20%20%20associated%20type%20Value%20%3D%20Direct.Value%0Aend%0A%0Areturn%20Direct). #### NUPP2136 A sealed interface is implemented outside its owning module. ```nupp [Reported] local spans = require("nupp.mem.span") local record Forged is spans.Span end return Forged ``` A sealed interface carries hidden invariants that its public shape cannot prove. Only a declaration in the interface's own module may explicitly name it after `is`; structural lookalikes do not satisfy it. Obtain the value from that module's constructors instead of declaring another implementation. ```nupp [Accepted] local spans = require("nupp.mem.span") local storage = ffi.new() local view = spans.fromFixedCarray(storage, 4) return #view ``` Related: [**NUPP2001**](#nupp2001), [**NUPP2117**](#nupp2117). Reference: [docs/learn/language/types/interfaces.md#sealed-interfaces](docs/learn/language/types/interfaces.md#sealed-interfaces). [Open the reported program in the playground](/playground/#source=local%20spans%20%3D%20require%28%22nupp.mem.span%22%29%0A%0Alocal%20record%20Forged%20is%20spans.Span%3Cint32%3E%0Aend%0A%0Areturn%20Forged). #### NUPP2137 A switch pattern is not static or cannot bind what it names. ```nupp [Reported] local n: integer = 2 local value = switch n do case 1 + 1 -> 'two' else -> 'other' end return value ``` Static switch cases are finite scalar literals or names whose checked type is one exact scalar. Type destructuring binds direct fields only, and every bound name and field must exist once in that arm. ```nupp [Accepted] local n: integer = 2 local value = switch n do case 2 -> 'two' else -> 'other' end return value ``` Related: [**NUPP2138**](#nupp2138), [**NUPP2140**](#nupp2140). Reference: [docs/learn/language/switch-expressions.md#switch-expressions](docs/learn/language/switch-expressions.md#switch-expressions). [Open the reported program in the playground](/playground/#source=local%20n%3A%20integer%20%3D%202%0Alocal%20value%20%3D%20switch%20n%20do%0A%20%20%20%20case%201%20%2B%201%20-%3E%20%27two%27%0A%20%20%20%20else%20-%3E%20%27other%27%0Aend%0Areturn%20value). #### NUPP2138 A static switch value is handled more than once. ```nupp [Reported] local n: number = 1 local value = switch n do case 1, 1.0 -> 'one' else -> 'other' end return value ``` Static cases compare Lua values, not source spellings. Numerically equivalent literals such as 1 and 1.0 are therefore duplicates. ```nupp [Accepted] local n: number = 1 local value = switch n do case 1 -> 'one' else -> 'other' end return value ``` Related: [**NUPP2137**](#nupp2137), [**NUPP2139**](#nupp2139). Reference: [docs/learn/language/switch-expressions.md#switch-expressions](docs/learn/language/switch-expressions.md#switch-expressions). [Open the reported program in the playground](/playground/#source=local%20n%3A%20number%20%3D%201%0Alocal%20value%20%3D%20switch%20n%20do%0A%20%20%20%20case%201%2C%201.0%20-%3E%20%27one%27%0A%20%20%20%20else%20-%3E%20%27other%27%0Aend%0Areturn%20value). #### NUPP2139 A switch case cannot be selected. ```nupp [Reported] local n: 1 = 1 local value = switch n do case 2 -> 'two' else -> 'other' end return value ``` Cases are checked in source order against the selector type left by earlier cases. A value outside that type and an arm reached after the residue is empty are unreachable. ```nupp [Accepted] local n: number = 1 local value = switch n do case 2 -> 'two' else -> 'other' end return value ``` Related: [**NUPP2138**](#nupp2138), [**NUPP2140**](#nupp2140). Reference: [docs/learn/language/switch-expressions.md#switch-expressions](docs/learn/language/switch-expressions.md#switch-expressions). [Open the reported program in the playground](/playground/#source=local%20n%3A%201%20%3D%201%0Alocal%20value%20%3D%20switch%20n%20do%0A%20%20%20%20case%202%20-%3E%20%27two%27%0A%20%20%20%20else%20-%3E%20%27other%27%0Aend%0Areturn%20value). #### NUPP2140 A switch leaves possible selector values unhandled. ```nupp [Reported] local type Mode = 'read' | 'write' local mode: Mode = 'read' local value = switch mode do case 'read' -> 1 end return value ``` A switch is an expression and must produce a value whenever its selector returns. Cover every member of a finite checked type, or add else when the remaining set is open. A union declared with `nupp.types.nonExhaustive()` is always open, so it always needs else. ```nupp [Accepted] local type Mode = 'read' | 'write' local mode: Mode = 'read' local value = switch mode do case 'read' -> 1 case 'write' -> 2 end return value ``` Related: [**NUPP2139**](#nupp2139), [**NUPP2141**](#nupp2141). Reference: [docs/learn/language/switch-expressions.md#switch-expressions](docs/learn/language/switch-expressions.md#switch-expressions). [Open the reported program in the playground](/playground/#source=local%20type%20Mode%20%3D%20%27read%27%20%7C%20%27write%27%0Alocal%20mode%3A%20Mode%20%3D%20%27read%27%0Alocal%20value%20%3D%20switch%20mode%20do%20case%20%27read%27%20-%3E%201%20end%0Areturn%20value). #### NUPP2141 A block switch arm does not yield on every completing path. ```nupp [Reported] local value = switch 1 do else -> do local answer = 1 end end return value ``` A block arm produces the switch value with contextual `yield value`. Every path that does not return from the enclosing function must reach one, and no statement may follow it in the same path. ```nupp [Accepted] local value = switch 1 do else -> do local answer = 1 yield answer end end return value ``` Related: [**NUPP2140**](#nupp2140), [**NUPP2142**](#nupp2142). Reference: [docs/learn/language/switch-expressions.md#switch-expressions](docs/learn/language/switch-expressions.md#switch-expressions). [Open the reported program in the playground](/playground/#source=local%20value%20%3D%20switch%201%20do%0A%20%20%20%20else%20-%3E%20do%0A%20%20%20%20%20%20%20%20local%20answer%20%3D%201%0A%20%20%20%20end%0Aend%0Areturn%20value). #### NUPP2142 A switch is in a conditionally evaluated expression position. The first lexical lowering lifts switches from statement-root eager expressions. Lifting one out of `and`, `or`, `??`, a ternary arm, or guarded safe-navigation work would run it when the source does not, so that placement waits for general expression normalization. Bind the switch in a preceding statement instead. Related: [**NUPP2140**](#nupp2140). Reference: [docs/learn/language/switch-expressions.md#switch-expressions](docs/learn/language/switch-expressions.md#switch-expressions). #### NUPP2143 An export carrying a runtime value sits beside `export =`. ```nupp [Reported] module geom.shapes local shapes = {} export record Point x: number end export = shapes ``` A module either exports its declarations, and the compiler builds the table, or it names its value with `export =`, and that value is the module. A declaration that carries a runtime value cannot do both: there is no table of the compiler's for it to land on, and the named value is the author's to fill. Put it on that value instead. An interface or type alias is erased and stays legal beside one, because it contributes a type and no value. ```nupp [Accepted] module geom.shapes local shapes = {} record shapes.Point x: number end export = shapes ``` Related: [**NUPP2119**](#nupp2119). Reference: [docs/learn/language/modules.md#migrating-a-table-shaped-module](docs/learn/language/modules.md#migrating-a-table-shaped-module). [Open the reported program in the playground](/playground/#source=module%20geom.shapes%0A%0Alocal%20shapes%20%3D%20%7B%7D%0A%0Aexport%20record%20Point%0A%20%20%20%20x%3A%20number%0Aend%0A%0Aexport%20%3D%20shapes). #### NUPP2144 An internal module is imported outside its package namespace. A leading @!internal or an internal module segment restricts static imports to modules with the same first canonical namespace segment. An internal init.nupp restricts its descendants too. Import the package's public module instead. This applies to require, qualified values and qualified types; dynamic Lua loading remains gradual and is not an access-control sandbox. Reference: [docs/learn/language/modules.md#internal-modules](docs/learn/language/modules.md#internal-modules). #### NUPP2201 A struct field has no C-representable layout. ```nupp [Reported] local struct Loop me: Loop n: int32 end return Loop ``` A struct is laid out in C memory, so every field needs a fixed size and offset. A field type has to be reifiable: a numeric primitive, another struct held by value, a pointer, or a fixed C array. A struct cannot hold itself by value, because the layout would then have to contain a copy of itself and so would have no size; hold self-reference through a pointer instead. ```nupp [Accepted] local struct Loop me: Loop*? n: int32 end return Loop ``` Related: [**NUPP2204**](#nupp2204), [**NUPP2205**](#nupp2205), [**NUPP2402**](#nupp2402). Reference: [docs/learn/language/types/records-and-structs.md#pointing-at-itself](docs/learn/language/types/records-and-structs.md#pointing-at-itself). [Open the reported program in the playground](/playground/#source=local%20struct%20Loop%0A%20%20%20%20me%3A%20Loop%0A%20%20%20%20n%3A%20int32%0Aend%0Areturn%20Loop). #### NUPP2202 A declaration is built with 'new'. ```nupp [Reported] local record Point x: integer end local p = Point{x = 1} return p ``` Records and structs are constructed with `new`. Calling a declaration used to be the construction, which meant the call was never the program's own: a record's `__call` was shadowed whenever the argument was a table, and a struct's ctype call was claimed outright. Saying construction in the source gives those hooks back. A record that does declare a `__call` contract is called, not reported. ```nupp [Accepted] local record Point x: integer end local p = new Point(x = 1) return p ``` Related: [**NUPP2206**](#nupp2206). Reference: [docs/learn/language/types/records-and-structs.md#records](docs/learn/language/types/records-and-structs.md#records). [Open the reported program in the playground](/playground/#source=local%20record%20Point%0A%20%20%20%20x%3A%20integer%0Aend%0A%0Alocal%20p%20%3D%20Point%7Bx%20%3D%201%7D%0A%0Areturn%20p). #### NUPP2203 A C declaration uses a type C cannot represent. ```nupp [Reported] cdef function process(values: {number}): int32 return process ``` A `cdef` signature and struct layout cross the C ABI, so their types must have a C representation. Lua tables, records, and other garbage-collected values need an explicit pointer or handle boundary. ```nupp [Accepted] cdef function process(values: voidptr): int32 return process ``` Related: [**NUPP2201**](#nupp2201), [**NUPP2602**](#nupp2602). Reference: [docs/learn/runtime/c-interop/index.md#hand-write-a-small-binding](docs/learn/runtime/c-interop/index.md#hand-write-a-small-binding). [Open the reported program in the playground](/playground/#source=cdef%20function%20process%28values%3A%20%7Bnumber%7D%29%3A%20int32%0Areturn%20process). #### NUPP2204 A struct declares a Lua array part. ```nupp [Reported] local struct P {integer} x: float end return P ``` The array part `{T}` makes a record's own instances behave as a one-based Lua sequence, which only makes sense for a table-backed value. A struct has no Lua table underneath it, so it cannot declare one; use a fixed C array field `T[N]` for an inline vector instead. ```nupp [Accepted] local struct P x: float[4] end return P ``` Related: [**NUPP2201**](#nupp2201), [**NUPP2205**](#nupp2205). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20struct%20P%0A%20%20%20%20%7Binteger%7D%0A%20%20%20%20x%3A%20float%0Aend%0Areturn%20P). #### NUPP2205 An array part is not written as a single element type. ```nupp [Reported] local record R {x: integer} y: integer end return R ``` The array part is spelled `{T}`: braces around exactly the element type. Anything else inside the braces, such as a shape with named fields, resolves to a different kind of type and is not a valid array part. ```nupp [Accepted] local record R {integer} y: integer end return R ``` Related: [**NUPP2204**](#nupp2204), [**NUPP2201**](#nupp2201). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20record%20R%0A%20%20%20%20%7Bx%3A%20integer%7D%0A%20%20%20%20y%3A%20integer%0Aend%0Areturn%20R). #### NUPP2206 Only a record or a struct can be constructed. ```nupp [Reported] local interface Named name: string end local n = new Named(name = "ada") return n ``` `new` names a type and builds a value of it, so the operand has to be a declaration with something to build. An interface declares a contract and has no runtime table to stamp; an enum value is one of its declared strings, written directly. The operand is answered as a type rather than through whatever value stands under the name, because an interface binds none. ```nupp [Accepted] local interface Named name: string end local record User is Named name: string end local n = new User(name = "ada") return n ``` Related: [**NUPP2202**](#nupp2202). Reference: [docs/learn/language/types/records-and-structs.md#records](docs/learn/language/types/records-and-structs.md#records). [Open the reported program in the playground](/playground/#source=local%20interface%20Named%0A%20%20%20%20name%3A%20string%0Aend%0A%0Alocal%20n%20%3D%20new%20Named%28name%20%3D%20%22ada%22%29%0A%0Areturn%20n). #### NUPP2207 A binding is read before it holds a value. ```nupp [Reported] local record Point x: integer end local p: Point return p.x ``` `local v: Vec2` used to construct one where it was declared, which was a construction the source did not say. It no longer does, so the binding holds nil until something assigns to it, and reading it before that indexes nil at run time rather than yielding a value of the declared type. Strict code holds every annotation that does not admit nil to this, and a binding is assigned only once every path reaching the read assigned it: an assignment on one arm of an `if`, or inside a loop that may not run, is not one. Assign it first, or declare it optional if it is meant to start empty. A declaration file states what exists elsewhere and assigns nothing, so it is exempt. ```nupp [Accepted] local record Point x: integer end local p: Point = new Point(x = 0) return p.x ``` Related: [**NUPP2202**](#nupp2202), [**NUPP2206**](#nupp2206). Reference: [docs/learn/language/types/records-and-structs.md#records](docs/learn/language/types/records-and-structs.md#records). [Open the reported program in the playground](/playground/#source=local%20record%20Point%0A%20%20%20%20x%3A%20integer%0Aend%0A%0Alocal%20p%3A%20Point%0A%0Areturn%20p.x). #### NUPP2208 A constructor does not hold up its declaration. ```nupp [Reported] local record Account name: string balance: number constructor(self, name: string) self.name = name end end return Account ``` A `constructor(self, ...)` body is what `new T(...)` runs. The instance is made before it and returned after it, so its whole job is to fill the fields in, and every field that cannot hold nil has to be filled, or the value handed back does not match the declaration it claims. That guarantee is the reason to prefer a constructor over a literal, so declaring one closes the literal form for that declaration. An interface builds nothing and cannot carry one. Constructors may be overloaded by parameter pack. A result annotation is the successful construction policy: it must be one value that erases to the record itself, such as `affine(T, T.destroy)`, because the body still returns the instance it allocated. ```nupp [Accepted] local record Account name: string balance: number constructor(self, name: string) self.name = name self.balance = 0 end end return Account ``` Related: [**NUPP2202**](#nupp2202), [**NUPP2207**](#nupp2207). Reference: [docs/learn/language/types/records-and-structs.md#constructors-and-result-policies](docs/learn/language/types/records-and-structs.md#constructors-and-result-policies). [Open the reported program in the playground](/playground/#source=local%20record%20Account%0A%20%20%20%20name%3A%20string%0A%20%20%20%20balance%3A%20number%0A%0A%20%20%20%20constructor%28self%2C%20name%3A%20string%29%0A%20%20%20%20%20%20%20%20self.name%20%3D%20name%0A%20%20%20%20end%0Aend%0A%0Areturn%20Account). #### NUPP2209 A private record field is used outside its module. ```nupp [Reported] local h = require('nupp.mem.heap') local a = h.allocate(ffi.typeof(), 1) return a.pointer ``` A private field is representation available only while checking the canonical module that declares its record. Expose a public method when another module needs an operation without revealing that representation. ```nupp [Accepted] local h = require('nupp.mem.heap') local a = h.allocate(ffi.typeof(), 1) return a:read()[1] ``` Related: [**NUPP2004**](#nupp2004), [**NUPP2202**](#nupp2202). Reference: [docs/learn/language/types/records-and-structs.md#private-fields](docs/learn/language/types/records-and-structs.md#private-fields). [Open the reported program in the playground](/playground/#source=local%20h%20%3D%20require%28%27nupp.mem.heap%27%29%0Alocal%20a%20%3D%20h.allocate%28ffi.typeof%3Cint32%3E%28%29%2C%201%29%0Areturn%20a.pointer). #### NUPP2301 cheader's header path is not a literal. ```nupp [Reported] local mini = cheader("mini" .. ".h") return mini ``` cheader reads the named header at compile time, so its path has to be known to the compiler before the program runs. It accepts only a literal string as its first argument, never a computed or run-time expression. ```nupp [Accepted] local mini = cheader("mini.h") return mini ``` Related: [**NUPP2302**](#nupp2302). Reference: [docs/learn/runtime/c-interop/index.md#type-the-header-in-place](docs/learn/runtime/c-interop/index.md#type-the-header-in-place). [Open the reported program in the playground](/playground/#source=local%20mini%20%3D%20cheader%28%22mini%22%20..%20%22.h%22%29%0Areturn%20mini). #### NUPP2302 A cheader path cannot be read. ```nupp [Reported] local mini = cheader("missing.h") return mini ``` cheader resolves its literal path relative to the file, then as written, then against each project root, and reads whichever candidate exists first. When none can be opened, the diagnostic reports why, the same way an unreadable source path is reported. Related: [**NUPP2301**](#nupp2301), [**NUPP0001**](#nupp0001). Reference: [docs/learn/runtime/c-interop/index.md#type-the-header-in-place](docs/learn/runtime/c-interop/index.md#type-the-header-in-place). [Open the reported program in the playground](/playground/#source=local%20mini%20%3D%20cheader%28%22missing.h%22%29%0Areturn%20mini). #### NUPP2303 A literal ffi.cdef block does not parse as C. ```nupp [Reported] local ffi = require("ffi") ffi.cdef[[ this is not C ]] return 0 ``` A literal string given directly to ffi.cdef declares to the checker as well as to LuaJIT at runtime, so it has to be declarations LuaJIT's own C parser accepts. A string built at runtime, or reached only through a guard, is not read this way and so cannot be checked. ```nupp [Accepted] local ffi = require("ffi") ffi.cdef[[ int mini_add(int a, int b); ]] return 0 ``` Related: [**NUPP2301**](#nupp2301), [**NUPP2304**](#nupp2304). Reference: [docs/learn/runtime/c-interop/index.md#hand-write-a-small-binding](docs/learn/runtime/c-interop/index.md#hand-write-a-small-binding). [Open the reported program in the playground](/playground/#source=local%20ffi%20%3D%20require%28%22ffi%22%29%0Affi.cdef%5B%5B%20this%20is%20not%20C%20%5D%5D%0Areturn%200). #### NUPP2304 A typed FFI operation's type string does not name a C type. ```nupp [Reported] local ffi = require("ffi") ffi.cdef[[ struct CstA { int x; }; ]] local p = ffi.new("struct CstNoSuch") return p ``` ffi.new, ffi.cast, ffi.typeof, ffi.istype, ffi.sizeof, and ffi.alignof read a literal string argument through LuaJIT's own type-string parser when one is given, so that string has to name a type already declared, through a cdef or cheader the checker has already seen. ```nupp [Accepted] local ffi = require("ffi") ffi.cdef[[ struct CstA { int x; }; ]] local p = ffi.new("struct CstA") return p ``` Related: [**NUPP2303**](#nupp2303), [**NUPP2203**](#nupp2203). Reference: [docs/learn/runtime/c-interop/index.md#typed-ffi-operations](docs/learn/runtime/c-interop/index.md#typed-ffi-operations). [Open the reported program in the playground](/playground/#source=local%20ffi%20%3D%20require%28%22ffi%22%29%0Affi.cdef%5B%5B%20struct%20CstA%20%7B%20int%20x%3B%20%7D%3B%20%5D%5D%0Alocal%20p%20%3D%20ffi.new%28%22struct%20CstNoSuch%22%29%0Areturn%20p). #### NUPP2401 carray's arguments are not a struct type and an element count. ```nupp [Reported] local record Point x: number y: number end local points = carray(Point, 4) return points ``` carray(T, n) allocates a zero-based C array of n elements of T. T must resolve to a reified struct type -- a record is a table with no C layout to array up -- and n must be a number, or the gradual `any`, usable as an element count. ```nupp [Accepted] local struct Point x: float y: float end local points = carray(Point, 4) return points ``` Related: [**NUPP2402**](#nupp2402), [**NUPP2201**](#nupp2201). Reference: [docs/learn/runtime/c-interop/index.md#typed-ffi-operations](docs/learn/runtime/c-interop/index.md#typed-ffi-operations). [Open the reported program in the playground](/playground/#source=local%20record%20Point%0A%20%20%20%20x%3A%20number%0A%20%20%20%20y%3A%20number%0Aend%0A%0Alocal%20points%20%3D%20carray%28Point%2C%204%29%0Areturn%20points). #### NUPP2402 layoutof was asked about something with no C layout. ```nupp [Reported] local record Point x: number y: number end return layoutof(Point) ``` layoutof(T) answers how a reified struct sits in memory: its fields in declaration order with their offsets, sizes and padding, the whole size, and a fingerprint over all of it. Only a struct has one -- a record is a table, not C memory -- and a struct with no fields has nothing to lay out. ```nupp [Accepted] local struct Point x: float y: float end return layoutof(Point) ``` Related: [**NUPP2401**](#nupp2401), [**NUPP2201**](#nupp2201). Reference: [docs/learn/runtime/c-interop/index.md#read-a-structs-layout](docs/learn/runtime/c-interop/index.md#read-a-structs-layout). [Open the reported program in the playground](/playground/#source=local%20record%20Point%0A%20%20%20%20x%3A%20number%0A%20%20%20%20y%3A%20number%0Aend%0A%0Areturn%20layoutof%28Point%29). #### NUPP2403 An SoA allocation or field projection has no resolved stored field. ```nupp [Reported] local soa = require("nupp.mem.soa") local ffi = require("ffi") local rows = soa.allocate(ffi.typeof(), 4) return rows ``` Structure-of-arrays storage accepts only a reified struct whose top-level fields have fixed C storage; a record, union, interface, GC-managed field, owned field, borrowed field, or variable-size field is not SoA-eligible. A row's `field` projection must likewise name one such stored field of that struct, not a dynamic name or an unknown one. ```nupp [Accepted] local soa = require("nupp.mem.soa") local ffi = require("ffi") local struct Point x: float y: float end local rows = soa.allocate(ffi.typeof(), 4) rows:close() ``` Related: [**NUPP2402**](#nupp2402), [**NUPP2009**](#nupp2009). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20soa%20%3D%20require%28%22nupp.mem.soa%22%29%0Alocal%20ffi%20%3D%20require%28%22ffi%22%29%0A%0Alocal%20rows%20%3D%20soa.allocate%28ffi.typeof%3Cint32%3E%28%29%2C%204%29%0Areturn%20rows). #### NUPP2410 A comptime block reads or writes a binding it cannot reach. ```nupp [Reported] local n = 5 local value: integer = comptime do return n end print(value) ``` A comptime block evaluates in a scope of its own: its own locals and parameters, comptime-declared functions, and the allowlisted compile-time environment. A runtime local, upvalue, module-level binding, global, or an ambient library the environment does not expose -- `io`, `os`, `require`, `debug` among them -- cannot be read, and the same set cannot be assigned. ```nupp [Accepted] local value: integer = comptime do return 5 end print(value) ``` Related: [**NUPP2411**](#nupp2411), [**NUPP2412**](#nupp2412). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20n%20%3D%205%0Alocal%20value%3A%20integer%20%3D%20comptime%20do%0A%20%20%20%20return%20n%0Aend%0Aprint%28value%29). #### NUPP2411 A construct or library member is unavailable at comptime. ```nupp [Reported] local value: number = comptime do return math.random() end print(value) ``` The comptime evaluator supports a fixed subset of Nupp -- literals, control flow, operators over ordinary values, and calls into an explicit allowlist of library members -- and names what falls outside it: a nested function declaration, a multi-value result, a logical operator observing an opaque value, or a library member the allowlist leaves out because it is nondeterministic, such as `math.random`. ```nupp [Accepted] local value: number = comptime do return math.floor(7 / 2) end print(value) ``` Related: [**NUPP2410**](#nupp2410), [**NUPP2412**](#nupp2412). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20value%3A%20number%20%3D%20comptime%20do%0A%20%20%20%20return%20math.random%28%29%0Aend%0Aprint%28value%29). #### NUPP2412 A comptime evaluation exceeded a bound or used a value it cannot. ```nupp [Reported] local value: string = comptime do return tostring({}) end print(value) ``` Comptime evaluation is bounded so a block always finishes, and defined only over values its operations actually support: a step count, a helper call's frame depth, and an allocation size are each checked against a fixed limit, a comptime function must return and must be called with the argument count its parameters declare, and indexing, calling, or printing a value in a way its runtime kind does not support fails the same way. ```nupp [Accepted] local value: string = comptime do return tostring(5) end print(value) ``` Related: [**NUPP2410**](#nupp2410), [**NUPP2411**](#nupp2411). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20value%3A%20string%20%3D%20comptime%20do%0A%20%20%20%20return%20tostring%28%7B%7D%29%0Aend%0Aprint%28value%29). #### NUPP2413 A comptime result has no literal spelling. ```nupp [Reported] local value: {{integer}} = comptime do local shared = {1} return {shared, shared} end print(value) ``` A block's result is quoted into the generated Lua as a literal: nil, booleans, numbers that read back unchanged, strings, and acyclic tables of those with no metatable. NaN, the infinities, functions, and cdata have no such spelling, and a table reachable by more than one path is refused rather than quoted twice into two tables where the block built one. ```nupp [Accepted] local value: {{integer}} = comptime do return {{1}, {1}} end print(value) ``` Related: [**NUPP2414**](#nupp2414), [**NUPP2416**](#nupp2416). Reference: [docs/learn/language/comptime.md#values-checked-at-their-destination](docs/learn/language/comptime.md#values-checked-at-their-destination). [Open the reported program in the playground](/playground/#source=local%20value%3A%20%7B%7Binteger%7D%7D%20%3D%20comptime%20do%0A%20%20%20%20local%20shared%20%3D%20%7B1%7D%0A%20%20%20%20return%20%7Bshared%2C%20shared%7D%0Aend%0Aprint%28value%29). #### NUPP2414 An opaque comptime result did not reach a materialization boundary. ```nupp [Reported] local record Position x: number y: number end const codec = comptime do return nupp.reflect.fieldCodec(nupp.reflect(Position)) end print(codec) ``` A sealed provider -- reflection's field-codec builder among them -- may return a description with no literal spelling. It materializes only when the comptime block directly initializes a declaration whose annotated type the provider owns; an inferred binding, or an opaque value nested inside an ordinary table, gives it nowhere to materialize into. ```nupp [Accepted] local record Position x: number y: number end const codec: nupp.reflect.FieldCodec = comptime do return nupp.reflect.fieldCodec(nupp.reflect(Position)) end print(codec) ``` Related: [**NUPP2415**](#nupp2415), [**NUPP2416**](#nupp2416). Reference: [docs/learn/language/reflection.md#comptime-reflection](docs/learn/language/reflection.md#comptime-reflection). [Open the reported program in the playground](/playground/#source=local%20record%20Position%0A%20%20%20%20x%3A%20number%0A%20%20%20%20y%3A%20number%0Aend%0A%0Aconst%20codec%20%3D%20comptime%20do%0A%20%20%20%20return%20nupp.reflect.fieldCodec%28nupp.reflect%28Position%29%29%0Aend%0A%0Aprint%28codec%29). #### NUPP2415 A comptime-only value failed validation where it landed. ```nupp [Reported] local comptime function answer(): integer return 42 end local escaped = answer return escaped() ``` A comptime function's own identity, a type or const-function argument crossing into the isolated comptime worker, and an opaque provider result reaching a materialization boundary are all checked against exactly where they land. A comptime function read as an ordinary runtime value has nowhere to run there, an argument that cannot import into the worker cannot be evaluated, and a declared type with no registered materialization relation for what a provider produced cannot receive it. ```nupp [Accepted] local comptime function answer(): integer return 42 end return comptime do return answer() end ``` Related: [**NUPP2414**](#nupp2414), [**NUPP2416**](#nupp2416). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20comptime%20function%20answer%28%29%3A%20integer%20return%2042%20end%0Alocal%20escaped%20%3D%20answer%0Areturn%20escaped%28%29). #### NUPP2416 A comptime result or worker message exceeds its bound. ```nupp [Reported] return comptime do local values = {} for index = 1, 12000 do values[index] = index end return values end ``` Comptime evaluation runs in an isolated worker with fixed step, memory, and result-graph limits, and talks to the compiler over a bounded protocol. A finalized value graph over the item limit, and a worker request or response over the protocol byte limit, are reported here instead of being silently truncated or left to crash the worker. ```nupp [Accepted] return comptime do local values = {} for index = 1, 10 do values[index] = index end return values end ``` Related: [**NUPP2415**](#nupp2415), [**NUPP2412**](#nupp2412). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=return%20comptime%20do%0A%20%20%20%20local%20values%20%3D%20%7B%7D%0A%20%20%20%20for%20index%20%3D%201%2C%2012000%20do%20values%5Bindex%5D%20%3D%20index%20end%0A%20%20%20%20return%20values%0Aend). #### NUPP2417 A PEG intrinsic call or finalized grammar is malformed. ```nupp [Reported] const Bad: nupp.peg.Peg = comptime do return nupp.peg.compile("('')*") end return Bad ``` Every nupp.peg intrinsic is checked against the pattern arguments it needs, and a finalized grammar is checked against PEG's own rules once assembled. Repetition is possessive and must consume at least one byte whenever it succeeds, so a nullable repetition such as ('')* is refused, and direct or indirect left recursion is refused because a top-down parser cannot re-enter a rule at the position it started. ```nupp [Accepted] const Good: nupp.peg.Peg = comptime do return nupp.peg.compile("('x')*") end return Good ``` Related: [**NUPP2415**](#nupp2415), [**NUPP2416**](#nupp2416). Reference: [](nupp.peg#repetition). [Open the reported program in the playground](/playground/#source=const%20Bad%3A%20nupp.peg.Peg%3Cinteger%3E%20%3D%20comptime%20do%0A%20%20%20%20return%20nupp.peg.compile%28%22%28%27%27%29%2A%22%29%0Aend%0Areturn%20Bad). #### NUPP2418 nupp.reflect was applied to something other than a resolvable type. ```nupp [Reported] local value = 1 const Bad: nupp.reflect.FieldCodec = comptime do return nupp.reflect.fieldCodec(nupp.reflect(value)) end return Bad, value ``` nupp.reflect(T) resolves T in type position; it does not accept an expression that merely holds a runtime value, or one that fails to name a type at all. nupp.reflect.fieldCodec further requires that descriptor to describe a record, since only a record has present declared fields for the codec to copy. ```nupp [Accepted] local record Item value: integer end const Good: nupp.reflect.FieldCodec = comptime do return nupp.reflect.fieldCodec(nupp.reflect(Item)) end return Good ``` Related: [**NUPP2415**](#nupp2415), [**NUPP2416**](#nupp2416). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20value%20%3D%201%0Aconst%20Bad%3A%20nupp.reflect.FieldCodec%3Cany%3E%20%3D%20comptime%20do%0A%20%20%20%20return%20nupp.reflect.fieldCodec%28nupp.reflect%28value%29%29%0Aend%0Areturn%20Bad%2C%20value). #### NUPP2419 A layout intrinsic has no target, a bad shape, or an unknown field. ```nupp [Reported] local struct Value n: int32 end return comptime do return nupp.sizeof(Value) end ``` nupp.sizeof, nupp.alignof, and nupp.offsetof run only inside a comptime block, against the actual nupp global, with one concrete type argument (plus a compile-time-known field name for offsetof). Once the call's shape is right, it still needs the selected build's layoutTarget configured and a type with a runtime C layout to measure; a record or an unmodeled type has none. A program that only needs the host's own layout should measure it at runtime with ffi.sizeof instead. ```nupp [Accepted] local struct Value n: int32 end local size = ffi.sizeof() return size ``` Related: [**NUPP2410**](#nupp2410), [**NUPP2416**](#nupp2416). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=local%20struct%20Value%0A%20%20%20%20n%3A%20int32%0Aend%0Areturn%20comptime%20do%20return%20nupp.sizeof%28Value%29%20end). #### NUPP2420 A comptime type function rejected its own application. ```nupp [Reported] local comptime function Positive(): type return nupp.types.error("always fails") end local value: Positive() = 1 return value ``` nupp.types.error(message) lets a comptime type function refuse a specific application deliberately, distinct from an evaluator crash or a result of the wrong kind. The authored message is reported at the application together with the bounded comptime call trace, so a computed type contract can say why an argument it inspected does not qualify. ```nupp [Accepted] local comptime function Positive(): type return nupp.types.number end local value: Positive() = 1 return value ``` Related: [**NUPP2415**](#nupp2415), [**NUPP2421**](#nupp2421). Reference: [docs/learn/language/types/comptime-types.md#inspection-and-construction](docs/learn/language/types/comptime-types.md#inspection-and-construction). [Open the reported program in the playground](/playground/#source=local%20comptime%20function%20Positive%28%29%3A%20type%0A%20%20%20%20return%20nupp.types.error%28%22always%20fails%22%29%0Aend%0Alocal%20value%3A%20Positive%28%29%20%3D%201%0Areturn%20value). #### NUPP2421 A compiler-only member or type-position call was used where it cannot run. ```nupp [Reported] local leaked = nupp.types.string return leaked ``` Type handles, comptime type aliases, and members typed comptime type or comptime function(...) are opaque and exist only for the comptime evaluator. Reading one into an ordinary runtime binding, naming a comptime alias in a runtime annotation, or writing a type-position call whose callee, argument, or result does not resolve to a checked comptime function signature is refused because none has anything to erase to at runtime. ```nupp [Accepted] local comptime function Self(): type return nupp.types.string end local value: Self() = "ok" return value ``` Related: [**NUPP2133**](#nupp2133), [**NUPP2420**](#nupp2420). Reference: [docs/learn/language/types/comptime-types.md#inspection-and-construction](docs/learn/language/types/comptime-types.md#inspection-and-construction). [Open the reported program in the playground](/playground/#source=local%20leaked%20%3D%20nupp.types.string%0Areturn%20leaked). #### NUPP2501 A cast to a C pointer type has no lifetime root to hold the string alive. ```nupp [Reported] local pointer = ffi.cast('a' .. 'b') print(pointer) ``` The `string-pointer` lint reports a `ffi.cast(value)` to a pointer type whose argument is a Lua string but is not itself a named binding or a preserved parameter: the pointer aliases the string's bytes, and a fresh string is scratch storage LuaJIT is free to collect once the expression that produced it goes unread. Bind the string to a local, or thread it through a preserving parameter, before casting a pointer out of it. Default level is `warning`, and a project may reconfigure or suppress it like any other lint. ```nupp [Accepted] local text = 'a' .. 'b' local pointer = ffi.cast(text) print(pointer) ``` Reported by the `string-pointer` lint, category `suspicious`, at `warning` by default. Configure it by name or by category. Related: [**NUPP2608**](#nupp2608), [**NUPP2603**](#nupp2603). Reference: [docs/reference/lints.md#string-pointer](docs/reference/lints.md#string-pointer). [Open the reported program in the playground](/playground/#source=local%20pointer%20%3D%20ffi.cast%3Ccstring%3E%28%27a%27%20..%20%27b%27%29%0Aprint%28pointer%29). #### NUPP2502 A Lua function is cast to a C callback. ```nupp [Reported] unsafe do local callback = function() end local pointer = ffi.cast(callback) local handle = nupp.pin(pointer, callback) end ``` The `jit-callback` lint reports an `unsafe` cast of a Lua function to a C-derived callback type. The cast is legal only inside `unsafe` with an explicit retained owner, but LuaJIT registers the function as a permanent C callback once it happens, and no trace can compile through that call boundary for as long as it stays registered. Call `jit.off` on the registering function when the callback is intentional, or pass a plain pointer instead. Default level is `warning`, and a project may reconfigure or suppress it like any other lint. ```nupp [Accepted] unsafe do local pointer = ffi.cast(8) print(pointer) end ``` Reported by the `jit-callback` lint, category `suspicious`, at `warning` by default. Configure it by name or by category. Related: [**NUPP2604**](#nupp2604), [**NUPP2603**](#nupp2603). Reference: [docs/reference/lints.md#jit-callback](docs/reference/lints.md#jit-callback). [Open the reported program in the playground](/playground/#source=unsafe%20do%0A%20%20%20%20local%20callback%20%3D%20function%28%29%0A%20%20%20%20end%0A%20%20%20%20local%20pointer%20%3D%20ffi.cast%3Cvoidptr%3E%28callback%29%0A%20%20%20%20local%20handle%20%3D%20nupp.pin%28pointer%2C%20callback%29%0Aend). #### NUPP2504 An operator uses its customary C-style spelling. ```nupp [Reported] local ready = true local pending = !ready return pending ``` The C-style spellings `!`, `&&`, `||`, and `!=` are accepted, but the style lint prefers Lua's `not`, `and`, `or`, and `~=`. The spelling changes; the expression's meaning does not. ```nupp [Accepted] local ready = true local pending = not ready return pending ``` Reported by the `customary-operator` lint, category `style`, at `warning` by default. Configure it by name or by category. Related: [**NUPP2108**](#nupp2108). Reference: [docs/reference/lints.md#customary-operator](docs/reference/lints.md#customary-operator). [Open the reported program in the playground](/playground/#source=local%20ready%20%3D%20true%0Alocal%20pending%20%3D%20%21ready%0Areturn%20pending). #### NUPP2505 A loop builds the same non-capturing function on every iteration. ```nupp [Reported] local function register(item: any, callback: function(): boolean): nil print(item, callback()) end local function run(items: {any}): nil for _, item in ipairs(items) do register(item, function(): boolean return true end) end end return run ``` The `loop-invariant-closure` lint reports a function literal built inside a loop body that reads nothing from the iteration, so every built copy is the same function and building one afresh each time buys nothing. LuaJIT has no recorder for the bytecode that builds a function, so a loop containing one aborts trace recording every time it is tried and is eventually blacklisted, running interpreted from then on however hot it gets. Declare the function once above the loop and pass its name. Default level is `warning`; the sibling `jit-loop-closure` reports the case where the function does read the iteration and cannot be lifted. ```nupp [Accepted] local function register(item: any, callback: function(): boolean): nil print(item, callback()) end local function alwaysTrue(): boolean return true end local function run(items: {any}): nil for _, item in ipairs(items) do register(item, alwaysTrue) end end return run ``` Reported by the `loop-invariant-closure` lint, category `suspicious`, at `warning` by default. Configure it by name or by category. Related: [**NUPP2515**](#nupp2515), [**NUPP2707**](#nupp2707). Reference: [docs/reference/lints.md#loop-invariant-closure](docs/reference/lints.md#loop-invariant-closure). [Open the reported program in the playground](/playground/#source=local%20function%20register%28item%3A%20any%2C%20callback%3A%20function%28%29%3A%20boolean%29%3A%20nil%0A%20%20%20%20print%28item%2C%20callback%28%29%29%0Aend%0A%0Alocal%20function%20run%28items%3A%20%7Bany%7D%29%3A%20nil%0A%20%20%20%20for%20_%2C%20item%20in%20ipairs%28items%29%20do%0A%20%20%20%20%20%20%20%20register%28item%2C%20function%28%29%3A%20boolean%0A%20%20%20%20%20%20%20%20%20%20%20%20return%20true%0A%20%20%20%20%20%20%20%20end%29%0A%20%20%20%20end%0Aend%0A%0Areturn%20run). #### NUPP2506 A documented function can raise without saying when. ```nupp [Reported] --- Reads a name. --- @param name the name to read local function read(name: string): string if name == "" then error("empty name") end return name end return read ``` A function with a docblock that calls `error` must document the condition with `@raises`. Callers reading generated documentation then see the exceptional exit beside the ordinary result. ```nupp [Accepted] --- Reads a name. --- @param name the name to read --- @raises when name is empty local function read(name: string): string if name == "" then error("empty name") end return name end return read ``` Reported by the `undocumented-raise` lint, category `suspicious`, at `warning` by default. Configure it by name or by category. Related: [**NUPP2507**](#nupp2507). Reference: [docs/reference/lints.md#undocumented-raise](docs/reference/lints.md#undocumented-raise). [Open the reported program in the playground](/playground/#source=---%20Reads%20a%20name.%0A---%20%40param%20name%20the%20name%20to%20read%0Alocal%20function%20read%28name%3A%20string%29%3A%20string%0A%20%20%20%20if%20name%20%3D%3D%20%22%22%20then%20error%28%22empty%20name%22%29%20end%0A%20%20%20%20return%20name%0Aend%0A%0Areturn%20read). #### NUPP2507 A local is declared and nothing reads it. ```nupp [Reported] local function shout(text: string): string local prefix = "> " return text .. "!" end return shout ``` A binding nothing reads is a leftover from an edit, or a use that went to another name. Delete it, or name it `_` when the value is deliberately not wanted. A parameter and a loop variable are bound by a signature and an iterator rather than by their author, so neither is judged. ```nupp [Accepted] local function shout(text: string): string local prefix = "> " return prefix .. text .. "!" end return shout ``` Reported by the `unused-binding` lint, category `suspicious`, at `warning` by default. Configure it by name or by category. Related: [**NUPP2120**](#nupp2120), [**NUPP2603**](#nupp2603). Reference: [docs/reference/lints.md](docs/reference/lints.md). [Open the reported program in the playground](/playground/#source=local%20function%20shout%28text%3A%20string%29%3A%20string%0A%20%20%20%20local%20prefix%20%3D%20%22%3E%20%22%0A%20%20%20%20return%20text%20..%20%22%21%22%0Aend%0A%0Areturn%20shout). #### NUPP2508 A call that does nothing but return had its result dropped. ```nupp [Reported] local function double(value: number): number return value * 2 end double(21) return double ``` A call written as a statement is made for what it does. When the callee is proved to do nothing but produce a value, and the statement drops that value, the whole statement does nothing. Use the result, or delete the call. ```nupp [Accepted] local function double(value: number): number return value * 2 end local answer = double(21) print(answer) return double ``` Reported by the `discarded-result` lint, category `suspicious`, at `warning` by default. Configure it by name or by category. Related: [**NUPP2112**](#nupp2112), [**NUPP2603**](#nupp2603). Reference: [docs/reference/lints.md](docs/reference/lints.md). [Open the reported program in the playground](/playground/#source=local%20function%20double%28value%3A%20number%29%3A%20number%0A%20%20%20%20return%20value%20%2A%202%0Aend%0A%0Adouble%2821%29%0A%0Areturn%20double). #### NUPP2509 A record's fields would all fit in C memory. The `reifiable-record` lint reports a `record` declaration whose every member -- fields, constructors, methods -- is one a `struct` would also accept: every field's type reifies, and nothing else in the body (an indexer, a Lua array part, a declaration-only metamethod, a nested declaration, a property capability, generics, or a declared supertype) rules `struct` out. Declaring it `struct` instead moves instances into C memory, off the collector's graph, at the cost of a fixed layout and giving up `pairs`/table-serializer support without an explicit hook. This is a suggestion rather than a correctness rule, so it defaults to `off`; a project turns it on with `lints = {["reifiable-record"] = "note"}` or by raising the whole `performance` category. Reported by the `reifiable-record` lint, category `performance`, at `off` by default. Configure it by name or by category. Related: [**NUPP2201**](#nupp2201), [**NUPP2012**](#nupp2012). Reference: [docs/reference/lints.md#reifiable-record](docs/reference/lints.md#reifiable-record). #### NUPP2510 An else contains only a nested if. ```nupp [Reported] local function classify(primary: boolean, fallback: boolean): nil if primary then print("primary") else if fallback then print("fallback") end end end return classify ``` The `else-if` lint reports an `else` block whose only statement is an unannotated `if`, which is the long way to write `elseif`; it also recognizes adjacent `if` statements that compare the same local to different literals when the first body does not reassign it. Write `elseif` (or fold the adjacent ifs into one chain) so the chain reads as one dispatch rather than nested blocks. Default level is `warning`, and a project may reconfigure or suppress it like any other lint. ```nupp [Accepted] local function classify(primary: boolean, fallback: boolean): nil if primary then print("primary") elseif fallback then print("fallback") end end return classify ``` Reported by the `else-if` lint, category `style`, at `warning` by default. Configure it by name or by category. Related: [**NUPP2107**](#nupp2107). Reference: [docs/reference/lints.md#else-if](docs/reference/lints.md#else-if). [Open the reported program in the playground](/playground/#source=local%20function%20classify%28primary%3A%20boolean%2C%20fallback%3A%20boolean%29%3A%20nil%0A%20%20%20%20if%20primary%20then%0A%20%20%20%20%20%20%20%20print%28%22primary%22%29%0A%20%20%20%20else%0A%20%20%20%20%20%20%20%20if%20fallback%20then%0A%20%20%20%20%20%20%20%20%20%20%20%20print%28%22fallback%22%29%0A%20%20%20%20%20%20%20%20end%0A%20%20%20%20end%0Aend%0A%0Areturn%20classify). #### NUPP2511 An associated type was erased because inference did not reach its head. ```nupp [Reported] local interface Holds associated type Item end local function held(x: T): T.Item return nil as any end local erased = held(nil as any) return erased ``` A projection is checked as `any` when inference never worked out what answers it, which is the feature declining to say anything rather than saying the call is right. Reported once per call and member, where the erasure happened, because a parameter erased that way accepts whatever it is given. ```nupp [Accepted] local interface Holds associated type Item end local record Lines is Holds associated type Item = string end local function held(x: T): T.Item return nil as any end local kept = held(new Lines()) return kept ``` Reported by the `gradual-projection` lint, category `suspicious`, at `warning` by default. Configure it by name or by category. Related: [**NUPP2134**](#nupp2134). Reference: [docs/learn/language/types/associated-types.md](docs/learn/language/types/associated-types.md). [Open the reported program in the playground](/playground/#source=local%20interface%20Holds%0A%20%20%20%20associated%20type%20Item%0Aend%0A%0Alocal%20function%20held%3CT%20is%20Holds%3E%28x%3A%20T%29%3A%20T.Item%0A%20%20%20%20return%20nil%20as%20any%0Aend%0A%0Alocal%20erased%20%3D%20held%28nil%20as%20any%29%0A%0Areturn%20erased). #### NUPP2512 A record is built by field order rather than by naming its fields. ```nupp [Reported] local record Point x: integer y: integer end local p = new Point(1, 2) return p ``` A record without a declared constructor may be built either way, and both build the same table. Naming the fields says at the call site which value lands where; leaving it to the order says it in the declaration, so a reader has to go there, and adding a field silently changes what an existing call means. A struct is exempt: it is its C layout, and that order is the layout's rather than the program's to name. Turn it off by name or by its `style` category, or write `@allow("positional-record-construction")`. ```nupp [Accepted] local record Point x: integer y: integer end local p = new Point(x = 1, y = 2) return p ``` Reported by the `positional-record-construction` lint, category `style`, at `warning` by default. Configure it by name or by category. Related: [**NUPP2202**](#nupp2202), [**NUPP2208**](#nupp2208). Reference: [docs/reference/lints.md](docs/reference/lints.md). [Open the reported program in the playground](/playground/#source=local%20record%20Point%0A%20%20%20%20x%3A%20integer%0A%20%20%20%20y%3A%20integer%0Aend%0A%0Alocal%20p%20%3D%20new%20Point%281%2C%202%29%0A%0Areturn%20p). #### NUPP2513 An API marked deprecated is used. ```nupp [Reported] local function current(): integer return 1 end @deprecated(replacement = "current") local function legacy(): integer return current() end return legacy() ``` `@deprecated` keeps an API available while telling callers to move away from it. The optional reason explains why and the replacement names what to use instead. The annotation changes tooling only: it reports this suppressible lint at use sites and emits no runtime behavior. ```nupp [Accepted] local function current(): integer return 1 end @deprecated(replacement = "current") local function legacy(): integer return current() end return current() ``` Reported by the `deprecated` lint, category `suspicious`, at `warning` by default. Configure it by name or by category. Related: [**NUPP2115**](#nupp2115). Reference: [docs/reference/lints.md](docs/reference/lints.md). [Open the reported program in the playground](/playground/#source=local%20function%20current%28%29%3A%20integer%20return%201%20end%0A%0A%40deprecated%28replacement%20%3D%20%22current%22%29%0Alocal%20function%20legacy%28%29%3A%20integer%20return%20current%28%29%20end%0A%0Areturn%20legacy%28%29). #### NUPP2514 A variadic FFI call is reachable from a compiled trace. ```nupp [Reported] cdef function printf(format: cstring, ...): int32 local function report(value: int32): nil printf("%d", value) end return report ``` The `jit-boundary` lint reports a call to a `cdef` function declared with a `...` variadic tail from code LuaJIT may try to compile: a variadic C call cannot safely execute on a trace. Move the call into a helper disabled with `jit.off`, so the interpreter always runs it, rather than leaving the boundary reachable from hot code. Default level is `warning`; inside an `@jit` function the same boundary is instead the non-suppressible `NUPP2707` contract error. ```nupp [Accepted] cdef function printf(format: cstring, ...): int32 local function coldReport(value: int32): nil printf("%d", value) end jit.off(coldReport) local function report(value: int32): nil coldReport(value) end return report ``` Reported by the `jit-boundary` lint, category `suspicious`, at `warning` by default. Configure it by name or by category. Related: [**NUPP2707**](#nupp2707), [**NUPP2502**](#nupp2502). Reference: [docs/reference/lints.md#jit-boundary](docs/reference/lints.md#jit-boundary). [Open the reported program in the playground](/playground/#source=cdef%20function%20printf%28format%3A%20cstring%2C%20...%29%3A%20int32%0A%0Alocal%20function%20report%28value%3A%20int32%29%3A%20nil%0A%20%20%20%20printf%28%22%25d%22%2C%20value%29%0Aend%0A%0Areturn%20report). #### NUPP2515 A loop builds a function and so never compiles. LuaJIT has no recording for the bytecode that builds a function, so a loop containing one aborts trace recording, is blacklisted after enough attempts, and then runs interpreted however hot it gets. A function that reads nothing from the iteration is `loop-invariant-closure` and lifts out unchanged; this is the other one, which reads the iteration and cannot. The way out where there is one is to declare a function above the loop that takes what varies as arguments, so the loop calls one rather than builds one. Nothing about the program's answers changes either way, which is why this is off until a project asks for it. `@jit` is where it is not optional: that annotation promises the function compiles, so the same hazard inside one is reported as NUPP2707, and `jit.off` on the enclosing function silences it. `nupp bc --check` finds the same loops in any file without running it. Reported by the `jit-loop-closure` lint, category `performance`, at `off` by default. Configure it by name or by category. Related: [**NUPP2505**](#nupp2505), [**NUPP2707**](#nupp2707). Reference: [docs/reference/lints.md](docs/reference/lints.md). #### NUPP2516 An export exposes a private nominal type. ```nupp [Reported] module geom.shapes local record Coordinate x: number end export record Point coordinate: Coordinate end ``` The `private-export-type` lint reports a private record, interface, or struct reached through an exported field, function signature, container, generic argument, or other public type position. The boundary remains valid: callers can infer and forward values of the hidden type, but cannot name or independently construct it. Export the nominal or an alias when callers should have a public name, or suppress the lint at the export when that opacity is intentional. Transparent aliases and private record fields do not expose a nominal identity. ```nupp [Accepted] module geom.shapes export record Coordinate x: number end export record Point coordinate: Coordinate end ``` Reported by the `private-export-type` lint, category `suspicious`, at `warning` by default. Configure it by name or by category. Related: [**NUPP2004**](#nupp2004). Reference: [docs/reference/lints.md#private-export-type](docs/reference/lints.md#private-export-type). [Open the reported program in the playground](/playground/#source=module%20geom.shapes%0A%0Alocal%20record%20Coordinate%0A%20%20%20%20x%3A%20number%0Aend%0A%0Aexport%20record%20Point%0A%20%20%20%20coordinate%3A%20Coordinate%0Aend). #### NUPP2518 a closed no-input function repeats work that comptime can erase. A lint, reported at off by default and configurable by name or by category. The program parses but does not mean something the checker can accept, or means something its author probably did not intend. This code has no example pair of its own. The rule above is its family's, which is what the compiler knows about it. Reported by the `prefer-comptime` lint, category `performance`, at `off` by default. Configure it by name or by category. Reference: [docs/reference/lints.md](docs/reference/lints.md). #### NUPP2601 A moved-from owner is used again. ```nupp [Reported] cdef struct resource value: int32 end cdef function resource_create(): resource* cdef function resource_free(takes value: resource*) local function resource_new(): affine(resource*, resource_free) return resource_create() end local function useTwice(): nil local value = resource_new() drop(value) drop(value) end return useTwice ``` An affine binding or affine field is discharged by exactly one move, drop, or return. The name stays in scope after that, but its value is gone: reading it, moving it a second time, or dropping it again all reuse a transfer that already happened. ```nupp [Accepted] cdef struct resource value: int32 end cdef function resource_create(): resource* cdef function resource_free(takes value: resource*) local function resource_new(): affine(resource*, resource_free) return resource_create() end local function useOnce(): nil local value = resource_new() drop(value) end return useOnce ``` Related: [**NUPP2602**](#nupp2602), [**NUPP2603**](#nupp2603). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=cdef%20struct%20resource%0A%20%20%20%20value%3A%20int32%0Aend%0Acdef%20function%20resource_create%28%29%3A%20resource%2A%0Acdef%20function%20resource_free%28takes%20value%3A%20resource%2A%29%0A%0Alocal%20function%20resource_new%28%29%3A%20affine%28resource%2A%2C%20resource_free%29%0A%20%20%20%20return%20resource_create%28%29%0Aend%0A%0Alocal%20function%20useTwice%28%29%3A%20nil%0A%20%20%20%20local%20value%20%3D%20resource_new%28%29%0A%20%20%20%20drop%28value%29%0A%20%20%20%20drop%28value%29%0Aend%0A%0Areturn%20useTwice). #### NUPP2602 An ownership operation is invalid for the value's current state. ```nupp [Reported] cdef struct resource value: int32 end cdef function resource_create(): resource* cdef function resource_free(takes value: resource*) local function resource_new(): affine(resource*, resource_free) return resource_create() end local value = resource_new() do local view = borrow(value) resource_free(value) end resource_free(value) ``` A capability-qualified value only accepts the operations its current state permits: a value with a live borrow cannot itself move or be consumed until that borrow ends, a partially moved record cannot move or drop as a whole, and a plain value cannot be transferred as though it carried an affine or pinned capability. NUPP2602 covers whichever one of those the attempted operation violated. ```nupp [Accepted] cdef struct resource value: int32 end cdef function resource_create(): resource* cdef function resource_free(takes value: resource*) local function resource_new(): affine(resource*, resource_free) return resource_create() end local value = resource_new() do local view = borrow(value) print(view.value) end resource_free(value) ``` Related: [**NUPP2601**](#nupp2601), [**NUPP2608**](#nupp2608), [**NUPP2615**](#nupp2615). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=cdef%20struct%20resource%0A%20%20%20%20value%3A%20int32%0Aend%0Acdef%20function%20resource_create%28%29%3A%20resource%2A%0Acdef%20function%20resource_free%28takes%20value%3A%20resource%2A%29%0A%0Alocal%20function%20resource_new%28%29%3A%20affine%28resource%2A%2C%20resource_free%29%0A%20%20%20%20return%20resource_create%28%29%0Aend%0A%0Alocal%20value%20%3D%20resource_new%28%29%0Ado%0A%20%20%20%20local%20view%20%3D%20borrow%28value%29%0A%20%20%20%20resource_free%28value%29%0Aend%0Aresource_free%28value%29). #### NUPP2603 An ownership obligation is not discharged or cannot escape. ```nupp [Reported] cdef function begin_request(): affine(voidptr) local request = begin_request() return 0 ``` An affine value must be consumed, dropped, returned under a matching ownership contract, or explicitly released in unsafe. Borrows likewise stay within the lifetime and suspension boundaries their provenance permits. ```nupp [Accepted] cdef function begin_request(): affine(voidptr) cdef function submit_request(takes request: voidptr) local request = begin_request() submit_request(request) return 0 ``` Related: [**NUPP2601**](#nupp2601), [**NUPP2602**](#nupp2602), [**NUPP2605**](#nupp2605). Reference: [docs/learn/runtime/ownership/borrowing.md#consumption-and-lexical-destruction](docs/learn/runtime/ownership/borrowing.md#consumption-and-lexical-destruction). [Open the reported program in the playground](/playground/#source=cdef%20function%20begin_request%28%29%3A%20affine%28voidptr%29%0Alocal%20request%20%3D%20begin_request%28%29%0Areturn%200). #### NUPP2604 Raw pointer arithmetic lacks proof or an unsafe boundary. ```nupp [Reported] cdef struct resource value: int32 end local function advance(borrows base: resource*): resource* return base + 1 end return advance ``` Offsetting a raw C pointer with `+` or `-` needs a borrowed, rooted pointer to charge the access to, and needs an explicit `unsafe do` acknowledging that nothing checks the resulting address against a bound. Use a checked span instead when the bound should be proven rather than asserted. ```nupp [Accepted] cdef struct resource value: int32 end local function advance(borrows base: resource*): resource* borrows (base) unsafe do return base + 1 end end return advance ``` Related: [**NUPP2001**](#nupp2001), [**NUPP2004**](#nupp2004), [**NUPP2602**](#nupp2602). Reference: [docs/reference/diagnostics.md#diagnostic-index](docs/reference/diagnostics.md#diagnostic-index). [Open the reported program in the playground](/playground/#source=cdef%20struct%20resource%0A%20%20%20%20value%3A%20int32%0Aend%0A%0Alocal%20function%20advance%28borrows%20base%3A%20resource%2A%29%3A%20resource%2A%0A%20%20%20%20return%20base%20%2B%201%0Aend%0A%0Areturn%20advance). #### NUPP2605 Adjusting a value pack would discard an affine value. ```nupp [Reported] local record Resource end local function release(takes value: Resource): nil end local function acquire(): affine(Resource, release) return new Resource() end acquire() return acquire ``` Lua may truncate extra results, but Nupp cannot silently lose an owned or pinned slot. Bind and discharge every affine result, or forward the complete generic pack to a matching result or parameter. ```nupp [Accepted] local record Resource end local function release(takes value: Resource): nil end local function acquire(): affine(Resource, release) return new Resource() end local value = acquire() release(value) return acquire ``` Related: [**NUPP2602**](#nupp2602), [**NUPP2603**](#nupp2603), [**NUPP2010**](#nupp2010). Reference: [docs/learn/language/types/packs.md#ownership-and-provenance](docs/learn/language/types/packs.md#ownership-and-provenance). [Open the reported program in the playground](/playground/#source=local%20record%20Resource%0Aend%0Alocal%20function%20release%28takes%20value%3A%20Resource%29%3A%20nil%0Aend%0Alocal%20function%20acquire%28%29%3A%20affine%28Resource%2C%20release%29%0A%20%20%20%20return%20new%20Resource%28%29%0Aend%0Aacquire%28%29%0Areturn%20acquire). #### NUPP2606 A preservation relation would lose or duplicate capability. ```nupp [Reported] local function forward(borrows value: T): T preserves value return value end return forward ``` `preserves source` conserves the source's complete capability. A source that may carry a cleanup, transfer-only obligation, pin, or foreign retention must be taken, and each movable part must reach exactly one related result. ```nupp [Accepted] local function forward(takes value: T): T preserves value return value end return forward ``` Related: [**NUPP2602**](#nupp2602), [**NUPP2603**](#nupp2603), [**NUPP2605**](#nupp2605). Reference: [docs/learn/runtime/ownership/borrowing.md#generic-preservation](docs/learn/runtime/ownership/borrowing.md#generic-preservation). [Open the reported program in the playground](/playground/#source=local%20function%20forward%3CT%3E%28borrows%20value%3A%20T%29%3A%20T%20preserves%20value%0A%20%20%20%20return%20value%0Aend%0Areturn%20forward). #### NUPP2607 Shared and exclusive regions overlap incompatibly. ```nupp [Reported] local function pair(exclusive a: table, exclusive b: table): nil end local value = {} pair(value, value) ``` A live exclusive region must be disjoint from every other live shared or exclusive region. Fixed sibling fields and distinct proven indexes are disjoint; a parent, unknown index, or unproved range overlaps conservatively. ```nupp [Accepted] local function pair(exclusive a: table, exclusive b: table): nil end pair({}, {}) ``` Related: [**NUPP2602**](#nupp2602), [**NUPP2608**](#nupp2608), [**NUPP2609**](#nupp2609). Reference: [docs/learn/runtime/ownership/borrowing.md#regions-and-loop-carried-capabilities](docs/learn/runtime/ownership/borrowing.md#regions-and-loop-carried-capabilities). [Open the reported program in the playground](/playground/#source=local%20function%20pair%28exclusive%20a%3A%20table%2C%20exclusive%20b%3A%20table%29%3A%20nil%20end%0Alocal%20value%20%3D%20%7B%7D%0Apair%28value%2C%20value%29). #### NUPP2608 A rooted value escapes its permitted lifetime. ```nupp [Reported] local function leak(borrows value: table): table return borrow(value) end return leak ``` A borrowed or scoped value may leave its scope only through an explicit result relation whose named roots outlive the result. It may not be returned, stored, captured, or erased without that relation. ```nupp [Accepted] local function view(borrows value: table): table borrows (value) return borrow(value) end return view ``` Related: [**NUPP2603**](#nupp2603), [**NUPP2607**](#nupp2607), [**NUPP2611**](#nupp2611). Reference: [docs/learn/runtime/ownership/borrowing.md#borrowing-and-pinning](docs/learn/runtime/ownership/borrowing.md#borrowing-and-pinning). [Open the reported program in the playground](/playground/#source=local%20function%20leak%28borrows%20value%3A%20table%29%3A%20table%0A%20%20%20%20return%20borrow%28value%29%0Aend%0Areturn%20leak). #### NUPP2609 A loop back edge changes capability state unsafely. ```nupp [Reported] local record Resource end local function close(takes value: Resource): nil end local function open(): affine(Resource, close) return new Resource() end local function run(again: boolean): nil local value = open() while again do drop(value) end end return run ``` Every repeatable back edge must restore the loop header's live obligations and region loans. An iteration-local borrow must end before the edge, and an outer affine value cannot be consumed only on a possibly repeating path. ```nupp [Accepted] local record Resource end local function close(takes value: Resource): nil end local function open(): affine(Resource, close) return new Resource() end local function run(again: boolean): nil local value = open() if again then print('once') end drop(value) end return run ``` Related: [**NUPP2601**](#nupp2601), [**NUPP2603**](#nupp2603), [**NUPP2607**](#nupp2607). Reference: [docs/learn/runtime/ownership/borrowing.md#regions-and-loop-carried-capabilities](docs/learn/runtime/ownership/borrowing.md#regions-and-loop-carried-capabilities). [Open the reported program in the playground](/playground/#source=local%20record%20Resource%0Aend%0Alocal%20function%20close%28takes%20value%3A%20Resource%29%3A%20nil%0Aend%0Alocal%20function%20open%28%29%3A%20affine%28Resource%2C%20close%29%20return%20new%20Resource%28%29%20end%0Alocal%20function%20run%28again%3A%20boolean%29%3A%20nil%0A%20%20%20%20local%20value%20%3D%20open%28%29%0A%20%20%20%20while%20again%20do%0A%20%20%20%20%20%20%20%20drop%28value%29%0A%20%20%20%20end%0Aend%0Areturn%20run). #### NUPP2610 A public capability contract is implicit. ```nupp [Reported] local m = {} function m.forward(value: T): T return value end return m ``` An exported parameter or result that may carry a cleanup, pin, retention, or rooted view must state its mode or result relation. Ordinary public values remain unannotated. ```nupp [Accepted] local m = {} function m.forward(takes value: T): T preserves value return value end return m ``` Related: [**NUPP2606**](#nupp2606), [**NUPP2611**](#nupp2611). Reference: [docs/learn/runtime/ownership/borrowing.md#public-capability-contracts](docs/learn/runtime/ownership/borrowing.md#public-capability-contracts). [Open the reported program in the playground](/playground/#source=local%20m%20%3D%20%7B%7D%0Afunction%20m.forward%3CT%3E%28value%3A%20T%29%3A%20T%0A%20%20%20%20return%20value%0Aend%0Areturn%20m). #### NUPP2611 A dynamic boundary would erase a live capability. ```nupp [Reported] local function erase(value: affine(table)): any return value end return erase ``` A value carrying an obligation, root, exclusive loan, pin, or foreign retention cannot be converted to any or passed to untyped Lua. Keep the boundary typed, move a self-contained owner into nupp.manage, or use an explicit unsafe release. ```nupp [Accepted] local function erase(takes value: affine(table)): any return unsafe release value end return erase ``` Related: [**NUPP2603**](#nupp2603), [**NUPP2608**](#nupp2608), [**NUPP2612**](#nupp2612). Reference: [docs/learn/runtime/ownership/borrowing.md#dynamic-boundaries](docs/learn/runtime/ownership/borrowing.md#dynamic-boundaries). [Open the reported program in the playground](/playground/#source=local%20function%20erase%28value%3A%20affine%28table%29%29%3A%20any%0A%20%20%20%20return%20value%0Aend%0Areturn%20erase). #### NUPP2612 A managed value is not self-contained. ```nupp [Reported] local record Resource end local function begin(): affine(Resource) return new Resource() end local cell = nupp.manage(begin()) ``` nupp.manage accepts only a capability it can discharge without outside state. Transfer-only obligations, external roots, outstanding exclusive loans, and unmatched foreign retentions cannot be managed. ```nupp [Accepted] local record Resource end local function close(takes value: Resource): nil end local function open(): affine(Resource, close) return new Resource() end local cell = nupp.manage(open()) ``` Related: [**NUPP2608**](#nupp2608), [**NUPP2611**](#nupp2611), [**NUPP2614**](#nupp2614). Reference: [docs/learn/runtime/ownership/borrowing.md#dynamic-boundaries](docs/learn/runtime/ownership/borrowing.md#dynamic-boundaries). [Open the reported program in the playground](/playground/#source=local%20record%20Resource%0Aend%0Alocal%20function%20begin%28%29%3A%20affine%28Resource%29%20return%20new%20Resource%28%29%20end%0Alocal%20cell%20%3D%20nupp.manage%28begin%28%29%29). #### NUPP2613 A managed alias has the wrong type policy. ```nupp [Reported] local record Plain end local erased: any = {} local recovered = nupp.recoverAlias(erased) local plain = recovered and recovered:downcast() ``` Downcasting an alias requires an exactly droppable target and compares its canonical representation and cleanup policy with the policy recorded by nupp.manage. Recovery from any separately validates the runtime brand. ```nupp [Accepted] local record File is nupp.Closeable function flush(exclusive self): nil end function close(takes self): nil end end local owner = nupp.manage(new File()) local erased: any = owner:alias() local recovered = nupp.recoverAlias(erased) local file = recovered and recovered:downcast() ``` Related: [**NUPP2611**](#nupp2611), [**NUPP2614**](#nupp2614). Reference: [docs/learn/runtime/ownership/borrowing.md#dynamic-boundaries](docs/learn/runtime/ownership/borrowing.md#dynamic-boundaries). [Open the reported program in the playground](/playground/#source=local%20record%20Plain%0Aend%0Alocal%20erased%3A%20any%20%3D%20%7B%7D%0Alocal%20recovered%20%3D%20nupp.recoverAlias%28erased%29%0Alocal%20plain%20%3D%20recovered%20and%20recovered%3Adowncast%3CPlain%3E%28%29). #### NUPP2614 A managed alias is invalid or its cell is terminal. An alias must carry the runtime brand of one managed cell. Closing or taking the payload leaves that cell as a permanent tombstone, and every copied alias fails before reading the payload. Related: [**NUPP2612**](#nupp2612), [**NUPP2613**](#nupp2613). Reference: [docs/learn/runtime/ownership/borrowing.md#dynamic-boundaries](docs/learn/runtime/ownership/borrowing.md#dynamic-boundaries). #### NUPP2615 An affine value names an invalid terminal. ```nupp [Reported] local record Resource end local record Other end local function release(takes value: Other): nil end local function acquire(): affine(Resource, release) return new Resource() end return acquire ``` The terminal must be visible and have the exact shape `nosuspend function(takes Representation): nil`. An affine interface declares exactly one such terminal and cannot inherit a competing one. ```nupp [Accepted] local record Resource end local record Other end local function release(takes value: Resource): nil end local function acquire(): affine(Resource, release) return new Resource() end return acquire ``` Related: [**NUPP2602**](#nupp2602), [**NUPP2603**](#nupp2603). Reference: [docs/learn/runtime/ownership/affine-types.md#cleanup-and-transfer-only-forms](docs/learn/runtime/ownership/affine-types.md#cleanup-and-transfer-only-forms). [Open the reported program in the playground](/playground/#source=local%20record%20Resource%0Aend%0Alocal%20record%20Other%0Aend%0Alocal%20function%20release%28takes%20value%3A%20Other%29%3A%20nil%0Aend%0Alocal%20function%20acquire%28%29%3A%20affine%28Resource%2C%20release%29%0A%20%20%20%20return%20new%20Resource%28%29%0Aend%0Areturn%20acquire). #### NUPP2616 A function declaring an owning result returns an input borrow. ```nupp [Reported] local record Buffer value: string end local function closeBuffer(takes value: Buffer): nil end local function bad(borrows source: Buffer): affine(Buffer, closeBuffer) return source end return bad ``` A result annotated with an owning (affine) type promises every caller a value they will eventually discharge themselves. Returning a value borrowed from a parameter breaks that promise, since the caller would receive something whose lifetime it never actually controls. Declare the result `borrows (param)` instead of an owning type when the function only ever forwards a borrow. ```nupp [Accepted] local record Buffer value: string end local function ok(borrows source: Buffer): Buffer borrows (source) return source end return ok ``` Related: [**NUPP2608**](#nupp2608), [**NUPP2619**](#nupp2619). Reference: [docs/learn/runtime/ownership/borrowing.md#borrowing-and-pinning](docs/learn/runtime/ownership/borrowing.md#borrowing-and-pinning). [Open the reported program in the playground](/playground/#source=local%20record%20Buffer%0A%20%20%20%20value%3A%20string%0Aend%0A%0Alocal%20function%20closeBuffer%28takes%20value%3A%20Buffer%29%3A%20nil%0Aend%0A%0Alocal%20function%20bad%28borrows%20source%3A%20Buffer%29%3A%20affine%28Buffer%2C%20closeBuffer%29%0A%20%20%20%20return%20source%0Aend%0A%0Areturn%20bad). #### NUPP2618 A result declares a borrow from a parameter the function consumes. ```nupp [Reported] local record Buffer value: string end local function bad(takes value: Buffer): Buffer borrows (value) return value end return bad ``` `borrows (param)` on a result ties the result's lifetime to a live argument at the call site. A `takes` parameter is moved into the function and its argument is gone once the call returns, so nothing in the result can still be borrowing from it; name a `borrows` parameter instead, or drop the result's borrow annotation. ```nupp [Accepted] local record Buffer value: string end local function ok(borrows value: Buffer): Buffer borrows (value) return value end return ok ``` Related: [**NUPP2109**](#nupp2109), [**NUPP2616**](#nupp2616). Reference: [docs/learn/runtime/ownership/borrowing.md#borrowing-and-pinning](docs/learn/runtime/ownership/borrowing.md#borrowing-and-pinning). [Open the reported program in the playground](/playground/#source=local%20record%20Buffer%0A%20%20%20%20value%3A%20string%0Aend%0A%0Alocal%20function%20bad%28takes%20value%3A%20Buffer%29%3A%20Buffer%20borrows%20%28value%29%0A%20%20%20%20return%20value%0Aend%0A%0Areturn%20bad). #### NUPP2619 A borrowed value's provenance does not reach its declared root. ```nupp [Reported] local record Buffer value: string end local function view(borrows source: Buffer): Buffer borrows (source) return source end local record Cursor source: Buffer bytes: Buffer borrows (source) end local left = new Buffer(value = "left") local right = new Buffer(value = "right") local cursor = new Cursor(source = left, bytes = view(right)) print(cursor.bytes.value) ``` A declared borrow names where its value comes from, and the value has to be provably derived from there, sharing the provenance chain rather than merely equalling or coincidentally matching. The same code reports each place that promise is checked: a field declared `T borrows (source)` constructed from a different root than the sibling passed as `source`; a result declared `T borrows (param)` returned from something that cannot be traced to that parameter; a call whose borrowed result has no rooted argument to borrow from; and a C output declared borrowed without a named lifetime source. Derive the value from the root the declaration names, or name the root it is derived from. ```nupp [Accepted] local record Buffer value: string end local function view(borrows source: Buffer): Buffer borrows (source) return source end local record Cursor source: Buffer bytes: Buffer borrows (source) end local left = new Buffer(value = "left") local cursor = new Cursor(source = left, bytes = view(left)) print(cursor.bytes.value) ``` Related: [**NUPP2602**](#nupp2602), [**NUPP2203**](#nupp2203), [**NUPP2707**](#nupp2707). Reference: [docs/learn/runtime/ownership/borrowing.md#borrowing-and-pinning](docs/learn/runtime/ownership/borrowing.md#borrowing-and-pinning). [Open the reported program in the playground](/playground/#source=local%20record%20Buffer%0A%20%20%20%20value%3A%20string%0Aend%0A%0Alocal%20function%20view%28borrows%20source%3A%20Buffer%29%3A%20Buffer%20borrows%20%28source%29%0A%20%20%20%20return%20source%0Aend%0A%0Alocal%20record%20Cursor%0A%20%20%20%20source%3A%20Buffer%0A%20%20%20%20bytes%3A%20Buffer%20borrows%20%28source%29%0Aend%0A%0Alocal%20left%20%3D%20new%20Buffer%28value%20%3D%20%22left%22%29%0Alocal%20right%20%3D%20new%20Buffer%28value%20%3D%20%22right%22%29%0Alocal%20cursor%20%3D%20new%20Cursor%28source%20%3D%20left%2C%20bytes%20%3D%20view%28right%29%29%0Aprint%28cursor.bytes.value%29). #### NUPP2620 A managed cell has a conflicting runtime borrow. Shared alias callbacks exclude an exclusive callback, and an exclusive callback excludes every other borrow. Close and take likewise require no active callback borrow. The runtime releases a borrow before propagating a callback error. Related: [**NUPP2607**](#nupp2607), [**NUPP2614**](#nupp2614). Reference: [docs/learn/runtime/ownership/borrowing.md#dynamic-boundaries](docs/learn/runtime/ownership/borrowing.md#dynamic-boundaries). #### NUPP2621 More than one result declares a borrow. ```nupp [Reported] local record Buffer value: string end local function bad(borrows left: Buffer, borrows right: Buffer): (Buffer borrows (left), Buffer borrows (right)) return left, right end return bad ``` A callable carries one borrow relation, and it states the result it is about: `borrows (param)` may be written on any result, but only one of them. Two results borrowing from two different parameters would be two relations, which the callable has nowhere to record and a call site nowhere to attach; return the second value owned, or hand the caller the borrowed one and let it derive the rest. ```nupp [Accepted] local record Buffer value: string end local function ok(borrows left: Buffer, borrows right: Buffer): (Buffer borrows (left, right), integer) return left, #right.value end return ok ``` Related: [**NUPP2608**](#nupp2608), [**NUPP2618**](#nupp2618), [**NUPP2109**](#nupp2109). Reference: [docs/learn/runtime/ownership/borrowing.md#borrowing-and-pinning](docs/learn/runtime/ownership/borrowing.md#borrowing-and-pinning). [Open the reported program in the playground](/playground/#source=local%20record%20Buffer%0A%20%20%20%20value%3A%20string%0Aend%0A%0Alocal%20function%20bad%28borrows%20left%3A%20Buffer%2C%20borrows%20right%3A%20Buffer%29%3A%20%28Buffer%20borrows%20%28left%29%2C%20Buffer%20borrows%20%28right%29%29%0A%20%20%20%20return%20left%2C%20right%0Aend%0A%0Areturn%20bad). #### NUPP2630 A counted C pointer does not match its physical count parameter. ```nupp [Reported] cdef function visit( borrows values: const int32* countedBy(missing), count: uint64 ) ``` countedBy(count) replaces a call-duration borrowed pointer/count pair with a checked span. The qualified value must be a pointer, and the named plain count must currently be uint64. ```nupp [Accepted] cdef function visit( borrows values: const int32* countedBy(count), count: uint64 ) ``` Related: [**NUPP2203**](#nupp2203), [**NUPP2602**](#nupp2602). Reference: [docs/learn/runtime/c-interop/index.md#counted-pointer-adapters](docs/learn/runtime/c-interop/index.md#counted-pointer-adapters). [Open the reported program in the playground](/playground/#source=cdef%20function%20visit%28%0A%20%20%20%20borrows%20values%3A%20const%20int32%2A%20countedBy%28missing%29%2C%20count%3A%20uint64%0A%29). #### NUPP2701 A non-suspending region can reach suspension. ```nupp [Reported] local function wait(): nil coroutine.yield() end nosuspend do wait() end ``` A `nosuspend` region and every cleanup contract must finish without parking the current coroutine. Remove the yielding call, move it before the protected region, or call an operation whose type or visible body proves that it cannot suspend. ```nupp [Accepted] local function finish(): nil end nosuspend do finish() end ``` Related: [**NUPP2602**](#nupp2602), [**NUPP2603**](#nupp2603). Reference: [docs/learn/runtime/concurrency/suspension.md#non-suspending-regions](docs/learn/runtime/concurrency/suspension.md#non-suspending-regions). [Open the reported program in the playground](/playground/#source=local%20function%20wait%28%29%3A%20nil%0A%20%20%20%20coroutine.yield%28%29%0Aend%0A%0Anosuspend%20do%0A%20%20%20%20wait%28%29%0Aend). #### NUPP2702 A non-yieldable C callback can reach suspension. ```nupp [Reported] table.sort({2, 1}, function(a, b): boolean coroutine.yield() return a < b end) ``` LuaJIT cannot yield through every C frame. Make the callback and every call it reaches non-suspending, or invoke it from a yieldable Lua boundary. ```nupp [Accepted] table.sort({2, 1}, function(a, b): boolean return a < b end) ``` Related: [**NUPP2701**](#nupp2701). Reference: [docs/learn/runtime/concurrency/suspension.md#c-call-boundaries](docs/learn/runtime/concurrency/suspension.md#c-call-boundaries). [Open the reported program in the playground](/playground/#source=table.sort%28%7B2%2C%201%7D%2C%20function%28a%2C%20b%29%3A%20boolean%0A%20%20%20%20coroutine.yield%28%29%0A%20%20%20%20return%20a%20%3C%20b%0Aend%29). #### NUPP2706 Control cannot jump into a handled suspension region. ```nupp [Reported] goto inside handle suspension with handler do ::inside:: end ``` Entering a `handle suspension` body from outside would bypass handler installation and its cleanup obligation. Move the label outside the region or move the jump inside it. Structured exits from the region are allowed. ```nupp [Accepted] handle suspension with handler do end ::outside:: ``` Related: [**NUPP2701**](#nupp2701), [**NUPP2702**](#nupp2702). Reference: [docs/learn/runtime/concurrency/suspension.md#handler-scope-follows-the-coroutine](docs/learn/runtime/concurrency/suspension.md#handler-scope-follows-the-coroutine). [Open the reported program in the playground](/playground/#source=goto%20inside%0Ahandle%20suspension%20with%20handler%20do%0A%20%20%20%20%3A%3Ainside%3A%3A%0Aend). #### NUPP2707 A function required to compile crosses an unsupported JIT boundary. ```nupp [Reported] cdef function printf(format: cstring, ...): int32 @jit local function hot(): nil printf('%d', 1) end ``` `@jit` promises that a function contains no variadic FFI call and does not pass a Lua callback into C. Move the boundary into a function disabled with `jit.off`, or remove `@jit` when tracing is not required. ```nupp [Accepted] cdef function printf(format: cstring, ...): int32 local function cold(): nil printf('%d', 1) end jit.off(cold) ``` Related: [**NUPP2502**](#nupp2502), [**NUPP2514**](#nupp2514). Reference: [docs/learn/runtime/c-interop/index.md](docs/learn/runtime/c-interop/index.md). [Open the reported program in the playground](/playground/#source=cdef%20function%20printf%28format%3A%20cstring%2C%20...%29%3A%20int32%0A%0A%40jit%0Alocal%20function%20hot%28%29%3A%20nil%0A%20%20%20%20printf%28%27%25d%27%2C%201%29%0Aend). #### NUPP2710 A non-allocating region can reach allocation. ```nupp [Reported] local function build(): nil local values = {} end noalloc do build() end ``` A `noalloc` region may only call operations whose visible bodies or exact exported guarantees prove they perform no managed allocation. Remove the construction, move it outside the region, or give an unseen declaration a truthful trusted @effects contract. ```nupp [Accepted] local function update(): nil local n = 1 end noalloc do update() end ``` Related: [**NUPP2112**](#nupp2112), [**NUPP2711**](#nupp2711). Reference: [docs/learn/language/effects.md#allocation-and-raising-regions](docs/learn/language/effects.md#allocation-and-raising-regions). [Open the reported program in the playground](/playground/#source=local%20function%20build%28%29%3A%20nil%0A%20%20%20%20local%20values%20%3D%20%7B%7D%0Aend%0A%0Anoalloc%20do%0A%20%20%20%20build%28%29%0Aend). #### NUPP2711 A non-raising region can reach an error path. ```nupp [Reported] local function fail(): nil error('failed') end noraise do fail() end ``` A `noraise` region may only call operations whose visible bodies or exact exported guarantees prove they cannot raise a catchable language error. Validate the precondition first, remove the error path, or move the operation outside the region. ```nupp [Accepted] local function finish(): nil end noraise do finish() end ``` Related: [**NUPP2112**](#nupp2112), [**NUPP2710**](#nupp2710). Reference: [docs/learn/language/effects.md#allocation-and-raising-regions](docs/learn/language/effects.md#allocation-and-raising-regions). [Open the reported program in the playground](/playground/#source=local%20function%20fail%28%29%3A%20nil%0A%20%20%20%20error%28%27failed%27%29%0Aend%0A%0Anoraise%20do%0A%20%20%20%20fail%28%29%0Aend). #### NUPP2801 A derive provider name is unknown or duplicated. Each @derive argument names one resolved exported comptime provider, and an identity may occur only once across every derive annotation on the record. Related: [**NUPP2113**](#nupp2113), [**NUPP2802**](#nupp2802). Reference: [docs/reference/derives.md](docs/reference/derives.md). #### NUPP2802 A generated derive member conflicts with the declaration. A derive never overrides a written instance or static member. Remove the written member or remove the provider so the behavior has one owner. Related: [**NUPP2801**](#nupp2801), [**NUPP2115**](#nupp2115). Reference: [docs/reference/derives.md](docs/reference/derives.md). #### NUPP2803 A field cannot participate in derived Debug. Every visible Debug field needs a supported scalar or container type, another Debug record, an exact nupp.Debug contract, or the dynamic any fallback. A field cannot be both skipped and redacted. Related: [**NUPP2802**](#nupp2802). Reference: [docs/reference/derives.md#debug](docs/reference/derives.md#debug). #### NUPP2806 A record does not describe a supported JSON schema. Derived JSON needs a closed supported field graph and consistent JSON options. Rename duplicate keys, default omitted fields, avoid erased generic records and unsupported values, and do not use int64 or uint64 as JSON numbers. Related: [**NUPP2001**](#nupp2001). Reference: [docs/reference/derives.md#json](docs/reference/derives.md#json). #### NUPP2807 A derive dependency cycle has no valid lowering. Recursive Debug and JSON graphs are supported. A package provider dependency cycle must still reduce to a closed recipe rather than require another unfinished provider result. Related: [**NUPP2803**](#nupp2803), [**NUPP2806**](#nupp2806), [**NUPP2810**](#nupp2810). Reference: [docs/reference/derives.md](docs/reference/derives.md). #### NUPP2808 A derive exceeds a compiler generation limit. Generated recipes, expressions, locals, upvalues, and emitted output are bounded compiler resources. Reduce the derived declaration or split the schema rather than depending on an unbounded generated function. Related: [**NUPP2807**](#nupp2807). Reference: [docs/reference/derives.md](docs/reference/derives.md). #### NUPP2809 A comptime derive provider declaration or reference is invalid. A public derive provider is an exported, nongeneric comptime function with the exact shape function(nupp.derive.Info): nupp.derive.Result, where I is one existing interface. @derive must name that resolved export. Related: [**NUPP2411**](#nupp2411), [**NUPP2801**](#nupp2801). Reference: [docs/reference/derives.md#package-providers](docs/reference/derives.md#package-providers). #### NUPP2810 A comptime derive provider failed or returned an invalid blueprint. Providers execute in the bounded comptime worker and must return only nupp.derive.implement or nupp.derive.error. Closed builders reject foreign, cyclic, malformed, stale, and over-limit recipe graphs. Related: [**NUPP2412**](#nupp2412), [**NUPP2808**](#nupp2808). Reference: [docs/reference/derives.md#package-providers](docs/reference/derives.md#package-providers). #### NUPP2811 A derive recipe declares an invalid generated member. A bare forward fills a bodyless callable requirement of Result. A member recipe may instead provide a bounded function signature. Neither form may replace written members or interface defaults. Related: [**NUPP2118**](#nupp2118), [**NUPP2802**](#nupp2802). Reference: [docs/reference/derives.md#closed-forwarding-recipes](docs/reference/derives.md#closed-forwarding-recipes). #### NUPP2812 A forwarding argument does not exist on the generated method. A forwarding recipe may pass its receiver, a named method parameter, a readable stored field, a bounded frozen constant, or a fresh array of those. Every name is resolved against the written owner and interface requirement. Related: [**NUPP2004**](#nupp2004), [**NUPP2811**](#nupp2811). Reference: [docs/reference/derives.md#closed-forwarding-recipes](docs/reference/derives.md#closed-forwarding-recipes). #### NUPP2813 A runtime forwarding helper does not satisfy the generated call. The helper must be an ordinary exported Nupp function. Forwarded argument types must fit its parameters, its result pack must satisfy the interface requirement, and it may not suspend where the requirement cannot. Related: [**NUPP2001**](#nupp2001), [**NUPP2701**](#nupp2701), [**NUPP2812**](#nupp2812). Reference: [docs/reference/derives.md#runtime-helpers](docs/reference/derives.md#runtime-helpers). #### NUPP2901 One function is promised to two compilers. ```nupp [Reported] @jit @aot local function hot(scale: number): number return scale * 2.0 end ``` `@jit` promises a function compiles as a LuaJIT trace. `@aot` reserves that body for ahead-of-time compilation. Stacking them asks two compilers for the same code, so one of the two has to go: keep `@aot` when the function should compile ahead of time, and `@jit` when it should trace. ```nupp [Accepted] @aot local function hot(scale: number): number return scale * 2.0 end ``` Related: [**NUPP2707**](#nupp2707), [**NUPP2902**](#nupp2902). Reference: [docs/neps/0009-ahead-of-time-compilation.md](docs/neps/0009-ahead-of-time-compilation.md). [Open the reported program in the playground](/playground/#source=%40jit%0A%40aot%0Alocal%20function%20hot%28scale%3A%20number%29%3A%20number%0A%20%20%20%20return%20scale%20%2A%202.0%0Aend). #### NUPP2902 `@aot` is attached to something that is not a whole function. ```nupp [Reported] local record Point x: number @aot constructor(self, x: number) self.x = x end end ``` `@aot` compiles one complete function and puts a wrapper in front of the function value callers see. A constructor and an inline interface requirement are neither: the compiler generates code around both, and neither is a value the wrapper can stand in front of. A function nested inside another function is a fresh value on every call of the one holding it, which is not something one wrapper can stand in front of either. Move the work into an ordinary top-level function and annotate that, then call it from wherever it was. ```nupp [Accepted] @aot local function scaled(x: number): number return x * 2.0 end local record Point x: number constructor(self, x: number) self.x = scaled(x) end end ``` Related: [**NUPP2901**](#nupp2901), [**NUPP2903**](#nupp2903). Reference: [docs/neps/0009-ahead-of-time-compilation.md](docs/neps/0009-ahead-of-time-compilation.md). [Open the reported program in the playground](/playground/#source=local%20record%20Point%0A%20%20%20%20x%3A%20number%0A%20%20%20%20%40aot%0A%20%20%20%20constructor%28self%2C%20x%3A%20number%29%0A%20%20%20%20%20%20%20%20self.x%20%3D%20x%0A%20%20%20%20end%0Aend). #### NUPP2903 An `@aot` body contains a construct with no AOT IR representation. ```nupp [Reported] @aot local function total(scale: number): number local function double(x: number): number return x * 2.0 end return double(scale) end ``` The body of an `@aot` function is ordinary Nupp, but only the part of it the AOT IR can represent. The IR has no closure, table, interpolated string, vararg, `goto`, dynamic call, or unsafe operation, so a body containing one cannot compile ahead of time and says so here rather than failing in a backend. Rewrite the body without it, or remove `@aot` and let the function run as ordinary Nupp. ```nupp [Accepted] local function double(x: number): number return x * 2.0 end @aot local function total(scale: number): number return double(scale) end ``` Related: [**NUPP2901**](#nupp2901), [**NUPP2902**](#nupp2902). Reference: [docs/neps/0009-ahead-of-time-compilation.md](docs/neps/0009-ahead-of-time-compilation.md). [Open the reported program in the playground](/playground/#source=%40aot%0Alocal%20function%20total%28scale%3A%20number%29%3A%20number%0A%20%20%20%20local%20function%20double%28x%3A%20number%29%3A%20number%0A%20%20%20%20%20%20%20%20return%20x%20%2A%202.0%0A%20%20%20%20end%0A%0A%20%20%20%20return%20double%28scale%29%0Aend). #### NUPP2904 A construct needs a capability the selected target does not have. A target profile states what a destination admits: a dynamic loader, a tracing JIT, FFI callbacks, and a way to resolve a default-namespace symbol out of a static archive. `@jit` on a target with no trace compiler asserts a contract nothing there can meet, and a `cdef ... from "name"` on a target with no loader asks the platform to load something it cannot; both are refused where they are written rather than on the device. Reach the same C through the default namespace, which a static link puts in the process image, and drop `@jit` where nothing traces. Which target is selected is what decides this, so no isolated file exhibits it on its own: a target is described by the built-in profile for its triple or by a verified descriptor in a compiler pack, and a target nothing describes refuses nothing. Related: [**NUPP2707**](#nupp2707), [**NUPP2901**](#nupp2901). Reference: [docs/learn/projects/build.md](docs/learn/projects/build.md). ### Code generation #### NUPP3001 `is` has nothing to test against this type. ```nupp [Reported] local interface Drawable width: number end local record Sprite is Drawable width: number end local unknown: any = new Sprite(width = 1) return unknown is Drawable ``` A record is identified by the metatable it stamps and a struct by its ctype, so both answer `is` exactly. An interface has neither, by design, being conformance rather than provenance, so something has to stand in for one. Three things can. A literal-typed field is a tag, and the test is read off it with nothing written. A `satisfies` declaration says the test outright, for a shape no tag describes. And a subject whose own type declares the interface needs no test at all: the declaration already answered, so the `is` compiles to `true`. An alias has none of these and never will. ```nupp [Accepted] local interface Drawable kind: "drawable" width: number end local record Sprite is Drawable kind: "drawable" width: number end local unknown: any = new Sprite(kind = "drawable", width = 1) return unknown is Drawable ``` Related: [**NUPP2122**](#nupp2122). Reference: [docs/learn/language/types/interfaces.md](docs/learn/language/types/interfaces.md). [Open the reported program in the playground](/playground/#source=local%20interface%20Drawable%0A%20%20%20%20width%3A%20number%0Aend%0A%0Alocal%20record%20Sprite%20is%20Drawable%0A%20%20%20%20width%3A%20number%0Aend%0A%0Alocal%20unknown%3A%20any%20%3D%20new%20Sprite%28width%20%3D%201%29%0A%0Areturn%20unknown%20is%20Drawable). #### NUPP3002 A struct field's type has no C declarator the generator can emit. ```nupp [Reported] local record Widget id: integer end local struct Holder items: Widget*[3] end return Holder ``` Every field of a reified `struct` lowers to a literal C declarator inside the `ffi.typeof` string the record compiles to. The checker admits any pointer or fixed array uniformly as a reifiable field, but the generator still has to spell each concrete shape syntactically, and some admitted shapes have no case in that spelling, chief among them an array whose element is itself a pointer. Such a field is refused at generation rather than at check. ```nupp [Accepted] cdef struct Widget id: integer end local struct Holder items: Widget* count: int32 end return Holder ``` Related: [**NUPP2201**](#nupp2201), [**NUPP3003**](#nupp3003), [**NUPP3004**](#nupp3004). Reference: [docs/learn/runtime/c-interop/index.md#type-mapping](docs/learn/runtime/c-interop/index.md#type-mapping). [Open the reported program in the playground](/playground/#source=local%20record%20Widget%0A%20%20%20%20id%3A%20integer%0Aend%0A%0Alocal%20struct%20Holder%0A%20%20%20%20items%3A%20Widget%2A%5B3%5D%0Aend%0A%0Areturn%20Holder). #### NUPP3003 A cdef struct field or function signature has no C spelling. ```nupp [Reported] local record Widget id: integer end cdef function inspect(target: Widget*) return inspect ``` `cdef struct` and `cdef function` declare literal C, and every field and parameter type must render as one. A pointer to an ordinary Nupp record, one with no `cdef struct` layout of its own, passes the checker's general rule that any pointer is reifiable, but has no C declarator: only cdef-declared aggregates, primitives, `cstring`, `voidptr`, and pointers built from those actually render. Point at a `cdef struct` instead of a plain `record`. ```nupp [Accepted] cdef struct Widget id: integer end cdef function inspect(target: Widget*) return inspect ``` Related: [**NUPP2203**](#nupp2203), [**NUPP3002**](#nupp3002), [**NUPP3004**](#nupp3004). Reference: [docs/learn/runtime/c-interop/index.md#type-mapping](docs/learn/runtime/c-interop/index.md#type-mapping). [Open the reported program in the playground](/playground/#source=local%20record%20Widget%0A%20%20%20%20id%3A%20integer%0Aend%0A%0Acdef%20function%20inspect%28target%3A%20Widget%2A%29%0A%0Areturn%20inspect). #### NUPP3004 An FFI operation's explicit type argument has no C spelling. ```nupp [Reported] local record Widget id: integer end local w = ffi.new() return w ``` `ffi.new`, `ffi.cast`, `ffi.typeof` and their siblings take T as a literal C type argument to LuaJIT, not an ordinary Nupp value type. The checker resolves any named type here without verifying it is renderable; only a `cdef struct`, a Nupp `struct`, a primitive, or a pointer built from those actually is. Naming a plain `record` fails at generation instead. ```nupp [Accepted] local struct Widget id: integer end local w = ffi.new() return w ``` Related: [**NUPP2203**](#nupp2203), [**NUPP3002**](#nupp3002), [**NUPP3003**](#nupp3003). Reference: [docs/learn/runtime/c-interop/index.md#type-mapping](docs/learn/runtime/c-interop/index.md#type-mapping). [Open the reported program in the playground](/playground/#source=local%20record%20Widget%0A%20%20%20%20id%3A%20integer%0Aend%0A%0Alocal%20w%20%3D%20ffi.new%3CWidget%3E%28%29%0Areturn%20w). #### NUPP3005 Generated code that a Lua VM will not load. The generator writes Lua and this is that Lua refusing to parse. Almost always it is one limit: a function may capture at most sixty names from around it, and one that reaches past that cannot be loaded at all. A function reading that many things from its scope is usually reading a record it could take as one argument instead. Pass what varies, or gather what it reads into one value and capture that. Any other spelling of this is a bug in the compiler rather than in the program, and `nupp bc FILE` shows the code it wrote. It is reported where the file is built rather than where the module is first required, because the line a VM would name belongs to generated text and the line here is the one that was written. Related: [**NUPP3004**](#nupp3004). Reference: [docs/reference/diagnostics.md#code-families](docs/reference/diagnostics.md#code-families). #### NUPP3006 The selected dialect cannot represent a required language capability. A dialect fixes the language representations and operations an artifact may use. Choose a target that supports the operation, or express it with a portable contract. Runtime provider selection cannot add a compiler representation. Related: [**NUPP3005**](#nupp3005). Reference: [docs/learn/projects/portability/libraries.md](docs/learn/projects/portability/libraries.md). #### NUPP3009 The portable dialect cannot lower an authored jump. An authored label or `goto` can jump across arbitrary lexical regions. Lowering a function containing one to Lua 5.1 would require a state machine, changing stack frames and error sites. Use structured control flow instead. Compiler-generated cleanup and `continue` transfers are structured and are lowered separately. Related: [**NUPP3006**](#nupp3006). Reference: [docs/learn/projects/portability/libraries.md](docs/learn/projects/portability/libraries.md). #### NUPP3010 A resolved prelude identity is outside the portable runtime surface. The `lua51` dialect runs on Lua 5.1 through 5.4 and LuaJIT, so an ambient prelude identity is available only when it has the same contract throughout that matrix or the compiler lowers it explicitly. A same-spelled local or ordinary table field is a different definition and remains application code. Related: [**NUPP3006**](#nupp3006), [**NUPP3009**](#nupp3009). Reference: [docs/learn/projects/portability/libraries.md](docs/learn/projects/portability/libraries.md). ### Formatting #### NUPP4001 The formatter's own output would change the token stream. Formatting may only move whitespace, reflow comments, add or remove a schema-proven single-value annotation label, and parenthesize a method call's sugar-form arguments. Before writing its result the formatter re-lexes it and compares the token fingerprint against the input; a mismatch means the rewrite it just performed would have changed the program's meaning, so it discards the rewrite and leaves the file untouched instead of risking that change. Reference: [docs/reference/diagnostics.md#code-families](docs/reference/diagnostics.md#code-families). ### Development runtime #### NUPP5001 A running program cannot apply a change without a restart. Hot reload replaces a watched module's callable bodies in place, but cannot rebind top-level executable statements or initializers, add, remove, rename or move a named declaration, change a callable's signature or capture set, change a record, struct, native or component layout, or repatch a function that took ownership of a cleanup-bearing capture. Loaded FFI declarations, native libraries, and values already built from them are likewise fixed for the life of the process. Any such edit is rejected with NUPP5001 and the previous generation keeps running until the process restarts. Reference: [docs/learn/projects/hot-reload.md#changes-that-require-restart](docs/learn/projects/hot-reload.md#changes-that-require-restart).