{"id":51061199,"url":"https://github.com/dereckscompany/roxyassert","last_synced_at":"2026-06-23T02:02:24.083Z","repository":{"id":361514098,"uuid":"1254753322","full_name":"dereckscompany/roxyassert","owner":"dereckscompany","description":"A roxygen2 roclet that generates argument/return assertions from documentation — built on the assert package","archived":false,"fork":false,"pushed_at":"2026-06-07T01:24:03.000Z","size":1910,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-07T03:38:28.276Z","etag":null,"topics":["pkgdown","r","rcpp","testthat"],"latest_commit_sha":null,"homepage":"https://dereckscompany.github.io/roxyassert/","language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dereckscompany.png","metadata":{"files":{"readme":"README.Rmd","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-31T00:51:54.000Z","updated_at":"2026-06-07T01:15:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dereckscompany/roxyassert","commit_stats":null,"previous_names":["dereckscompany/roxyassert"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dereckscompany/roxyassert","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dereckscompany%2Froxyassert","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dereckscompany%2Froxyassert/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dereckscompany%2Froxyassert/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dereckscompany%2Froxyassert/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dereckscompany","download_url":"https://codeload.github.com/dereckscompany/roxyassert/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dereckscompany%2Froxyassert/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34672250,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["pkgdown","r","rcpp","testthat"],"created_at":"2026-06-23T02:02:23.313Z","updated_at":"2026-06-23T02:02:24.077Z","avatar_url":"https://github.com/dereckscompany.png","language":"R","funding_links":[],"categories":[],"sub_categories":[],"readme":"---\noutput: github_document\n---\n\n```{r, include=FALSE}\nknitr::opts_chunk$set(\n    warning = FALSE,\n    message = FALSE,\n    fig.path = \"./man/figures/README-\",\n    fig.align = \"center\",\n    fig.width = 12,\n    fig.height = 10,\n    dpi = 150,\n    collapse = TRUE,\n    comment = \"#\u003e\"\n)\n```\n\n# roxyassert\n\n\u003e Write the contract once — in your roxygen2 docs — and generate the runtime checks.\n\n`roxyassert` turns **structured type annotations** in your roxygen2 documentation into **per-function assertion helpers**. At `document()` time it parses your `@param`/`@return` tags and generates argument and return checks — plain calls to the [`assert`](https://github.com/dereckscompany/assert) package — into a committed source file you can read and debug.\n\nR is **dynamically typed**: nothing stops a caller from passing a character where you expected a number, or a function from returning the wrong shape. The usual fix is to hand-write a validation function — which repeats, in code, the types you already wrote in your docs, and then drifts from them. `roxyassert` makes the **documentation the single source of truth**: the same annotations render for humans *and* generate the checks, so they cannot disagree.\n\n## Key ideas\n\n- **One source of truth.** The typed `@param`/`@return` you write for humans *is* what generates the runtime checks — they can never drift.\n- **No magic, no coercion.** Value constraints (ranges, sets) are plain R expressions **copied verbatim** into the generated check. You write `as.POSIXct(\"2024-01-01\", tz = \"America/New_York\")` and roxyassert pastes it — it never guesses a format, a constructor, or a time zone on your behalf.\n- **An open *value* universe.** Interval bounds and set elements are arbitrary R, so you can range- or membership-check against *any* value type — `Date`, `POSIXct`, [`lubridate`](https://lubridate.tidyverse.org/), `bit64`, your own classes — without roxyassert having to \"know about\" it. (The set of *declared* types is fixed; `any` is the wildcard for a value you deliberately don't constrain.)\n- **Readable, debuggable output.** The generated helpers are ordinary `assert` calls in a committed file you can open, read, and step through.\n\n## How it works\n\n```\n  R/*.R  (your code + typed roxygen tags)\n     |\n     |   devtools::document()   \u003c- roxyassert runs here, as a roclet\n     v\n  R/contracts-generated.R       \u003c- generated assert_args_* / assert_return_* helpers\n     |\n     |   you call them inside your functions, and commit the file\n     v\n  runtime validation via the `assert` package\n```\n\n`roxyassert` is a **roxygen2 roclet** — it runs in the documentation step (the one that writes `NAMESPACE` and `man/*.Rd`), never during `R CMD build`/`check`. It parses tag *text* only; it never executes your code.\n\n| Layer | Package | Role |\n|-------|---------|------|\n| Vocabulary | [`assert`](https://github.com/dereckscompany/assert) | Runtime primitives (`assert_scalar_character()`, `assert_data_table()`, ...). |\n| Generator | **`roxyassert`** | Parses typed docs -\u003e emits helpers that *call* `assert`. |\n\n`assert` does not depend on `roxyassert` and stays lightweight; `roxyassert` depends on `assert` + `roxygen2`.\n\n## Installation\n\n```r\nrenv::install(\"dereckscompany/roxyassert\")\n# or: remotes::install_github(\"dereckscompany/roxyassert\")\n```\n\n## Setup\n\nRegister the roclet in your package's `DESCRIPTION`:\n\n```dcf\nRoxygen: list(markdown = TRUE, roclets = c(\"namespace\", \"rd\", \"roxyassert::contract_roclet\"))\n```\n\nThe generated helpers call `assert::*` functions by their bare names, so make `assert` an import of your package — add it to `Imports` and bring its namespace in once (e.g. a package-level `#' @import assert`):\n\n```dcf\nImports: assert\n```\n\n```r\n#' @import assert\nNULL\n```\n\n`devtools::document()` then runs `roxyassert` alongside the built-in roclets and (re)writes `R/contracts-generated.R`.\n\n\u003e **Note — a harmless markdown warning.** With `markdown = TRUE`, roxygen2's link resolver sees the `[ ]` of an interval (`[0, 1]`) or a `[[ ]]` subscript bound in a `@param`/`@return` description and reports a \"could not resolve link\" **warning** during `document()`. It is cosmetic: the `.Rd` still builds and the generated checks are unaffected — `roxyassert` reads each tag's untouched **raw** text, never the markdown-rendered version. You can ignore the warning, or avoid it by escaping the bracket (`\\[0, 1\\]`) in the description if your help pages must be warning-clean.\n\n\u003e **Note — type rendering under markdown is handled for you.** With `markdown = TRUE`, roxygen2 lowers a bare-word type fragment such as `\u003cPOSIXct\u003e` (in `scalar\u003cPOSIXct\u003e`, `class\u003cDuration\u003e`, `promise\u003cT\u003e`, a nested generic, etc.) into raw inline HTML, which a browser would otherwise eat as an unknown tag — hiding the type so it shows as just `(scalar)`. `roxyassert` repairs the generated `man/*.Rd` automatically so the full type renders in your help pages and pkgdown site. Write the `\u003c...\u003e` type syntax exactly as documented here; no backticks or escaping needed for it. (The separate `[ ]` interval-bracket link warning above is unrelated to rendering — the interval type itself renders fine.)\n\n## The annotation grammar\n\nA type annotation is a parenthesised token at the **start** of a `@param` or `@return` description. Tags without a leading `(...)` token are ignored, so adoption is incremental and opt-in.\n\nThis section is the working overview; the [complete grammar reference](https://dereckscompany.github.io/roxyassert/articles/grammar.html) (formal EBNF, type categories, precedence, and exhaustive demos) is the authoritative specification.\n\n**Default arity follows R: a bare *atomic* type is a *vector* of any length.** A scalar (length 1) is declared explicitly with `scalar\u003c...\u003e`. (Reference types — `function`/`class\u003c...\u003e` — are length-1, and a bare composite is an unconstrained list/table.)\n\nThe `\u003c\u003e` generic wraps the **element type** (and, for `vector`, its length); the whole-argument modifiers sit outside it:\n\n- **Shape (`\u003c\u003e`):** `scalar\u003c...\u003e` (length 1) and `vector\u003c..., length\u003e` wrap an atomic element. (`class\u003c...\u003e` also uses `\u003c\u003e` but is a *reference type*, not a shape — see Base types.) A **bare *atomic* type is already a vector**, so you only reach for `scalar`/`vector` when you need length 1 or an explicit length.\n- **Element constraints:** `in \u003cinterval-or-set\u003e` and `| NA` attach to the *element type* — **bare or wrapped**. `(numeric in [0, Inf[)` is a non-negative numeric vector; `(scalar\u003cnumeric in [0, Inf[\u003e)` is a single non-negative number.\n- **Slot (whole argument):** a trailing `?` (may be `NULL`) and `|` type-unions.\n\nThe bracket / token reference:\n\n- `\u003c \u003e` — generics: `scalar` / `vector` wrap the element type (+ length), `class` names a class.\n- `[ ] / ] [` — a numeric **interval** (`[`/`]` closed, `]`/`[` open, ISO/Bourbaki).\n- `in` — a value-constraint on the element type: an interval, or an R set.\n- `c(...)` / a name — a **set** of allowed values (an enum).\n- `,` — a vector **length** (inside `\u003c\u003e`).\n- `| NA` — elements may be `NA` (bare or wrapped); default: **not** allowed.\n- `?` / `| NULL` — the whole argument may be `NULL` (slot level).\n- `| \u003ctype\u003e` — a union with another type, e.g. `numeric | character`.\n\nThe `|` operator is read by what follows it: **`NA`** = elements may be missing, **`NULL`** = the whole argument may be `NULL`, anything else = a **type union**.\n\n### Base types\n\nThe type token in an annotation or a column/field bullet (subject to the per-category rules — `scalar\u003c\u003e`/`vector\u003c\u003e` wrap only atomics and `any`; `function`/`class\u003c...\u003e` are reference types written bare; `list`/`data.table`/`data.frame` are composites written bare or refined by nested bullets, and `list` additionally as `list\u003cT\u003e`):\n\n| Type | R meaning |\n|------|-----------|\n| `logical` | `TRUE`/`FALSE` vector |\n| `integer` | integer vector |\n| `numeric` | double vector |\n| `complex` | complex vector |\n| `character` | character vector |\n| `raw` | raw vector |\n| `factor` | factor |\n| `Date` | `Date` vector |\n| `POSIXct` | date-time vector |\n| `count` | non-negative whole number(s), `20` or `20L` — `assert_scalar_count` / `assert_count` (no `NA`, no set; interval-capable) |\n| `any` | any R object — no type check (the wildcard) |\n| `list` | list — bare = unconstrained; `list\u003cT\u003e` = homogeneous; + bullets = named record |\n| `function` | a function/closure (a bare, length-1 reference) |\n| `data.table` | a `data.table` (see composite form) |\n| `data.frame` | a `data.frame` (see composite form) |\n| `class\u003cClass\u003e` | an object of class `Class` — any object system (S3/S4/RC/R6/S7), subclasses match (a bare, length-1 reference) |\n| `promise\u003cT\u003e` | a result resolving to `T`, delivered sync or async (see **Asynchronous returns** below) |\n\n`function` and `class\u003cClass\u003e` are **reference types**: written bare (`(function)`, `(class\u003cEngine\u003e)`), nullable as a slot (`(class\u003cEngine\u003e?)`), never wrapped in `scalar\u003c\u003e`/`vector\u003c\u003e` and never carrying `in`/`| NA`/length. `class\u003cClass\u003e` checks the value's class with `inherits()`, so it works for any object system (S3, S4, Reference Classes, R6, S7) and matches subclasses (`class\u003cAbstractClock\u003e` accepts a `RealClock`). `Class` names a single class, not a `pkg::Class` reference — name the source package in prose if it helps. Intervals (`in [ , ]`) apply to the ordered types (`integer`/`numeric`/`Date`/`POSIXct`); sets (`in c(...)`) apply to the ordered and enumerable atomics (`integer`/`numeric`/`Date`/`POSIXct`/`character`/`factor`) — `complex`, `logical`, `raw`, and `any` take no set. `any` asserts nothing about type (length/nullability only). See the [grammar reference](https://dereckscompany.github.io/roxyassert/articles/grammar.html) for the full per-category rules.\n\n### Inline forms\n\n- vector of any length (default) — `(character)`\n- scalar (length 1) — `(scalar\u003ccharacter\u003e)`\n- exactly *n* — `(vector\u003cnumeric, 10\u003e)`\n- length range / at least *n* — `(vector\u003cnumeric, 1..10\u003e)` / `(vector\u003cnumeric, 2..\u003e)`\n- between 1 and 5 (inclusive) — `(scalar\u003cnumeric in [1, 5]\u003e)`\n- greater than 0 and finite — `(scalar\u003cnumeric in ]0, Inf[\u003e)` (an open bracket at a `±Inf` sentinel excludes that infinity; close it — `]0, Inf]` — to allow `Inf`)\n- at most 1 and finite — `(scalar\u003cnumeric in ]-Inf, 1]\u003e)`\n- every element of a vector in a range — `(numeric in [1, 5])`\n- enum, inline set (scalar) — `(scalar\u003ccharacter in c(\"BUY\", \"SELL\")\u003e)`\n- enum, vector from a constant — `(character in ORDER_SIDE)`\n- `NA` elements allowed — `(numeric | NA)`\n- nullable slot — `(scalar\u003cnumeric\u003e?)` ≡ `(scalar\u003cnumeric\u003e | NULL)` (use one, not both)\n- union of types — `(numeric | character)`\n- a count, `20` or `20L` — `(scalar\u003ccount\u003e)`; a positive count — `(scalar\u003ccount in [1, Inf[\u003e)`\n- object of a class — `(class\u003cEngine\u003e)`\n- any (no type check) — `(any)`\n- homogeneous list / list-column — `(list\u003ccharacter\u003e)` / `(list\u003cany\u003e)`\n\nEverything composes. For example `(vector\u003cnumeric in ]0, 1] | NA, 10\u003e)` means: a numeric vector of length 10, every element in `(0, 1]`, `NA` allowed.\n\n### Documenting a type without enforcing it — `@noassert`\n\nA `(type)` both renders in the help page and generates a check. When a parameter is already validated by a hand-written guard, add **`@noassert`** so the type is still documented but no (redundant) check is generated:\n\n```r\n#' @param symbol (scalar\u003ccharacter\u003e) a normalised BASE/QUOTE pair.\n#' @noassert symbol\n```\n\n`@noassert \u003cnames\u003e` exempts the named parameters; a bare `@noassert` makes the whole function (or R6 method) documented-only. Exempted parameters are still parsed and validated — only their code generation is skipped — and naming an undocumented parameter is an error.\n\n### Composite types — nested bullets\n\n`list`, `data.table`, and `data.frame` describe their contents as a **markdown bullet list** beneath the tag. Bullets nest arbitrarily, so you can compose tables inside lists inside lists. Each bullet is `- name (type) description`; a `:` after the `)` is tolerated but optional, and **bold around the name is optional** (`- **name** (type) ...` also works).\n\n```r\n#' @return (list) the query result:\n#' - ok (scalar\u003clogical\u003e) whether the query succeeded.\n#' - rows (data.table) matched rows:\n#'   - id (character) row identifier.\n#'   - value (numeric in [0, Inf[) the (non-negative) value.\n#' - meta (list) pagination metadata:\n#'   - page (scalar\u003cinteger in [1, Inf[\u003e) current page.\n#'   - cursor (scalar\u003ccharacter\u003e?) next-page cursor, or NULL at the end.\n```\n\nA column declared with an **atomic** type is checked for that type, so a column declared `(numeric)` that actually holds a list **fails** — the intended way to catch an accidental list-column. To declare one *on purpose*, type it `list\u003cT\u003e` (each cell a `T`) or `list\u003cany\u003e` (arbitrary cells). `list\u003cT\u003e` is also how you write any homogeneous list — `list\u003cfunction\u003e` (callbacks), `list\u003cclass\u003cModel\u003e\u003e` (model objects):\n\n```r\n#' @return (data.table) rows:\n#' - id (character) identifier.\n#' - tags (list\u003ccharacter\u003e) a list-column; each cell a character vector.\n#' - meta (list\u003cany\u003e) a list-column of arbitrary cells (unchecked).\n```\n\n## Quick example\n\n```r\n#' Submit an order.\n#'\n#' @param symbol (scalar\u003ccharacter\u003e) normalised `BASE/QUOTE` pair.\n#' @param side (scalar\u003ccharacter in c(\"BUY\", \"SELL\")\u003e) order side.\n#' @param quantity (scalar\u003cnumeric in ]0, Inf[\u003e) order size (positive).\n#' @param price_limit (scalar\u003cnumeric in ]0, Inf[\u003e?) limit price; `NULL` for market orders.\n#' @return (data.table) the accepted order:\n#' - order_id (character) exchange order id.\n#' - status (character) order status.\n#' - quantity (numeric) accepted size.\n#' - datetime (POSIXct) acceptance time.\n#' @export\nsubmit_order \u003c- function(symbol, side, quantity, price_limit = NULL) {\n  assert_args_submit_order(symbol, side, quantity, price_limit)   # generated\n  result \u003c- ...\n  return(assert_return_submit_order(result))                      # generated\n}\n```\n\n## Demos\n\n### Deeply nested composition\n\n```r\n#' Run a report.\n#'\n#' @param symbols (character) one or more `BASE/QUOTE` pairs.\n#' @param top_n (scalar\u003cinteger in [1, Inf[\u003e) how many rows to keep.\n#' @return (list) the report:\n#' - status (scalar\u003ccharacter in c(\"ok\", \"partial\", \"failed\")\u003e) outcome.\n#' - result (list) the payload:\n#'   - rows (data.table) ranked rows:\n#'     - symbol (character) the pair.\n#'     - score (numeric in [0, Inf[) rank score.\n#'   - pagination (list) cursor state:\n#'     - page (scalar\u003cinteger in [1, Inf[\u003e) current page.\n#'     - cursor (scalar\u003ccharacter\u003e?) next cursor, or NULL at the end.\n#' - diagnostics (list) run diagnostics:\n#'   - warnings (character) messages (possibly length 0).\n#'   - timings (list) millisecond timings:\n#'     - parse_ms (scalar\u003cnumeric in [0, Inf[\u003e) parse time.\n#'     - run_ms (scalar\u003cnumeric in [0, Inf[\u003e) run time.\n#' @export\nreport \u003c- function(symbols, top_n) {\n  assert_args_report(symbols, top_n)\n  result \u003c- ...\n  return(assert_return_report(result))\n}\n```\n\n### A data.table with mixed column types\n\n```r\n#' Open orders for a symbol.\n#'\n#' @param symbol (scalar\u003ccharacter\u003e) the pair.\n#' @param statuses (character in c(\"OPEN\", \"PARTIALLY_FILLED\")) statuses to include.\n#' @return (data.table) the open orders, newest first:\n#' - order_id (character) exchange id.\n#' - side (factor) BUY or SELL.\n#' - quantity (numeric in ]0, Inf[) remaining size.\n#' - reduce_only (logical) reduce-only flag.\n#' - created_at (POSIXct) submission time.\n#' @export\nopen_orders \u003c- function(symbol, statuses) {\n  assert_args_open_orders(symbol, statuses)\n  result \u003c- ...\n  return(assert_return_open_orders(result))\n}\n```\n\nSee the [Getting started vignette](https://dereckscompany.github.io/roxyassert/articles/demo.html) for a fuller pattern: an abstract class whose every method enforces its inputs and outputs from the docs.\n\n## Asynchronous returns — `promise\u003cT\u003e`\n\nMany APIs return a result either **synchronously** (the value) or **asynchronously** (a [`promises::promise`](https://rstudio.github.io/promises/) that resolves to the *same* value). roxyassert supports this with two forms:\n\n- `promise\u003cT\u003e` — the function always returns a promise resolving to `T`.\n- `T | promise\u003cT\u003e` — the function returns `T` directly **or** a promise resolving to `T` (e.g. a client with a per-instance `async` switch).\n\n**The key idea: roxyassert stays promise-agnostic.** It generates a *plain value-validator* for the resolved type `T` — `assert_return_fn(value)` validates a `T` and returns it. It never emits `then()` or `is.promise()`. **You** decide how to apply that validator to your promise; because the helper is a `function(value) value`, it is *exactly* the callback `promises::then()` wants.\n\n\u003e **Note:** `promise\u003cT\u003e` is most natural on `@return`, but roxyassert doesn't police it — it's allowed anywhere. A helper that takes a promise *input* (say, to attach a callback) is a fine use case; you just apply the generated validator to the resolved value yourself (e.g. inside your own `promises::then(p, ...)`).\n\n### Always-async (`promise\u003cT\u003e`)\n\n```r\n#' Fetch OHLCV bars.\n#'\n#' @param symbol (scalar\u003ccharacter\u003e) the pair.\n#' @return (promise\u003cdata.table\u003e) bars:\n#' - timestamp (POSIXct) candle open time.\n#' - close (numeric in ]0, Inf[) close price.\n#' @export\nohlcv = function(symbol) {\n  assert_args_ohlcv(symbol)\n  # validator is a then()-callback: validates on resolution, rejects on failure\n  return(promises::then(private$.impl_ohlcv(symbol), assert_return_ohlcv))\n}\n```\n\nThe generated `assert_return_ohlcv()` is just the `data.table` + column checks (no promise code). Dropped into `then()`, it validates the resolved table and passes it through; a failing check rejects the returned promise — it never blocks.\n\n### Sync-or-async (`T | promise\u003cT\u003e`)\n\nWhen the same method can return either shape, document the union and branch on the mode you already know (a constructor flag, here `private$.is_async`):\n\n```r\n#' @return (data.table | promise\u003cdata.table\u003e) bars:\n#' - timestamp (POSIXct) candle open time.\n#' - close (numeric in ]0, Inf[) close price.\nohlcv = function(symbol) {\n  assert_args_ohlcv(symbol)\n  result \u003c- private$.impl_ohlcv(symbol)      # a data.table OR a promise of one\n  if (private$.is_async) {\n    return(promises::then(result, assert_return_ohlcv))  # async: validate on resolve\n  }\n  return(assert_return_ohlcv(result))                    # sync: validate now\n}\n```\n\nA tidy way to capture that branch once is a tiny helper (drop it in your package):\n\n```r\nthen_or_now \u003c- function(x, fn, is_async) {\n  if (is_async) return(promises::then(x, fn))\n  return(fn(x))\n}\n\nohlcv = function(symbol) {\n  assert_args_ohlcv(symbol)\n  return(then_or_now(private$.impl_ohlcv(symbol), assert_return_ohlcv, private$.is_async))\n}\n```\n\nIf you don't track the mode explicitly, branch on the value instead — `if (promises::is.promise(result)) promises::then(result, assert_return_ohlcv) else assert_return_ohlcv(result)`.\n\nIn all cases the generated validator is identical; only *your* one-line wiring differs. (See **Known limitations** for `list\u003cpromise\u003cT\u003e\u003e`.)\n\n## Reusable types — `@type`\n\nRepeating the same shape across many functions is tedious and drifts. Declare it once with `@type` and reference it by name anywhere a type appears.\n\nDefine (anywhere — e.g. `R/types.R`):\n\n```r\n#' @type OrderAck (data.table) an order acknowledgement:\n#' - order_id (character) the exchange id.\n#' - status (scalar\u003ccharacter in c(\"FILLED\", \"REJECTED\")\u003e) outcome.\nNULL\n\n#' @type Bps (scalar\u003cnumeric in [0, Inf[\u003e)\nNULL\n```\n\nUse anywhere a type goes:\n\n```r\n#' @param ack (OrderAck) the ack.\n#' @param slippage (Bps) allowed slippage.\n#' @return (promise\u003cOrderAck\u003e) the ack, async.\n#' @return (list\u003cOrderAck\u003e) a batch.\n```\n\nA `@type` resolves at `document()` time by **inline expansion** — the generated checks are identical to writing the shape out, with no runtime cost. A `@type` may build on another (`@type Row (data.table) - score (Score) ...`); cycles, unknown names, and duplicate definitions are reported as errors.\n\nRules: a `@type` defines a *single type* — add `?` / `| NULL` / unions at the **use site**, not the definition; references work bare, nullable, in a union, and inside `list\u003c…\u003e` / `promise\u003c…\u003e`, but not inside `scalar\u003c\u003e` / `vector\u003c\u003e` (define a scalar alias directly, like `Bps`). Types are package-local.\n\n### Deriving one type from another — `extends`, override, `pick` / `omit`\n\nA record type can be **built from another** instead of being copied — the same idea as TypeScript's `interface B extends A` and `Pick` / `Omit`.\n\n\u003e **One rule for the parentheses:** the parentheses always mean *\"this is a type.\"* `extends` lives **inside** them, and the kind (`data.table` / `list` / `data.frame`) is **inherited from the base** — never restated. There is no second syntax to remember.\n\n**Inherit and add columns** — write `extends Base`, then list only the *new* columns as bullets:\n\n```r\n#' @type Order (data.table):\n#' - order_id (character) the exchange id.\n#' - status (character in unlist(ORDER_STATUS)) lifecycle state.\n#' ... (defined once)\n\n#' @type OrderModifyResult (extends Order):\n#' - order_id_old (character) the id before the change.\n#' - order_id_new (character) the id after the change.\n```\n\n`OrderModifyResult` is every `Order` column **plus** the two new ones, in order — with no duplicated definition to drift.\n\n**Override a column** — redeclare it by name; the redeclaration replaces the inherited one *in place* (its position is kept). This is a trusted full replacement: roxyassert is a generator, not a type checker, so it does not verify the new type is a narrowing of the old.\n\n```r\n#' @type ClosedOrder (extends Order):\n#' - status (character in unlist(c(\"FILLED\", \"CANCELLED\", \"EXPIRED\")))\n```\n\n**Multiple inheritance** — list several bases (all must be the same kind). A column defined by more than one base is an **error unless you resolve it** — either redeclare it (the override wins) or drop it with `pick`/`omit`:\n\n```r\n#' @type MarginOrder (extends Order, MarginFields):\n```\n\n**Subset with `pick` / `omit`** (mutually exclusive; every named column must exist in a base):\n\n```r\n#' @type OrderSummary (extends Order pick order_id, status):\n#' @type PublicOrder  (extends Order omit order_id_client):\n```\n\n`pick`/`omit` act on the **inherited** columns; redeclaring a column you just removed is an error. The keywords `extends` / `pick` / `omit` cannot be used as `@type` names, and listing a base or column twice is an error.\n\nAll of this works **inline** too — define the shape right in a `@param` / `@return`, no name needed:\n\n```r\n#' @param legs (extends Order pick order_id, status) the legs.\n#' @return (extends Order):\n#' - order_id_old (character) prior id.\n#' - order_id_new (character) new id.\n```\n\nDerivation is pure `document()`-time list algebra over the resolved columns: it reuses the existing `@type` registry, cycle / unknown-base detection, and lowering, with no runtime cost. Two derivations are deliberately **out of scope** for now (each is a separate, larger feature): *renaming* a column (use `omit` + re-add) and *generic / parameterized* types (`Paged\u003cT\u003e`).\n\n## Standalone, exportable asserts — `@genassert` / `@exportassert`\n\nBy default a `@type` only materialises as **inlined** checks inside a `@param`/`@return` that references it — so a shape used by no function (or one built internally and never passed as an argument) has no callable validator. Two tags lift that:\n\n- **`@genassert`** — on a block defining one or more `@type`, emit a standalone `assert_type_\u003cName\u003e(value)` for *every* `@type` in that block, even if nothing references it.\n- **`@exportassert`** — export the asserts generated **from that block** so other packages can call them. Works on a function or R6 method block too (exporting its `assert_args_*` / `assert_return_*`). Distinct from roxygen2's `@export`, which exports the documented object, not its assert helpers.\n\n``` r\n#' @type OrderAck (data.table) an order acknowledgement:\n#' - order_id (character) the exchange id.\n#' - status (scalar\u003ccharacter in c(\"FILLED\", \"REJECTED\")\u003e) outcome.\n#' @genassert      # also emit a callable assert_type_OrderAck()\n#' @exportassert   # ...and export it for downstream packages\n#' @name shapes\nNULL\n```\n\nBoth are **bare, whole-block flags**. They are deliberately *not* selective like `@noassert` (which exempts named *parameters* of one function): a `@type` is its own definition, so for per-type control put a `@type` in its own block — a stray name list on either tag is an error, not silently ignored.\n\n`@exportassert` appends a managed `export(...)` block to the package `NAMESPACE` **and** writes a `\\keyword{internal}` Rd documenting the exported helpers (R requires exported objects to be documented, so this keeps `R CMD check` clean). Both are rewritten deterministically on each `document()`. Note the exported names are roxyassert-generated (including R6 `assert_args_\u003cClass\u003e__\u003cmethod\u003e`): they become part of your package's public namespace, though `\\keyword{internal}` keeps them out of the help index.\n\n## Generated functions — conventions\n\n- **Two helpers, each optional.** `assert_args_\u003cfn\u003e` is emitted only if the function has at least one typed `@param`; `assert_return_\u003cfn\u003e` only if `@return` carries a parseable type.\n- **Explicit arguments.** `assert_args_\u003cfn\u003e(arg1, arg2, ...)` takes the documented parameters by name, in declaration order, so the contract is visible at the call site.\n- **Returns are passed through.** `assert_return_\u003cfn\u003e(value)` validates and returns `value` unchanged, so you can write `return(assert_return_fn(x))` or keep your own `invisible()`.\n- **`invisible()` is irrelevant** — generation depends only on a typed `@return`, not on how the value is returned.\n- **R6 methods are scoped by class.** Method `submit` on class `Engine` generates `assert_args_Engine__submit` (double-underscore separator), so two classes sharing a method name never collide; clashes abort generation rather than overwrite.\n- **Internal, undocumented, committed.** Generated helpers are not exported and carry no `.Rd`; they live in a single `R/contracts-generated.R` (with a do-not-edit banner) that you commit like `NAMESPACE`. Each package generates and uses its own.\n- **Using them is optional.** The helpers are generated for you regardless; *calling* them is opt-in. Adopt them in one function, a few, or all — a function with no typed tags is simply left alone.\n\n## Why annotations, not in-code types\n\nPackages like [`typed`](https://github.com/moodymudskipper/typed) declare types inside the function body with a custom operator. That works and checks at runtime, but it changes how every function is written. `roxyassert` keeps your R code exactly as it is and treats the documentation you already write as the contract — and because the checks are generated *for* you, you never write a validation function by hand again, even if you choose not to call every one.\n\n## Known limitations\n\nThings deliberately not supported yet — each is rejected with a clear error, and we'll revisit any of them if there's demand. (The grammar reference's *Non-goals* section is the full formal list.)\n\n- **A list of un-resolved promises** (`list\u003cpromise\u003cT\u003e\u003e`) — each element would be an unresolved promise that can't be checked synchronously, and roxyassert never emits per-element `then()` wiring. Await them (e.g. `promises::promise_all`) and annotate the result as `promise\u003clist\u003cT\u003e\u003e`.\n- **Refining a named type at the use site** — once `@type Price (scalar\u003cnumeric\u003e)` is defined you can't write `(Price in [0, 1])`; the refinement must live in the definition (define a second `@type`, or write the refined type inline). Named types are also package-local (no cross-package reuse yet).\n- **A named type shows as its bare name in rendered docs** — `@type` is resolved only for the generated checks, so a help page / pkgdown site shows `(OrderAck)` verbatim, not its expanded shape, with no auto-generated topic or link. To give a type a help page, document its `@type` block like any object (add a title/description and `@name`); reference it as a markdown link (`[OrderAck]`) for a clickable cross-reference. (Auto-generated type pages and links may come later.)\n- **A `class\u003cName\u003e` name is not verified at `document()` time** — roxyassert emits `assert_class(x, \"Name\")` blindly, so a typo such as `class\u003cDuraton\u003e` generates without complaint and fails only at runtime.\n- **An `extends` override is not checked for compatibility** — redeclaring an inherited column replaces it with whatever you write; roxyassert has no subtype lattice, so it does not verify the override narrows the base column (it trusts the author). Column *renaming* and *generic / parameterized* types (`Paged\u003cT\u003e`) are likewise not supported yet.\n\n## Status\n\nEarly development — the annotation grammar and generation conventions documented here are the design under active implementation.\n\n## License\n\nMIT © Dereck Mezquita. See [LICENSE](LICENSE) for details.\n\n## Citation\n\n```r\ncitation(\"roxyassert\")\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdereckscompany%2Froxyassert","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdereckscompany%2Froxyassert","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdereckscompany%2Froxyassert/lists"}