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 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, 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, and the user-facing introduction is
Stubs.
Why stubs exist
Section titled “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
Section titled “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 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 bindingsT : logicalF : logicalpi : double
# a fixed-arity function signaturelength : fn(x: Any) -> integer
# a parametric higher-order function, a real generic scheme rather than Anylapply : <T, U> fn(X: list[named: T], FUN: fn(T) -> U, ...: Any) -> list[named: U]Opaque types
Section titled “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 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
Section titled “Data-masking functions”A declaration may put the @masked attribute in front of its function type:
summarise : @masked fn(.data: Any, ...: Any) -> AnyThis 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.
Pure functions
Section titled “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) -> characterUnder strict mode, a constant at the top level of a package file may call only pure functions (see
a package file’s 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
Section titled “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) -> integersum : fn(...: double[] | integer[] | logical[], [na.rm]: logical) -> doublesum : fn(...: difftime, [na.rm]: logical) -> difftimesum : fn(...: data.frame | frame | matrix, [na.rm]: logical) -> doublesum : fn(...: complex[] | double[] | integer[] | logical[], [na.rm]: logical) -> complexA 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.
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, orsuppressWarnings, 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 threadslist[T]through and whoseAnyfallback 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.FiltertakesT[], thenlist[named: T], thenlist[T], and each candidate returns its own shape; a singlelist[T]return for all three would makeFilter(f, c(1, 2)) + 1a type error on code R runs.lapply,rev,unique,head, andtailcarry the samelist[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 isT | 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, andabswith its integer and double split) declare one candidate per atomic family, plus theAnyfallback. - 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 withseq,is, andgrep(value =), it returnsAnyrather than a falsely precise type. A call then yieldsAnyand never a spurious type or arity error, and the name still resolves.
Override precedence
Section titled “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
Section titled “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 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.Rtypesdeclaration if there is one, andUnknownotherwise. 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
.Rtypesfile 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 astracebackandstandardGeneric, which live inbaserather thanutilsormethods. A conditional namespace may additionally override a base name, as data.table’s class-preservingmergedoes.
The manifests mirror the three ways R exposes a namespace:
- Attached by default.
base,stats,utils,graphics,grDevices,methods, anddatasetsare visible everywhere without qualification, so their names always resolve.datasetsis 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 indatasets.Rtypes, which makesirisadata.framerather thanUnknown. - Shipped with R but not attached.
tools,parallel,compiler,grid,splines,stats4, andtcltkvalidate theirpkg::reads in every project, sotools::file_ext(path)needs nolibrary(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. 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 attachesdplyr,ggplot2,tibble,tidyr,readr,purrr,stringr,forcats, andlubridate, 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’sUnknown. The membership is easy to check, because the generator script prints what a livelibrary(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
Section titled “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:
TandFbecome a monomorphicScalar(Logical), andpibecomes aScalar(Double). - Base functions: each becomes a type scheme, seeded into the template environment next to the built-in kernel.
What stays hardcoded, and why
Section titled “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
Section titled “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
stopandq: without one, aNULLreturn would poison the join inx <- if (ok) v else stop(...), so these stayAny. - A return that mirrors the argument’s shape, as in
rev(opts)$timeouton a fixed-shapeopts: a declaration cannot say “the same record back”, so selection and reordering return a name-keyedlist[named: T], and a field read off it isT | NULLrather than the field’s own type. - A nullable result under a member-wise operator, as for
names,dim, andnrow: aT | NULLreturn would be a false report on1:nrow(df)andfor (nm in names(x))until flow narrowing or NULL-tolerant joins exist, so these returnAny.
Type-preserving reductions declare overload sets, 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
Section titled “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 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:
schemesmaps a name to its ordered list of schemes, one per declaration (see overload sets).nominalsis the set of@typenames.maskedmaps each@maskedname to the formals declared before its....pureholds each@purename, andpure_by_namespacethe same names per declaring namespace, forpkg::namecalls.exports_by_namespacemaps a namespace to the names it declares.declarationsmaps 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_exportsis 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
.Rtypesfile 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::nameresolves 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.
Conditional namespaces
Section titled “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 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
DESCRIPTIONdependency field (Depends,Imports,Suggests, orEnhances) names it, or aNAMESPACEimportorimportFromnames it as a source. - A project file attaches or loads it with
library(),require(),requireNamespace(), orloadNamespace(), 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>.Rtypesoverride 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
Section titled “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:
`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
Section titled “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
Section titled “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
UseMethodandNextMethod; do.call,match.arg,match.call,sys.call, andRecall;- reflective environment access, such as
environment,assign,get, andeval; - 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
Section titled “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
Anyeven though R documents a type for it, because R itself coerces arguments andnchar(42)is legal, so a declared type would reject calls R accepts. The checker’s own coercions reduce how often this is needed: anintegerwidens todoubleat a parameter, a whole-number double literal counts as aninteger, and a scalar coerces into a vector position. TheT[]generics restore the rest of the precision. - scalar-claim: an elementwise function declares its scalar result (
characterrather thancharacter[]). A scalar claim coerces into every vector position and so can never cause a false positive downstream, whereas a vector claim would breakif (grepl(...)). - type-preserving: the result’s type follows the input’s, as for
sum,sort, andrev. Element-preserving vector functions declare<T> fn(x: T[]) -> T[], and reductions whose result follows R’s coercion order declare one overload candidate per atomic family. Every set ends with anAnyfallback, which is also what a call selects while an argument’s type is still undetermined; because a fallback takingAnybinds nothing, it never narrows the caller. - NULL-hybrid: the result is
TorNULLdepending on the runtime value, as fornames,dim, andnrow. These returnAny, for the reason the gaps table above gives. - named-formals: declare the named formals whose types are worth checking, such as
paste’ssepandcollapseorread.csv’sheaderandsep. 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 forread.csv’s pass-through toread.tableandlm’s fitting controls. Declaring a formal buys a typed check; leaving it out buys pass-through.
Worked example
Section titled “Worked example”Here is a fragment of the base stubs:
T : logicalF : logicalpi : doublelength : fn(x: Any) -> integernchar : fn(x: Any, [type]: character, [allowNA]: logical, [keepNA]: logical) -> integerseq_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), 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:
n <- length(c(1L, 2L, 3L)) # integer, from the stub schemehalf <- pi / 2 # double, from the operator kernelflag <- T # logical, from the stub value bindingpaste’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) -> characterWhere 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
Section titled “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 ownstubs/<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
NAMESPACEimports. That would be R-correct, and whether to do it is a deliberate strictness decision; today’s flat fold is intentionally permissive.