## # So here's a list of base SPECIALSXPs:
## keep.where <- function(x, pred) x[mapply(pred, x)]
## specials <- (baseenv() |> as.list() |> keep.where(is.primitive)
## |> keep.where(function(x) capture.output(.Internal(inspect(x)))[1]
## |> grepl(pattern="SPECIAL"))
## |> names() |> sort())
`%is%` <- expect_equal
check_calls <- function(x) {capture.output(print(sys.calls())); x}
test_that("primitive functions that don't handle `...`", {
# watchdog test: demonstrate primitive functions that don't expand `...`
dotwrap <- function(fn) function(...) fn(...)
expect_dotfail <- function(call, pattern=NA) {
call_ <- arg(call)
native_result <- force(call) #what the call would no "natively"
expr(call_)[[1]] <- base::call("dotwrap", expr(call_)[[1]])
dots_result <- function() NULL
expect_error({
dots_result <- value(call_)
expect_identical(native_result, dots_result)
}, pattern)
if (identical(native_result, dots_result)) stop("didn't fail")
#now check that do_ does something better?
}
# i.e. expect_error(dotwrap(`::`)(async, do))
if (getRversion() >= '4.1.0') {
# these worked in R 4.0, huh? That shows the value of watchdog testing...
expect_dotfail(nseval::do, "1 argument")
expect_dotfail(nseval:::do__, "1 argument")
}
expect_dotfail({cat("hello"); cat("world")}, "...")
setClass("track", slots = c(x="numeric", y="numeric"))
myTrack <- new("track", x = -4:4, y = exp(-4:4))
expect_dotfail(myTrack@x, "1 argument")
expect_dotfail(TRUE && FALSE, "requires 2")
expect_dotfail(x <- 5, "number of arguments")
expect_dotfail(x <<- 5, "number of arguments")
expect_dotfail(`=`(y, 5), "number of arguments")
expect_dotfail(FALSE || TRUE, "requires 2")
expect_dotfail(x ~ y, "not identical")
x <- list(a=1, b=2)
expect_dotfail(x$a, "requires 2")
expect_dotfail(call("hello", "world"), "\\.\\.\\.")
expect_dotfail(expression(hello(world)), "not identical")
x <- 0
expect_dotfail(for (i in 1:10) {x <- x + i}, "1 argument")
expect_dotfail(forceAndCall(2, dots, 2+2, 3+3, 4+4, 5+5), "\\.\\.\\.")
expect_dotfail(function(x)y, "number of arguments")
expect_dotfail(if(TRUE) 1 else 2, "\\.\\.\\.")
y <- missing_value()
expect_dotfail(missing(y), "not identical")
expect_dotfail(on.exit("hello"), "\\.\\.\\.")
expect_dotfail(quote(x), "not identical")
expect_dotfail(repeat {cat(1); break}, "\\.\\.\\.")
(function(x) expect_dotfail(substitute(hello(x)), "not identical"))(1+1)
expect_dotfail(switch(3, 1, 2, 3, 4, 5), "\\.\\.\\.")
expect_dotfail(switch("b", a=1, b=2, c=3, d=4, e=5), "\\.\\.\\.")
# UseMethod fails but on the first UseMethod call. Because UseMethod
# expects to be called directly from the function's stack frame; it
# needs to be called in the lowest stack frame, can't wrap anything
# around it.
## test <- function(x) identity(UseMethod("test"))
## test <- function(x) expect_dotfail(UseMethod("test"), "inappropriate")
## test.list <- function(x) "a list!"
## test(list(1, 2))
expect_dotfail(while(TRUE) break, "requires 2")
})
#seems like these are the "trouble" primitives.
trouble_primitives <-
c("::", ":::", "{", "@", "&&", "<-", "<<-", "=", "||", "~",
"$", "expression", "for", "forceAndCall", "function",
"if", "missing", "on.exit", "quote", "repeat", "substitute", "switch",
"UseMethod", "while")
#Calling strategies for each:
strategies <- list(
`::` ="literal",
`:::`="literal",
`{`="promsxps",
`@`="slot", #promise head, call in 1st arg's env, literal rest
`&&` = "promsxps",
`<-` = "store", #promise head, call in 1st arg's env, promsxp rest
`<<-` = "store",
`~` = "literal",
`$` = "slot",
`expression` = "literal",
`for` = "literal", #you can promsxp the range I guess
`forceAndCall` = "forceAndCall", #first arg is promsxp, rest are quoted (but you can pass dots.... in "rest",
`function` = "literal",
`if`="promsxps",
`missing`="store",
`on.exit`="literal",
`quote`="literal",
`repeat`="literal",
`substitute`="promsxps",
`switch`="promsxps",
`UseMethod`="promsxps", #probably still won't work though
`while`="literal"
)
#Some checks on the above classifications
test_that("when can we use promsxps?", {
# test this right now with a locked environment
e <- new.env()
x <- "outie"
e$x <- "innie"
lockEnvironment(e)
if(getRversion() >= '4.1.0') {
expect_error(do_(quo(`::`, e), quo(nseval, e), quo(do)), "name")
expect_error(do_(quo(`:::`, e), quo(nseval, e), quo(do)), "name")
}
#{ can promsxp
fx <- function(x) {
do_(quo(`{`, e),
quo(x %is% "innie", e),
quo(x %is% "argie"),
quo(check_calls(TRUE)))
} #promsxp works and does not show in syscalls!
fx("argie") %is% TRUE
setClass("track", slots = c(x="numeric", y="numeric"))
myTrack <- new("track", x = -4:4, y = exp(-4:4))
expect_error(do_(dots_(alist(`@`, myTrack), e), dots(x)), "slot")
#&& can use promsxps
fg <- function() {
do_(dots_(alist(`&&`, check_calls(TRUE)), e),
dots(check_calls({x <- 2; TRUE})))
}
fg()
#forceAndCall can promsxp the first arg as promise and unpack ... after the second?
e <- new.env()
e$x <- 1
x <- 2
set_dots(e, c(dots(1+1, 2+2), quo(3+3, e), quo(4+4)))
expr <- quote(forceAndCall(nargs, dots, ...))
expr[[2]] <- .Call("_quotation_to_promsxp", quo(x+x, e))
eval(expr, e)
#"if" can use promsxps, just not ...
lockEnvironment(e)
do_(quo(`if`, e), quo(x==1, e), quo(x), quo(FALSE)) %is% 2
#"substitute" works perfectly with promsxps
e1 <- (function(l=a[[b]], e=e2) environment())()
e2 <- (function(l=x[[y]], e=e1) environment())()
expr <- quote(substitute(x <- y, e))
expr[[2]] <- .Call("_quotation_to_promsxp", quo(l, e1))
expr[[3]] <- .Call("_quotation_to_promsxp", quo(e, e2))
eval(expr) %is% quote(a[[b]])
#"switch" can promsxp fine
e1 <- (function(x=1, y="e1y", z="e1z") environment())()
e2 <- (function(x=2, y="e2y", z="e2z") environment())()
expr <- quote(switch(x, y, z))
expr[[2]] <- .Call("_quotation_to_promsxp", quo(x, e1))
expr[[3]] <- .Call("_quotation_to_promsxp", quo(check_calls(y), e2))
expr[[4]] <- .Call("_quotation_to_promsxp", quo(z, e1))
eval(expr) %is% "e2y"
#"for" can promsxp the range arg
e1 <- (function(x=2, y="e1y", z="e1z") environment())()
e2 <- (function(x=3, y="e2y", z="e2z") environment())()
expr <- quote(for(i in 1:check_calls(x)) {x <- check_calls(x) + 1})
expr[[3]] <- .Call("_quotation_to_promsxp", quo_(expr[[3]], e2))
eval(expr, e1)
e1$x %is% 5
#can you promsxp the head of a call?"
x <- 1
expr <- quote(x <- check_calls(TRUE))
e <- new.env()
expr[[1]] <- .Call("_quotation_to_promsxp", quo(`<-`))
eval(expr, e)
expect_equal(x, 1); expect_equal(e$x, TRUE)
#looks like yes, and the call is in env e...
})
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.