blob: 3f34e78f7a4d071a86c8b9af28105b261d90cd62 [file] [log] [blame]
# File src/library/utils/R/objects.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2016 The R Core Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# A copy of the GNU General Public License is available at
# https://www.R-project.org/Licenses/
## findGeneric(fname) : is 'fname' the name of an S3 generic ?
## [unexported function used only in this file]
findGeneric <- function(fname, envir, warnS4only = TRUE)
{
if(!exists(fname, mode = "function", envir = envir)) return("")
f <- get(fname, mode = "function", envir = envir)
## FIXME? In the first case, e.g. 'methods(qr)', we are very inefficient:
## inside methods() we transform the 'qr' function object into a character,
## whereas here, we revert this, searching around unnecessarily
##
if(.isMethodsDispatchOn() && methods::is(f, "genericFunction")) {
## maybe an S3 generic was turned into the S4 default
## Try to find it, otherwise warn :
fMethsEnv <- methods::getMethodsForDispatch(f)
meths <- as.list(fMethsEnv, all.names=TRUE)
r <- meths[grep("^ANY\\b", names(meths))]
if(any(ddm <- vapply(r, methods::is, logical(1L), "derivedDefaultMethod")))
f <- r[ddm][[1]]@.Data
else if(warnS4only)
warning(gettextf(
"'%s' is a formal generic function; S3 methods will not likely be found",
fname), domain = NA)
}
isUMEbrace <- function(e) {
for (ee in as.list(e[-1L]))
if (nzchar(res <- isUME(ee))) return(res)
""
}
isUMEif <- function(e) {
if (length(e) == 3L) isUME(e[[3L]])
else {
if (nzchar(res <- isUME(e[[3L]]))) res
else if (nzchar(res <- isUME(e[[4L]]))) res
else ""
}
}
isUME <- function(e) { ## is it an "UseMethod() calling function" ?
if (is.call(e) && (is.name(e[[1L]]) || is.character(e[[1L]]))) {
switch(as.character(e[[1L]]),
UseMethod = as.character(e[[2L]]),
"{" = isUMEbrace(e),
"if" = isUMEif(e),
"")
} else ""
}
isUME(body(f))
}
getKnownS3generics <-
function()
c(names(.knownS3Generics), tools:::.get_internal_S3_generics())
.S3methods <-
function(generic.function, class, envir=parent.frame())
{
rbindSome <- function(df, nms, msg) {
## rbind.data.frame() -- dropping rows with duplicated names
nms <- unique(nms)
n2 <- length(nms)
dnew <- data.frame(visible = rep.int(FALSE, n2),
from = rep.int(msg, n2),
row.names = nms)
n <- nrow(df)
if(n == 0L) return(dnew)
## else
keep <- !duplicated(c(rownames(df), rownames(dnew)))
rbind(df [keep[1L:n] , ],
dnew[keep[(n+1L):(n+n2)] , ])
}
S3MethodsStopList <- tools::nonS3methods(NULL)
knownGenerics <- getKnownS3generics()
sp <- search()
if(nzchar(lookup <-
Sys.getenv("_R_S3_METHOD_LOOKUP_BASEENV_AFTER_GLOBALENV_"))) {
lookup <- tools:::config_val_to_logical(lookup)
if(lookup) sp <- sp[c(1L, length(sp))]
}
methods.called <- identical(sys.call(-1)[[1]], as.symbol("methods"))
an <- lapply(seq_along(sp), ls)
lens <- lengths(an)
an <- unlist(an, use.names=FALSE)
names(an) <- rep.int(sp, lens)
an <- an[!duplicated(an)] # removed masked objects, *keep* names
info <- data.frame(visible = rep.int(TRUE, length(an)),
from = .rmpkg(names(an)),
row.names = an)
if (!missing(generic.function)) {
if (!is.character(generic.function))
generic.function <- deparse(substitute(generic.function))
## else
if(!exists(generic.function, mode = "function", envir = envir) &&
!any(generic.function == c("Math", "Ops", "Complex", "Summary")))
stop(gettextf("no function '%s' is visible", generic.function),
domain = NA)
warn.not.generic <- FALSE
if(!any(generic.function == knownGenerics)) {
truegf <- findGeneric(generic.function, envir, warnS4only = !methods.called)
if(truegf == "")
warn.not.generic <- TRUE
else if(truegf != generic.function) {
warning(gettextf("generic function '%s' dispatches methods for generic '%s'",
generic.function, truegf), domain = NA)
generic.function <- truegf
}
}
info <- info[startsWith(row.names(info), paste0(generic.function,".")), ]
info <- info[! row.names(info) %in% S3MethodsStopList, ]
## check that these are all functions
## might be none at this point
if(nrow(info)) {
keep <- vapply(row.names(info), exists, logical(1), mode="function")
info <- info[keep, ]
}
if(warn.not.generic && nrow(info))
warning(gettextf(
"function '%s' appears not to be S3 generic; found functions that look like S3 methods",
generic.function), domain = NA)
## also look for registered methods from namespaces
## we assume that only functions get registered.
defenv <- if(!is.na(w <- .knownS3Generics[generic.function]))
asNamespace(w)
else {
genfun <- get(generic.function, mode = "function", envir = envir)
if(.isMethodsDispatchOn() && methods::is(genfun, "genericFunction"))
genfun <- methods::finalDefaultMethod(genfun@default)
if (typeof(genfun) == "closure") environment(genfun)
else .BaseNamespaceEnv
}
S3reg <- names(get(".__S3MethodsTable__.", envir = defenv))
S3reg <- S3reg[startsWith(S3reg, paste0(generic.function,"."))]
if(length(S3reg))
info <- rbindSome(info, S3reg, msg =
paste("registered S3method for",
generic.function))
## both all() and all.equal() are generic, so
if(generic.function == "all")
info <- info[-grep("^all\\.equal", row.names(info)), ]
}
else if (!missing(class)) {
if (!is.character(class))
class <- paste(deparse(substitute(class)))
name <- paste0(".", class, "$")
name <- gsub("([.[])", "\\\\\\1", name)
info <- info[grep(name, row.names(info)), ]
info <- info[! row.names(info) %in% S3MethodsStopList, ]
if(nrow(info)) {
## check if we can find a generic matching the name
possible.generics <- gsub(name, "", row.names(info))
keep <- vapply(possible.generics, function(nm) {
if(nm %in% knownGenerics) return(TRUE)
where <- find(nm, mode = "function")
if(length(where))
any(vapply(where, function(w)
nzchar(findGeneric(nm, envir=as.environment(w))),
logical(1)))
else FALSE
}, logical(1))
info <- info[keep, ]
}
## also look for registered methods in loaded namespaces.
## These should only be registered in environments containing
## the corresponding generic, so we don't check again.
## Note that the generic will not necessarily be visible,
## as the package may not be loaded.
S3reg <- unlist(lapply(loadedNamespaces(), function(i)
ls(get(".__S3MethodsTable__.", envir = asNamespace(i)), pattern = name)))
## now methods like print.summary.aov will be picked up,
## so we do look for such mismatches.
if(length(S3reg))
S3reg <- S3reg[vapply(gsub(name, "", S3reg), exists, NA)]
if(length(S3reg))
info <- rbindSome(info, S3reg, msg = "registered S3method")
}
else stop("must supply 'generic.function' or 'class'")
info$generic <- if (!missing(generic.function))
rep.int(generic.function, nrow(info))
else sub(paste0("\\.", class, "$"), "", row.names(info))
info$isS4 <- rep.int(FALSE, nrow(info))
info <- info[sort.list(row.names(info)), , drop=FALSE]
res <- row.names(info)
class(res) <- "MethodsFunction"
attr(res, "info") <- info
attr(res, "byclass") <- missing(generic.function)
res
}
methods <-
function(generic.function, class)
{
envir <- parent.frame()
if(!missing(generic.function) && !is.character(generic.function)) {
what <- substitute(generic.function)
if(is.function(generic.function) &&
is.call(what) &&
(deparse(what[[1L]])[1L] %in% c("::", ":::"))) {
generic.function <- as.character(what[[3L]])
envir <- asNamespace(as.character(what[[2L]]))
} else
generic.function <- deparse(what)
}
if (!missing(class) && !is.character(class))
class <- paste(deparse(substitute(class)))
s3 <- .S3methods(generic.function, class, envir)
s4 <- if (.isMethodsDispatchOn()) {
methods::.S4methods(generic.function, class)
} else NULL
.MethodsFunction(s3, s4, missing(generic.function))
}
.MethodsFunction <-
function(s3, s4, byclass)
{
info3 <- attr(s3, "info")
info4 <- attr(s4, "info")
info <- rbind(info3, info4)
dups <- duplicated(c(rownames(info3), rownames(info4)))
info <- info[!dups, , drop=FALSE]
info <- info[order(rownames(info)), , drop=FALSE]
structure(rownames(info), info=info, byclass=byclass,
class="MethodsFunction")
}
print.MethodsFunction <- function(x, byclass = attr(x, "byclass"), ...)
{
info <- attr(x, "info")
values <-
if (byclass)
unique(info$generic)
else
paste0(rownames(info), visible = ifelse(info$visible, "", "*"))
if (length(values)) {
print(noquote(values))
cat("see '?methods' for accessing help and source code\n")
} else
cat("no methods found\n")
invisible(x)
}
getS3method <- function(f, class, optional = FALSE, envir = parent.frame())
{
if(!any(f == getKnownS3generics())) {
truegf <- findGeneric(f, envir)
if(nzchar(truegf)) f <- truegf
else {
if(optional) return(NULL)
else stop(gettextf("no function '%s' could be found", f), domain = NA)
}
}
method <- paste(f, class, sep=".")
if(!is.null(m <- get0(method, envir = envir, mode = "function")))
## FIXME(?): consider tools::nonS3methods(<pkg>) same as isS3method()
return(m)
## also look for registered method in namespaces
defenv <-
if(!is.na(w <- .knownS3Generics[f]))
asNamespace(w)
else if(f %in% tools:::.get_internal_S3_generics())
.BaseNamespaceEnv
else {
genfun <- get(f, mode="function", envir = envir)
if(.isMethodsDispatchOn() && methods::is(genfun, "genericFunction"))
## assumes the default method is the S3 generic function
genfun <- methods::selectMethod(genfun, "ANY")
if (typeof(genfun) == "closure") environment(genfun)
else .BaseNamespaceEnv
}
S3Table <- get(".__S3MethodsTable__.", envir = defenv)
if(!is.null(m <- get0(method, envir = S3Table, inherits = FALSE)))
m
else if(optional)
NULL
else stop(gettextf("S3 method '%s' not found", method), domain = NA)
}
##' Much in parallel to getS3method(), isS3method() gives TRUE/FALSE, but not an error
isS3method <- function(method, f, class, envir = parent.frame())
{
if(missing(method)) {
method <- paste(f, class, sep=".")
} else { # determine (f, class) from 'method'
f.c <- strsplit(method, ".", fixed=TRUE)[[1]]
nfc <- length(f.c)
if(nfc < 2 || !is.character(f.c))
return(FALSE) ## stop("Invalid 'method' specification; must be \"<fun>.<class>\"")
if(nfc == 2) {
f <- f.c[[1L]]
class <- f.c[[2L]]
} else { ## nfc > 2 : e.g., t.data.frame, is.na.data.frame
for(j in 2:nfc)
if(isS3method(f = paste(f.c[1:(j-1)], collapse="."),
class = paste(f.c[j: nfc ], collapse="."),
envir = envir))
return(TRUE)
return(FALSE)
}
}
if(!any(f == getKnownS3generics())) { ## either a known generic or found in 'envir'
if(!nzchar(f <- findGeneric(f, envir)))
return(FALSE)
}
if(!is.null(m <- get0(method, envir = envir, mode = "function"))) {
## know: f is a knownS3generic, and method m is a visible function
pkg <- if(isNamespace(em <- environment(m))) environmentName(em)
else if(is.primitive(m)) "base" ## else NULL
return(is.na(match(method, tools::nonS3methods(pkg)))) ## TRUE unless an exception
}
## also look for registered method in namespaces
defenv <-
if(!is.na(w <- .knownS3Generics[f]))
asNamespace(w)
else if(f %in% tools:::.get_internal_S3_generics())
.BaseNamespaceEnv
else {
genfun <- get(f, mode="function", envir = envir)
if(.isMethodsDispatchOn() && methods::is(genfun, "genericFunction"))
## assumes the default method is the S3 generic function
genfun <- methods::selectMethod(genfun, "ANY")
if (typeof(genfun) == "closure") environment(genfun)
else .BaseNamespaceEnv
}
S3Table <- get(".__S3MethodsTable__.", envir = defenv)
## return
exists(method, envir = S3Table, inherits = FALSE)
}
isS3stdGeneric <- function(f) {
bdexpr <- body(f)
## protect against technically valid but bizarre
## function(x) { { { UseMethod("gen")}}} by
## repeatedly consuming the { until we get to the first non { expr
while(as.character(bdexpr[[1L]]) == "{")
bdexpr <- bdexpr[[2L]]
## We only check if it is a "standard" s3 generic. i.e. the first non-{
## expression is a call to UseMethod. This will return FALSE if any
## work occurs before the UseMethod call ("non-standard" S3 generic)
ret <- is.call(bdexpr) && identical(bdexpr[[1L]], as.name("UseMethod"))
if(ret)
names(ret) <- bdexpr[[2L]] ## arg passed to UseMethod naming generic
ret
}
getFromNamespace <-
function(x, ns, pos = -1, envir = as.environment(pos))
{
if(missing(ns)) {
nm <- attr(envir, "name", exact = TRUE)
if(is.null(nm) || substr(nm, 1L, 8L) != "package:")
stop("environment specified is not a package")
ns <- asNamespace(substring(nm, 9L))
} else ns <- asNamespace(ns)
get(x, envir = ns, inherits = FALSE)
}
assignInMyNamespace <-
function(x, value)
{
f <- sys.function(-1)
ns <- environment(f)
## deal with subclasses of "function"
## that may insert an environment in front of the namespace
if(isS4(f))
while(!isNamespace(ns))
ns <- parent.env(ns)
if(bindingIsLocked(x, ns)) {
unlockBinding(x, ns)
assign(x, value, envir = ns, inherits = FALSE)
w <- options("warn")
on.exit(options(w))
options(warn = -1)
lockBinding(x, ns)
} else assign(x, value, envir = ns, inherits = FALSE)
if(!isBaseNamespace(ns)) {
## now look for possible copy as a registered S3 method
S3 <- getNamespaceInfo(ns, "S3methods")
if(!length(S3)) return(invisible(NULL))
S3names <- S3[, 3L]
if(x %in% S3names) {
i <- match(x, S3names)
genfun <- get(S3[i, 1L], mode = "function", envir = parent.frame())
if(.isMethodsDispatchOn() && methods::is(genfun, "genericFunction"))
genfun <- methods::slot(genfun, "default")@methods$ANY
defenv <- if (typeof(genfun) == "closure") environment(genfun)
else .BaseNamespaceEnv
S3Table <- get(".__S3MethodsTable__.", envir = defenv)
remappedName <- paste(S3[i, 1L], S3[i, 2L], sep = ".")
if(exists(remappedName, envir = S3Table, inherits = FALSE))
assign(remappedName, value, S3Table)
}
}
invisible(NULL)
}
assignInNamespace <-
function(x, value, ns, pos = -1, envir = as.environment(pos))
{
nf <- sys.nframe()
if(missing(ns)) {
nm <- attr(envir, "name", exact = TRUE)
if(is.null(nm) || substr(nm, 1L, 8L) != "package:")
stop("environment specified is not a package")
ns <- asNamespace(substring(nm, 9L))
} else ns <- asNamespace(ns)
ns_name <- getNamespaceName(ns)
if (nf > 1L) {
if(ns_name %in% tools:::.get_standard_package_names()$base)
stop("locked binding of ", sQuote(x), " cannot be changed",
domain = NA)
}
if(bindingIsLocked(x, ns)) {
in_load <- Sys.getenv("_R_NS_LOAD_")
if (nzchar(in_load)) {
if(in_load != ns_name) {
msg <-
gettextf("changing locked binding for %s in %s whilst loading %s",
sQuote(x), sQuote(ns_name), sQuote(in_load))
if (! in_load %in% c("Matrix", "SparseM"))
warning(msg, call. = FALSE, domain = NA, immediate. = TRUE)
}
} else if (nzchar(Sys.getenv("_R_WARN_ON_LOCKED_BINDINGS_"))) {
warning(gettextf("changing locked binding for %s in %s",
sQuote(x), sQuote(ns_name)),
call. = FALSE, domain = NA, immediate. = TRUE)
}
unlockBinding(x, ns)
assign(x, value, envir = ns, inherits = FALSE)
w <- options("warn")
on.exit(options(w))
options(warn = -1)
lockBinding(x, ns)
} else {
assign(x, value, envir = ns, inherits = FALSE)
}
if(!isBaseNamespace(ns)) {
## now look for possible copy as a registered S3 method
S3 <- .getNamespaceInfo(ns, "S3methods")
if(!length(S3)) return(invisible(NULL))
S3names <- S3[, 3L]
if(x %in% S3names) {
i <- match(x, S3names)
genfun <- get(S3[i, 1L], mode = "function", envir = parent.frame())
if(.isMethodsDispatchOn() && methods::is(genfun, "genericFunction"))
genfun <- methods::slot(genfun, "default")@methods$ANY
defenv <- if (typeof(genfun) == "closure") environment(genfun)
else .BaseNamespaceEnv
S3Table <- get(".__S3MethodsTable__.", envir = defenv)
remappedName <- paste(S3[i, 1L], S3[i, 2L], sep = ".")
if(exists(remappedName, envir = S3Table, inherits = FALSE))
assign(remappedName, value, S3Table)
}
}
invisible(NULL)
}
fixInNamespace <-
function(x, ns, pos = -1, envir = as.environment(pos), ...)
{
subx <- substitute(x)
if (is.name(subx))
subx <- deparse(subx)
if (!is.character(subx) || length(subx) != 1L)
stop("'fixInNamespace' requires a name")
if(missing(ns)) {
nm <- attr(envir, "name", exact = TRUE)
if(is.null(nm) || substr(nm, 1L, 8L) != "package:")
stop("environment specified is not a package")
ns <- asNamespace(substring(nm, 9L))
} else ns <- asNamespace(ns)
x <- edit(get(subx, envir = ns, inherits = FALSE), ...)
assignInNamespace(subx, x, ns)
}
getAnywhere <-
function(x)
{
if(tryCatch(!is.character(x), error = function(e) TRUE))
x <- as.character(substitute(x))
objs <- list(); where <- character(); visible <- logical()
## first look on search path
if(length(pos <- find(x, numeric = TRUE))) {
objs <- lapply(pos, function(pos, x) get(x, pos=pos), x=x)
where <- names(pos)
visible <- rep.int(TRUE, length(pos))
}
## next look for methods: a.b.c.d could be a method for a or a.b or a.b.c
if(length(grep(".", x, fixed=TRUE))) {
np <- length(parts <- strsplit(x, ".", fixed=TRUE)[[1L]])
for(i in 2:np) {
gen <- paste(parts[1L:(i-1)], collapse=".")
cl <- paste(parts[i:np], collapse=".")
if (gen == "" || cl == "") next
## want to evaluate this in the parent, or the utils namespace
## gets priority.
Call <- substitute(getS3method(gen, cl, TRUE), list(gen = gen, cl = cl))
f <- eval.parent(Call)
## Now try to fathom out where it is from.
## f might be a special, not a closure, and not have an environment,
if(!is.null(f) && !is.null(environment(f))) {
ev <- topenv(environment(f), baseenv())
nmev <- if(isNamespace(ev)) getNamespaceName(ev) else NULL
objs <- c(objs, list(f))
msg <- paste("registered S3 method for", gen)
if(!is.null(nmev))
msg <- paste(msg, "from namespace", nmev)
where <- c(where, msg)
visible <- c(visible, FALSE)
}
}
}
## now look in loaded namespaces
for(i in loadedNamespaces()) {
ns <- asNamespace(i)
if(exists(x, envir = ns, inherits = FALSE)) {
f <- get(x, envir = ns, inherits = FALSE)
objs <- c(objs, list(f))
where <- c(where, paste0("namespace:", i))
visible <- c(visible, FALSE)
}
}
# now check for duplicates
ln <- length(objs)
dups <- rep.int(FALSE, ln)
if(ln > 1L)
for(i in 2L:ln)
for(j in 1L:(i-1L))
if(identical(objs[[i]], objs[[j]],
ignore.environment = TRUE)) {
dups[i] <- TRUE
break
}
structure(list(name=x, objs=objs, where=where, visible=visible, dups=dups),
class = "getAnywhere")
}
print.getAnywhere <-
function(x, ...)
{
n <- sum(!x$dups)
if(n == 0L) {
cat("no object named", sQuote(x$name), "was found\n")
} else if (n == 1L) {
cat("A single object matching", sQuote(x$name), "was found\n")
cat("It was found in the following places\n")
cat(paste0(" ", x$where), sep="\n")
cat("with value\n\n")
print(x$objs[[1L]])
} else {
cat(n, "differing objects matching", sQuote(x$name),
"were found\n")
cat("in the following places\n")
cat(paste0(" ", x$where), sep="\n")
cat("Use [] to view one of them\n")
}
invisible(x)
}
`[.getAnywhere` <-
function(x, i)
{
if(!is.numeric(i)) stop("only numeric indices can be used")
if(length(i) == 1L) x$objs[[i]]
else x$objs[i]
}
argsAnywhere <-
function(x)
{
if(tryCatch(!is.character(x), error = function(e) TRUE))
x <- as.character(substitute(x))
fs <- getAnywhere(x)
if (sum(!fs$dups) == 0L)
return(NULL)
if (sum(!fs$dups) > 1L)
sapply(fs$objs[!fs$dups],
function(f) if (is.function(f)) args(f))
else args(fs$objs[[1L]])
}