const DOTS_CHECK_HELPER: &str = r#"# Internal helper: refuse the arguments in a wrapper's `...` that its
# `dots = empty | empty(allow = ...) | forward(allow = ...)` attribute does not
# take, or that a generated field method or env class `[[` (`empty`) gets.
# The wrapper calls it only when `...length()` is nonzero, with
# `...names()` and `...length()`, so no argument is forced. Every name outside
# `allow` is refused. An unnamed argument stands for an allowed name given by
# position: under `forward` (`forward = TRUE`) it passes, since the function
# the dots go on to matches it by position itself; under `empty` nothing reads
# it, so only the count matters, and the unnamed arguments past the number of
# allowed names not given by name are refused. All of them go in one
# condition: `argument` holds each one (`..<i>` for an unnamed one, its
# position in `...`) and `suggestion` the formal or allowed name each one most
# likely meant, or NA. That is the one name it is a prefix of, else the first
# one closest by edit distance (a swap of two neighbours counts as one edit),
# at most a third of its length in edits but at least one, and fewer than its
# length. The condition is an argument error (`kind = "conversion"`,
# `param = "..."`) classed `miniextendr_error_unused_argument` after the
# attribute's own `class`. `call` is the wrapper's call by default; a
# `call = caller` wrapper passes `.mx_call`, an S3 method its frame (resolved
# to the generic's call), a `call = none` wrapper NULL.
.miniextendr_check_dots <- function(names, n, formals, allow = character(), forward = FALSE, class = character(), call = sys.call(-1L)) {
if (is.null(names)) names <- character(n)
names[is.na(names)] <- ""
unnamed <- !nzchar(names)
bad <- !unnamed & !(names %in% allow)
if (!forward) bad <- bad | (unnamed & cumsum(unnamed) > length(setdiff(allow, names)))
bad <- which(bad)
if (!length(bad)) return(invisible())
if (is.environment(call)) call <- .miniextendr_frame_call(call)
candidates <- c(formals, allow)
distance <- function(a, b) {
a <- strsplit(a, "", fixed = TRUE)[[1L]]
b <- strsplit(b, "", fixed = TRUE)[[1L]]
d <- matrix(0L, length(a) + 1L, length(b) + 1L)
d[, 1L] <- seq.int(0L, length(a))
d[1L, ] <- seq.int(0L, length(b))
for (i in seq_along(a)) for (j in seq_along(b)) {
d[i + 1L, j + 1L] <- min(d[i, j + 1L] + 1L, d[i + 1L, j] + 1L, d[i, j] + (a[[i]] != b[[j]]))
if (i > 1L && j > 1L && a[[i]] == b[[j - 1L]] && a[[i - 1L]] == b[[j]]) {
d[i + 1L, j + 1L] <- min(d[i + 1L, j + 1L], d[i - 1L, j - 1L] + 1L)
}
}
d[[length(a) + 1L, length(b) + 1L]]
}
suggest <- function(name) {
if (!nzchar(name) || !length(candidates)) return(NA_character_)
prefixed <- candidates[startsWith(candidates, name)]
if (length(prefixed) == 1L) return(prefixed)
edits <- vapply(candidates, distance, 0L, b = name, USE.NAMES = FALSE)
best <- which(edits == min(edits) & edits <= max(1L, nchar(name) %/% 3L) & edits < nchar(name))
if (length(best)) candidates[[best[[1L]]]] else NA_character_
}
unnamed <- !nzchar(names[bad])
argument <- ifelse(unnamed, paste0("..", bad), names[bad])
suggestion <- vapply(names[bad], suggest, "", USE.NAMES = FALSE)
what <- sprintf("'%s'%s", argument, ifelse(unnamed, " (unnamed)", ""))
hint <- !is.na(suggestion)
what[hint] <- sprintf("%s (did you mean '%s'?)", what[hint], suggestion[hint])
message <- sprintf("unused argument%s %s", if (length(bad) > 1L) "s" else "", paste(what, collapse = ", "))
stop(structure(
list(message = message, call = call, kind = "conversion", param = "...", argument = argument, suggestion = suggestion),
class = c(class, "miniextendr_error_unused_argument", .miniextendr_conversion_error_class, "rust_error", "simpleError", "error", "condition")
))
}
"#;Expand description
The check of #[miniextendr(dots = empty | empty(allow = ..) | forward(allow = ..))] (#2157): a wrapper with such an attribute calls it as
if (...length()) .miniextendr_check_dots(...names(), ...length(), <formals>, allow = .., forward = TRUE, class = .., call = ..), the
statement miniextendr-macros’ dots_policy::DotsPolicy::r_check writes
(forward = TRUE for forward only). The [[, as.list() and [[<-
field methods of s3(r_data_accessors) and an env class’s [[ make the
empty check on a bare pointer (#2174). It builds its own condition, an
argument error with two more fields, and depends only on the helpers every
preamble has.