| # File src/library/methods/R/Methods.R |
| # Part of the R package, https://www.R-project.org |
| # |
| # Copyright (C) 1995-2018 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/ |
| |
| ## copy here to avoid importing from stats and hence loading stats |
| ## namespace when methods if loaded |
| setNames <- stats::setNames |
| |
| |
| setGeneric <- |
| ## Define `name' to be a generic function, for which methods will be defined. |
| ## |
| ## If there is already a non-generic function of this name, it will be used |
| ## to define the generic unless `def' is supplied, and the current |
| ## function will become the default method for the generic. |
| ## |
| ## If `def' is supplied, this defines the generic function. The |
| ## default method for a new generic will usually be an existing |
| ## non-generic. See the .Rd page |
| ## |
| function(name, def = NULL, group = list(), valueClass = character(), |
| where = topenv(parent.frame()), |
| package = NULL, signature = NULL, |
| useAsDefault = NULL, genericFunction = NULL, |
| simpleInheritanceOnly = NULL) |
| { |
| if(is.character(.isSingleName(name))) |
| stop(gettextf("invalid argument 'name': %s", |
| .isSingleName(name)), domain = NA) |
| if(exists(name, "package:base") && inBasicFuns(name)) { |
| |
| name <- switch(name, "as.double" = "as.numeric", name) |
| fdef <- getGeneric(name) # will fail if this can't have methods |
| compatibleSignature <- nargs() == 2L && !missing(signature) && |
| identical(signature, fdef@signature) |
| if(nargs() <= 1 || compatibleSignature) { |
| ## generics for primitives are global, so can & must always be cached |
| .cacheGeneric(name, fdef) |
| return(name) |
| } |
| ## you can only conflict with a primitive if you supply |
| ## useAsDefault to signal you really mean a different function |
| if(!is.function(useAsDefault) && !isFALSE(useAsDefault)) { |
| msg <- gettextf("%s dispatches internally; methods can be defined, but the generic function is implicit, and cannot be changed.", sQuote(name)) |
| stop(msg, domain = NA) |
| } |
| } |
| simpleCall <- { nargs() < 2 || |
| all(missing(def), missing(group), missing(valueClass), |
| missing(package), missing(signature), missing(useAsDefault), |
| missing(genericFunction), missing(simpleInheritanceOnly)) } |
| stdGenericBody <- substitute(standardGeneric(NAME), list(NAME = name)) |
| ## get the current function which may already be a generic |
| fdef <- |
| if(is.null(package)) |
| getFunction(name, mustFind = FALSE, where = where) |
| else { |
| ev <- .NamespaceOrPackage(package) |
| if(simpleCall) |
| implicitGeneric(name, ev) # generic or NULL |
| else |
| getFunction(name, mustFind = FALSE, where = ev) |
| } |
| if(simpleCall) { |
| if(is(fdef, "genericFunction")) |
| return(.GenericAssign(name, fdef, where)) |
| } |
| if(is.null(fdef)) { |
| if(isNamespace(where)) |
| fdef <- .getFromStandardPackages(name) |
| else |
| fdef <- getFunction(name, mustFind = FALSE) |
| } |
| if(is.null(fdef) && is.function(useAsDefault)) |
| fdef <- useAsDefault |
| ## Use the previous function definition to get the default |
| ## and to set the package if not supplied. |
| doUncache <- FALSE |
| if(is.object(fdef) && is(fdef, "genericFunction")) { |
| doUncache <- TRUE |
| oldDef <- fdef |
| prevDefault <- finalDefaultMethod(fdef@default) |
| if(is.null(package)) |
| package <- fdef@package |
| } |
| else if(is.function(fdef)) { |
| prevDefault <- fdef |
| if(is.primitive(fdef)) package <- "base" |
| if(is.null(package)) |
| package <- getPackageName(environment(fdef)) |
| } |
| else |
| prevDefault <- NULL |
| |
| if(is.primitive(fdef)) ## get the pre-defined version |
| fdef <- getGeneric(name, where = where) |
| else if(is.function(fdef)) |
| body(fdef, envir = as.environment(where)) <- stdGenericBody |
| if(!is.null(def)) { |
| if(is.primitive(def) || !is.function(def)) |
| stop(gettextf("if the 'def' argument is supplied, it must be a function that calls standardGeneric(\"%s\") or is the default", |
| name), domain = NA) |
| nonstandardCase <- .NonstandardGenericTest(body(def), name, stdGenericBody) |
| if(is.na(nonstandardCase)) { |
| if(is.null(useAsDefault)) {# take this as the default |
| useAsDefault <- def |
| } |
| body(def, envir = as.environment(where)) <- stdGenericBody |
| nonstandardCase <- FALSE |
| } |
| fdef <- def |
| if(is.null(genericFunction) && nonstandardCase) |
| genericFunction <- new("nonstandardGenericFunction") # force this class for fdef |
| } |
| thisPackage <- getPackageName(where) |
| if(is.null(package) || !nzchar(package)) |
| ## either no previous def'n or failed to find its package name |
| package <- thisPackage |
| if(is.null(fdef)) |
| stop(gettextf("must supply a function skeleton for %s, explicitly or via an existing function", sQuote(name)), domain = NA) |
| ensureGeneric.fdef <- function(sig = signature) { |
| if(!(is.object(fdef) && is(fdef, "genericFunction"))) { |
| fdeflt <- |
| if(is.function(useAsDefault)) useAsDefault |
| else if(isFALSE(useAsDefault)) NULL |
| else if(is.function(prevDefault) && |
| !identical(formalArgs(prevDefault), formalArgs(fdef)) && |
| !is.primitive(prevDefault)) |
| NULL |
| else prevDefault |
| if(is.function(fdeflt)) |
| fdeflt <- .derivedDefaultMethod(fdeflt) |
| fdef <<- |
| makeGeneric(name, fdef, fdeflt, group=group, valueClass=valueClass, |
| package = package, signature = sig, |
| genericFunction = genericFunction, |
| simpleInheritanceOnly = simpleInheritanceOnly) |
| } |
| } |
| if(identical(package, thisPackage)) { |
| ensureGeneric.fdef() |
| } else { |
| ## setting a generic for a function in another package. |
| ## In this case, the generic definition must agree with the implicit |
| ## generic for the given function and package |
| implicit <- implicitGeneric(name, .NamespaceOrPackage(package)) |
| if(is.null(implicit)) { # New function, go ahead |
| ensureGeneric.fdef() |
| } |
| else { |
| ## possibly take the signature from the *implicit* generic: |
| ensureGeneric.fdef(if(is.null(signature) && is.null(def)) |
| implicit@signature else signature) |
| cmp <- .identicalGeneric(fdef, implicit, |
| allow.extra.dots = |
| !nzchar(Sys.getenv("R_SETGENERIC_PICKY_DOTS"))) |
| if(isTRUE(cmp)) { |
| fdef <- implicit |
| } # go ahead silently |
| else if(is.function(implicit)) { |
| thisPName <- if(identical(thisPackage, ".GlobalEnv")) |
| "the global environment" else paste("package", sQuote(thisPackage)) |
| ## choose the implicit unless an explicit def was given |
| if(is.null(def) && is.null(signature)) { |
| message(gettextf( |
| "Creating a generic function for %s from %s in %s\n (from the saved implicit definition)", |
| sQuote(name), sQuote(package), |
| thisPName), domain = NA) |
| fdef <- implicit |
| } |
| else { |
| message(gettextf( |
| "Creating a new generic function for %s in %s", |
| sQuote(name), thisPName), |
| domain = NA) |
| fdef@package <- packageSlot(fdef@generic) <- packageSlot(environment(fdef)$.Generic) <- thisPackage |
| } |
| } |
| else { # generic prohibited |
| warning(gettextf( |
| "no generic version of %s on package %s is allowed;\n a new generic will be assigned for %s", |
| sQuote(name), sQuote(package), |
| thisPName), |
| domain = NA) |
| fdef@package <- packageSlot(fdef@generic) <- packageSlot(environment(fdef)$.Generic) <- thisPackage |
| } |
| } |
| } |
| if(identical(fdef@signature, "...")) |
| fdef <- .dotsGeneric(fdef) |
| if(doUncache) |
| .uncacheGeneric(name, oldDef) |
| groups <- fdef@group |
| for(group in groups) { # add as member of group generic(s) if not there |
| gdef <- getGeneric(group) |
| if(is(gdef, "groupGenericFunction") && |
| is.na(match(fdef@generic, as.character(gdef@groupMembers)))) { |
| gwhere <- .genEnv(group, where) |
| gdef@groupMembers <- c(gdef@groupMembers, list(fdef@generic)) |
| assign(group, gdef, gwhere) |
| } |
| } |
| .GenericAssign(name, fdef, where) |
| } |
| |
| .GenericAssign <- function(name, fdef, where) { |
| assign(name, fdef, where) |
| .cacheGeneric(name, fdef) |
| methods <- fdef@default # empty or containing the default |
| assignMethodsMetaData(name, methods, fdef, where) |
| .assignMethodsTableMetaData(name, fdef, where) |
| name |
| } |
| |
| ## Mimic the search for a function in the standard search() list for packages |
| ## with namespace, to be consistent with the evaluator's search for objects |
| ### Deprecate? Seems like we should search the imports, not the search path |
| .standardPackageNamespaces <- new.env() |
| .standardPackages <- c("stats", "graphics", "grDevices", "utils", "datasets", "methods") |
| .getFromStandardPackages <- function(name) { |
| namespaces <- as.list(.standardPackageNamespaces, all.names=TRUE) |
| if(length(namespaces) == 0L) { # initialize the table of namespaces |
| namespaces <- lapply(.standardPackages, function(pkg) { |
| tryCatch(loadNamespace(pkg), |
| error = function(e) new.env()) |
| }) |
| names(namespaces) <- .standardPackages |
| list2env(namespaces, .standardPackageNamespaces) |
| } else { |
| for(ns in namespaces) { |
| obj <- ns[[name]] |
| if(is.function(obj)) |
| return(obj) |
| } |
| } |
| return(NULL) |
| } |
| |
| ## |
| ## make a generic function object corresponding to the given function name. |
| ## |
| |
| isGeneric <- |
| ## Is there a function named `f', and if so, is it a generic? |
| ## |
| ## If the `fdef' argument is supplied, take this as the definition of the |
| ## generic, and test whether it is really a generic, with `f' as the name of |
| ## the generic. (This argument is not available in S-Plus.) |
| function(f, where = topenv(parent.frame()), fdef = NULL, getName = FALSE) |
| { |
| if(is.null(fdef) && missing(where)) { |
| fdef <- .getGenericFromCache(f, where) |
| ## a successful search will usually end here w/o other tests |
| if(!is.null(fdef)) |
| return(if(getName) fdef@generic else TRUE) |
| } |
| if(is.null(fdef)) |
| fdef <- getFunction(f, where=where, mustFind = FALSE) |
| if(is.null(fdef)) |
| return(FALSE) |
| ## check primitives. These are never found as explicit generic functions. |
| if(isBaseFun(fdef)) { |
| if(is.character(f) && f %in% "as.double") f <- "as.numeric" |
| ## the definition of isGeneric() for a base function is that methods are defined |
| ## (other than the default primitive) |
| gen <- genericForBasic(f, mustFind = FALSE) |
| return(is.function(gen) && length(names(.getMethodsTable(gen))) > 1L) |
| } |
| if(!is(fdef, "genericFunction")) |
| return(FALSE) |
| gen <- fdef@generic # the name with package attribute |
| if(missing(f) || .identC(gen, f)) { |
| if(getName) |
| gen |
| else |
| TRUE |
| } |
| else { |
| warning(gettextf("function %s appears to be a generic function, but with generic name %s", |
| sQuote(f), sQuote(gen)), |
| domain = NA) |
| FALSE |
| } |
| } |
| |
| removeGeneric <- |
| ## Remove the generic function of this name, specifically the first version |
| ## encountered from environment where |
| ## |
| function(f, where = topenv(parent.frame())) |
| { |
| fdef <- NULL |
| allEv <- findFunction(f, where = where) |
| for(maybeEv in allEv) { |
| fdef <- get(f, maybeEv) |
| if(is(fdef, "genericFunction")) |
| break |
| } |
| found <- is(fdef, "genericFunction") |
| if(found) { |
| .removeMethodsMetaTable(fdef, where) |
| oldMetaName <- methodsPackageMetaName("M",fdef@generic, fdef@package) |
| if(exists(oldMetaName, where, inherits = FALSE)) |
| rm(list = oldMetaName, pos = where) |
| .uncacheGeneric(f, fdef) |
| rm(list = fdef@generic, pos = where) |
| } |
| else { |
| if(!is.character(f)) |
| f <- deparse(f) |
| warning(gettextf("generic function %s not found for removal", |
| sQuote(f)), |
| domain = NA) |
| } |
| return(found) |
| } |
| |
| getMethods <- |
| ## The list of methods for the specified generic. If the function is not |
| ## a generic function, returns NULL. |
| ## The `f' argument can be either the character string name of the generic |
| ## or the object itself. |
| ## |
| ## The `where' argument optionally says where to look for the function, if |
| ## `f' is given as the name. |
| ## This function returns a MethodsList object, no longer used for method dispatch |
| ## A better structure for most purposes is the linear methods list returned by findMethods() |
| ## There are no plans currently to make getMethods defunct, but it will be less |
| ## efficient than findMethods() both for creating the object and using it. |
| |
| ## The function getMethods continues to |
| ## return a methods list object, but now this is the metadata from where, |
| ## or is converted from the internal table if where is missing |
| ## or Mlists are dummies. |
| |
| function(f, where = topenv(parent.frame()), table = FALSE) |
| { |
| if(!table) |
| .MlistDefunct("getMethods", "findMethods") |
| nowhere <- missing(where) |
| fdef <- getGeneric(f, where = where) |
| f <- fdef@generic |
| if(!is.null(fdef)) { |
| if(table) |
| return(getMethodsForDispatch(fdef, TRUE)) |
| } ## else NULL |
| } |
| |
| getMethodsForDispatch <- function(fdef, inherited = FALSE) |
| { |
| .getMethodsTable(fdef, environment(fdef), inherited = inherited) |
| } |
| |
| ## Some functions used in MethodsListSelect, that must be safe against recursive |
| ## method selection. |
| |
| .setIfBase <- function(f, fdef, mlist) { |
| if(is.null(f)) |
| FALSE |
| else { |
| found <- base::exists(f, "package:base") |
| if(found) { |
| ## force (default) computation of mlist in MethodsListSelect |
| base::assign(".Methods", envir = base::environment(fdef), |
| base::get(f, "package:base")) |
| } |
| found |
| } |
| } |
| |
| ## Must NOT use the standard version to prevent recursion {still true ?} |
| .getMethodsForDispatch <- function(fdef) { |
| ev <- base::environment(fdef) |
| if(base::exists(".Methods", envir = ev)) |
| base::get(".Methods", envir = ev) |
| ## else NULL |
| } |
| |
| .setMethodsForDispatch <- function(f, fdef, mlist) { |
| ev <- environment(fdef) |
| if(!is(fdef, "genericFunction") || |
| !exists(".Methods", envir = ev, inherits = FALSE)) |
| stop(sprintf("internal error: did not get a valid generic function object for function %s", |
| sQuote(f)), |
| domain = NA) |
| assign(".Methods", envir = ev, mlist) |
| } |
| |
| cacheMethod <- |
| ## cache the given definition in the method metadata for f |
| ## Support function: DON'T USE DIRECTLY (does no checking) |
| function(f, sig, def, args = names(sig), fdef, inherited = FALSE) { |
| ev <- environment(fdef) |
| |
| .cacheMethodInTable(fdef, sig, def, |
| .getMethodsTable(fdef, ev, inherited = inherited)) |
| ## if this is not an inherited method, update the inherited table as well |
| ## TODO: in this case, should uncache inherited methods, though the callin |
| ## function will normally have done this. |
| if(!inherited) |
| .cacheMethodInTable(fdef, sig, def, |
| .getMethodsTable(fdef, ev, inherited = TRUE)) |
| } |
| |
| .removeCachedMethod <- function(f, sig, fdef = getGeneric(f)) |
| cacheMethod(f, sig, NULL, names(sig), fdef) |
| |
| |
| setMethod <- |
| ## Define a method for the specified combination of generic function and signature. |
| ## The method is stored in the methods meta-data of the specified database. |
| ## |
| ## Note that assigning methods anywhere but the global environment (`where==1') will |
| ## not have a permanent effect beyond the current R session. |
| function(f, signature = character(), definition, |
| where = topenv(parent.frame()), valueClass = NULL, |
| sealed = FALSE) |
| { |
| ## Methods are stored in metadata in database where. A generic function will be |
| ## assigned if there is no current generic, and the function is NOT a primitive. |
| ## Primitives are dispatched from the main C code, and an explicit generic NEVER |
| ## is assigned for them. |
| if(is.function(f) && is(f, "genericFunction")) { |
| ## (two-part test to deal with bootstrapping of methods package) |
| fdef <- f |
| f <- fdef@generic |
| gwhere <- .genEnv(f) |
| } |
| else if(is.function(f)) { |
| if(is.primitive(f)) { |
| f <- .primname(f) |
| fdef <- genericForBasic(f) |
| gwhere <- .genEnv(f) |
| } |
| else |
| stop("a function for argument 'f' must be a generic function") |
| } |
| ## slight subtlety: calling getGeneric vs calling isGeneric |
| ## For primitive functions, getGeneric returns the (hidden) generic function, |
| ## even if no methods have been defined. An explicit generic MUST NOT be |
| ## for these functions, dispatch is done inside the evaluator. |
| else { |
| where <- as.environment(where) |
| gwhere <- .genEnv(f, where) |
| f <- switch(f, "as.double" = "as.numeric", f) |
| fdef <- getGeneric(f, where = if(identical(gwhere, baseenv())) where else gwhere) |
| } |
| if(.lockedForMethods(fdef, where)) |
| stop(gettextf("the environment %s is locked; cannot assign methods for function %s", |
| sQuote(getPackageName(where)), |
| sQuote(f)), |
| domain = NA) |
| hasMethods <- !is.null(fdef) |
| deflt <- getFunction(f, generic = FALSE, mustFind = FALSE, where = where) |
| ## where to insert the methods in generic |
| if(identical(gwhere, baseenv())) { |
| allWhere <- findFunction(f, where = where) |
| generics <- logical(length(allWhere)) |
| if(length(allWhere)) { # put methods into existing generic |
| for(i in seq_along(allWhere)) { |
| fi <- get(f, allWhere[[i]]) |
| geni <- is(fi, "genericFunction") |
| generics[[i]] <- geni |
| if(!geni && is.null(deflt)) |
| deflt <- fi |
| } |
| } |
| if(any(generics)) { |
| ## try to add method to the existing generic, but if the corresponding |
| ## environment is sealed, must create a new generic in where |
| gwhere <- as.environment(allWhere[generics][[1L]]) |
| if(.lockedForMethods(fdef, gwhere)) { |
| if(identical(as.environment(where), gwhere)) |
| stop(gettextf("the 'where' environment (%s) is a locked namespace; cannot assign methods there", |
| getPackageName(where)), domain = NA) |
| msg <- |
| gettextf("Copying the generic function %s to environment %s, because the previous version was in a sealed namespace (%s)", |
| sQuote(f), |
| sQuote(getPackageName(where)), |
| sQuote(getPackageName(gwhere))) |
| message(strwrap(msg), domain = NA) |
| assign(f, fdef, where) |
| gwhere <- where |
| } |
| } |
| } |
| if(!hasMethods) |
| fdef <- deflt |
| if(is.null(fdef)) |
| stop(gettextf("no existing definition for function %s", |
| sQuote(f)), |
| domain = NA) |
| if(!hasMethods) { |
| ## create using the visible non-generic as a pattern and default method |
| setGeneric(f, where = where) |
| doMessage <- !isS3Generic(fdef) |
| fdef <- getGeneric(f, where = where) |
| if(doMessage) { |
| thisPackage <- getPackageName(where) |
| thisPName <- if(identical(thisPackage, ".GlobalEnv")) |
| "the global environment" else paste("package", sQuote(thisPackage)) |
| if(identical(as.character(fdef@package), thisPackage)) |
| message(gettextf("Creating a generic function from function %s in %s", |
| sQuote(f), thisPName), domain = NA) |
| else |
| message(gettextf("Creating a generic function for %s from package %s in %s", |
| sQuote(f), sQuote(fdef@package), thisPName), |
| domain = NA) |
| } |
| } |
| else if(identical(gwhere, NA)) { |
| ## better be a primitive since getGeneric returned a generic, but none was found |
| if(is.null(.BasicFunsList[[f]])) |
| stop(sprintf("apparent internal error: a generic function was found for \"%s\", but no corresponding object was found searching from \"%s\"", |
| f, getPackageName(where)), domain = NA) |
| if(!isGeneric(f)) |
| setGeneric(f) # turn on this generic and cache it. |
| } |
| if(isSealedMethod(f, signature, fdef, where=where)) |
| stop(gettextf("the method for function %s and signature %s is sealed and cannot be re-defined", |
| sQuote(f), |
| .signatureString(fdef, signature)), |
| domain = NA) |
| signature <- matchSignature(signature, fdef, where) |
| createMethod <- FALSE # TRUE for "closure" only |
| switch(typeof(definition), |
| "closure" = { |
| fnames <- formalArgs(fdef) |
| mnames <- formalArgs(definition) |
| if(!identical(mnames, fnames)) { |
| ## fix up arg name for single-argument generics |
| ## useful for e.g. '!' |
| if(length(fnames) == length(mnames) && length(mnames) == 1L) { |
| warning(gettextf("For function %s, signature %s: argument in method definition changed from (%s) to (%s)", |
| sQuote(f), |
| sQuote(signature), |
| mnames, |
| fnames), |
| domain = NA, call. = FALSE) |
| formals(definition) <- formals(fdef) |
| ll <- list(as.name(formalArgs(fdef))); names(ll) <- mnames |
| body(definition) <- substituteDirect(body(definition), ll) |
| mnames <- fnames |
| } |
| else { |
| ## omitted arguments (classes) in method => "missing" |
| fullSig <- conformMethod(signature, mnames, fnames, f, fdef, definition) |
| if(!identical(fullSig, signature)) { |
| formals(definition, envir = environment(definition)) <- formals(fdef) |
| signature <- fullSig |
| } |
| ## extra arguments (classes) in method => use "..." to rematch |
| definition <- rematchDefinition(definition, fdef, mnames, fnames, signature) |
| } |
| } |
| definition <- matchDefaults(definition, fdef) # use generic's defaults if none in method |
| createMethod <- TRUE |
| }, |
| "builtin" = , "special" = { |
| ## the only primitive methods allowed are those equivalent |
| ## to the default, for generics that were primitives before |
| ## and will be dispatched by C code. |
| if(!identical(definition, deflt)) |
| stop("primitive functions cannot be methods; they must be enclosed in a regular function") |
| }, |
| "NULL" = { |
| |
| }, |
| stop(gettextf("invalid method definition: expected a function, got an object of class %s", |
| dQuote(class(definition))), domain = NA) |
| ) |
| fenv <- environment(fdef) |
| ## check length against active sig. length, reset if necessary in .addToMetaTable |
| nSig <- .getGenericSigLength(fdef, fenv, TRUE) |
| signature <- .matchSigLength(signature, fdef, fenv, TRUE) |
| margs <- (fdef@signature)[seq_along(signature)] |
| if(createMethod) { |
| definition <- asMethodDefinition(definition, signature, sealed, fdef) |
| definition@generic <- fdef@generic |
| } |
| is.not.base <- !identical(where, baseenv()) |
| whereMethods <- |
| ## do.mlist <- is.not.base && (!.noMlists() || all(signature == "ANY")) |
| if(is.not.base && !.noMlists()) # do.mlist |
| insertMethod(getMethodsMetaData(f, where), |
| signature, margs, definition) ## else NULL |
| mtable <- getMethodsForDispatch(fdef) |
| if(cacheOnAssign(where)) { # will be FALSE for sourceEnvironment's |
| ## cache in both direct and inherited tables |
| .cacheMethodInTable(fdef, signature, definition, mtable) #direct |
| .cacheMethodInTable(fdef, signature, definition) # inherited, by default |
| if(is.not.base) |
| .addToMetaTable(fdef, signature, definition, where, nSig) |
| resetGeneric(f, fdef, mtable, gwhere, deflt) # Note: gwhere not used by resetGeneric |
| } |
| ## assigns the methodslist object |
| ## and deals with flags for primitives & for updating group members |
| assignMethodsMetaData(f, whereMethods, fdef, where) |
| invisible(f) |
| } |
| |
| removeMethod <- function(f, signature = character(), where = topenv(parent.frame())) { |
| if(is.function(f)) { |
| if(is(f, "genericFunction")) |
| { fdef <- f; f <- f@generic} |
| else if(is.primitive(f)) |
| { f <- .primname(f); fdef <- genericForBasic(f, mustFind=FALSE)} |
| else |
| stop("function supplied as argument 'f' must be a generic") |
| } |
| else |
| fdef <- getGeneric(f, where = where) |
| if(is.null(fdef)) { |
| warning(gettextf("no generic function %s found", sQuote(f)), |
| domain = NA) |
| return(FALSE) |
| } |
| if(is.null(getMethod(fdef, signature, optional=TRUE))) { |
| warning(gettextf("no method found for function %s and signature %s", |
| sQuote(fdef@generic), |
| paste(.dQ(signature), collapse =", ")), |
| domain = NA) |
| return(FALSE) |
| } |
| setMethod(f, signature, NULL, where = where) |
| TRUE |
| } |
| |
| ## an extension to removeMethod that resets inherited methods as well |
| .undefineMethod <- function(f, signature = character(), where = topenv(parent.frame())) { |
| fdef <- getGeneric(f, where = where) |
| if(is.null(fdef)) { |
| warning(gettextf("no generic function %s found", |
| sQuote(f)), |
| domain = NA) |
| return(FALSE) |
| } |
| if(!is.null(getMethod(fdef, signature, optional=TRUE))) |
| setMethod(f, signature, NULL, where = where) |
| } |
| |
| findMethod <- function(f, signature, where = topenv(parent.frame())) { |
| if(is(f, "genericFunction")) { |
| fdef <- f |
| f <- fdef@generic |
| } |
| else |
| fdef <- getGeneric(f, where = where) |
| if(is.null(fdef)) { |
| warning(gettextf("no generic function %s found", |
| sQuote(f)), |
| domain = NA) |
| return(character()) |
| } |
| fM <- .TableMetaName(fdef@generic, fdef@package) |
| where <- .findAll(fM, where) |
| found <- logical(length(where)) |
| for(i in seq_along(where)) { |
| wherei <- where[[i]] |
| table <- get(fM, wherei, inherits=FALSE) |
| ## because we are using the table from the package, we must |
| ## search for both the unexpanded & expanded signature, which |
| ## .findMethodInTable does not do. |
| mi <- .findMethodForFdef(signature, table, fdef) |
| found[i] <- !is.null(mi) |
| } |
| value <- where[found] |
| ## to conform to the API, try to return a numeric or character vector |
| ## if possible |
| what <- vapply(value, class, "", USE.NAMES=FALSE) |
| if(identical(what, "numeric") || identical(what, "character")) |
| unlist(value) |
| else |
| value |
| } |
| |
| getMethod <- |
| ## Return the function that is defined as the method for this generic function and signature |
| ## (classes to be matched to the arguments of the generic function). |
| function(f, signature = character(), where = topenv(parent.frame()), optional = FALSE, |
| mlist, fdef ) |
| { |
| if(!missing(where)) { |
| env <- .NamespaceOrEnvironment(where) |
| if(is.null(env)) |
| stop(gettextf("no environment or package corresponding to argument where=%s", |
| deparse(where)), domain = NA) |
| where <- env |
| } |
| if(missing(fdef)) { |
| if(missing(where)) |
| fdef <- getGeneric(f, FALSE) |
| else { |
| fdef <- getGeneric(f, FALSE, where = where) |
| if(is.null(fdef)) |
| fdef <- getGeneric(f, FALSE) |
| } |
| } |
| if(!is(fdef, "genericFunction")) { |
| if(optional) |
| return(NULL) |
| ## else |
| if(!is.character(f)) f <- deparse(substitute(f)) |
| stop(gettextf("no generic function found for '%s'", f), domain = NA) |
| } |
| if(missing(mlist)) |
| mlist <- |
| if(missing(where)) |
| getMethodsForDispatch(fdef) |
| else |
| .getMethodsTableMetaData(fdef, where, optional) |
| if(is.environment(mlist)) { |
| signature <- matchSignature(signature, fdef) |
| value <- .findMethodInTable(signature, mlist, fdef) |
| if(is.null(value) && !optional) { |
| if(!is.character(f)) f <- deparse(substitute(f)) |
| stop(gettextf("no method found for function '%s' and signature %s", |
| f, paste(signature, collapse = ", "))) |
| } |
| return(value) |
| } |
| else if(is.null(mlist)) return(mlist) |
| |
| ## the rest of the code will be executed only if a methods list object is supplied |
| ## as an argument. Should be deleted from 2.8.0 --> Error from 3.2.0 |
| stop("defunct methods list search", domain = NA) |
| } |
| |
| dumpMethod <- |
| ## Dump the method for this generic function and signature. |
| ## The resulting source file will recreate the method. |
| function(f, signature=character(), file = defaultDumpName(f, signature), |
| where = topenv(parent.frame()), |
| def = getMethod(f, signature, where=where, optional = TRUE)) |
| { |
| if(!is.function(def)) |
| def <- getMethod(f, character(), where=where, optional = TRUE) |
| |
| ## sink() handling as general as possible -- unbelievably unpretty coding: |
| closeit <- TRUE ; isSTDOUT <- FALSE |
| if (is.character(file)) { |
| if(!(isSTDOUT <- file == "")) ## stdout() -- no sink() needed |
| file <- file(file, "w") |
| } |
| else if (inherits(file, "connection")) { |
| if (!isOpen(file)) open(file, "w") else closeit <- FALSE |
| } else stop("'file' must be a character string or a connection") |
| if(!isSTDOUT){ sink(file); on.exit({sink(); if(closeit) close(file)}) } |
| |
| cat("setMethod(\"", f, "\", ", deparse(signature), ",\n", sep="") |
| dput(def@.Data) |
| cat(")\n", sep="") |
| if(!isSTDOUT) { on.exit(); sink(); if(closeit) close(file) } |
| invisible(file) |
| } |
| |
| dumpMethods <- function(f, file = "", signature = NULL, methods= findMethods(f, where = where), |
| where = topenv(parent.frame()) ) |
| { |
| ## The signature argument was used in recursive calls to dumpMethods() |
| ## using the old MethodsList objects. It is not meaningful with |
| ## the current listOfMethods class |
| if(length(signature) > 0) |
| warning("argument 'signature' is not meaningful with the current implementation and is ignored \n(extract a subset of the methods list instead)") |
| |
| ## sink() handling as general as possible -- unbelievably unpretty coding: |
| closeit <- TRUE ; isSTDOUT <- FALSE |
| if (is.character(file)) { |
| if(!(isSTDOUT <- file == "")) ## stdout() -- no sink() needed |
| file <- file(file, "w") |
| } |
| else if (inherits(file, "connection")) { |
| if (!isOpen(file)) open(file, "w") else closeit <- FALSE |
| } else stop("'file' must be a character string or a connection") |
| if(!isSTDOUT){ sink(file); on.exit({sink(); if(closeit) close(file)}) } |
| sigs <- methods@signatures |
| for(i in seq_along(methods)) |
| dumpMethod(f, sigs[[i]], file = "", def = methods[[i]]) |
| } |
| |
| |
| selectMethod <- |
| ## Returns the method (a function) that R would use to evaluate a call to |
| ## generic 'f' with arguments corresponding to the specified signature. |
| function(f, signature, optional = FALSE, useInherited = TRUE, |
| mlist = if(!is.null(fdef)) getMethodsForDispatch(fdef), |
| fdef = getGeneric(f, !optional), verbose = FALSE, doCache = FALSE) |
| { |
| if(is.environment(mlist)) {# using methods tables |
| fenv <- environment(fdef) |
| nsig <- .getGenericSigLength(fdef, fenv, FALSE) |
| if(verbose) |
| cat("* mlist environment with", length(mlist),"potential methods\n") |
| if(length(signature) < nsig) |
| signature[(length(signature)+1):nsig] <- "ANY" |
| if(identical(fdef@signature, "...")) { |
| method <- .selectDotsMethod(signature, mlist, |
| if(useInherited) getMethodsForDispatch(fdef, inherited = TRUE)) |
| if(is.null(method) && !optional) |
| stop(gettextf("no method for %s matches class %s", |
| sQuote("..."), dQuote(signature)), |
| domain = NA) |
| return(method) |
| } |
| method <- .findMethodInTable(signature, mlist, fdef) |
| if(is.null(method)) { |
| if(missing(useInherited)) |
| useInherited <- (is.na(match(signature, "ANY")) & # -> vector |
| if(identical(fdef, coerce))# careful ! |
| c(TRUE,FALSE) else TRUE) |
| if(verbose) cat(" no direct match found to signature (", |
| paste(signature, collapse=", "),")\n", sep="") |
| methods <- |
| if(any(useInherited)) { |
| allmethods <- .getMethodsTable(fdef, fenv, check=FALSE, |
| inherited=TRUE) |
| ## look in the supplied (usually standard) table |
| .findInheritedMethods(signature, fdef, |
| mtable = allmethods, table = mlist, |
| useInherited = useInherited, |
| verbose = verbose, doCache = doCache) |
| ##MM: TODO? allow 'excluded' to be passed |
| } |
| ## else list() : just look in the direct table |
| |
| if(length(methods)) |
| return(methods[[1L]]) |
| else if(optional) |
| return(NULL) |
| else stop(gettextf("no method found for signature %s", |
| paste(signature, collapse=", "))) |
| } |
| else |
| return(method) |
| } |
| else if(is.null(mlist)) { |
| if(optional) |
| return(mlist) |
| else |
| stop(gettextf("%s has no methods defined", |
| sQuote(f)), |
| domain = NA) |
| } |
| else ## mlist not an environment nor NULL : |
| stop("selectMethod(): mlist is not an environment or NULL :\n", |
| "** should no longer happen!", domain = NA) |
| } |
| |
| hasMethod <- |
| ## returns `TRUE' if `f' is the name of a generic function with an (explicit or inherited) method for |
| ## this signature. |
| function(f, signature = character(), where = .genEnv(f, topenv(parent.frame()))) |
| { |
| fdef <- getGeneric(f, where = where) |
| if(is.null(fdef)) |
| FALSE |
| else |
| !is.null(selectMethod(f, signature, optional = TRUE, fdef = fdef)) |
| } |
| |
| existsMethod <- |
| ## returns `TRUE' if `f' is the name of a generic function with an (explicit) method for |
| ## this signature. |
| function(f, signature = character(), where = topenv(parent.frame())) |
| { |
| if(missing(where)) |
| method <- getMethod(f, signature, optional = TRUE) |
| else |
| method <- getMethod(f, signature, where = where, optional = TRUE) |
| !is.null(method) |
| } |
| |
| signature <- |
| ## A named list of classes to be matched to arguments of a generic function. |
| ## It is recommended to supply signatures to `setMethod' via a call to `signature', |
| ## to make clear which arguments are being used to select this method. |
| ## It works, however, just to give a vector of character strings, which will |
| ## be associated with the formal arguments of the function, in order. The advantage |
| ## of using `signature' is to provide a check on which arguments you meant, as well |
| ## as clearer documentation in your method specification. In addition, `signature' |
| ## checks that each of the elements is a single character string. |
| function(...) |
| { |
| value <- list(...) |
| names <- names(value) |
| for(i in seq_along(value)) { |
| sigi <- value[[i]] |
| if(!is.character(sigi) || length(sigi) != 1L) |
| stop(gettextf( |
| "bad class specified for element %d (should be a single character string)", |
| i), domain = NA) |
| |
| } |
| setNames(as.character(value), names) |
| } |
| |
| showMethods <- |
| ## Show all the methods for the specified function. |
| ## |
| ## If `where' is supplied, the definition from that database will |
| ## be used; otherwise, the current definition is used (which will |
| ## include inherited methods that have arisen so far in the |
| ## session). |
| ## |
| ## The output style is different from S-Plus in that it does not |
| ## show the database from which the definition comes, but can |
| ## optionally include the method definitions, if `includeDefs == TRUE'. |
| ## |
| function(f = character(), where = topenv(parent.frame()), classes = NULL, |
| includeDefs = FALSE, inherited = !includeDefs, |
| showEmpty, printTo = stdout(), fdef = getGeneric(f, where = where)) |
| { |
| if(missing(showEmpty)) |
| showEmpty <- !missing(f) |
| if(isFALSE(printTo)) |
| con <- textConnection(NULL, "w") |
| else |
| con <- printTo |
| ## must resolve showEmpty in line; using an equivalent default |
| ## fails because R resets the "missing()" result for f later on (grumble) |
| if(is.function(f)) { |
| fdef <- f ## note that this causes missing(fdef) to be FALSE below |
| if(missing(where)) |
| where <- environment(f) |
| f <- deparse(substitute(f)) |
| if(length(f) > 1L) f <- paste(f, collapse = "; ") |
| } |
| if(!is(f, "character")) |
| stop(gettextf("first argument should be the names of one of more generic functions (got object of class %s)", |
| dQuote(class(f))), domain = NA) |
| if(length(f) == 0L) { ## usually, the default character() |
| f <- if(missing(where)) getGenerics() else getGenerics(where) |
| } |
| if(length(f) == 0L) |
| cat(file = con, "no applicable functions\n") |
| else if(length(f) > 1L) { |
| for(ff in f) { ## recall for each |
| ffdef <- getGeneric(ff, where = where) |
| if(missing(where)) { |
| if(isGeneric(ff)) |
| Recall(ff, classes=classes, |
| includeDefs=includeDefs, inherited=inherited, |
| showEmpty=showEmpty, printTo=con, fdef = ffdef) |
| } |
| else if(isGeneric(ff, where)) { |
| Recall(ff, where=where, classes=classes, |
| includeDefs=includeDefs, inherited=inherited, |
| showEmpty=showEmpty, printTo=con, fdef = ffdef) |
| } |
| } |
| } |
| else { ## f of length 1 --- the "workhorse" : |
| out <- paste0("\nFunction \"", f, "\":\n") |
| if(!is(fdef, "genericFunction")) |
| cat(file = con, out, "<not an S4 generic function>\n") |
| else |
| ## maybe no output for showEmpty=FALSE |
| .showMethodsTable(fdef, includeDefs, inherited, |
| classes = classes, showEmpty = showEmpty, |
| printTo = con) |
| } |
| if(isFALSE(printTo)) { |
| txtOut <- textConnectionValue(con) |
| close(con) |
| txtOut |
| } |
| else |
| invisible(printTo) |
| } |
| |
| .methods_info <- |
| ## (not exported) simplify construction of standard data.frame |
| ## return value from .S4methodsFor* |
| function(generic=character(), signature=character(), |
| visible=rep(TRUE, length(signature)), from=character()) |
| { |
| if (length(signature)) |
| signature <- paste0(generic, ",", signature, "-method") |
| keep <- !duplicated(signature) |
| data.frame(visible=visible[keep], from=from[keep], |
| generic=generic[keep], isS4=rep(TRUE, sum(keep)), |
| row.names=signature[keep], stringsAsFactors=FALSE) |
| } |
| |
| .S4methodsForClass <- |
| ## (not exported) discover methods for specific class; |
| ## generic.function ignored |
| function(generic.function, class) |
| { |
| def <- tryCatch(getClass(class), error=function(...) NULL) |
| if (is.null(def)) |
| return(.methods_info()) |
| |
| classes <- c(class, names(getClass(class)@contains)) |
| generics <- as.vector(getGenerics(where=search())) |
| nms <- setNames(generics, generics) |
| |
| packages <- lapply(nms, function(generic) { |
| table <- environment(getGeneric(generic))[[".MTable"]] |
| lapply(table, function(m) environmentName(environment(m))) |
| }) |
| methods <- lapply(nms, function(generic) { |
| table <- environment(getGeneric(generic))[[".MTable"]] |
| lapply(table, function(m) { |
| if (is(m, "MethodDefinition") && any(m@defined %in% classes)) |
| setNames(as.vector(m@defined), names(m@defined)) |
| ## else NULL |
| }) |
| }) |
| |
| geom <- lapply(methods, function(method) { |
| !vapply(method, is.null, logical(1)) |
| }) |
| filter <- function(elt, geom) elt[geom] |
| packages <- Map(filter, packages, geom) |
| methods <- Map(filter, methods, geom) |
| non0 <- lengths(methods) != 0L |
| packages <- packages[non0] |
| methods <- methods[non0] |
| |
| ## only derived methods |
| geom <- lapply(methods, function(method, classes) { |
| sig <- simplify2array(method) |
| if (!is.matrix(sig)) |
| sig <- matrix(sig, ncol=length(method)) |
| idx <- apply(sig, 2, match, classes, 0) |
| if (!is.matrix(idx)) |
| idx <- matrix(idx, ncol=ncol(sig)) |
| keep <- colSums(idx != 0) != 0 |
| sidx <- idx[,keep, drop=FALSE] |
| |
| ## 'nearest' method |
| shift <- c(0, cumprod(pmax(1, apply(sidx, 1, max)))[-nrow(sidx)]) |
| score <- colSums(sidx + shift) |
| sig0 <- sig <- sig[,keep, drop=FALSE] |
| sig0[sidx != 0] <- "*" |
| sig0 <- apply(sig0, 2, paste, collapse="#") |
| split(score, sig0) <- |
| lapply(split(score, sig0), function(elt) elt == min(elt)) |
| score == 1 |
| }, classes) |
| |
| packages <- Map(filter, packages, geom) |
| methods <- Map(filter, methods, geom) |
| |
| generic <- rep(names(methods), lengths(methods)) |
| signature <- unlist(lapply(methods, function(method) { |
| vapply(method, paste0, character(1L), collapse=",") |
| }), use.names=FALSE) |
| package <- unlist(packages, use.names=FALSE) |
| |
| .methods_info(generic=generic, signature=signature, from=package) |
| } |
| |
| .S4methodsForGeneric <- |
| ## (not exported) discover methods for specific generic; class |
| ## ignored. |
| function(generic.function, class) |
| { |
| if (is.null(getGeneric(generic.function))) |
| return(.methods_info()) |
| |
| mtable <- ".MTable" |
| generic <- generic.function |
| table <- get(mtable, environment(getGeneric(generic))) |
| packages <- sapply(names(table), function(nm, table) { |
| environmentName(environment(table[[nm]])) |
| }, table) |
| |
| methods <- names(table) |
| signatures <- lapply(methods, function(method, classes) { |
| m <- table[[method]] |
| if (is(m, "MethodDefinition")) |
| setNames(as.vector(m@defined), names(m@defined)) |
| else |
| NULL |
| }) |
| |
| geom <- vapply(signatures, Negate(is.null), logical(1)) |
| packages <- packages[geom] |
| methods <- methods[geom] |
| signatures <- sapply(signatures[geom], function(elt) { |
| paste0(as.vector(elt), collapse=",") |
| }) |
| |
| .methods_info(generic=rep(generic.function, length(packages)), from=packages, |
| signature=signatures) |
| } |
| |
| .S4methods <- |
| ## discover methods by generic or class, primarily for interactive |
| ## display via utils::methods() |
| function(generic.function, class) |
| { |
| info <- if (!missing(generic.function)) |
| .S4methodsForGeneric(generic.function, class) |
| else if (!missing(class)) |
| .S4methodsForClass(generic.function, class) |
| else |
| stop("must supply 'generic.function' or 'class'") |
| structure(rownames(info), info=info, byclass=missing(generic.function), |
| class="MethodsFunction") |
| } |
| |
| removeMethods <- |
| ## removes all the methods defined for this generic function. Returns `TRUE' if |
| ## `f' was a generic function, `FALSE' (silently) otherwise. |
| ## |
| ## If there is a default method, the function will be re-assigned as |
| ## a simple function with this definition; otherwise, it will be removed. The |
| ## assignment or removal can be controlled by optional argument `where', which |
| ## defaults to the first element of the search list having a function called `f'. |
| function(f, where = topenv(parent.frame()), all = missing(where)) |
| { |
| ## NOTE: The following is more delicate than one would like, all because of |
| ## methods for primitive functions. For those, no actual generic function exists, |
| ## but isGeneric(f) is TRUE if there are methods. We have to get the default from |
| ## the methods object BEFORE calling removeMethodsObject, in case there are no more |
| ## methods left afterwards. AND we can't necessarily use the same default "where" |
| ## location for methods object and generic, for the case of primitive functions. |
| ## And missing(where) only works in R BEFORE the default is calculated. Hence |
| ## the peculiar order of computations and the explicit use of missing(where). |
| fdef <- getGeneric(f, where = where) |
| if(!is(fdef, "genericFunction")) { |
| warning(gettextf("%s is not an S4 generic function in %s; methods not removed", |
| sQuote(f), |
| sQuote(getPackageName(where))), |
| domain = NA) |
| return(FALSE) |
| } |
| |
| methods <- getMethodsForDispatch(fdef) |
| default <- getMethod(fdef, "ANY", optional = TRUE) |
| fMetaName <- .TableMetaName(fdef@generic, fdef@package) |
| oldMetaName <- methodsPackageMetaName("M",fdef@generic, fdef@package) |
| allWhere <- .findAll(fMetaName, where) |
| if(!all) |
| allWhere <- allWhere[1L] |
| value <- rep(TRUE, length(allWhere)) |
| ## cacheGenericsMetaData is called to clear primitive methods if there |
| ## are none for this generic on other databases. |
| cacheGenericsMetaData(f, fdef, FALSE, where) |
| .uncacheGeneric(f, fdef) # in case it gets removed or re-assigned |
| doGeneric <- TRUE # modify the function |
| for(i in seq_along(allWhere)) { |
| db <- as.environment(allWhere[[i]]) |
| if(environmentIsLocked(db)) { |
| warning(gettextf("cannot remove methods for %s in locked environment/package %s", |
| sQuote(f), sQuote(getPackageName(db))), |
| domain = NA) |
| value[[i]] <- FALSE |
| next |
| } |
| if(exists(fMetaName, db, inherits = FALSE)) { |
| ## delete these methods from the generic |
| theseMethods <- get(fMetaName, db) |
| .mergeMethodsTable(fdef, methods, theseMethods, FALSE) |
| rm(list = fMetaName, pos = db) |
| if(exists(oldMetaName, db, inherits = FALSE)) |
| rm(list = oldMetaName, pos = db) |
| } |
| } |
| all <- all && base::all(value) # leave methods on any locked packages |
| # now find and reset the generic function |
| for(i in seq_along(allWhere)) { |
| db <- as.environment(allWhere[[i]]) |
| if(doGeneric && isGeneric(f, db)) { |
| ## restore the original function if one was used as default |
| if(all && is(default, "derivedDefaultMethod")) { |
| default <- as(default, "function") # strict, removes slots |
| rm(list=f, pos = db) |
| if(!existsFunction(f, FALSE, db)) { |
| message(gettextf("Restoring default function definition of %s", |
| sQuote(f)), |
| domain = NA) |
| assign(f, default, db) |
| } |
| ## else the generic is removed, nongeneric will be found elsewhere |
| } |
| ## else, leave the generic in place, with methods removed |
| ## and inherited methods reset |
| else { |
| resetGeneric(f, fdef, where = db, deflt = default) |
| } |
| doGeneric <- FALSE |
| } |
| } |
| any(value) |
| } |
| |
| |
| resetGeneric <- function(f, fdef = getGeneric(f, where = where), |
| mlist = getMethodsForDispatch(fdef), |
| where = topenv(parent.frame()), |
| deflt = finalDefaultMethod(mlist)) |
| { |
| if(!is(fdef, "genericFunction")) { |
| stop(gettextf("error in updating S4 generic function %s; the function definition is not an S4 generic function (class %s)", sQuote(f), dQuote(class(fdef))), |
| domain = NA) |
| } |
| ## reset inherited methods |
| .updateMethodsInTable(fdef, attach = "reset") |
| f |
| } |
| |
| setReplaceMethod <- |
| function(f, ..., where = topenv(parent.frame())) |
| setMethod(paste0(f, "<-"), ..., where = where) |
| |
| setGroupGeneric <- |
| ## create a group generic function for this name. |
| function(name, def = NULL, group = list(), valueClass = character(), |
| knownMembers = list(), package = getPackageName(where), where = topenv(parent.frame())) |
| { |
| if(is.null(def)) { |
| def <- getFunction(name, where = where) |
| if(isGroup(name, fdef = def)) { |
| if(nargs() == 1) { |
| message(gettextf("Function %s is already a group generic; no change", |
| sQuote(name)), |
| domain = NA) |
| return(name) |
| } |
| } |
| } |
| ## By definition, the body must generate an error. |
| body(def, envir = environment(def)) <- substitute( |
| stop(MSG, domain = NA), |
| list(MSG = |
| gettextf("Function %s is a group generic; do not call it directly", |
| sQuote(name)))) |
| if(is.character(knownMembers)) |
| knownMembers <- as.list(knownMembers) # ? or try to find them? |
| setGeneric(name, def, group = group, valueClass = valueClass, |
| package = package, useAsDefault = FALSE, |
| genericFunction = |
| new("groupGenericFunction", def, groupMembers = knownMembers), |
| where = where) |
| .MakeImplicitGroupMembers(name, knownMembers, where) |
| name |
| } |
| |
| isGroup <- |
| function(f, where = topenv(parent.frame()), fdef = getGeneric(f, where = where)) |
| { |
| is(fdef, "groupGenericFunction") |
| } |
| |
| getGenericFromCall <- function(call, methodEnv) { |
| generic <- methodEnv$.Generic |
| if(is.null(generic)) { |
| fdef <- if (is.name(call[[1L]])) |
| getGeneric(as.character(call[[1L]]), mustFind=TRUE, where=methodEnv) |
| else call[[1L]] |
| generic <- environment(fdef)$.Generic |
| } |
| generic |
| } |
| |
| fromNextMethod <- function(call) { |
| identical(call[[1L]], quote(.nextMethod)) |
| } |
| |
| callGeneric <- function(...) { |
| call <- sys.call(sys.parent(1L)) |
| .local <- identical(call[[1L]], quote(.local)) |
| methodCtxInd <- 1L + if (.local) 1L else 0L |
| callerCtxInd <- methodCtxInd + 1L |
| methodCall <- sys.call(sys.parent(methodCtxInd)) |
| if (fromNextMethod(methodCall)) { |
| methodCtxInd <- methodCtxInd + 1L |
| } |
| methodFrame <- parent.frame(methodCtxInd) |
| genericName <- getGenericFromCall(methodCall, methodFrame) |
| if (is.null(genericName)) { |
| stop("callGeneric() must be called from within a method body") |
| } |
| if (nargs() == 0L) { |
| callerFrame <- sys.frame(sys.parent(callerCtxInd)) |
| methodDef <- sys.function(sys.parent(1L)) |
| call <- match.call(methodDef, |
| methodCall, |
| expand.dots=FALSE, |
| envir=callerFrame) |
| call[-1L] <- lapply(names(call[-1L]), as.name) |
| } else { |
| call <- sys.call() |
| } |
| call[[1L]] <- as.name(genericName) |
| eval(call, parent.frame()) |
| } |
| |
| ## This uses 'where' to record the methods namespace: default may not be that |
| initMethodDispatch <- function(where = topenv(parent.frame())) |
| .Call(C_R_initMethodDispatch, as.environment(where))# C-level initialization |
| |
| ### dummy version for booting |
| isSealedMethod <- function(f, signature, fdef = getGeneric(f, FALSE, where = where), |
| where = topenv(parent.frame())) FALSE |
| |
| ### real version |
| .isSealedMethod <- function(f, signature, fdef = getGeneric(f, FALSE, where = where), |
| where = topenv(parent.frame())) |
| { |
| ## look for the generic to see if it is a primitive |
| fGen <- getFunction(f, TRUE, FALSE, where = where) |
| if(!is.primitive(fGen)) { |
| mdef <- getMethod(f, signature, optional = TRUE, where = where, fdef = fGen) |
| return(is(mdef, "SealedMethodDefinition")) |
| } |
| ## else, a primitive |
| if(is(fdef, "genericFunction")) |
| signature <- matchSignature(signature, fdef) |
| if(length(signature) == 0L) |
| TRUE # default method for primitive |
| else if(f %in% .subsetFuns) |
| ## primitive dispatch requires some argument to be an S4 object. |
| ## This does not quite guarantee an S4 object; e.g., a class union might have only basic types in it. |
| !any(is.na(match(signature, .BasicClasses))) |
| else { |
| sealed <- !is.na(match(signature[[1L]], .BasicClasses)) |
| if(sealed && |
| (!is.na(match("Ops", c(f, getGroup(f, TRUE)))) |
| || !is.na(match(f, c("%*%", "crossprod"))))) |
| ## Ops methods are only sealed if both args are basic classes |
| sealed <- sealed && (length(signature) > 1L) && |
| !is.na(match(signature[[2L]], .BasicClasses)) |
| sealed |
| } |
| } |
| |
| .subsetFuns <- c("[", "[[","[<-","[[<-") |
| |
| .lockedForMethods <- function(fdef, env) { |
| ## the env argument is NULL if setMethod is only going to assign into the |
| ## table of the generic function, and not to assign methods list object |
| if(is.null(env) || !environmentIsLocked(env)) |
| return(FALSE) #? can binding be locked and envir. not? |
| if(!is(fdef, "genericFunction")) |
| return(TRUE) |
| name <- fdef@generic |
| package <- fdef@package |
| objs <- c(name, .TableMetaName(name, package)) |
| for(obj in objs) { |
| hasIt <- exists(obj, env, inherits = FALSE) |
| ## the method object may be bound, or a new one may be needed |
| ## in which case the env. better not be locked |
| if((!hasIt || bindingIsLocked(obj, env))) |
| return(TRUE) |
| } |
| FALSE |
| } |
| |
| implicitGeneric <- function(...) NULL |
| |
| ## real version, installed after methods package initialized |
| |
| .implicitGeneric <- function(name, where = topenv(parent.frame()), |
| generic = getGeneric(name, where = where)) |
| ### Add the named function to the table of implicit generics in environment where. |
| ### |
| ### If there is a generic function of this name, it is saved to the |
| ### table. This is the reccomended approach and is required if you |
| ### want the saved generic to include any non-default methods. |
| ### |
| { |
| if(!nzchar(name)) |
| stop(gettextf('expected a non-empty character string for argument name'), domain = NA) |
| if(!missing(generic) && is(generic, "genericFunction") && !.identC(name, generic@generic)) |
| stop(gettextf('generic function supplied was not created for %s', |
| sQuote(name)), |
| domain = NA) |
| createGeneric <- (missing(generic) || !is(generic, "genericFunction")) && !isGeneric(name, where) |
| if(createGeneric) { |
| fdefault <- getFunction(name, where = where, mustFind = FALSE) |
| if(is.null(fdefault)) |
| return(NULL) # no implicit generic |
| env <- environment(fdefault) # the environment for an implicit generic table |
| fdefault <- .derivedDefaultMethod(fdefault) |
| if(isBaseFun(fdefault)) { |
| value <- genericForBasic(name) |
| if (is.function(value)) { |
| if(!missing(generic) && !identical(value, generic)) |
| stop(gettextf("%s is a primitive function; its generic form cannot be redefined", |
| sQuote(name)), |
| domain = NA) |
| generic <- value |
| fdefault <- generic@default |
| } |
| package <- "base" |
| } |
| else |
| package <- getPackageName(env) |
| ## look for a group |
| group <- |
| .getImplicitGroup(name, |
| if(identical(package,"base")) |
| .methodsNamespace else environment(fdefault)) |
| if(missing(generic)) { |
| generic <- .getImplicitGeneric(name, env, package) |
| if(is.null(generic)) { # make a new one |
| generic <- makeGeneric(name, fdefault = fdefault, package = package, |
| group = group) |
| .cacheImplicitGeneric(name, generic) |
| } |
| } |
| else { |
| generic <- makeGeneric(name, generic, fdefault, package = package, |
| group = group) |
| .cacheImplicitGeneric(name, generic) |
| } |
| } |
| generic |
| } |
| |
| setGenericImplicit <- function(name, where = topenv(parent.frame()), restore = TRUE) { |
| if(!isGeneric(name, where)) { |
| warning(gettextf("%s is not currently a generic: define it first to create a non-default implicit form", |
| sQuote(name)), |
| domain = NA) |
| return(FALSE) |
| } |
| generic <- getGeneric(name, where = where) |
| if(restore) |
| removeMethods(name, where, TRUE) |
| else |
| removeGeneric(name, where) |
| .saveToImplicitGenerics(name, generic, where) |
| } |
| |
| prohibitGeneric <- function(name, where = topenv(parent.frame())) |
| ### store a definition in the implicit generic table that explicitly prohibits |
| ### a function from being made generic |
| { |
| .saveToImplicitGenerics(name, FALSE, where) |
| } |
| |
| registerImplicitGenerics <- function(what = .ImplicitGenericsTable(where), |
| where = topenv(parent.frame())) |
| { |
| if(!is.environment(what)) |
| stop(gettextf("must provide an environment table; got class %s", |
| dQuote(class(what))), domain = NA) |
| objs <- as.list(what, all.names = TRUE) |
| mapply(.cacheImplicitGeneric, names(objs), objs) |
| NULL |
| } |
| |
| |
| ### the metadata name for the implicit generic table |
| .ImplicitGenericsMetaName <- ".__IG__table" # methodsPackageMetaName("IG", "table") |
| |
| .ImplicitGenericsTable <- function(where) |
| { |
| ### internal utility to add a function to the implicit generic table |
| if(!exists(.ImplicitGenericsMetaName, where, inherits = FALSE)) |
| assign(.ImplicitGenericsMetaName, new.env(TRUE), where) |
| get(.ImplicitGenericsMetaName, where) |
| } |
| |
| .saveToImplicitGenerics <- function(name, def, where) |
| .cacheGenericTable(name, def, .ImplicitGenericsTable(where)) |
| |
| .getImplicitGeneric <- function(name, where, pkg = "") |
| { |
| value <- .getImplicitGenericFromCache(name, where, pkg) |
| if(is.null(value) && !is.null(tbl <- where[[.ImplicitGenericsMetaName]])) |
| .getGenericFromCacheTable(name, where, pkg, tbl) |
| else |
| value |
| } |
| |
| ## only called from setGeneric, f1 = supplied, f2 = implicit |
| .identicalGeneric <- function(f1, f2, allow.extra.dots = FALSE) |
| { |
| gpString <- function(gp) { |
| if(length(gp)) |
| paste(as.character(gp), collapse = ", ") |
| else |
| "<none>" |
| } |
| if(isFALSE(f2)) |
| return(gettext("original function is prohibited as a generic function")) |
| if(!(is.function(f2) && is.function(f1))) |
| return(gettext("not both functions!")) |
| ## environments will be different |
| if(!identical(class(f1), class(f2))) |
| return(sprintf("classes: %s, %s", |
| .dQ(class(f1)), .dQ(class(f2)))) |
| if(!isS4(f1)) return(gettextf("argument %s is not S4", |
| deparse(substitute(f1)))) |
| if(!isS4(f2)) return(gettextf("argument %s is not S4", |
| deparse(substitute(f2)))) |
| f1d <- f1@.Data |
| f2d <- f2@.Data |
| ## xtra... <- FALSE |
| if(!identical(formals(f1d), formals(f2d))) { |
| a1 <- names(formals(f1d)); a2 <- names(formals(f2d)) |
| if(identical(a1, a2)) |
| return(gettext("formal arguments differ (in default values?)")) |
| else if(identical(c(a1, "..."), a2) && allow.extra.dots) |
| ## silently accept an extra "..." |
| { } ## xtra... <- TRUE |
| ## and continue |
| else |
| return(gettextf("formal arguments differ: (%s), (%s)", |
| paste(a1, collapse = ", "), |
| paste(a2, collapse = ", "))) |
| } |
| if(!identical(f1@valueClass, f2@valueClass)) |
| return(gettextf("value classes differ: %s, %s", |
| .dQ(gpString(f1@valueClass)), |
| .dQ(gpString(f2@valueClass)))) |
| if(!identical(body(utils::removeSource(f1d)), |
| body(utils::removeSource(f2d)))) |
| return("function body differs") |
| if(!identical(f1@signature, f2@signature)) |
| return(gettextf("signatures differ: (%s), (%s)", |
| paste(f1@signature, collapse = ", "), |
| paste(f2@signature, collapse = ", "))) |
| if(!identical(f1@package, f2@package)) |
| return(gettextf("package slots differ: %s, %s", |
| .dQ(gpString(f1@package)), |
| .dQ(gpString(f2@package)))) |
| if(!identical(f1@group, f2@group)) { |
| return(gettextf("groups differ: %s, %s", |
| .dQ(gpString(f1@group)), |
| .dQ(gpString(f2@group)))) |
| } |
| if(!identical(as.character(f1@generic), as.character(f2@generic))) |
| return(gettextf("generic names differ: %s, %s", |
| .dQ(f1@generic), .dQ(f2@generic))) |
| TRUE |
| } |
| |
| .ImplicitGroupMetaName <- ".__IGM__table" |
| .MakeImplicitGroupMembers <- function(group, members, where) { |
| if(!exists(.ImplicitGroupMetaName, where, inherits = FALSE)) |
| assign(.ImplicitGroupMetaName, new.env(TRUE), where) |
| tbl <- get(.ImplicitGroupMetaName, where) |
| for(what in members) |
| assign(what, as.list(group), envir = tbl) |
| NULL |
| } |
| |
| .getImplicitGroup <- function(name, where) { |
| if(!is.null(tbl <- where[[.ImplicitGroupMetaName]])) { |
| if(!is.null(r <- tbl[[name]])) |
| return(r) |
| } |
| list() |
| } |
| |
| findMethods <- function(f, where, classes = character(), inherited = FALSE, package = "") { |
| if(is(f, "genericFunction")) { |
| fdef <- f |
| f <- fdef@generic |
| } |
| else if(.isSingleString(f)) { |
| if(missing(where)) |
| fdef <- getGeneric(f, package = package) |
| else { # the generic may not be in the where= environment |
| ## but we prefer that version if it is |
| fdef <- getGeneric(f, where = where, package = package) |
| if(is.null(fdef)) |
| fdef <- getGeneric(f, package = package) |
| } |
| } |
| else if(!is.function(f)) |
| stop(gettextf("argument %s must be a generic function or a single character string; got an object of class %s", |
| sQuote("f"), dQuote(class(f))), |
| domain = NA) |
| else { |
| fdef <- f |
| f <- deparse(substitute(f)) |
| } |
| if(!is(fdef, "genericFunction")) { |
| warning(gettextf("non-generic function '%s' given to findMethods()", f), |
| domain = NA) |
| return(list()) |
| } |
| object <- new("listOfMethods", arguments = fdef@signature, |
| generic = fdef) # empty list of methods |
| if(missing(where)) |
| table <- get(if(inherited) ".AllMTable" else ".MTable", envir = environment(fdef)) |
| else { |
| if(!isFALSE(inherited)) |
| stop(gettextf("only FALSE is meaningful for 'inherited', when 'where' is supplied (got %s)", inherited), domain = NA) |
| where <- as.environment(where) |
| what <- .TableMetaName(f, fdef@package) |
| if(is.null(table <- where[[what]])) |
| return(object) |
| } |
| objNames <- sort(names(table)) |
| if(length(classes)) { |
| classesPattern <- paste0("#", classes, "#", collapse = "|") |
| which <- grep(classesPattern, paste0("#",objNames,"#")) |
| objNames <- objNames[which] |
| } |
| object@.Data <- mget(objNames, table) |
| object@names <- objNames |
| object@signatures <- strsplit(objNames, "#", fixed = TRUE) |
| object |
| } |
| |
| findMethodSignatures <- function(..., target = TRUE, methods = findMethods(...)) |
| { |
| what <- methods@arguments |
| if(target) |
| sigs <- methods@signatures |
| else { |
| anySig <- rep("ANY", length(what)) |
| ## something of a kludge for the case of some primitive |
| ## default methods to get a vector of "ANY" of right length |
| for(m in methods) |
| if(!is.primitive(m)) { |
| length(anySig) <- length(m@defined) |
| break |
| } |
| sigs <- lapply(methods, function(x) |
| if(is.primitive(x)) anySig else as.character(x@defined)) |
| } |
| lens <- unique(vapply(sigs, length, 1, USE.NAMES=FALSE)) |
| if(length(lens) == 0) |
| return(matrix(character(), 0, length(methods@arguments))) |
| if(length(lens) > 1L) { |
| lens <- max(lens) |
| anys <- rep("ANY", lens) |
| sigs <- lapply(sigs, function(x) { |
| if(length(x) < lens) { |
| anys[seq_along(x)] <- x |
| anys |
| } else x |
| }) |
| } |
| length(what) <- lens # if not all possible arguments used |
| t(matrix(unlist(sigs), nrow = lens, dimnames = list(what, NULL))) |
| } |
| |
| hasMethods <- function(f, where, package = "") |
| { |
| fdef <- NULL |
| nowhere <- missing(where) # because R resets this if where is assigned |
| if(is(f, "genericFunction")) { |
| fdef <- f |
| f <- fdef@generic |
| if(missing(package)) |
| package <- fdef@package |
| } |
| else if(!.isSingleString(f)) |
| stop(gettextf("argument 'f' must be a generic function or %s", |
| .notSingleString(f)), domain = NA) |
| else if(missing(package)) { |
| package <- packageSlot(f) # maybe a string with package slot |
| if(is.null(package)) { |
| if(missing(where)) |
| fdef <- getGeneric(f) |
| else { # the generic may not be in this package, but prefer it if so |
| fdef <- getGeneric(f, where = where) |
| if(is.null(fdef)) |
| fdef <- getGeneric(f) |
| } |
| if(is(fdef, "genericFunction")) |
| package <- fdef@package |
| else |
| stop(gettextf("'%s' is not a known generic function {and 'package' not specified}", |
| f), |
| domain = NA) |
| } |
| } |
| what <- .TableMetaName(f, package) |
| testEv <- function(ev) |
| exists(what, envir = ev, inherits = FALSE) && |
| length(names(get(what, envir = ev))) > 0L |
| if(nowhere) { |
| for(i in seq_along(search())) { |
| if(testEv(as.environment(i))) |
| return(TRUE) |
| } |
| return(FALSE) |
| } |
| else |
| testEv(as.environment(where)) |
| } |
| ## returns TRUE if the argument is a non-empty character vector of length 1 |
| ## otherwise, returns a diagnostic character string reporting the non-conformance |
| .isSingleName <- function(x) { |
| if(!is.character(x)) |
| return(paste0('required to be a character vector, got an object of class "', class(x)[[1L]], '"')) |
| if(length(x) != 1) |
| return(paste0("required to be a character vector of length 1, got length ",length(x))) |
| if(is.na(x) || !nzchar(x)) |
| return(paste0('required a non-empty string, got "',x, '"')) |
| TRUE |
| } |