# Authoring stubs

This page is for contributors who edit the shipped stub corpus, and for anyone writing a project's
own stubs. The corpus lives in the repository's top-level `types/` directory: about 1,700 typed
declarations in 77 `.Rtypes` files, plus an `.exports` [manifest](#export-manifests) for every
namespace R ships and for every CRAN package in the benchmark dependency set.

Shipping a package's types means dropping `types/<namespace>.Rtypes` (and its manifest) into that
directory and nothing else: `crates/semantics/build.rs` embeds every file it finds there. R's own
packages load first, in search-path order, then the CRAN namespaces alphabetically, then ry's
standard library. Every namespace that is not one of R's own packages or the standard library is
[conditional](#conditional-namespaces), so its names exist only in projects that use the package.
The typed declarations of R's packages that R does not attach by default (`tools`, `parallel`,
`compiler`, `grid`, `splines`, `stats4`, `tcltk`) are gated the same way, while their export
manifests always answer `pkg::name`. The typing rules the stubs feed are in the
[typing reference](/reference/type-system.md), and the user-facing introduction is
[Stubs](/development/authoring-stubs.md).

## Why stubs exist

The checker has only a small kernel built in: the operators, plus `c` and `list`. Everything else in
the standard library, from `T` and `pi` to `length`, `paste`, and `seq_len`, gets its type from a
stub: a declaration-only description of a package, loaded as an immutable input.

## Stub format

A stub file has the extension `.Rtypes` ("R type information"), which echoes R's `.Rd` without
colliding with `.Rd`, `.Rmd`, `.Rda`, or `.RData`. Every line that is not blank or a comment is one declaration:

```
name : <type-expr>
```

The type expression uses exactly the `#:` annotation grammar, read by the same parser, so there is
no second notation to keep in sync. A stub file has nowhere to write a function body, which makes
"declaration-only" a structural fact, as it is for TypeScript's `.d.ts` and Python's `.pyi` files.
Blank lines and `#` comments, whole-line or trailing, are ignored, with one exception: consecutive
`#'` lines directly above a declaration are its documentation, which hover and completion show
before the signature. A later source that declares the same name replaces that documentation, or
clears it when its own declaration has none.

```
#' Applies `f` to each element.
List.map : <A, B> fn(list: list[A], f: fn(A) -> B) -> list[B]
```

A declared `name` can be an R identifier, an infix operator such as `%in%` (or a project's own
`%||%`), or an S3 operator method such as `+.Date`, `-.POSIXct`, `Arith.difftime`, `Compare.Date`,
or `Ops.gg`, or a subset method such as `[.xts`, `[[.Date`, or `$.request`, which types `x[i]`,
`x[[i]]`, or `x$name` on that class. An object whose members are reached with `$`, such as a
polars frame or another environment-backed object, declares each member as `Class$member`:
`polars_data_frame$select : fn(...: Any) -> polars_data_frame` types `frame$select(...)`, and
`polars_data_frame$height : integer` types a property. A member the stub does not list stays
`Unknown`. The operator spellings are how a stub gives a class arithmetic
or comparison, and
[operator methods on a class](/reference/type-system.md#operator-methods-on-a-class) describes the
dispatch order. The suffix is the nominal type's own name, so a project stub line
`+.ggplot : fn(e1: ggplot, e2: Any) -> ggplot` is all a `+`-based DSL needs.

```
# base.Rtypes, a fragment

# plain value bindings
T : logical
F : logical
pi : double

# a fixed-arity function signature
length : fn(x: Any) -> integer

# a parametric higher-order function, a real generic scheme rather than Any
lapply : <T, U> fn(X: list[named: T], FUN: fn(T) -> U, ...: Any) -> list[named: U]
```

### Opaque types

Besides values, a stub can declare a type. A line `@type NAME` declares an *opaque nominal type*: a
named type with no inspectable representation, for standard-library values the type grammar cannot
describe structurally, such as `factor`, `connection`, or `Date`.

An opaque nominal is compatible only with itself, and a value of it can only come from a function
declared to return it. So `Sys.Date()` is a `Date`, and passing it where a `factor` is expected is a
type error. The functions that *consume* such values usually keep `Any` parameters, because R
coerces liberally; the nominals exist to tighten return types. Using `$`, `[`, or `[[` on an opaque
nominal gives `Unknown` rather than an error, so value-dependent access stays usable, and strict
mode flags each such access. The [typing reference](/reference/type-system.md#nominal-types) has the
full rules.

An opaque type may take type parameters, as `@type Dict<K, V>` does in `types/std.Rtypes`. Its
parameters are invariant, so a `Dict<character, double>` is neither a `Dict<character, Any>` nor
the other way around, and `Dict<K, V>` in a signature binds both parameters at a call.

A stub can also declare a structural alias, written exactly like a project's `#: @alias`:

```
@alias Result<T, E> {:Ok(T) | :Err(E)}
```

The alias then works in every annotation, as `Result<integer, :NotFound>`.

A stub type may also declare a representation, written `@type NAME {REPRESENTATION}` exactly like
a project's `#: @type`. `types/base.Rtypes` declares `@type data.frame {list[named: Unknown]}`, so
a data frame passes wherever a named list is expected, as `lapply(frame, f)` needs, and `$` and
`[[` read its columns as `Unknown`, which strict mode reports at the read. Unlike a project's own nominal type, a mismatch against a stub type does
not suggest `@new`, because its values come from their own constructors such as `read.csv()`.
Declare the class's operator and `[` methods beside it when R keeps or changes the class, as
`[.data.frame` and `Arith.data.frame` do: dispatch looks at the declared class before the
representation.

A type name may contain an interior dot, as `data.frame` does, and a project's own `@type` or
`@alias` of the same name shadows the stub's type. A type declaration that does not load, such as a
`@type` line whose name is not a plain identifier or whose representation does not parse, is
reported as an error on its own line, and its name stays unknown to the rest of the file.

### Data-masking functions

A declaration may put the `@masked` attribute in front of its function type:

```
summarise : @masked fn(.data: Any, ...: Any) -> Any
```

This marks a function that evaluates the arguments it collects in `...` inside a data mask, the
dplyr style of non-standard evaluation. A bare name in one of those positions is treated as a
column reference and raises no unresolved-name warning, while an argument that matches a declared
formal resolves normally. `@masked` requires a variadic function type. The typing reference
specifies the details under
[data frames and non-standard evaluation](/reference/type-system.md#data-frames-and-non-standard-evaluation).

### Pure functions

The `@pure` attribute declares that calling the function has no effect beyond returning a value,
whatever its arguments:

```
paste0 : @pure fn(...: Any, [sep]: character, [collapse]: character | NULL, [recycle0]: logical) -> character
```

Under strict mode, a constant at the top level of a package file may call only pure functions (see
[a package file's top level](/reference/type-system.md#a-package-files-top-level)). Purity belongs to
the name: marking any candidate of an overload set marks the name, and a later source that redeclares
the name without the marker clears it. `@pure` and `@masked` may appear in either order. Mark a
function only when no argument can make it read or change state outside its result: `cat`,
`Sys.time` and `options` are not pure, and neither is a function that calls back into an argument it
was given, such as `lapply`.

## Overloads and generics

Declaring the same name more than once in one file builds an ordered *overload set*, with each
further declaration appending a candidate:

```
sum : fn(...: integer[] | logical[], [na.rm]: logical) -> integer
sum : fn(...: double[] | integer[] | logical[], [na.rm]: logical) -> double
sum : fn(...: difftime, [na.rm]: logical) -> difftime
sum : fn(...: data.frame | frame | matrix, [na.rm]: logical) -> double
sum : fn(...: complex[] | double[] | integer[] | logical[], [na.rm]: logical) -> complex
```

A call commits to the first candidate that accepts its arguments, so `sum(1L, 2L)` is `integer`,
`sum(1L, 2.5)` is `double`, and `sum("a")` is an error, as it is in R. The typing reference
specifies the selection rules under [overload sets](/reference/type-system.md#overload-sets).

Order each set from most specific to most general, and end it with the most general candidate,
because that is also what the name resolves to when it is used without being called. Write the
candidates so that together they accept exactly what R accepts. An `Any` fallback is the escape hatch
for a function whose accepted inputs no candidate list can describe, but it also accepts every input
R rejects, so `sum` and the other numeric reductions do without one. A wrapper such as
`function(x) sum(x)` then leaves its parameter `Unknown`, which is unchecked, rather than generic.
A project override that redeclares a name replaces the whole set, so an override that wants
overloads must declare all of them itself.

Notice where the rest parameter sits in those three lines. A parameter declared before `...` can be
filled by position, so `[na.rm]` has to come after it, or `sum(1L, 2L)` would bind `1L` to `na.rm`
and fail.

The editor shows the whole set. Signature help on a call lists every candidate, with the committed
one active and filled in with that call's types, and the others showing their declared type
parameters. An incomplete call matches no candidate yet but still lists the set, which is exactly
when the list helps most. Hover on the name shows the committed candidate's signature (or, when the
name is not being called, the last declaration's) plus a `(+N overloads)` note, and
go-to-definition jumps to the first declaration.

These rules shape the current corpus:

- **A genuinely parametric function gets a real generic.** A higher-order helper whose result
  depends on the *type* of its argument, such as `lapply`, `Reduce`, `print`, `invisible`,
  `setNames`, or `suppressWarnings`, is written with `<T> fn(...)` binders and keeps a precise
  polymorphic scheme. Element-preserving vector functions (`rev`, `sort`, `unique`, `head`, `tail`,
  `sample`, `rep`, and the set operations) use `<T> fn(x: T[]) -> T[]`, where the element parameter
  is limited to atomic types. That signature is usually the first candidate of an overload set whose
  list form threads `list[T]` through and whose `Any` fallback covers the rest.
- **A function that hands back the shape it was given declares each shape, narrowest first.** R's
  selection and reordering operations go through `[`, which preserves both the atomic type and the
  names, so the declarations say so. `Filter` takes `T[]`, then `list[named: T]`, then `list[T]`,
  and each candidate returns its own shape; a single `list[T]` return for all three would make
  `Filter(f, c(1, 2)) + 1` a type error on code R runs. `lapply`, `rev`, `unique`, `head`, and
  `tail` carry the same `list[named: T]` candidate ahead of their plain list one, so a named list in
  is a named list out. A fixed-shape input still coerces to a name-keyed list on the way in, though,
  so a field read off the result is `T | NULL`: the operation might drop a name, and the exact field
  types are not carried through.
- **A type-preserving reduction gets an overload set.** Reductions whose result follows R's coercion
  order rather than one input's type (`sum`, `min`, `max`, `range`, `pmin`, `pmax`, `cumsum`,
  `cummax`, `cummin`, and `abs` with its integer and double split) declare one candidate per atomic
  family, plus the `Any` fallback.
- **A value-dependent result falls back to `Any`.** When a function's return type depends on an
  argument's *value* or on how many arguments it gets, as with `seq`, `is`, and `grep(value =)`, it
  returns `Any` rather than a falsely precise type. A call then yields `Any` and never a spurious type
  or arity error, and the name still resolves.

## Override precedence

A project overrides or extends the shipped stubs with its own `.Rtypes` files under
`<project>/stubs/`. The loader folds the project's files over the shipped corpus in sorted path
order, so a project declaration replaces the shipped declaration of the same name. That is how a
project corrects a return type or adds a name the corpus lacks. A missing directory is ignored, and a
malformed line is dropped, because one bad line must never block analysis.

A project stub file's name declares its namespace. `stubs/arrow.Rtypes` declares the namespace
`arrow`, so its declarations type bare reads and also validate qualified ones: with only
`read_parquet` declared, `arrow::read_parquet` has the stub's type, while `arrow::write_parquet`
warns that it is not exported by `arrow`. This is how a project types a third-party package today,
by hand. Exports are tracked per declaration, not
per winning declaration, so overriding a shipped name's type does not remove the name from its
shipped namespace, and `stats::sd` stays valid under an `sd` override. Hover shows a name's origin as
the namespace of its winning declaration.

`ry check` reports every dropped override declaration as a `stub` error on its own line: a line that fails to parse, a declaration naming a type that does not
resolve, and a `@type` line whose name is not an identifier. An unreadable override file is an IO
failure. While a `.Rtypes` file is open, the editor shows the same problems as diagnostics.

## Export manifests

Base R alone exports about 1,400 names, and the typed corpus deliberately covers only a curated
subset. On its own, that would make every real export outside the subset warn as unresolved,
because the checker could not tell `recover`, a real `utils` export, from a typo.

Export manifests close the gap. Every shipped namespace's `.Rtypes` file is paired with a
`types/<namespace>.exports` file listing, one per line, every name the namespace really exports.
`scripts/export-manifests.R` generates them from a live R session and records the R version in the
header, so refreshing them means rerunning the script against a newer R. The script refuses to
overwrite a manifest recorded against a newer R than the running one, because regenerating on an
older R would silently drop the names the newer version added, and every use of one would become a
false `unresolved` finding.

A manifest does not need an `.Rtypes` file beside it. Some namespaces, such as `tibble`, `tidyr`,
`forcats`, `magrittr`, `glue`, `scales`, `knitr` and `tidyverse`, have a manifest and no typed
declarations at all, so every name from them is `Unknown`. What they contribute is a *known export set*. Attaching a package whose exports the
checker cannot list turns off unresolved-name detection for the whole project, as the
[typing reference](/reference/type-system.md#naming-and-scoping) specifies, so without its manifest a
single `library(stringr)` would silence every typo. Adding typed declarations for any of these packages later is purely additive:
the manifest stays the export set, and the `.Rtypes` file supplies the types.

A manifest name is a known global that makes the weakest possible claim:

- A bare or `pkg::`-qualified read of a manifest name always resolves, with neither an unresolved nor
  a not-exported warning. It has the type of its `.Rtypes` declaration if there is one, and
  `Unknown` otherwise. Precision is the typed corpus's job; the manifest's job is to stay quiet
  about names that really exist.
- Completion offers manifest-only names without decoration, since there is no type to show. A
  non-syntactic name is skipped when completing a bare name and backtick-quoted after `pkg::`, since
  inserting it raw would change the syntax. Typo suggestions draw on manifest names too.
- The unit suite pins the pairing in both directions: every value declaration in a shipped
  `.Rtypes` file must appear in its namespace's manifest, and a stubbed non-export is a hard
  failure. That catches a declaration added to the wrong file, and names R has moved between
  namespaces, such as `traceback` and `standardGeneric`, which live in `base` rather than `utils` or
  `methods`. A conditional namespace may additionally override a base name, as data.table's
  class-preserving `merge` does.

The manifests mirror the three ways R exposes a namespace:

- **Attached by default.** `base`, `stats`, `utils`, `graphics`, `grDevices`, `methods`, and
  `datasets` are visible everywhere without qualification, so their names always resolve.
  `datasets` is special: its objects are lazy data rather than namespace exports, so its manifest is
  built from the search-path listing. The famous data frames are typed in `datasets.Rtypes`, which
  makes `iris` a `data.frame` rather than `Unknown`.
- **Shipped with R but not attached.** `tools`, `parallel`, `compiler`, `grid`, `splines`, `stats4`,
  and `tcltk` validate their `pkg::` reads in every project, so `tools::file_ext(path)` needs no
  `library(tools)`, exactly as in R. A bare read resolves only once the project attaches or declares
  the package.
- **Conditional.** `data.table`, `dplyr`, `ggplot2`, `testthat`, and every manifest-only CRAN
  namespace switch on their manifest and their stubs together, as described under
  [conditional namespaces](#conditional-namespaces). While a package is inactive, its names warn
  whether they are qualified or not. A meta-package activates the packages it attaches:
  `library(tidyverse)` exports almost nothing itself, but it attaches `dplyr`, `ggplot2`, `tibble`,
  `tidyr`, `readr`, `purrr`, `stringr`, `forcats`, and `lubridate`, so those nine switch on with it.
  That is also how such a project gets dplyr's and ggplot2's typed declarations instead of a
  manifest's `Unknown`. The membership is easy to check, because the generator script prints what a
  live `library(tidyverse)` attaches.

The manifests vendor R's export lists, so analysis never needs an R installation. They are data,
not types, and a manifest name costs nothing at check time until some code actually reads it.

## The base environment

Everything that can be written as a type scheme has one source of truth, the stubs, plus a small
hardcoded kernel:

- **Value bindings**: `T` and `F` become a monomorphic `Scalar(Logical)`, and `pi` becomes a
  `Scalar(Double)`.
- **Base functions**: each becomes a type scheme, seeded into the template environment next to the
  built-in kernel.

### What stays hardcoded, and why

The operators, `c`, and `list` are algorithms, not types. The operators need the numeric promotion
lattice and the comparison families, `c` needs variadic atomic promotion that drops `NULL`, and
`list` needs to synthesize records and tuples. The `#:` grammar can express none of that, so these
stay built in. The one-source-of-truth goal holds for everything a type can describe, and the kernel
is a deliberate exception, not an oversight.

### What the stub grammar cannot say yet

A rest parameter is written `...: TYPE`, as in `fn(...: Any) -> character`, and parameter and field
names may contain an interior `.`, as in `na.rm`. Each gap below limits how precise the affected
declarations can be:

- **A trailing dot in a parameter name**, as in `stop(call. =)`: parameter names allow only an
  interior dot.
- **An empty type for a function that never returns**, such as `stop` and `q`: without one, a `NULL`
  return would poison the join in `x <- if (ok) v else stop(...)`, so these stay `Any`.
- **A return that mirrors the argument's shape**, as in `rev(opts)$timeout` on a fixed-shape `opts`:
  a declaration cannot say "the same record back", so selection and reordering return a name-keyed
  `list[named: T]`, and a field read off it is `T | NULL` rather than the field's own type.
- **A nullable result under a member-wise operator**, as for `names`, `dim`, and `nrow`: a
  `T | NULL` return would be a false report on `1:nrow(df)` and `for (nm in names(x))` until flow
  narrowing or NULL-tolerant joins exist, so these return `Any`.

Type-preserving reductions declare [overload sets](#overloads-and-generics), and element-preserving
functions declare generic `T[]` signatures. A function serves a callback interface whenever it
accepts every call shape the interface promises, so a callback-style stub can declare its real
signature, spare optional formals included. What remains a scalar claim is a function whose result's
atomic type differs from its input's while its shape follows the input, such as `nchar` or
`toupper`.

## Loading and namespacing

Stubs are immutable, set-once inputs, the analogue of rust-analyzer's `Durability::HIGH`: they are
loaded, parsed, and interned once, and no ordinary edit invalidates them. There is one deliberate
exception. Assembling the library also reads the package-metadata input, which switches the
[conditional namespaces](#conditional-namespaces) on and off, so when a project starts or stops
declaring or attaching such a package, the library is rebuilt. That is rare, and worth the full
refresh.

The `StubLibrary` is a handful of flat, string-keyed tables, built once and folded over every
source:

- `schemes` maps a name to its ordered list of schemes, one per declaration (see
  [overload sets](#overloads-and-generics)).
- `nominals` is the set of `@type` names.
- `masked` maps each `@masked` name to the formals declared before its `...`.
- `pure` holds each `@pure` name, and `pure_by_namespace` the same names per declaring namespace,
  for `pkg::name` calls.
- `exports_by_namespace` maps a namespace to the names it declares.
- `declarations` maps a name to the source index and range of its winning declaration. The declaring
  namespace is derived from the source index rather than stored, so the two can never disagree.
- `known_exports` is the union of every manifest's names.

Typing is not partitioned by namespace. The per-namespace export table answers `pkg::name`
validation next to the flat scheme map, which is why an override that wins a name's type never
un-exports it from the namespace that declared it.

- Every shipped `.Rtypes` file is folded into the one flat map in file order. A later source that
  redeclares a name replaces its whole entry, the same rule that governs project overrides, while
  repeated declarations within one source build the name's overload set. The seven default-attached
  namespaces therefore join the base scope together, and a conditional namespace joins the fold only
  while it is active.
- The flat map seeds the per-document inference template, so every stub name resolves as a bare
  global no matter which shipped file declared it. A name's *last* scheme becomes its plain
  environment binding, because the corpus orders candidates from specific to general. A name with
  several schemes also registers its overload set for call-site selection.
- `pkg::name` resolves against the same flat map, so a qualified read has exactly the type of the
  bare name. The per-namespace export table powers the warnings for an unknown namespace and for a
  name a namespace does not export, which the typing reference specifies under
  [namespace access](/reference/type-system.md#namespace-access).

### Conditional namespaces

Some shipped namespaces describe packages R does not attach by default: `data.table`, `dplyr`,
`ggplot2`, `testthat` and the benchmark packages come with types, and the
[manifest-only](#export-manifests) CRAN set comes without. Folding them in unconditionally would let `fread`, `mutate`, or `str_to_upper` resolve in a
project that never uses them, stealing a typo warning. So a conditional namespace joins the library
only when the project uses the package, and any one of these signals is enough:

- A `DESCRIPTION` dependency field (`Depends`, `Imports`, `Suggests`, or `Enhances`) names it, or a
  `NAMESPACE` `import` or `importFrom` names it as a source.
- A project file attaches or loads it with `library()`, `require()`, `requireNamespace()`, or
  `loadNamespace()`, with a literal name or string as the package argument, anywhere in the file.
  This is the only signal a script workspace has, since only packages carry metadata files.
- The project ships its own `stubs/<pkg>.Rtypes` override for the namespace. Writing one is the
  clearest possible statement that the project uses the package, and the override then folds over
  the shipped declarations as usual.
- A meta-package that attaches it is active: `library(tidyverse)` activates the nine packages it
  attaches, because attaching is how R makes their names reachable.

`testthat` is in the set because a package's `tests/` directory is often a third of its source,
and without a known export set, attaching testthat would silence typos in the package's own
functions. With the stubs, `expect_eqaul` is reported, along with a suggestion. An expectation's `object` parameter is `Any`, because an
expectation compares whatever the code under test produced, and each `expect_*` returns its
`object`, which is the value testthat passes through invisibly.

While a namespace is inactive, it simply is not there: its names warn as unresolved, a `pkg::` read
behaves like a read from any package without stubs, and its `@type` nominals are unknown type names.
The hosts keep the attach facts current for each synced file. The language server rescans only the
changed document and fills in the rest of the workspace during idle priming, so switching a
namespace on never costs a whole-project sweep on a keystroke.

### NAMESPACE import validation

`ry check` and the language server both validate a package's `NAMESPACE` file against the loaded
stubs. An `importFrom(pkg, name)` whose namespace the corpus knows (shipped or project) but which
does not export `name` is an error at the import:

```text
`medain` is not exported by `stats`, so this package will not load.
```

R refuses to load a package with such an import, so the code cannot run at all; it is the same rule
as an `export()` of a name the package never defines. A namespace without stubs is not checked,
because stubs are the only export source the checker has. Resolution itself is unchanged, and a
stubbed name resolves bare with or without an import.

The check reads a string-keyed export table built once from the loaded corpus, not the engine's
interner, which is what lets an open `NAMESPACE` buffer be validated live in isolation.

### Incremental hygiene

The `StubSources` input is set once, and the library derived from it changes only when a
conditional namespace switches on or off, so an ordinary edit never invalidates a query that reads
the stubs, and a document pays inference time only for the stub names it uses.

A package binding that shadows a stub name is an ordinary structural edit. It changes which
definition wins that name in the package symbol index, and that change flows through the per-symbol
interface to exactly the files that reference the name, the same path as any definition appearing
or disappearing. A stub value and an unrelated user type that share a name, such as the value `T` and
`#: @type T`, live in the value and type namespaces respectively, and never interact.

## Scope: model only what is statically reasonable

The corpus models:

- fixed-arity signatures;
- scalar constants;
- common vectorized atomic operations with a stable result shape;
- prenex rank-1 generics;
- nominal type and class declarations.

It deliberately does not model the following. For these, declare the part of the signature that is
statically stable and stop, or use `Any`:

- non-standard evaluation and data masking, such as `subset`, `with`, and formulas;
- tricks that forward `...`;
- the dispatch dynamics of S3, S4, R5, and R6, such as `UseMethod` and `NextMethod`;
- `do.call`, `match.arg`, `match.call`, `sys.call`, and `Recall`;
- reflective environment access, such as `environment`, `assign`, `get`, and `eval`;
- partial argument matching;
- replacement functions, such as `` `names<-` ``.

The rule of thumb: if a function's return type is not a static function of its argument types, but
depends on a runtime value or class, leave the declaration out or give it `Any`.

### Corpus compromise vocabulary

The corpus optimizes for two things, in this order: no false errors on idiomatic calls, and then the
most precise return type that is still sound. The compromises that keeps forcing are named once in
the header of `base.Rtypes` and referred to by name in individual entries:

- **Any-param**: a parameter is `Any` even though R documents a type for it, because R itself coerces
  arguments and `nchar(42)` is legal, so a declared type would reject calls R accepts. The checker's
  own coercions reduce how often this is needed: an `integer` widens to `double` at a parameter, a
  whole-number double literal counts as an `integer`, and a scalar coerces into a vector position.
  The `T[]` generics restore the rest of the precision.
- **scalar-claim**: an elementwise function declares its scalar result (`character` rather than
  `character[]`). A scalar claim coerces into every vector position and so can never cause a false
  positive downstream, whereas a vector claim would break `if (grepl(...))`.
- **type-preserving**: the result's type follows the input's, as for `sum`, `sort`, and `rev`.
  Element-preserving vector functions declare `<T> fn(x: T[]) -> T[]`, and reductions whose result
  follows R's coercion order declare one [overload candidate](#overloads-and-generics) per atomic
  family. Every set ends with an `Any` fallback, which is also what a call selects while an
  argument's type is still undetermined; because a fallback taking `Any` binds nothing, it never
  narrows the caller.
- **NULL-hybrid**: the result is `T` or `NULL` depending on the runtime value, as for `names`, `dim`,
  and `nrow`. These return `Any`, for the reason the gaps table above gives.
- **named-formals**: declare the named formals whose types are worth checking, such as `paste`'s
  `sep` and `collapse` or `read.csv`'s `header` and `sep`. An undeclared named argument to a
  variadic function is not an error: the rest parameter absorbs it and checks it against the rest
  parameter's element type, just as R would accept it. So an open space of named arguments works
  with `...: Any`, as for `read.csv`'s pass-through to `read.table` and `lm`'s fitting controls.
  Declaring a formal buys a typed check; leaving it out buys pass-through.

## Worked example

Here is a fragment of the base stubs:

```
T : logical
F : logical
pi : double
length : fn(x: Any) -> integer
nchar : fn(x: Any, [type]: character, [allowNA]: logical, [keepNA]: logical) -> integer
seq_len : fn(length.out: Any) -> integer[]
```

Each line produces one scheme, which hover renders back in the same notation.

`nchar`'s subject and `seq_len`'s count are Any-params (see the
[compromise vocabulary](#corpus-compromise-vocabulary)), so `nchar(42)` and `seq_len(10)` check
clean while the return types stay precise.

This R type-checks against those stubs plus the built-in kernel:

```r
n    <- length(c(1L, 2L, 3L))  # integer, from the stub scheme
half <- pi / 2                 # double, from the operator kernel
flag <- T                      # logical, from the stub value binding
```

`paste`'s variadic arguments and its `sep` and `collapse` named formals can be declared directly,
just like `seq_len`'s dotted `length.out`:

```
paste : fn(...: Any, [sep]: character, [collapse]: character | NULL, [recycle0]: logical) -> character
```

Where the rest parameter sits among the named ones is part of the signature, because it decides
which parameters a positional argument can still fill.

## What is not built yet

- **Third-party packages beyond the shipped set.** Any other CRAN package's names are `Unknown`,
  with an unknown-namespace warning, unless the project writes its own `stubs/<pkg>.Rtypes`. The
  intended answer is generating stubs by introspecting the installed library, because which
  packages and versions are installed is a property of the project, not of ry.
- **Keying the corpus to an R version.** The shipped corpus describes one snapshot of base R rather
  than whichever version a project runs.
- **A name-to-scheme table per namespace**, instead of one flat fold. Today two shipped packages
  cannot declare the same name with different types.
- **`library(pkg)` attaching a namespace on demand** for a package outside the shipped set.
- **Gating bare third-party names on `NAMESPACE` imports.** That would be R-correct, and whether to
  do it is a deliberate strictness decision; today's flat fold is intentionally permissive.
