blob: f2c219b56cbcebc296904e6599c6f751114ede44 [file] [log] [blame]
# File src/library/methods/R/RMethodUtils.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/
## The real version of makeGeneric, to be installed after there are some
## generic functions to boot the definition (in particular, coerce and coerce<-)
.makeGeneric <-
## Makes a generic function object corresponding to the given function name.
## and definition.
function(f, fdef,
fdefault = fdef,
group = list(),
valueClass = character(),
package = getPackageName(environment(fdef)),
signature = NULL,
genericFunction = NULL,
simpleInheritanceOnly = NULL)
{
checkTrace <- function(fun, what, f) {
if(is(fun, "traceable")) {
warning(gettextf("the function being used as %s in making a generic function for %s is currently traced; the function used will have tracing removed",
what,
sQuote(f)),
domain = NA)
.untracedFunction(fun)
}
else
fun
}
if(missing(fdef)) {
if(missing(fdefault))
stop(gettextf("must supply either a generic function or a function as default for %s",
sQuote(f)),
domain = NA)
else if(isBaseFun(fdefault)) {
fun <- genericForBasic(f)
if (is.function(fun)) {
return(fun)
}
}
fdef <- fdefault
body(fdef) <- substitute(standardGeneric(NAME), list(NAME = f))
environment(fdef) <- .NamespaceOrPackage(package)
}
## give the function a new environment, to cache methods later
ev <- new.env()
parent.env(ev) <- environment(fdef)
environment(fdef) <- ev
packageSlot(f) <- package
assign(".Generic", f, envir = ev)
fdef <- checkTrace(fdef)
if(length(valueClass))
fdef <- .ValidateValueClass(fdef, f, valueClass)
group <- .asGroupArgument(group)
if(is.null(genericFunction))
value <- new("standardGeneric")
else if(is(genericFunction, "genericFunction"))
value <- genericFunction
else
stop(gettextf("the %s argument must be NULL or a generic function object; got an object of class %s",
sQuote("genericFunction"),
dQuote(class(genericFunction))),
domain = NA)
value@.Data <- fdef
value@generic <- f
value@group <- group
value@valueClass <- valueClass
value@package <- package
args <- formalArgs(fdef)
if(is.null(signature))
signature <- args
else if(any(is.na(match(signature, args))))
stop(sprintf(ngettext(sum(is.na(match(signature, args))),
"non-argument found in the signature: %s",
"non-arguments found in the signature: %s"),
paste(signature[is.na(match(signature, args))], collapse = ", ")),
domain = NA)
dots <- match("...", signature)
if(!is.na(dots)) { # remove "..." unless it is the only element of the signature
if(length(signature) > 1L)
signature <- signature[-dots]
}
if(length(signature) == 0L)
stop("no suitable arguments to dispatch methods in this function")
attr(signature, "simpleOnly") <- simpleInheritanceOnly # usually NULL
value@signature <- signature
## name <- signature[[1L]]
if(is.null(fdefault))
{} # pre 2.11.0: methods <- MethodsList(name)
else {
fdefault <- checkTrace(fdefault)
if(!identical(formalArgs(fdefault), formalArgs(fdef)) &&
!is.primitive(fdefault))
stop(sprintf(ngettext(length(fdef),
"the formal argument of the generic function for %s (%s) differs from that of the non-generic to be used as the default (%s)",
"the formal arguments of the generic function for %s (%s) differ from those of the non-generic to be used as the default (%s)"),
f,
paste(formalArgs(fdef), collapse = ", "),
paste(formalArgs(fdefault), collapse = ", ")),
domain = NA)
fdefault <- asMethodDefinition(fdefault, fdef = value)
if(is(fdefault, "MethodDefinition"))
fdefault@generic <- value@generic
## pre 2.11.0 methods <- MethodsList(name, fdefault)
}
value@default <- fdefault # pre 2.11.0 methods
assign(".Methods", fdefault, envir = ev) ## ? why
.setupMethodsTables(value, TRUE)
value@skeleton <- generic.skeleton(f, fdef, fdefault)
value
}
## stripped down version of asS4 in base (asS4 can't be used until the methods
## namespace is available -- no longer true)
.asS4 <- function (object)
asS4(object, TRUE, 0L)
.notS4 <- function (object)
asS4(object, FALSE, 0L)
## the bootstrap version: "#----" brackets lines that replace parts of the real version
makeGeneric <-
function(f, fdef,
fdefault = getFunction(f, generic = FALSE, mustFind = FALSE),
group = list(), valueClass = character(), package, signature = NULL,
genericFunction = NULL, simpleInheritanceOnly = NULL)
{
## give the function a new environment, to cache methods later
ev <- new.env()
parent.env(ev) <- environment(fdef)
environment(fdef) <- ev
packageSlot(f) <- package
assign(".Generic", f, envir = ev)
if(length(valueClass))
fdef <- .ValidateValueClass(fdef, f, valueClass)
group <- .asGroupArgument(group)
###--------
value <- .asS4(fdef)
if(is.null(genericFunction))
class(value) <- .classNameFromMethods("standardGeneric")
else
class(value) <- class(genericFunction)
slot(value, "generic", FALSE) <- f
slot(value, "group", FALSE) <- group
slot(value, "valueClass", FALSE) <- valueClass
slot(value, "package", FALSE) <- package
###--------
args <- formalArgs(fdef)
if(is.null(signature))
signature <- args
else if(any(is.na(match(signature, args))))
stop(sprintf(ngettext(sum(is.na(match(signature, args))),
"non-argument found in the signature: %s",
"non-arguments found in the signature: %s"),
paste(signature[is.na(match(signature, args))], collapse = ", ")),
domain = NA)
attr(signature, "simpleOnly") <- simpleInheritanceOnly # usually NULL
dots <- match("...", signature)
if(!is.na(dots)) ## ... is not currently supported in method signatures
signature <- signature[-dots]
if(length(signature) == 0L)
stop("no suitable arguments to dispatch methods in this function")
###--------
slot(value, "signature", FALSE) <- signature
###--------
name <- signature[[1L]]
if(is.null(fdefault))
{}
else
fdefault <- asMethodDefinition(fdefault, fdef = value)
if(is(fdefault, "MethodDefinition"))
fdefault@generic <- value@generic
## pre 2.11.0 methods <- MethodsList(name, fdefault)
###--------
assign(".Methods", fdefault, envir = ev)
slot(value, "default", FALSE) <- fdefault
slot(value, "skeleton", FALSE) <- generic.skeleton(f, fdef, fdefault)
###--------
value
}
### FIXME: Not used by methods, but exposed through namespace. Deprecate?
makeStandardGeneric <-
## a utility function that makes a valid function calling
## standardGeneric for name f Works (more or less) even if the
## actual definition, fdef, is not a proper function, that is, it is
## a primitive or internal
function(f, fdef)
{
fgen <- fdef
body(fgen) <- substitute(standardGeneric(FNAME), list(FNAME=f))
## detect R specials and builtins: these don't provide an argument list
if(typeof(fdef) != "closure") {
## Look in a list of pre-defined functions (and also of
## functions for which methods are prohibited)
fgen <- genericForBasic(f)
message(gettextf("making a generic for special function %s",
sQuote(f)),
domain = NA)
setPrimitiveMethods(f, fdef, "reset", fgen, NULL)
## Note that the body of the function comes from the list. In
## a few cases ("$"), this body is not just a call to
## standardGeneric
}
fgen
}
generic.skeleton <- function(name, fdef, fdefault)
{
anames <- formalArgs(fdef)
skeleton <- lapply(as.list(c(name, anames)), as.name)
## any arguments after "..." have to be named
dots <- match("...", anames)
if(!is.na(dots) && dots < length(anames)) {
anames[1L:dots] <- ""
names(skeleton) <- c("", anames)
}
if(is.null(fdefault)) {
fdefault <- fdef
msg <- gettextf("invalid call in method dispatch to '%s' (no default method)",
name)
body(fdefault) <- substitute(stop(MESSAGE, domain = NA),
list(MESSAGE = msg))
environment(fdefault) <- baseenv()
}
skeleton[[1L]] <- fdefault
as.call(skeleton)
}
defaultDumpName <-
## the default name to be used for dumping a method.
function(generic, signature)
{
if(missing(signature))
paste(generic, "R", sep=".", collapse =".")
else
paste(generic, paste(signature, collapse ="."), "R", sep=".")
}
mergeMethods <-
## merge the methods in the second MethodsList object into the first,
## and return the merged result.
function(m1, m2, genericLabel = character())
{
.MlistDeprecated("mergeMethods()")
if(length(genericLabel) && is(m2, "MethodsList"))
m2 <- .GenericInPrimitiveMethods(m2, genericLabel)
if(is.null(m1) || is(m1, "EmptyMethodsList"))
return(m2)
tmp <- listFromMlist(m2)
sigs <- tmp[[1]]
methods <- tmp[[2]]
for(i in seq_along(sigs)) {
sigi <- sigs[[i]]
if(.noMlists() && !identical(unique(sigi), "ANY"))
next
args <- names(sigi)
m1 <- insertMethod(m1, as.character(sigi), args, methods[[i]], FALSE)
}
m1
}
doPrimitiveMethod <-
## do a primitive call to builtin function 'name' the definition and call
## provided, and carried out in the environment 'ev'.
##
## A call to 'doPrimitiveMethod' is used when the actual method is a .Primitive.
## (because primitives don't behave correctly as ordinary functions,
## not having either formal arguments nor a function body).
function(name, def, call = sys.call(sys.parent()), ev = sys.frame(sys.parent(2)))
{
cat("called doPrimitiveMethod\n\n")
## Store a local version of function 'name' back where the current version was
## called. Restore the previous state there on exit, either removing or re-assigning.
if(!is.null(prev <- ev[[name]])) {
on.exit(assign(name, prev, envir = ev))
}
else
on.exit(rm(list=name, envir=ev))
assign(name, def, envir = ev)
eval(call, ev)
}
.renderSignature <- function(f, signature)
{
nm <- names(signature)
nm[nzchar(nm)] <- paste0(nm[nzchar(nm)], "=")
msig <- paste0(nm, '"', as.vector(signature), '"')
msig <- paste(msig, collapse = ",")
gettextf("in method for %s with signature %s: ", sQuote(f), sQuote(msig))
}
conformMethod <- function(signature, mnames, fnames,
f = "<unspecified>", fdef, method)
{
sig0 <- signature
fsig <- fdef@signature
if(is.na(match("...", mnames)) && !is.na(match("...", fnames)))
fnames <- fnames[-match("...", fnames)]
imf <- match(fnames, mnames)
omitted <- is.na(imf)
if(is.unsorted(imf[!omitted]))
stop(.renderSignature(f, signature),
"formal arguments in method and generic do not appear in the same order",
call. = FALSE)
if(!any(omitted)) ## i.e. mnames contains all fnames
return(signature)
sigNames <- names(signature)
omittedSig <- sigNames %in% fnames[omitted] # names in signature & generic but not in method defn
### FIXME: the test below is too broad, with all.names(). Would be nice to have a test
### for something like assigning to one of the omitted arguments.
## missingFnames <- fnames[omitted]
## foundNames <- missingFnames %in% all.names(body(method), unique = TRUE)
## if(any(foundNames))
## warning(gettextf("%s function arguments omitted from method arguments, (%s), were found in method definition",
## label, paste(missingFnames[foundNames], collapse = ", ")),
## domain = NA)
if(!any(omittedSig))
return(signature)
if(any(is.na(match(signature[omittedSig], c("ANY", "missing"))))) {
bad <- omittedSig & is.na(match(signature[omittedSig], c("ANY", "missing")))
bad2 <- paste0(fnames[bad], " = \"", signature[bad], "\"", collapse = ", ")
stop(.renderSignature(f, sig0),
gettextf("formal arguments (%s) omitted in the method definition cannot be in the signature", bad2),
call. = TRUE, domain = NA)
}
else if(!all(signature[omittedSig] == "missing")) {
omittedSig <- omittedSig && (signature[omittedSig] != "missing")
.message("Note: ", .renderSignature(f, sig0),
gettextf("expanding the signature to include omitted arguments in definition: %s",
paste(sigNames[omittedSig], "= \"missing\"",collapse = ", ")))
omittedSig <- seq_along(omittedSig)[omittedSig] # logical index will extend signature!
signature[omittedSig] <- "missing"
}
## remove trailing "ANY"'s
n <- length(signature)
while(.identC(signature[[n]], "ANY"))
n <- n - 1L
length(signature) <- n
length(fsig) <- n
setNames(signature, fsig)
}
rematchDefinition <- function(definition, generic, mnames, fnames, signature)
{
added <- any(is.na(match(mnames, fnames)))
keepsDots <- !is.na(match("...", mnames))
if(!added && keepsDots) {
## the formal args of the method must be identical to generic
formals(definition, envir = environment(definition)) <- formals(generic)
return(definition)
}
dotsPos <- match("...", fnames)
if(added && is.na(dotsPos))
stop(gettextf("methods can add arguments to the generic %s only if '...' is an argument to the generic", sQuote(generic@generic)),
call. = TRUE)
## pass down all the names in common between method & generic,
## plus "..." even if the method doesn't have it. But NOT any
## arguments having class "missing" implicitly (see conformMethod),
## i.e., are not among 'mnames':
useNames <- !is.na(imf <- match(fnames, mnames)) | fnames == "..."
newCall <- lapply(c(".local", fnames[useNames]), as.name)
## Should not be needed, if conformMethod() has already been called:
if(is.unsorted(imf[!is.na(imf)]))
stop(.renderSignature(generic@generic, signature),
"formal arguments in method and generic do not appear in the same order",
call. = FALSE)
## leave newCall as a list while checking the trailing args
if(keepsDots && dotsPos < length(fnames)) {
## Trailing arguments are required to match. This is a little
## stronger than necessary, but this is a dicey case, because
## the argument-matching may not be consistent otherwise (in
## the generic, such arguments have to be supplied by name).
## The important special case is replacement methods, where
## value is the last argument.
ntrail <- length(fnames) - dotsPos
trailingArgs <- fnames[seq.int(to = length(fnames), length.out = ntrail)]
if(!identical( mnames[seq.int(to = length(mnames), length.out = ntrail)],
trailingArgs))
stop(gettextf("%s arguments (%s) after %s in the generic must appear in the method, in the same place at the end of the argument list",
.renderSignature(generic@generic, signature),
paste(sQuote(trailingArgs), collapse = ", "),
sQuote("...")),
call. = FALSE, domain = NA)
newCallNames <- character(length(newCall))
newCallNames[seq.int(to = length(newCallNames), length.out = ntrail)] <-
trailingArgs
names(newCall) <- newCallNames
}
newCall <- as.call(newCall)
newBody <- substitute({.local <- DEF; NEWCALL},
list(DEF = definition, NEWCALL = newCall))
generic <- .copyMethodDefaults(generic, definition)
body(generic, envir = environment(definition)) <- newBody
generic
}
isRematched <- function(definition)
{
## detect the effects of rematchDefinition, if it was used.
## Has the obvious disadvantage of depending on the implementation.
## If we considered the rematching part of the API, a cleaner solution
## would be to include the "as given to setMethod" definition as a slot
bdy <- body(definition)
if(.identC(class(bdy),"{") && length(bdy) > 1L) {
bdy <- bdy[[2L]]
.identC(class(bdy), "<-") && identical(bdy[[2L]], as.name(".local"))
} else FALSE
}
unRematchDefinition <- function(definition)
{
if(isRematched(definition))
definition <- body(definition)[[2]][[3]] # value in assignmt to .local
definition
}
getGeneric <-
## return the definition of the function named f as a generic.
##
## If there is no definition, throws an error or returns
## NULL according to the value of mustFind.
function(f, mustFind = FALSE, where, package = "")
{
if(is.function(f)) {
if(is(f, "genericFunction"))
return(f)
else if(is.primitive(f))
return(genericForBasic(.primname(f), mustFind=mustFind))
else
stop("argument 'f' must be a string, generic function, or primitive: got an ordinary function")
}
value <- if(missing(where))
.getGeneric(f, , package)
else .getGeneric(f, where, package)
if(is.null(value) && !is.null(baseDef <- baseenv()[[f]])) {
if(is.function(baseDef)) {
value <- genericForBasic(f, mustFind=FALSE)
if(is(value, "genericFunction"))
value <- .cacheGeneric(f, value)
}
}
if(is.function(value))
value
else {
if(nzchar(package) && is.na(match(package, c("methods", "base")))) {
value <- tryCatch({
## load package namespace or error
ev <- getNamespace(package)
.getGeneric(f, ev, package)
}, error = function(e) NULL)
}
if(is.function(value))
value
else if(mustFind)
## the C code will have thrown an error if f is not a single string
stop(gettextf("no generic function found for %s", sQuote(f)),
domain = NA)
else
NULL
}
}
## low-level version
.getGeneric <- function(f, where = .GlobalEnv, # default only for C search
package = "")
{
## do not search the cache if getGeneric() was called with explicit where=
value <- if(missing(where))
.getGenericFromCache(f, where, package) ## else NULL
if(is.null(value)) {
if(is.character(f) && f %in% "as.double") f <- "as.numeric"
if(is.character(f) && !nzchar(f)) {
message("Empty function name in .getGeneric")
dput(sys.calls())
}
value <- .Call(C_R_getGeneric, f, FALSE, as.environment(where), package)
## cache public generics (usually these will have been cached already
## and we get to this code for non-exported generics)
if(!is.null(value) && !is.null(vv <- .GlobalEnv[[f]]) &&
identical(vv, value))
.cacheGeneric(f, value)
}
## if(is.null(value) && nzchar(package) && !identical(package, "base")) {
## env <- .requirePackage(package, FALSE)
## if(is.environment(env))
## value <- .Call("R_getGeneric", f, FALSE, env, package,
## PACKAGE = "methods")
## }
value
}
## cache and retrieve generic functions. If the same generic name
## appears for multiple packages, a named list of the generics is cached.
.genericTable <- new.env(TRUE, baseenv())
.implicitTable <- new.env(TRUE, baseenv())
.cacheGeneric <- function(name, def)
.cacheGenericTable(name, def, .genericTable)
.cacheImplicitGeneric <- function(name, def)
.cacheGenericTable(name, def, .implicitTable)
.cacheGenericTable <- function(name, def, table)
{
fdef <- def
if(!is.null(prev <- table[[name]])) {
newpkg <- def@package
if(is.function(prev)) {
if(identical(prev, def))
return(fdef)
## the following makes the cached version != package
## fdef <- def <- .makeGenericForCache(def)
pkg <- prev@package
if(identical(pkg, newpkg)) { # redefinition
table[[name]] <- def
return(fdef)
}
prev <- list(prev) # start a per-package list
names(prev) <- pkg
}
i <- match(newpkg, names(prev))
if(is.na(i))
prev[[newpkg]] <- def # or, .makeGenericForCache(def) as above
else if(identical(def, prev[[i]]))
return(fdef)
else
prev[[i]] <- def # or, .makeGenericForCache(def) as above
def <- prev
}
.getMethodsTable(fdef) # force initialization
table[[name]] <- def
fdef
}
.uncacheGeneric <- function(name, def)
.uncacheGenericTable(name, def, .genericTable)
.uncacheImplicitGeneric <- function(name, def)
.uncacheGenericTable(name, def, .implicitTable)
.uncacheGenericTable <- function(name, def, table)
{
if(exists(name, envir = table, inherits = FALSE)) {
newpkg <- def@package
prev <- get(name, envir = table)
if(is.function(prev)) # we might worry if prev not identical
return(remove(list = name, envir = table))
i <- match(newpkg, names(prev))
if(!is.na(i))
prev[[i]] <- NULL
else # we might warn about unchaching more than once
return()
if(length(prev) == 0L)
return(remove(list = name, envir = table))
else if(length(prev) == 1L)
prev <- prev[[1L]]
assign(name, prev, envir = table)
}
}
.getGenericFromCache <- function(name, where, pkg = "")
.getGenericFromCacheTable(name, where, pkg, .genericTable)
.getImplicitGenericFromCache <- function(name, where, pkg = "")
.getGenericFromCacheTable(name, where, pkg, .implicitTable)
.getGenericFromCacheTable <- function(name, where, pkg = "", table)
{
if(exists(name, envir = table, inherits = FALSE)) {
value <- get(name, envir = table)
if(is.list(value)) { # multiple generics with this name
## force a check of package name, even if argument is ""
if(!nzchar(pkg)) {
if(is.character(where))
pkg <- where
else {
pkg <- attr(name, "package")
if(is.null(pkg))
pkg <- getPackageName(where, FALSE)
if(identical(pkg, ".GlobalEnv"))
pkg <- ""
}
}
pkgs <- names(value)
i <- match(pkg, pkgs, 0L)
if(i > 0L)
return(value[[i]])
i <- match("methods", pkgs, 0L)
if(i > 0L)
return(value[[i]])
i <- match("base", pkgs, 0L)
if(i > 0L)
return(value[[i]])
else
return(NULL)
}
else if(nzchar(pkg) && !identical(pkg, value@package))
NULL
else
value
}
else
NULL
}
.genericOrImplicit <- function(name, pkg, env)
{
fdef <- .getGenericFromCache(name, env, pkg)
if(is.null(fdef)) {
penv <- tryCatch(getNamespace(pkg), error = function(e)e)
if(!isNamespace(penv)) { # no namespace--should be rare!
pname <- paste0("package:", pkg)
penv <- if(pname %in% search()) as.environment(pname) else env
}
fdef <- getFunction(name, TRUE, FALSE, penv)
if(!is(fdef, "genericFunction")) {
if(is.primitive(fdef))
fdef <- genericForBasic(name, penv)
else
fdef <- implicitGeneric(name, penv)
}
}
fdef
}
## copy the environments in the generic function so later merging into
## the cached generic will not modify the generic in the package.
## NOT CURRENTLY USED: see comments in .getGeneric()
.makeGenericForCache <- function(fdef)
{
value <- fdef
ev <- environment(fdef)
objs <- lapply(as.list(ev, all.names=TRUE), function(obj) {
if(is.environment(obj))
obj <- .copyEnv(obj)
obj
})
environment(value) <- list2env(objs, hash=TRUE, parent=parent.env(ev))
value
}
.copyEnv <- function(env)
{
list2env(as.list(env, all.names=TRUE), hash=TRUE, parent=parent.env(env))
}
getGroup <-
## return the groups to which this generic belongs. If 'recursive=TRUE', also all the
## group(s) of these groups.
function(fdef, recursive = FALSE, where = topenv(parent.frame()))
{
if(is.character(fdef))
fdef <- getGeneric(fdef, where = where)
if(is(fdef, "genericFunction"))
group <- fdef@group
else
group <- list()
if(recursive && length(group)) {
allGroups <- group
for(gp in group) {
fgp <- getGeneric(gp, where = where)
if(is(fgp, "groupGenericFunction"))
allGroups <- c(allGroups, Recall(fgp, TRUE, where))
}
if(length(allGroups) > 1L) {
ids <- sapply(allGroups, function(x) {
pkg <- packageSlot(x)
if(is.null(pkg)) x
else paste(x, pkg, sep=":")
})
allGroups <- allGroups[!duplicated(ids)]
}
allGroups
}
else
group
}
getMethodsMetaData <- function(f, where = topenv(parent.frame()))
{
fdef <- getGeneric(f, where = where)
if(is.null(fdef))
return(NULL)
if(.noMlists()) {
warning(sprintf("Methods list objects are not maintained in this version of R: request for function %s may return incorrect information",
sQuote(fdef@generic)),
domain = NA)
}
mname <- methodsPackageMetaName("M",fdef@generic, fdef@package)
if (exists(mname, where = where, inherits = missing(where)))
get(mname, where)
else if(missing(where))
.makeMlistFromTable(fdef)
else
.makeMlistFromTable(fdef, where)
}
assignMethodsMetaData <-
## assign value to be the methods metadata for generic f on database where.
## as of R 2.7.0 the mlist metadata is deprecated.
## If value is not a MethodsList, only turns on primitives & groups
function(f, value, fdef, where)
{
where <- as.environment(where)
if(is(value, "MethodsList")) {
.MlistDeprecated()
mname <- methodsPackageMetaName("M",fdef@generic, fdef@package)
if(exists(mname, envir = where, inherits = FALSE) &&
bindingIsLocked(mname, where))
{} # may be called from trace() with locked binding; ignore
else
assign(mname, value, where)
}
if(dispatchIsInternal(fdef))
setPrimitiveMethods(f, fdef@default, "reset", fdef, NULL)
if(is(fdef, "groupGenericFunction")) # reset or turn on members of group
cacheGenericsMetaData(f, fdef, where = where, package = fdef@package)
}
## utility for getGenerics to return package(s)
.packageForGeneric <- function(object)
{
if(is.list(object)) # a list of objects
lapply(object, .packageForGeneric)
else if(is(object, "genericFunction"))
object@package
else ## ?? possibly a primitive
"base"
}
getGenerics <- function(where, searchForm = FALSE)
{
if(missing(where)) {
## all the packages cached ==? all packages with methods
## globally visible. Assertion based on cacheMetaData + setMethod
fdefs <- as.list(.genericTable, all.names=TRUE, sorted=TRUE)
fnames <- mapply(function(nm, obj) {
if (is.list(obj)) names(obj) else nm
}, names(fdefs), fdefs, SIMPLIFY=FALSE)
packages <- lapply(fdefs, .packageForGeneric)
new("ObjectsWithPackage", unlist(fnames), package=unlist(packages))
}
else {
if(is.environment(where)) where <- list(where)
## The order matters ... and there might be no objects.
these <- unlist(lapply(where, objects, all.names=TRUE), use.names=FALSE)
metaNameUndo(unique(these), prefix = "T", searchForm = searchForm)
}
}
## Find the pattern for methods lists or tables
## Currently driven by mlists, but eventually these will go away
## in favor of tables.
## always returns a compatible list, with an option of prefix
.getGenerics <- function(where, trim = TRUE)
{
if(missing(where)) where <- .envSearch(topenv(parent.frame()))
else if(is.environment(where)) where <- list(where)
these <- unlist(lapply(where, objects, all.names=TRUE), use.names=FALSE)
these <- unique(these)
these <- these[startsWith(these, ".__T__")]
if(length(these) == 0L)
return(character())
funNames <- gsub("^.__T__(.*):([^:]+)", "\\1", these)
## FIXME: length(funNames) == length(these) != 0 ==> this never triggers:
## if(length(funNames) == 0L && any(startsWith(these, ".__M__")))
## warning(sprintf("package %s seems to have out-of-date methods; need to reinstall from source",
## sQuote(getPackageName(where[[1L]]))))
packageNames <- gsub(".__T__(.*):([^:]+(.*))", "\\2", these)
attr(funNames, "package") <- packageNames
## Would prefer following, but may be trouble bootstrapping methods
## funNames <- new("ObjectsWithPackage", funNames, package = packageNames)
if(isTRUE(trim))
funNames
else if(isFALSE(trim))
these
else
gsub(".__T__", as.character(trim), these)
}
cacheMetaData <-
function(where, attach = TRUE, searchWhere = as.environment(where),
doCheck = TRUE)
{
## a collection of actions performed on attach or detach
## to update class and method information.
pkg <- getPackageName(where)
classes <- getClasses(where)
if (attach) {
for(cl in classes) {
## NOT getClassDef, it will use cache
cldef <- get(classMetaName(cl), where)
if(is(cldef, "classRepresentation"))
.cacheClass(cl, cldef, is(cldef, "ClassUnionRepresentation"),
where)
}
} else {
for(cl in classes) {
cldef <- getClassDef(cl, searchWhere)
if(is(cldef, "classRepresentation") &&
identical(cldef@package, pkg)) {
.uncacheClass(cl, cldef)
.removeSuperclassBackRefs(cl, cldef, searchWhere)
if(is(cldef, "ClassUnionRepresentation")) {
subclasses <- names(cldef@subclasses)
for(subclass in subclasses)
.removeSuperClass(subclass, cl)
}
}
}
}
generics <- .getGenerics(where)
packages <- attr(generics, "package")
if(length(packages) < length(generics))
packages <- rep(packages, length.out = length(generics))
if(attach && exists(".requireCachedGenerics", where, inherits = FALSE)) {
others <- get(".requireCachedGenerics", where)
generics <- c(generics, others)
packages <- c(packages, attr(others, "package"))
}
## check for duplicates
dups <- duplicated(generics) & duplicated(packages)
generics <- generics[!dups]
for(i in seq_along(generics)) {
f <- generics[[i]]
fpkg <- packages[[i]]
if(!identical(fpkg, pkg) && doCheck) {
if(attach) {
env <- as.environment(where)
## All instances of this generic in different attached packages must
## agree with the cached version of the generic for consistent
## method selection.
if(exists(f, envir = env, inherits = FALSE)) {
def <- get(f, envir = env)
fdef <- .genericOrImplicit(f, fpkg, env)
if(is.function(def)) {
## exclude a non-function of the same name as a primitive with methods (!)
if(identical(environment(def), environment(fdef)))
next # the methods are identical
else if( is(fdef, "genericFunction")) {
.assignOverBinding(f, fdef, env, FALSE)
}
} # else, go ahead to update primitive methods
}
else # either imported generic or a primitive
fdef <- getGeneric(f, FALSE, searchWhere, fpkg)
}
else
fdef <- getGeneric(f, FALSE, searchWhere, fpkg)
}
else
fdef <- getGeneric(f, FALSE, searchWhere, fpkg)
if(!is(fdef, "genericFunction"))
next ## silently ignores all generics not visible from searchWhere
if(attach)
.cacheGeneric(f, fdef)
else
.uncacheGeneric(f, fdef)
methods <- .updateMethodsInTable(fdef, where, attach)
cacheGenericsMetaData(f, fdef, attach, where, fdef@package, methods)
}
.doLoadActions(where, attach)
invisible(NULL) ## as some people call this at the end of functions
}
cacheGenericsMetaData <- function(f, fdef, attach = TRUE,
where = topenv(parent.frame()),
package, methods)
{
if(!is(fdef, "genericFunction")) {
warning(gettextf("no methods found for %s; cacheGenericsMetaData() will have no effect",
sQuote(f)),
domain = NA)
return(FALSE)
}
if(missing(package))
package <- fdef@package
### Assertion: methods argument unused except for primitives
### and then only for the old non-table case.
deflt <- finalDefaultMethod(fdef@default) #only to detect primitives
if(dispatchIsInternal(fdef)) {
if(missing(methods)) ## "reset"
setPrimitiveMethods(f, deflt, "reset", fdef, NULL)
else ## "set"
setPrimitiveMethods(f, deflt, "set", fdef, methods)
}
else if(isGroup(f, fdef = fdef)) {
members <- fdef@groupMembers
## do the computations for the members as well; important if the
## members are primitive functions.
for(ff in members) {
ffdef <- getGeneric(ff, where = where)
if(is(ffdef, "genericFunction"))
Recall(ff, ffdef, attach, where,
methods = .getMethodsTable(ffdef))
}
}
TRUE
}
setPrimitiveMethods <-
function(f, fdef, code, generic, mlist = get(".Methods", envir = environment(generic)))
.Call(C_R_M_setPrimitiveMethods, f, fdef, code, generic, mlist)
### utility to turn ALL primitive methods on or off (to avoid possible inf. recursion)
.allowPrimitiveMethods <- function(onOff) {
code <- if(onOff) "SET" else "CLEAR"
.Call(C_R_M_setPrimitiveMethods, "", NULL, code, NULL, NULL)
}
findUnique <- function(what, message, where = topenv(parent.frame()))
{
where <- .findAll(what, where = where)
if(length(where) > 1L) {
if(missing(message))
message <- sQuote(what)
if(is.list(where))
where <- unlist(where)
if(is.numeric(where))
where <- search()[where]
warning(message,
sprintf(" found on: %s; using the first one",
paste(sQuote(where), collapse = ", ")),
domain = NA)
where <- where[1L]
}
where
}
MethodAddCoerce <- function(method, argName, thisClass, methodClass)
{
if(.identC(thisClass, methodClass))
return(method)
ext <- possibleExtends(thisClass, methodClass)
## if a non-simple coerce is required to get to the target class for
## dispatch, insert it in the method.
if(is.logical(ext) || ext@simple)
return(method)
methodInsert <- function(method, addExpr) {
if(is.function(method)) {
newBody <- substitute({firstExpr; secondExpr},
list(firstExpr = addExpr,
secondExpr = body(method)))
body(method, envir = environment(method)) <- newBody
}
else if(is(method, "MethodsList")) {
.MlistDeprecated()
methods <- method@allMethods
for(i in seq_along(methods))
methods[[i]] <- Recall(methods[[i]], addExpr)
method@allMethods <- methods
}
method
}
addExpr <- substitute(XXX <- as(XXX, CLASS),
list(XXX = argName, CLASS = methodClass))
methodInsert(method, addExpr)
}
missingArg <- function(symbol, envir = parent.frame(), eval = FALSE)
.Call(C_R_missingArg, if(eval) symbol else substitute(symbol), envir)
balanceMethodsList <- function(mlist, args, check = TRUE)
{
.MlistDeprecated("balanceMethodsList()")
moreArgs <- args[-1L]
if(length(moreArgs) == 0L)
return(mlist)
methods <- mlist@methods
if(check && length(methods)) {
## check whether the current depth is enough (i.e.,
## whether a method with this no. of args or more was set before
depth <- 0
el <- methods[[1L]]
while(is(el, "MethodsList")) {
mm <- el@methods
if(length(mm) == 0L)
break
depth <- depth+1L
el <- mm[[1L]]
}
if(depth >= length(args))
## already balanced to this length: An assertion
## relying on balance having been used consistently,
## which in turn relies on setMethod being called to
## add methods. If you roll your own, tough luck!
return(mlist)
}
for(i in seq_along(methods)) {
el <- methods[[i]]
if(is(el, "MethodsList"))
el <- Recall(el, moreArgs, FALSE)
else {
if(is(el, "MethodDefinition")) {
el@target[moreArgs] <- "ANY"
el@defined[moreArgs] <- "ANY"
}
for(what in rev(moreArgs))
el <- new("MethodsList", argument = as.name(what),
methods = list(ANY = el))
}
methods[[i]] <- el
}
mlist@methods <- methods
mlist
}
sigToEnv <- function(signature, generic)
{
genericSig <- generic@signature
package <- packageSlot(signature)
if(is.null(package))
parent <- environment(generic)
else
parent <- .requirePackage(package)
value <- new.env(parent = parent)
classes <- as.character(signature)
args <- names(signature)
for(i in seq_along(args))
assign(args[[i]], classes[[i]], envir = value)
## missing args in signature have class "ANY"
if(length(args) < length(genericSig))
for(other in genericSig[is.na(match(genericSig, args))])
assign(other, "ANY", envir = value)
value
}
methodSignatureMatrix <- function(object, sigSlots = c("target", "defined"))
{
if(length(sigSlots)) {
allSlots <- lapply(sigSlots, slot, object = object)
n <- max(lengths(allSlots))
mm <- unlist(lapply(allSlots, function(s) {
length(s) <- n
s[is.na(s)] <- "ANY"
s
}))
mm <- matrix(mm, nrow = length(allSlots), byrow = TRUE)
dimnames(mm) <- list(sigSlots, names(allSlots[[1L]]))
mm
}
else matrix(character(), 0L, 0L)
}
.valueClassTest <- function(object, classes, fname)
{
if(length(classes)) {
for(Cl in classes)
if(is(object, Cl)) return(object)
stop(gettextf("invalid value from generic function %s, class %s, expected %s",
sQuote(fname),
dQuote(class(object)),
paste(dQuote(classes), collapse = " or ")),
domain = NA)
}
## empty test is allowed
object
}
.getOrMakeMethodsList <- function(f, where, genericFun)
{
allMethods <- getMethodsMetaData(f, where = where)
if(is.null(allMethods)) {
argName <- genericFun@signature[[1L]]
warning("\"MethodsList\" is defunct; allMethods now are empty")
##- allMethods <- new("MethodsList", argument = as.name(argName))
# other <- getMethodsMetaData(f)
# if(is.null(other))
# ## this utility is called AFTER ensuring the existence of a generic for f
# ## Therefore, the case below can only happen for a primitive for which
# ## no methods currently are attached. Make the primitive the default
# deflt <- getFunction(f, generic = FALSE, mustFind = FALSE)
# else
# ## inherit the default method, if any
# deflt <- finalDefaultMethod(other)
# if(!is.null(deflt))
# allMethods <- insertMethod(allMethods, "ANY", argName, deflt)
}
allMethods
}
.makeCallString <- function(def, name = substitute(def), args = formalArgs(def))
{
if(is.character(def)) {
if(missing(name))
name <- def
def <- getFunction(def)
}
if(is.function(def))
paste0(name, "(", paste(args, collapse=", "), ")")
else
""
}
.ValidateValueClass <- function(fdef, name, valueClass)
{
## include tests for value
fbody <- body(fdef)
body(fdef, envir = environment(fdef)) <-
substitute({
ans <- EXPR
.valueClassTest(ans, VALUECLASS, FNAME)
}, list(EXPR = fbody, VALUECLASS = valueClass, FNAME = name))
fdef
}
## interpret the group= argument to makeGeneric, allowing for char. argument
## and "" for compatibility.
## TO DO: make it possible for this argument to be a group generic function
## (it may in fact work now).
.asGroupArgument <- function(group)
{
if(is.character(group)) {
if(identical(group, ""))
list()
else
as.list(group) ## should we allow c(group, package) ?
}
else
group
}
metaNameUndo <- function(strings, prefix, searchForm = FALSE)
{
pattern <- methodsPackageMetaName(prefix, "")
n <- nchar(pattern, "c")
matched <- substr(strings, 1L, n) == pattern
value <- substring(strings[matched], n+1L)
pkg <- sub("^[^:]*", "", value) # will be "" if no : in the name
if(searchForm) {
global <- grep(".GlobalEnv", value)
if(length(global)) {
pkg[-global] <- paste0("package", pkg[-global])
pkg[global] <- substring(pkg[global], 2L)
}
}
else
pkg <- substring(pkg, 2L)
value <- sub(":.*","", value)
new("ObjectsWithPackage", value, package = pkg)
}
.recursiveCallTest <- function(x, fname)
{
if(is(x, "call")) {
if(identical(x[[1L]], quote(standardGeneric))) {
if(!identical(x[[2L]], fname))
warning(gettextf("the body of the generic function for %s calls 'standardGeneric' to dispatch on a different name (\"%s\")!",
sQuote(fname),
paste(as.character(x[[2L]]), collapse = "\n")),
domain = NA)
TRUE
}
else {
for(i in seq.int(from=2L, length.out = length(x)-1L)) {
if(Recall(x[[i]], fname))
return(TRUE)
}
FALSE
}
}
else if(is(x, "language")) {
for(i in seq.int(from=2L, length.out = length(x)-1L)) {
if(Recall(x[[i]], fname))
return(TRUE)
}
FALSE
}
else
FALSE
}
.NonstandardGenericTest <- function(body, fname, stdBody)
{
if(identical(body, stdBody))
FALSE
else if(.recursiveCallTest(body, fname))
TRUE
else
NA
}
.GenericInPrimitiveMethods <- function(mlist, f)
{
methods <- mlist@methods
for(i in seq_along(methods)) {
mi <- methods[[i]]
if(is.function(mi)) {
body(mi, envir = environment(mi)) <-
substitute({.Generic <- FF; BODY},
list(FF = f,BODY = body(mi)))
}
else if(is(mi, "MethodsList")) {
.MlistDeprecated()
mi <- Recall(mi, f)
} else
stop(sprintf("internal error: Bad methods list object in fixing methods for primitive function %s",
sQuote(f)),
domain = NA)
methods[[i]] <- mi
}
mlist@methods <- methods
mlist
}
.signatureString <- function(fdef, signature)
{
snames <- names(signature)
if(is.null(snames)) {
if(is(fdef, "genericFunction")) {
snames <- fdef@signature
signature <- matchSignature(signature, fdef)
if(length(snames) > length(signature))
length(snames) <- length(signature)
}
else # shouldn't happen,...
return(paste(signature, collapse=", "))
}
else
signature <- as.character(signature)
paste(paste0(snames, "=\"", signature, "\""), collapse = ", ")
}
.ChangeFormals <- function(def, defForArgs, msg = "<unidentified context>")
{
if(!is.function(def))
stop(gettextf("trying to change the formal arguments in %s in an object of class %s; expected a function definition",
msg, dQuote(class(def))),
domain = NA)
if(!is.function(defForArgs))
stop(gettextf("trying to change the formal arguments in %s, but getting the new formals from an object of class %s; expected a function definition",
msg, dQuote(class(def))),
domain = NA)
old <- formalArgs(def)
new <- formalArgs(defForArgs)
if(length(old) < length(new))
stop(gettextf("trying to change the formal arguments in %s, but the number of existing arguments is less than the number of new arguments: (%s) vs (%s)",
msg, paste0("\"", old, "\"", collapse=", "),
paste0("\"", new, "\"", collapse=", ")),
domain = NA)
if(length(old) > length(new))
warning(gettextf("trying to change the formal arguments in %s, but the number of existing arguments is greater than the number of new arguments (the extra arguments won't be used): (%s) vs (%s)",
msg, paste0("\"", old, "\"", collapse=", "),
paste0("\"", new, "\"", collapse=", ")),
domain = NA)
if(identical(old, new)) # including the case of 0 length
return(def)
dlist <- as.list(def)
slist <- lapply(c(old, new), as.name)
names(slist) <- c(new, old)
vlist <- dlist
for(i in seq_along(vlist))
vlist[[i]] <- do.call("substitute", list(vlist[[i]], slist))
dnames <- names(dlist)
whereNames <- match(old, dnames)
if(anyNA(whereNames))
stop(gettextf("in changing formal arguments in %s, some of the old names are not in fact arguments: %s",
msg, paste0("\"", old[is.na(match(old, names(dlist)))], "\"", collapse=", ")),
domain = NA)
dnames[whereNames] <- new
names(vlist) <- dnames
as.function(vlist, envir = environment(def))
}
## The search list, or a namespace's static search list, or an environment
.envSearch <- function(env = topenv(parent.frame()))
{
if(identical(env, .GlobalEnv))
seq_along(search())
else if(isNamespace(env) && !isBaseNamespace(env)) {
## the static environments for this namespace, ending with the base namespace
value <- list(env)
repeat {
if(identical(env, emptyenv()))
stop("botched namespace: failed to find 'base' namespace in its parents", domain = NA)
env <- parent.env(env)
value <- c(value, list(env))
if(isBaseNamespace(env))
break
}
value
}
else
list(env)
}
.genericName <- function(f)
{
if(is(f, "genericFunction"))
f@generic
else
as.character(f)
}
## the environment in which to start searching for methods, etc. related
## to this generic function. Will normally be the namespace of the generic's
## home package, or else the global environment
.genericEnv <- function(fdef)
parent.env(environment(fdef))
## the default environment in which to start searching for methods, etc. relative to this
## call to a methods package utility. In the absence of other information, the current
## strategy is to look at the function _calling_ the methods package utility.
##TODO: this utility can't really work right until the methods package itself has a
## namespace, so that calls from within the package can be detected. The
## heuristic is that all callers are skipped as long as their enviornment is identical
## to .methodsNamespace. But that is currently initialized to .GlobalEnv.
##
## The logic will fail if a function in a package with a namespace calls a (non-methods)
## function in a package with no namespace, and that function then calls a methods package
## function. The right answer then is .GlobalEnv, but we will instead get the package
## namespace.
.externalCallerEnv <- function(n = 2, nmax = sys.nframe() - n + 1)
{
## start n generations back; by default the caller of the caller to this function
## go back nmax at most (e.g., a function in the methods package that knows it's never
## called more than nmax levels in could supply this argument
if(nmax < 1) stop("got a negative maximum number of frames to look at")
ev <- topenv(parent.frame()) # .GlobalEnv or the environment in which methods is being built.
for(back in seq.int(from = -n, length.out = nmax)) {
fun <- sys.function(back)
if(is.function(fun)) {
## Note that "fun" may actually be a method definition, and still will be counted.
## This appears to be the correct semantics, in
## the sense that, if the call came from a method, it's the method's environment
## where one would expect to start the search (for a class definition, e.g.)
ev <- environment(fun)
if(!identical(ev, .methodsNamespace))
break
}
}
ev
}
## a list of environments, starting from ev, going back to the base package,
## or else terminated by finding a namespace
.parentEnvList <- function(ev)
{
ev <- as.environment(ev)
value <- list(ev)
while(!isNamespace(ev)) {
if(identical(ev, baseenv())) {
value[[length(value)]] <- .BaseNamespaceEnv
break
} else if(identical(ev, emptyenv())) {
break
}
ev <- parent.env(ev)
value <- c(value, list(ev))
}
value
}
.genericAssign <- function(f, fdef, methods, where, deflt)
{
ev <- environment(fdef)
assign(".Methods", methods, ev)
}
## Mark the method as derived from a non-generic.
.derivedDefaultMethod <- function(fdef, internal = NULL)
{
if(is.function(fdef) && !is.primitive(fdef)) {
if (!is.null(internal)) {
value <- new("internalDispatchMethod", internal = internal)
} else {
value <- new("derivedDefaultMethod")
}
value@.Data <- fdef
value@target <- value@defined <- .newSignature(list(.anyClassName), formalArgs(fdef))
value
}
else
fdef
}
.identC <- function(c1 = NULL, c2 = NULL)
{
## are the two objects identical class or genric function string names?
.Call(C_R_identC, c1, c2)
}
## match default exprs in the method to those in the generic
## if the method does not itself specify a default, and the
## generic does
matchDefaults <- function(method, generic)
{
changes <- FALSE
margs <- formals(method)
gargs <- formals(generic)
for(arg in names(margs)) {
##!! weird use of missing() here is required by R's definition
## of a missing arg as a name object with empty ("") name
## This is dangerously kludgy code but seems the only way
## to avoid spurious errors ("xxx missing with no default")
marg <- margs[[arg]]
garg <- gargs[[arg]]
if(missing(marg) && !missing(garg)) {
changes <- TRUE
margs[arg] <- gargs[arg] # NOT [[]], which woud fail for NULL element
}
}
if(changes)
formals(method, envir = environment(method)) <- margs
method
}
getGroupMembers <- function(group, recursive = FALSE, character = TRUE)
{
.recMembers <- function(members, where) {
all = vector("list", length(members))
for(i in seq_along(members)) {
what <- members[[i]]
f <- getGeneric(what, FALSE, where)
if(!is.null(f))
all[[i]] <- what
if(is(f, "groupGenericFunction")) {
newMem <- f@groupMembers
all <- c(all, Recall(newMem, where))
}
}
all
}
f <- getGeneric(group)
if(is.null(f)) {
warning(gettextf("%s is not a generic function (or not visible here)",
sQuote(f)),
domain = NA)
return(character())
}
else if(!is(f, "groupGenericFunction"))
character()
else {
members <- f@groupMembers
if(recursive) {
where <- f@package
if(identical(where, "base")) {
where <- "methods" # no generics actually on base
members <- .recMembers(members, .methodsNamespace)
}
else
members <- .recMembers(members, .requirePackage(where))
}
if(character)
sapply(members, function(x){
if(is(x, "character"))
x
else if(is(x, "genericFunction"))
x@generic
else
stop(gettextf("invalid element in the \"groupMembers\" slot (class %s)",
dQuote(class(x))),
domain = NA)
})
else
members
}
}
.primname <- function(object)
{
## the primitive name is 'as.double', but S4 methods are
## traditionally set on 'as.numeric'
f <- .Call(C_R_get_primname, object)
if(f == "as.double") "as.numeric" else f
}
.copyMethodDefaults <- function(generic, method)
{
emptyDefault <- function(value) missing(value) ||
(is.name(value) && nzchar(as.character(value)) )
fg <- formals(generic)
mg <- formals(method)
emptyDef <- vapply(mg, emptyDefault, logical(1L))
mg <- mg[!emptyDef]
i <- match(names(fg), names(mg))
formals(generic)[!is.na(i)] <- mg[i[!is.na(i)]]
generic
}
.NamespaceOrPackage <- function(what)
{
name <- as.name(what)
ns <- .getNamespace(name)
if(!is.null(ns))
asNamespace(ns)
else {
i <- match(paste0("package:", what), search())
if(is.na(i))
.GlobalEnv
else
as.environment(i)
}
}
.NamespaceOrEnvironment <- function(where)
{
value <- NULL
if(is.environment(where))
value <- where
else if(is.character(where) && nzchar(where)) {
ns <- .getNamespace(where)
if(isNamespace(ns))
value <- ns
else if(where %in% search())
value <- as.environment(where)
else {
where <- paste0("package:", where)
if(where %in% search())
value <- as.environment(where)
}
}
else if(is.numeric(where) && where %in% seq_along(search()))
value <- as.environment(where)
value
}
## is this really right?
## cf .methodsPackageMetaNamePattern <- "^[.]__[A-Z]+__"
.hasS4MetaData <- function(env) {
nms <- names(env)
any(startsWith(nms, ".__C__")) ||
any(startsWith(nms, ".__T__")) ||
any(startsWith(nms, ".__A__"))
}
## turn ordinary generic into one that dispatches on "..."
## currently only called in one place from setGeneric()
.dotsGeneric <- function(f)
{
if(!is(f, "genericFunction"))
f <- getGeneric(f)
if(!is(f, "genericFunction") || !identical(f@signature, "..."))
stop("argument f must be a generic function with signature \"...\"")
def <- .standardGenericDots
body(def) <- eval(call("substitute", body(def),
list(.dotsMethod=as.name(f@generic))))
environment(def) <- environment(f)
assign("standardGeneric", def, envir = environment(f))
f
}
utils::globalVariables(c(".MTable", ".AllMTable", ".dotsCall"))
## NB this is used with a modified environment in .dotsGeneric,
## so methods::: calls are necessary.
.standardGenericDots <- function(name)
{
env <- sys.frame(sys.parent())
dots <- eval(quote(list(...)), env)
classes <- unique(unlist(lapply(dots, methods:::.class1)))
method <- methods:::.selectDotsMethod(classes, .MTable, .AllMTable)
if(is.null(method))
stop(gettextf("no method or default matching the \"...\" arguments in %s",
deparse(sys.call(sys.parent()), nlines = 1)), domain = NA)
mc <- match.call(sys.function(sys.parent()), sys.call(sys.parent()),
expand.dots=FALSE, envir=parent.frame(2))
args <- names(mc)[-1L]
mc[args] <- lapply(args, as.name)
names(mc)[names(mc) == "..."] <- ""
mc[[1L]] <- quote(.dotsMethod)
assign(name, method, env)
eval(mc, env)
}
.selectDotsMethod <- function(classes, mtable, allmtable)
{
.pasteC <- function(names) paste0('"', names, '"', collapse = ", ")
found <- character()
distances <- numeric()
methods <- names(mtable)
direct <- classes %in% methods
if(all(direct)) {
if(length(classes) > 1L) {
warning(gettextf("multiple direct matches: %s; using the first of these", .pasteC(classes)), domain = NA)
classes <- classes[1L]
}
else if(length(classes) == 0L)
return( if(is.na(match("ANY", methods))) NULL else get("ANY", envir = mtable))
return(mtable[[classes]])
}
if(is.null(allmtable))
return(NULL)
## Else, look for an acceptable inherited method, which must match or be a superclass
## of the class of each of the arguments.
classes <- sort(classes) # make slection depend only on the set of classes
label <- .sigLabel(classes)
if(exists(label, envir = allmtable, inherits = FALSE))
## pre-cached, but possibly NULL to indicate no match
return(get(label, envir = allmtable))
for(i in seq_along(classes)) {
classi <- classes[[i]]
defi <- getClassDef(classi)
if(is.null(defi)) next
extendsi <- defi@contains
namesi <- c(classi, names(extendsi))
if(i == 1)
namesi <- namesi[namesi %in% methods]
else { # only the superclass methods matching all arguments are kept
namesi <- namesi[namesi %in% found]
found <- namesi
if(length(found) == 0L) break # no possible non-default match
}
for(namei in namesi) {
disti <- if(identical(namei, classi)) 0 else extendsi[[namei]]@distance
prev <- match(namei, found)
if(is.na(prev)) { # must be the 1st element
found <- c(found, namei)
distances <- c(distances, disti)
}
else if(disti < distances[[prev]])
distances[[prev]] <- disti
}
}
if(length(found) == 0L)
method <- if(is.na(match("ANY", methods))) NULL else get("ANY", envir = mtable)
else {
classes <- found[which.min(distances)]
if(length(classes) > 1L) {
warning(gettextf("multiple equivalent inherited matches: %s; using the first of these",
.pasteC(classes)), domain = NA)
classes <- classes[1L]
}
method <- get(classes,envir = mtable)
}
if(!is.null(method))
method@target <- new("signature", ... = label) # ?? not a legal class name if > 1 classes
assign(label, method, allmtable)
method
}
.isSingleString <- function(what)
is.character(what) && isTRUE(nzchar(what))
.notSingleString <- function(what)
{
if(identical(what, ""))
"non-empty string; got \"\""
else if(is.character(what))
paste("single string; got a character vector of length", length(what))
else
gettextf("single string; got an object of class %s",
dQuote(class(what)[[1L]]))
}
.dotsClass <- function(...) {
if(missing(..1))
"missing"
else
class(..1)
}
## a utility to exclude various annoying glitches during
## loading of the methods package
.methodsIsLoaded <- function()
isTRUE(.saveImage)
if(FALSE) {
## Defined but not currently used:
## utilitity to test well-defined classes in signature,
## for setMethod(), setAs() [etc.?], the result to be
## assigned in package where=
## Returns a list of signature, messages and level of error
## Has undefined ns an package
.validSignature <- function(signature, generic, where) {
thisPkg <- getPackageName(where, FALSE)
checkDups <- .duplicateClassesExist()
if(is(signature, "character")) { # including class "signature"
classes <- as.character(signature)
names <- allNames(signature)
pkgs <- attr(signature, "package")
}
else if(is(signature, "list")) {
classes <- sapply(signature, as.character)
names <- names(signature)
pkgs <- character(length(signature))
for(i in seq_along(pkgs)) {
pkgi <- attr(signature[[i]], "package")
pkgs[[i]] <- if(is.null(pkgi)) "" else pkgi
}
}
msgs <- character(); level <- integer()
for(i in seq_along(classes)) {
## classes must be defined
## if duplicates exist check for them
## An ambiguous duplicate is a warning if it can match thisPkg
## else, an error
classi <- classes[[i]]
pkgi <- pkgs[[i]]
classDefi <- getClassDef(classi, where=if (pkgi == "") where else pkgi)
if(checkDups && classi %in% multipleClasses()) { # hardly ever, we hope
clDefsi <- get(classi, envir = .classTable)
if(nzchar(pkgi) && pkgi %in% names(clDefsi))
## use the chosen class, no message
classDefi <- clDefsi[[pkgi]]
else if(nzchar(pkgi)){
## this is only a warning because it just might
## be the result of identical class defs (e.g., from setOldClass()
msgs <- c(msgs,
gettextf("multiple definitions exist for class %s, but the supplied package (%s) is not one of them (%s)",
dQuote(classi), sQuote(pkgi),
paste(dQuote(get(classi, envir = .classTable)), collapse = ", ")))
level <- c(level, 2) #warn
}
else {
msgs <- c(msgs,
gettextf("multiple definitions exist for class %s; should specify one of them (%s), e.g. by className()",
dQuote(classi),
paste(dQuote(get(classi, envir = .classTable)), collapse = ", ")))
}
}
else {
## just possibly the first reference to an available
## package not yet loaded. It's an error to specify
## a non-loadable package
if(nzchar(pkgi)) {
loadNamespace(pkgi)
classDefi <- getClass(classi, where = ns)
}
if(is.null(classDefi)) {
classDefi <- getClassDef
msgi <- gettextf("no definition found for class %s",
dQuote(classi))
## ensure only one error message
if(length(level) && any(level == 3))
msgs[level == 3] <- paste(msgs[level == 3], msgi, sep = "; ")
else
msgs <- c(msgs, msgi)
level <- c(level, 3)
}
## note that we do not flag a pkgi different from
## the package of the def., mainly because of setOldClass()
## which currently generates potentially multiple versions
## of the same S3 class.
}
## except for the obscure multiple identical class case
## we should not get here w/o a valid class def.
if(is.null(classDefi)) {}
else
pkgs[[i]] <- classDefi@package
}
signature <- .MakeSignature(new("signature"), generic,
structure(classes, names = names, package = package))
if(length(msgs) > 1) {
## sort by severity, to get all messages before errror
ii <- sort.list(level)
msgs <- msgs[ii]; level <- level[ii]
}
list(signature = signature, message = msgs, level = level)
}
}
.ActionMetaPattern <- function()
paste0("^[.]",substring(methodsPackageMetaName("A",""),2))
.actionMetaName <- function(name)
methodsPackageMetaName("A", name)
.doLoadActions <- function(where, attach) {
## at the moment, no unload actions
if(!attach)return()
actionListName <- .actionMetaName("")
if(!exists(actionListName, envir = where, inherits = FALSE))
return(list())
actions <- get(actionListName, envir = where)
for(what in actions) {
aname <- .actionMetaName(what)
if(!exists(aname, envir = where, inherits = FALSE)) {
warning(gettextf("missing function for load action: %s", what))
next
}
f <- get(aname, envir = where)
value <- eval(substitute(tryCatch(FUN(WHERE), error = function(e)e),
list(FUN = f, WHERE = where)), where)
if(is(value, "error")) {
callString <- deparse(value$call)[[1]]
stop(gettextf("error in load action %s for package %s: %s: %s",
aname, getPackageName(where), callString, value$message))
}
}
}
setLoadAction <- function(action,
aname = "",
where = topenv(parent.frame())) {
currentAnames <- .assignActionListNames(where)
if(!nzchar(aname))
aname <- paste0(".", length(currentAnames)+1)
.assignActions(list(action), aname, where)
if(is.na(match(aname, currentAnames))) {
actionListName <- .actionMetaName("")
assign(actionListName, c(currentAnames, aname), envir = where)
}
}
.assignActions <- function(actions, anames, where) {
## check all the actions before assigning any
for(i in seq_along(actions)) {
f <- actions[[i]]
fname <- anames[[i]]
if(!is.function(f))
stop(gettextf("non-function action: %s",
sQuote(fname)),
domain = NA)
if(length(formals(f)) == 0)
stop(gettextf("action function %s has no arguments, should have at least 1",
sQuote(fname)),
domain = NA)
}
for(i in seq_along(actions))
assign(.actionMetaName(anames[[i]]), actions[[i]], envir = where)
}
.assignActionListNames <- function(where) {
actionListName <- .actionMetaName("")
if(exists(actionListName, envir = where, inherits = FALSE))
get(actionListName, envir = where)
else
character()
}
setLoadActions <- function(..., .where = topenv(parent.frame())) {
actionListName <- .actionMetaName("")
currentAnames <- .assignActionListNames(.where)
actions <- list(...)
anames <- allNames(actions)
## first, replacements
previous <- anames %in% currentAnames
if(any(previous)) {
.assignActions(actions[previous], anames[previous], .where)
if(all(previous))
return(list())
anames <- anames[!previous]
actions <- actions[!previous]
}
anon <- !nzchar(anames)
if(any(anon)) {
n <- length(currentAnames)
deflts <- paste0(".",seq(from = n+1, length.out = length(actions)))
anames[anon] <- deflts[anon]
}
.assignActions(actions, anames, .where)
assign(actionListName, c(currentAnames, anames), envir = .where)
}
hasLoadAction <- function(aname, where = topenv(parent.frame()))
exists(.actionMetaName(aname), envir = where, inherits = FALSE)
getLoadActions <- function(where = topenv(parent.frame())) {
actionListName <- .actionMetaName("")
if(!exists(actionListName, envir = where, inherits = FALSE))
return(list())
actions <- get(actionListName, envir = where)
if(length(actions)) {
allExists <- sapply(actions, function(what) exists(.actionMetaName(what), envir = where, inherits = FALSE))
if(!all(allExists)) {
warning(gettextf("some actions are missing: %s",
paste(actions[!allExists], collapse =", ")),
domain = NA)
actions <- actions[allExists]
}
allFuns <- lapply(actions, function(what) get(.actionMetaName(what), envir = where))
names(allFuns) <- actions
allFuns
}
else
list()
}
evalOnLoad <- function(expr, where = topenv(parent.frame()), aname = "") {
f <- function(env)NULL
body(f, where) <- substitute(eval(EXPR,ENV), list(EXPR = expr, ENV = where))
setLoadAction(f, aname, where)
}
evalqOnLoad <- function(expr, where = topenv(parent.frame()), aname = "")
evalOnLoad(substitute(expr), where, aname)
## a utility function used to flag non-generics at the loadNamespace phase
## The calculation there used to ignore the generic cache, which is wrong logic
## if the package being loaded had a DEPENDS on a package containing the generic
## version of the function.
.findsGeneric <- function(what, ns) {
if(is(get(what, mode = "function", envir = ns), "genericFunction"))
1L
else if(!is.null(.getGenericFromCache(what, ns)))
2L
else
0L
}
## test whether this function _could be_ an S3 generic, either
## a primitive or a function calling UseMethod()
isS3Generic <- function(fdef) {
switch(typeof(fdef),
"special" = FALSE,
"builtin" = TRUE,
## otherwise:
"UseMethod" %in% .getGlobalFuns(fdef)) # from refClass.R
}