# File src/library/methods/R/RMethodUtils.R # Part of the R package, https://www.R-project.org # # Copyright (C) 1995-2021 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(not.s.in.a <- is.na(match(signature, args)))) stop(sprintf(ngettext(sum(not.s.in.a), "non-argument found in the signature: %s", "non-arguments found in the signature: %s"), paste(signature[not.s.in.a], 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(not.s.in.a <- is.na(match(signature, args)))) stop(sprintf(ngettext(sum(not.s.in.a), "non-argument found in the signature: %s", "non-arguments found in the signature: %s"), paste(signature[not.s.in.a], 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 <- "invalid call in method dispatch to '%s' (no default method)" body(fdefault) <- substitute(stop(gettextf(MSG, NAME), domain=NA), list(MSG = msg, NAME = name)) 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 = "", 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(iiN <- is.na(match(signature[omittedSig], c("ANY", "missing"))))) { bad <- omittedSig & iiN 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(any(omittedSig <- omittedSig & signature != "missing")) { .message("Note: ", .renderSignature(f, sig0), gettextf("expanding the signature to include omitted arguments in definition: %s", paste(sigNames[omittedSig], "= \"missing\"",collapse = ", "))) 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 <- anyNA(match(mnames, fnames)) keepsDots <- !is.na(match("...", mnames)) if(!added && keepsDots) { ## the formal args of the method must be identical to generic formals(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 <- (useNm <- !is.na(imf <- match(fnames, mnames))) | fnames == "..." ## Should not be needed, if conformMethod() has already been called: if(is.unsorted(imf[useNm])) stop(.renderSignature(generic@generic, signature), "formal arguments in method and generic do not appear in the same order", call. = FALSE) clArgs <- fnames[useNames] ## leave newCall as a list while checking the trailing args if(keepsDots && dotsPos < length(fnames)) { ## Trailing arguments (those after "...") 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) clNames <- character(length(clArgs)) clNames[seq.int(to = length(clNames), length.out = ntrail)] <- trailingArgs } else clNames <- NULL if((iMi <- match("missing", signature, nomatch=0L)) && length(iNm <- which(useNm)) && any(i <- (iMi <= iNm & iNm <= if(is.na(dotsPos)) length(fnames) else dotsPos-1L))) { ## name args in .local(..) call because we have "missing" in method signature if(is.null(clNames)) clNames <- character(length(clArgs)) ## fnames[iNm] == fnames[useNm] is subset of clArgs := fnames[useNames] im <- match(fnames[iNm][i], clArgs) clNames[im] <- clArgs[im] } if(!is.null(clNames)) names(clArgs) <- clNames newCall <- as.call(lapply(c(".local", clArgs), as.name)) ##== newCall <- as.call(c(quote(.local), lapply(clArgs, as.name))) 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 (called from high level) .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()) } ## ../src/methods_list_dispatch.c -- R_getGeneric(SEXP name, SEXP mustFind, SEXP env, SEXP package) : 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) } 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, fixed=TRUE) } ## also called from base::loadNamespace, unloadNamespace(), attach() & detach() 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) lev <- 0L if (attach) { msg <- Sys.getenv("_R_TRACE_LOADNAMESPACE_", "") if (nzchar(msg)) { if(pkg %in% c("base", "tools", "utils", "grDevices", "graphics", "stats", "datasets", "methods", "grid", "splines", "stats4", "tcltk", "compiler", "parallel")) lev <- 0L else { lev <- as.integer(msg) if(is.na(lev)) lev <- 0L } } for(cl in classes) { if(lev > 2L) message("--- caching class ", sQuote(cl)) ## NOT getClassDef, it will use cache cldef <- get(classMetaName(cl), where) if(is(cldef, "classRepresentation")) .cacheClass(cl, cldef, is(cldef, "ClassUnionRepresentation"), where) if(lev > 2L) message("--- done caching class ", sQuote(cl)) } } 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]] .tr <- attach && (lev > 2L || lev == -5L) if(!identical(fpkg, pkg) && doCheck) { if(.tr) message("--- getting generic ", sQuote(f), " (and methods)") 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) if(.tr) message("--- done getting generic ", sQuote(f)) } .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 = "") { 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 }