Nothing
#' Get all global objects of an expression
#'
#' @param expr An R expression.
#'
#' @param envir The environment from where to search for globals.
#'
#' @param \dots Not used.
#'
#' @param method A character string specifying what type of search algorithm
#' to use.
#'
#' @param tweak An optional function that takes an expression
#' and returns a tweaked expression.
#'
#' @param locals Should globals part of any "local" environment of
#' a function be included or not?
#'
#' @param substitute If TRUE, the expression is \code{substitute()}:ed,
#' otherwise not.
#'
#' @param mustExist If TRUE, an error is thrown if the object of the
#' identified global cannot be located. Otherwise, the global
#' is not returned.
#'
#' @param unlist If TRUE, a list of unique objects is returned.
#' If FALSE, a list of \code{length(expr)} sublists.
#'
#' @param recursive If TRUE, globals that are closures (functions) and that
#' exist outside of namespaces ("packages"), will be recursively
#' scanned for globals.
#'
#' @param skip (internal) A list of globals not to be searched for
#' additional globals. Ignored unless \code{recursive} is TRUE.
#'
#' @return \code{globalsOf()} returns a \link{Globals} object.
#'
#' @details
#' There currently three strategies for identifying global objects.
#'
#' The \code{method = "ordered"} search method identifies globals such that
#' a global variable preceding a local variable with the same name
#' is not dropped (which the \code{"conservative"} method would).
#'
#' The \code{method = "conservative"} search method tries to keep the number
#' of false positive to a minimum, i.e. the identified objects are
#' most likely true global objects. At the same time, there is
#' a risk that some true globals are not identified (see example).
#' This search method returns the exact same result as the
#' \code{\link[codetools]{findGlobals}()} function of the
#' \pkg{codetools} package.
#'
#' The \code{method = "liberal"} search method tries to keep the
#' true-positive ratio as high as possible, i.e. the true globals
#' are most likely among the identified ones. At the same time,
#' there is a risk that some false positives are also identified.
#'
#' With \code{recursive = TRUE}, globals part of locally defined
#' functions will also be found, otherwise not.
#'
#' @example incl/globalsOf.R
#'
#' @seealso
#' Internally, the \pkg{\link{codetools}} package is utilized for
#' code inspections.
#'
#' @aliases findGlobals
#' @export
globalsOf <- function(expr, envir = parent.frame(), ...,
method = c("ordered", "conservative", "liberal"),
tweak = NULL,
locals = NA,
substitute = FALSE, mustExist = TRUE,
unlist = TRUE, recursive = TRUE, skip = NULL) {
method <- match.arg(method, choices = c("ordered", "conservative", "liberal"))
if (is.na(locals)) locals <- getOption("globals.globalsOf.locals", TRUE)
stop_if_not(is.logical(locals), length(locals) == 1L, !is.na(locals))
if (substitute) expr <- substitute(expr)
stop_if_not(is.null(skip) || is.list(skip))
debug <- mdebug("globalsOf(..., method = '%s', mustExist = %s, unlist = %s, recursive = %s) ...", method, mustExist, unlist, recursive) #nolint
## 1. Identify global variables (static code inspection)
names <- findGlobals(expr, envir = envir, ..., method = method,
tweak = tweak, substitute = FALSE, unlist = unlist)
debug && mdebug(" - preliminary globals (by name): [%d] %s",
length(names), hpaste(sQuote(names)))
## 2. Locate them (run time)
globals <- tryCatch({
globalsByName(names, envir = envir, mustExist = mustExist)
}, error = function(ex) {
## HACK: Tweak error message to also include the expression inspected.
msg <- conditionMessage(ex)
msg <- sprintf("Identified global objects via static code inspection (%s). %s", hexpr(expr), msg) #nolint
ex$message <- msg
stop(ex)
})
debug && mdebug(" - preliminary globals (by value): [%d] %s",
length(globals), hpaste(sQuote(names(globals))))
## If a function, drop any globals that are part of any of the functions
## local environments, e.g. 'a' in f <- local({ a <- 1; function() a })
if (!locals && is.function(expr) && length(globals) > 0) {
env <- environment(expr) ## the environment of the function
eenv <- emptyenv()
genv <- globalenv()
where <- attr(globals, "where", exact = TRUE)
while (length(where) > 0 && !identical(env, eenv) && !identical(env, genv)) {
## Any 'where' for the current environment?
keep <- !vapply(where, FUN.VALUE = FALSE, FUN = identical, env)
where <- where[keep]
env <- parent.env(env)
}
## Anything to drop?
if (length(where) != length(globals)) globals <- globals[names(where)]
}
## 3. Among globals that are closures (functions) and that exist outside
## of namespaces ("packages"), check for additional globals?
if (recursive) {
debug && mdebug(" - recursive scan of preliminary globals ...")
## Don't enter functions in namespaces / packages
where <- attr(globals, "where", exact = TRUE)
stop_if_not(length(where) == length(globals))
where <- vapply(where, FUN = envname, FUN.VALUE = NA_character_,
USE.NAMES = FALSE)
globals_t <- globals[!(where %in% loadedNamespaces())]
debug && mdebug(" - subset of globals to be scanned (not in loaded namespaces): [%d] %s", length(globals_t), hpaste(sQuote(names(globals_t)))) #nolint
## Enter only functions
## NOTE: This excludes functions "not found", but also primitives
## not dropped above.
globals_t <- globals_t[vapply(globals_t, FUN = typeof, FUN.VALUE = NA_character_, USE.NAMES = FALSE) == "closure"]
if (length(globals_t) > 0) {
debug && mdebug(" - subset of globals to be scanned: [%d] %s",
length(globals_t), hpaste(sQuote(names(globals_t))))
names_t <- names(globals_t)
## Avoid recursive scanning of already scanned ("known") globals
skip_t <- c(skip, globals_t)
for (gg in seq_along(globals_t)) {
debug && mdebug(" + scanning global #%d (%s) ...",
gg, sQuote(names_t[[gg]]))
fcn <- globals_t[[gg]]
## Is function 'fcn' among the already identified globals?
already_scanned <- any(vapply(skip, FUN = identical, fcn, FUN.VALUE = NA, USE.NAMES = FALSE))
if (already_scanned) next;
env <- environment(fcn) ## was 'env <- envir' in globals 0.8.0.
globals_gg <- globalsOf(fcn, envir = env, ..., method = method,
tweak = tweak,
locals = locals,
substitute = FALSE,
mustExist = mustExist, unlist = unlist,
recursive = recursive,
skip = skip_t)
if (length(globals_gg) > 0) {
globals <- c(globals, globals_gg)
skip_gg <- globals_gg[vapply(globals_gg, FUN = typeof, FUN.VALUE = NA_character_, USE.NAMES = FALSE) == "closure"]
skip_t <- c(skip_t, skip_gg)
}
}
globals <- unique(globals)
debug && mdebug(" - updated set of globals found: [%d] %s",
length(globals), hpaste(sQuote(names(globals))))
} else {
debug && mdebug(" - subset of globals to be scanned: [0]")
}
debug && mdebug(" - recursive scan of preliminary globals ... DONE")
}
debug && mdebug(" - globals found: [%d] %s",
length(globals), hpaste(sQuote(names(globals))))
debug && mdebug("globalsOf(..., method = '%s', mustExist = %s, unlist = %s, recursive = %s) ... DONE", method, mustExist, unlist, recursive) #nolint
globals
} ## globalsOf()
#' Locates and retrieves a set of global variables by their names
#'
#' @param names A character vector of global variable names.
#' @param envir The environment from where to search for globals.
#' @param mustExist If TRUE, an error is thrown if the object of the
#' identified global cannot be located. Otherwise, the global
#' is not returned.
#' @param ... Not used.
#'
#' @return A \link{Globals} object.
#'
#' @export
globalsByName <- function(names, envir = parent.frame(), mustExist = TRUE,
...) {
names <- as.character(names)
nnames <- length(names)
debug <- mdebug("globalsByName(<%d names>, mustExist = %s) ...",
nnames, mustExist)
debug && mdebug("- search from environment: %s", sQuote(envname(envir)))
## Locate and retrieve the specified globals
idxs <- grep("^[.][.]([.]|[0-9]+)$", names)
if (length(idxs) > 0L) {
dotdotdots <- unique(names[idxs])
names <- names[-idxs]
idxs <- NULL
debug && mdebug("- dotdotdots: %s", commaq(dotdotdots))
} else {
dotdotdots <- NULL
debug && mdebug("- dotdotdots: <none>")
}
globals <- list()
where <- list()
for (kk in seq_along(names)) {
name <- names[kk]
debug && mdebug("- locating #%d (%s)", kk, sQuote(name))
env <- where(name, envir = envir, inherits = TRUE)
debug && mdebug(" + found in environment: %s", sQuote(envname(env)))
if (!is.null(env)) {
where[[name]] <- env
value <- get(name, envir = env, inherits = FALSE)
if (is.null(value)) {
globals[name] <- list(NULL)
} else {
globals[[name]] <- value
}
} else {
globals[name] <- list(NULL)
where[name] <- list(NULL)
if (mustExist) {
stop(sprintf("Failed to locate global object in the relevant environments: %s", sQuote(name))) #nolint
}
}
}
if (length(dotdotdots) > 0L) {
for (name in dotdotdots) {
if (exists(name, envir = envir, inherits = TRUE)) {
where[[name]] <- where(name, envir = envir, inherits = TRUE)
expr <- substitute(list(arg), list(arg = as.name(name)))
ddd <- eval(expr, envir = envir, enclos = envir)
} else {
where[name] <- list(NULL)
ddd <- NA
}
class(ddd) <- c("DotDotDotList", class(ddd))
globals[[name]] <- ddd
}
}
stop_if_not(
is.list(where),
length(where) == length(globals),
all(names(where) == names(globals))
)
attr(globals, "where") <- where
class(globals) <- c("Globals", class(globals))
debug && mdebug("globalsByName(<%d names>, mustExist = %s) ... DONE",
nnames, mustExist)
globals
} ## globalsByName()
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.