| # File src/library/methods/R/methodsTable.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/ |
| |
| ### merge version called from namespace imports code. Hope to avoid using generic |
| .mergeMethodsTable2 <- function(table, newtable, envir, metaname) { |
| old <- as.list(table, all.names=TRUE) |
| mm <- 1 |
| for( what in old) { |
| if(is(what, "MethodDefinition")) { |
| mm <- length(what@defined) |
| break |
| } |
| } |
| new <- as.list(newtable, all.names=TRUE) |
| ## check that signature length doesn't change |
| canStore <- TRUE |
| for(what in new) { |
| if(is(what, "MethodDefinition") && |
| length(what@defined) != mm) { |
| canStore <- FALSE |
| break |
| } |
| } |
| if(canStore) { |
| list2env(new, table) |
| table |
| } |
| else { # rats! have to get the generic function |
| f <- gsub(".__T__(.*):([^:]+)", "\\1", metaname) |
| package <- gsub(".__T__(.*):([^:]+(.*))", "\\2", metaname) |
| generic <- getGeneric(f, TRUE, envir, package) |
| .mergeMethodsTable(generic, table, newtable, TRUE) |
| table |
| } |
| } |
| |
| ## action on attach, detach to merge methods tables |
| .mergeMethodsTable <- function(generic, table, newtable, add = TRUE) { |
| fenv <- environment(generic) |
| ## signature <- generic@signature |
| if(!exists(".SigLength", envir = fenv, inherits = FALSE)) |
| .setupMethodsTables(generic) |
| allTable <- if(!add) get(".AllMTable", envir = fenv) ## else NULL |
| # .AllMTable but only if required |
| n <- get(".SigLength", envir = fenv) |
| anySig <- rep("ANY", n) # assert doesn't need to be a real signature |
| ## anyLabel <- .sigLabel(anySig) |
| newMethods <- names(newtable) |
| for(what in newMethods) { |
| obj <- newtable[[what]] |
| if(is.primitive(obj)) |
| sig <- anySig |
| else if(is(obj, "MethodDefinition")) |
| sig <- obj@defined |
| else if(is.environment(obj)) { |
| objsWhat <- as.list(obj, all.names=TRUE, sorted=TRUE) |
| if(length(objsWhat) == 0) |
| next # empty environment, ignore |
| isDef <- vapply(objsWhat, is, logical(1L), "MethodDefinition") |
| if (any(isDef)) { |
| sig <- objsWhat[[utils::tail(which(isDef), 1L)]]@defined |
| } else { |
| sig <- anySig |
| } |
| } |
| else |
| stop(gettextf("invalid object in meta table of methods for %s, label %s, had class %s", |
| sQuote(generic@generic), |
| sQuote(what), |
| dQuote(class(obj))), |
| domain = NA) |
| ns <- length(sig) |
| if(ns == n) {} |
| else { |
| if(ns < n) { |
| nadd <- n - ns |
| sigPackage <- packageSlot(sig) |
| if(length(sigPackage)< ns) # probably out of date? |
| sigPackage <- c(sigPackage, rep("", ns - length(sigPackage))) |
| sig <- .simpleSignature(c(sig, rep("ANY", nadd)), |
| names = generic@signature[1:n], |
| packages = c(sigPackage, rep("methods", nadd))) |
| obj <- .xpdSignature(obj, sig, n-ns) |
| what <- .sigLabel(sig) |
| ns <- n |
| } |
| else if(add) { # and ns > n |
| signames <- generic@signature |
| length(signames) <- ns |
| .resetTable(table, ns, signames) |
| fenv[[".SigLength"]] <- ns |
| n <- ns |
| } |
| } |
| if(add) { |
| if(exists(what, envir = table, inherits = FALSE)) { |
| obj <- .newOrMultipleMethod(obj, what, table) |
| ## must replace in .AllMTable also |
| if(is.null(allTable)) |
| allTable <- get(".AllMTable", envir = fenv) |
| allTable[[what]] <- obj |
| } |
| table[[what]] <- obj |
| } |
| else if(exists(what, envir = table, inherits = FALSE) && |
| !all(obj@defined == "ANY") ) { |
| ## remove methods, but not the default |
| remove(list = what, envir = allTable) |
| remove(list = what, envir = table) |
| } |
| ## else warning? |
| } |
| NULL |
| } |
| |
| .xpdSignature <- function(obj, sig, nadd) { |
| if(is(obj, "MethodDefinition")) { |
| obj@defined <- sig |
| obj@target <- sig |
| } |
| else if(is.environment(obj)) { |
| ## xtrPkg <- rep("methods", nadd) |
| for(what in names(obj)) { |
| objw <- get(what, envir = obj) |
| if(is(objw, "MethodDefinition")) { |
| sigw <- objw@defined |
| pkgw <- packageSlot(sigw) |
| if(length(pkgw) < length(sigw)) |
| pkgw <- c(pkgw, rep("", length(sigw) - length(pkgw))) |
| sigw <- .simpleSignature( c(sigw, rep("ANY", nadd)), |
| names = names(sig), |
| packages = c(pkgw, rep("methods", nadd))) |
| objw@defined <- objw@target <- sigw |
| remove(list = what, envir = obj) |
| var <- .pkgMethodLabel(objw) |
| if(nzchar(var)) obj[[var]] <- objw |
| } |
| } |
| } |
| obj |
| } |
| |
| ## a simpler version of setting up a signature object |
| ## For better or worse, the initialize() method expects |
| ## a function definition and calls .MakeSignature() |
| .simpleSignature <- function(classes, names, packages) { |
| object <- new("signature") |
| object@.Data <- classes |
| object@names <- names |
| object@package <- packages |
| object |
| } |
| |
| .newOrMultipleMethod <- function(obj, what, table) { |
| if(!.duplicateClassesExist()) |
| return(obj) |
| current <- get(what, envir = table) |
| if(is.environment(current)) { |
| if(is.environment(obj)) |
| list2env(as.list(obj, all.names=TRUE), current) |
| else if(is(obj, "MethdodDefinition")) { |
| var <- .pkgMethodLabel(obj) |
| if(nzchar(var)) assign(var, obj, envir = current) |
| } |
| current |
| } |
| else if(is(current, "MethodDefinition")) { |
| curPkg <- packageSlot(current@defined) |
| if(is(obj, "MethodDefinition")) { |
| objPkg <- packageSlot(obj@defined) |
| if(is.null(curPkg) || is.null(objPkg) || |
| identical(curPkg, objPkg)) |
| return(obj) |
| else { |
| merge <- new.env() |
| var <- .pkgMethodLabel(obj) |
| if(nzchar(var)) assign(var, obj, envir = merge) |
| var <- .pkgMethodLabel(current) |
| if(nzchar(var)) assign(var, current, envir = merge) |
| return(merge) |
| } |
| } |
| else if(is.environment(obj)) { |
| merge <- new.env() |
| assign(.pkgMethodLabel(current), current, envir = merge) |
| list2env(as.list(obj, all.names=TRUE), merge) |
| return(merge) |
| } |
| ## else adding a primitive, should do nothing |
| else |
| current |
| } |
| } |
| |
| |
| .mlistAddToTable <- function(generic, mlist, table = new.env(TRUE, fenv), add = TRUE) { |
| fenv <- environment(generic) |
| signature <- generic@signature |
| if(!exists(".SigLength", envir = fenv, inherits = FALSE)) |
| .setupMethodsTables(generic) |
| n <- get(".SigLength", envir = fenv, inherits = FALSE) |
| .storeMlist(table, rep("ANY", n), mlist, 1, add, fenv) |
| ## check for more args in the mlist than in the table |
| nNow <- get(".SigLength", envir = fenv, inherits = FALSE) |
| if(nNow > n) { |
| length(signature) <- nNow |
| .resetTable(table, nNow, signature) |
| } |
| table |
| } |
| |
| ## utility now *only* called once above |
| .storeMlist <- function(table, sig, mlist, i, add, fenv) { |
| ## once generic functions are installed from 2.11.0 or later, this should |
| ## only be called with mlist a method or NULL. |
| if(is.null(mlist)) return(table) |
| m <- if(is(mlist, "MethodsList")) { .MlistDefunct(); mlist@methods } |
| else list(ANY=mlist) |
| |
| ## once MethodsList is defunct, this should be rewritten (and renamed!) |
| |
| ## the methods slot is a list named by class, with elements either |
| ## method definitions or mlists |
| classes <- names(m) |
| for(j in seq_along(m)) { |
| el <- m[[j]] |
| sig[[i]] <- classes[[j]] |
| if(is(el, "MethodDefinition") || is.primitive(el)) { |
| if(add) |
| assign(.sigLabel(sig), el, envir = table) |
| else |
| remove(list = .sigLabel(sig), envir = table) |
| } |
| else if(is(el,"MethodsList")) { |
| .MlistDefunct() |
| i1 <- i+1 |
| if(i1 >= length(sig)) { |
| ## a reset of the labels will be needed |
| assign(".SigLength", i1, envir = fenv) |
| sig <- c(sig, rep("ANY", i1-length(sig))) |
| } |
| Recall(table, sig, el, i1, add, fenv) |
| } |
| else |
| stop(gettextf( |
| "invalid mlist element for signature %s at level %d (should be MethodDefinition or .Primitive, had class %s)", |
| sQuote(classes[[j]]), |
| i, |
| dQuote(class(el))), |
| domain = NA) |
| } |
| table |
| } |
| |
| .cacheMethodInTable <- function(fdef, sig, def, |
| table = get(".AllMTable", envir = fenv)) { |
| ## store method in cache table. |
| ## called from setMethod() |
| ## also Called from cacheMethod (from as(), as<-()) |
| fenv <- environment(fdef) |
| if(missing(table) && !exists(".AllMTable", envir = fenv, inherits = FALSE)) |
| .setupMethodsTables(fdef) |
| sig <- .matchSigLength(sig, fdef, fenv, TRUE) |
| label <- .sigLabel(sig) |
| isCurrent <- exists(label, envir = table, inherits = FALSE) |
| if(is.null(def)) { # remove the method (convention for setMethod) |
| if(isCurrent) |
| remove(list = label, envir = table) |
| } |
| else { |
| dupl <- .duplicateClassesExist() |
| ## ensure that a valid object is assigned: if duplicate classes |
| ## exist, may need a table by package label; else, make sure |
| ## the target and defined slots are complete |
| ## IF we believed all methods up to date, the call could be conditional |
| ## if(dupl || isCurrent) |
| def <- .methodPackageSlots(def, label, table, dupl, isCurrent) |
| assign(label, def, envir = table) |
| } |
| } |
| |
| ## check for duplicate classes and embed method in an environment if so |
| .methodPackageSlots <- function(def, ...) def |
| |
| ## the real version |
| ..methodPackageSlots <- function(def, label, table, duplicatesExist, isCurrent) { |
| sig <- def@target |
| dups <- FALSE |
| if(duplicatesExist) { |
| def <- .fixPackageSlot(def, sig) |
| for(cl in sig) { |
| if(exists(cl, envir = .classTable, inherits = FALSE) && is.list(get(cl, envir = .classTable))) { |
| dups <- TRUE |
| break |
| } |
| } |
| if(isCurrent) { # check that this is overwriting identical signature |
| current <- get(label, envir = table) |
| dups <- dups || !identical(current@target, sig) |
| } |
| if(dups) { |
| if(isCurrent) { |
| if(is(current, "MethodDefinition")) { |
| pkg <- attr(current@target, "package") |
| if(length(pkg) == 0) |
| current <- .fixPackageSlot(current, current@target) |
| env <- new.env() |
| ## zero-length seen 2011-07-29 |
| var <- .pkgMethodLabel(current) |
| if(nzchar(var)) assign(var, current, envir = env) |
| } |
| else if(is.environment(current)) |
| env <- current |
| else |
| stop( |
| gettextf("bad method object stored in method table, class %s", |
| dQuote(class(current))), |
| domain = NA) |
| } |
| else |
| env <- new.env() |
| assign(.pkgMethodLabel(def), def, envir = env) |
| env |
| } |
| else # no change |
| def |
| } |
| else # no duplicate classes |
| def |
| } |
| |
| .fixPackageSlot <- function(def, sig) { |
| ## check the pkg slot |
| pkgs <- attr(sig, "package") |
| if(is.null(pkgs)) |
| pkgs <- character(length(sig)) |
| fixme <- !nzchar(pkgs) |
| if(any(fixme)) { |
| for(i in seq_along(pkgs)[fixme]) |
| pkgs[[i]] <- getClass(sig[[i]], .Force = TRUE)@package |
| attr(sig, "package") <- pkgs |
| def@target <- sig |
| ## check the defined signature as well |
| sig <- def@defined |
| pkgs <- attr(sig, "package") |
| if(is.null(pkgs)) |
| pkgs <- character(length(sig)) |
| fixme <- !nzchar(pkgs) |
| if(any(fixme)) { |
| for(i in seq_along(pkgs)[fixme]) |
| pkgs[[i]] <- getClass(sig[[i]], .Force = TRUE)@package |
| attr(sig, "package") <- pkgs |
| def@defined <- sig |
| } |
| } |
| def |
| } |
| |
| .okMethodLabel <- function(method) { |
| if(is(method, "MethodDefinition")) { |
| pkgs <- packageSlot(method@target) |
| length(pkgs) > 0 && all(nzchar(pkgs)) |
| } |
| else |
| TRUE # primitive or environment |
| } |
| |
| |
| .pkgMethodLabel <- function(method) { |
| sig <- method@target |
| pkgs <- packageSlot(sig) |
| if( (length(pkgs) < length(as.character(sig))) || any(!nzchar(pkgs))) |
| stop("package slot missing from signature for generic ", |
| sQuote(method@generic), "\n", |
| "and classes ", paste(sig, collapse = ", "), "\n", |
| "cannot use with duplicate class names (the package may need to be re-installed)", |
| call. = FALSE, domain = NA) |
| paste(pkgs, collapse = "#") |
| } |
| |
| .resetTable <- function(table, n, signames) { |
| ## protect this computation, in case it's resetting |
| ## something used in the computation |
| primMethods <- .allowPrimitiveMethods(FALSE) |
| on.exit(.allowPrimitiveMethods(primMethods)) |
| ## after updating a methods table, the maximum no. of arguments in |
| ## the signature increased to n. Reassign any objects whose label |
| ## does not match n classes from the defined slot |
| anyLabel <- rep("ANY", n) |
| anyPkg <- rep("methods", n) |
| seqN <- 1L:n |
| labels <- names(table) |
| for(what in labels) { |
| method <- get(what, envir = table) |
| if(is.primitive(method)) # stored as default ? |
| newSig <- anyLabel |
| else if(is(method, "MethodDefinition")) |
| newSig <- method@defined |
| else if(is(method, "environment")) { |
| newSig <- strsplit(what, "#", fixed = TRUE)[[1]] |
| .resetTable(method, n, signames) |
| } |
| else |
| stop(gettextf("invalid object in methods table (%s), expected a method, got an object of class %s", |
| sQuote(what), |
| dQuote(class(method))), |
| domain = NA) |
| |
| if(is(method, "MethodDefinition")) { |
| pkgs <- packageSlot(newSig) |
| newSig <- as(ifelse(seqN > length(newSig), anyLabel, newSig), "signature") |
| newSig@names <- signames |
| newSig@package <- ifelse(seqN > length(pkgs), anyPkg, pkgs) |
| method@defined <- method@target <- newSig |
| newLabel <- .sigLabel(newSig) |
| } |
| else |
| newLabel <- .sigLabel(ifelse(seqN > length(newSig), anyLabel, newSig)) |
| remove(list=what, envir = table) |
| assign(newLabel, method, envir = table) |
| } |
| NULL |
| } |
| |
| ### the tag associated with a method signature. |
| ### Should perhaps use the same C code as dispatch, for consistency, |
| ### however, that code breaks out early in the collapse loop if no match. |
| ### This code is not used for quick matching, so efficiency less critical. |
| .sigLabel <- function(sig) |
| paste(sig, collapse = "#") |
| |
| .fillSignatures <- function(sigs, n) { |
| siglens <- lengths(strsplit(sigs, "#", fixed=TRUE)) |
| short <- n > siglens |
| suffix <- vapply(n - siglens[short], |
| function(ni) paste(rep("ANY", ni), collapse="#"), |
| character(1L)) |
| sigs[short] <- paste(sigs[short], suffix, sep="#") |
| sigs |
| } |
| |
| ## workhorse of selectMethod() [ -> ../Methods.R ] " |
| .findInheritedMethods <- |
| function(classes, fdef, mtable = NULL, |
| table = get(".MTable", envir = environment(fdef)), |
| excluded = NULL, useInherited, |
| simpleOnly = .simpleInheritanceGeneric(fdef), verbose = FALSE, |
| doCache = is.environment(mtable), |
| where = environment(fdef)) |
| { |
| ## to avoid infinite recursion, and somewhat for speed, turn off S4 methods for primitives |
| primMethods <- .allowPrimitiveMethods(FALSE) |
| on.exit(.allowPrimitiveMethods(primMethods)) |
| ## classes is a list of the class(x) for each arg in generic |
| ## signature, with "missing" for missing args |
| if(!is.environment(table)) { |
| if(is(fdef, "standardGeneric")) |
| stop(gettextf("invalid or unset methods table in generic function %s", sQuote(fdef@generic)), damain = NA) |
| else |
| stop("trying to find a methods table in a non-generic function") |
| } |
| hasGroup <- length(fdef@group) > 0L |
| if(hasGroup) |
| groupGenerics <- .getAllGroups(list(fdef)) |
| doExcluded <- length(excluded) > 0L |
| if(verbose) { |
| plist <- function(x) paste(x, collapse = ", ") |
| cat(" .findInheritedMethods(): (hasGroup, doCache, doExcluded)= (", |
| plist(c("f","T")[1+c(hasGroup, doCache, doExcluded)]), ")\n", |
| if(hasGroup) paste0(" Group generics: ", |
| plist(vapply(groupGenerics, slot, |
| character(1), "generic")), "\n"), |
| sep='') |
| } |
| nargs <- length(classes) |
| if(!missing(useInherited) && length(useInherited) < nargs) |
| useInherited <- rep(useInherited, length.out = nargs) |
| if(hasGroup && !doExcluded) { |
| ## first try for an exact match in a group generic |
| ## If this matches & is cached, it then will be treated as a non-inherited method |
| ## so no further calls here should occur. |
| ## |
| ## doExcluded is the findNextMethod case; we don't regard group methods as |
| ## inherited in the nextMethod sense, since they have the same signature |
| label <- .sigLabel(classes) |
| direct <- .getGroupMethods(label, groupGenerics, FALSE) |
| if(length(direct)) { |
| if(doCache) |
| assign(label, direct[[1L]], envir = mtable) |
| return(direct) |
| } |
| ## else, continue because we may want all defined methods |
| } |
| cl1 <- classes[[1L]] |
| def <- getClass(cl1, where = where, .Force = TRUE) |
| labels <- |
| if(missing(useInherited) || useInherited[[1L]]) |
| c(cl1, .eligibleSuperClasses(def@contains, simpleOnly), "ANY") |
| else cl1 |
| supersList <- list(labels) |
| classDefs <- vector("list", nargs) |
| classDefs[[1L]] <- def |
| if(nargs > 1) { ## further arguments |
| for(i in 2:nargs) { |
| cc <- classDefs[[i]] <- getClass(classes[[i]], where = where, |
| .Force = TRUE) |
| allLabels <- if(missing(useInherited) || useInherited[[i]]) |
| c(cc@className, .eligibleSuperClasses(cc@contains, simpleOnly), |
| "ANY") |
| else cc@className |
| labels <- outerLabels(labels, allLabels) |
| supersList <- c(supersList, list(allLabels)) |
| } |
| } |
| labels <- labels[-1L] # drop exact match |
| labels <- unique(labels)# only needed while contains slot can have duplicates(!) |
| if(verbose) { |
| cat(" .fI> length(unique(method labels)) = ", length(labels)) |
| if(verbose >= 2) { cat("; labels = \n") ; print(labels) } |
| } |
| allMethods <- names(table) |
| m <- match(labels, .fillSignatures(allMethods, length(classes))) |
| found <- !is.na(m) |
| methods <- mget(allMethods[m[found]], table) |
| if(verbose) cat(" >> found: ", length(methods), "\n") |
| if(hasGroup) { |
| ## add the group methods recursively found but each time |
| ## only those not already included in found. |
| groupmethods <- .getGroupMethods(labels, groupGenerics, found) |
| fromGroup <- c(rep(FALSE, length(methods)), |
| rep(TRUE, length(groupmethods))) |
| if(verbose) cat(" .fI> #{additional group methods}:", |
| length(groupmethods),"\n") |
| methods <- c(methods, groupmethods) |
| } |
| else |
| fromGroup <- rep(FALSE, length(methods)) |
| ## resolve any duplicate-class ambiguities |
| if(.duplicateClassesExist()) { |
| found <- integer() |
| nm <- names(methods) |
| for(i in seq_along(methods)) { |
| m <- methods[[i]] |
| if(is.environment(m)) { |
| methods[[i]] <- .checkDuplicateMethodClasses(classDefs, m, nm[[i]]) |
| found <- c(found, i) |
| } |
| } |
| if(length(found)) |
| methods <- unlist(methods, recursive = FALSE) |
| if(!is.list(methods)) # reduced to a single method? |
| methods <- list(methods) |
| } |
| if(doExcluded) |
| methods <- methods[is.na(match(names(methods), as.character(excluded)))] |
| ## remove default (ANY,..,ANY) if its not the only method: |
| if(length(methods) > 1L) { |
| defaultLabel <- paste(rep.int("ANY", nargs), collapse = "#") |
| i <- match(defaultLabel, names(methods), 0L) |
| if(i > 0L) { |
| methods <- methods[-i] |
| fromGroup <- fromGroup[-i] |
| } |
| } |
| if(length(methods) > 1L) { |
| if(verbose) cat(" .fI> length(methods) = ", length(methods), |
| " --> ambiguity\n") |
| ## have ambiguity to resolve |
| select <- .getBestMethods(methods, supersList, fromGroup, verbose=verbose) |
| ## -------------- |
| if(length(select) > 1L) { |
| if(verbose) cat(" .fI> found", length(select)," best methods\n") |
| |
| ## target <- .sigLabel(classes) |
| condAction <- getOption("ambiguousMethodSelection") |
| if(is.null(condAction)) |
| condAction <- .ambiguousMethodMessage |
| else if(!is.function(condAction)) |
| stop(gettextf("the \"ambiguousMethodSelection\" option should be a function to be called as the condition action; got an object of class %s", |
| dQuote(class(condAction))), |
| domain = NA) |
| |
| select <- withCallingHandlers( |
| .disambiguateMethods(classes, select, fdef@generic, |
| methods, supersList, fromGroup, |
| classDefs, verbose), |
| ambiguousMethodSelection=condAction) |
| } |
| methods <- methods[select] |
| } |
| if(simpleOnly && length(methods) == 0L) { |
| ## Seems to be *unused* [below, 'simpleOnly' argument was missing for years!] |
| methods <- Recall(classes, fdef, mtable, table, excluded, useInherited, |
| simpleOnly, verbose, FALSE) |
| if(length(methods) > 0L) |
| message(gettextf("No simply inherited methods found for function %s; using non-simple method", |
| sQuote(fdef@generic)), |
| domain = NA) |
| } |
| if(length(methods)) { |
| tlabel <- .sigLabel(classes) |
| m <- methods[[1L]] |
| if(is(m, "MethodDefinition")) { # else, a primitive |
| m@target <- .newSignature(classes, fdef@signature) |
| ## if any of the inheritance is not simple, must insert coerce's in method body |
| coerce <- .inheritedArgsExpression(m@target, m@defined, body(m)) |
| if(!is.null(coerce)) |
| body(m) <- coerce |
| methods[[1L]] <- m |
| } |
| if(doCache) { |
| if(verbose) cat(" .fI> caching newly found methods ..\n") |
| assign(tlabel, m, envir = mtable) |
| } |
| } |
| methods |
| } |
| |
| .checkDuplicateMethodClasses <- function(classDefs, env, label){ |
| supers <- strsplit(label, "#", TRUE)[[1]] |
| sigs <- sort(names(env)) |
| plabels <- strsplit(sigs, "#", TRUE) |
| hasSubclass <- vapply(plabels, .hasThisSubclass, logical(1L), |
| classDefs=classDefs, supers=supers) |
| mget(sigs[hasSubclass], env) |
| } |
| |
| .hasThisSubclass <- function(classDefs, supers, plabel) { |
| for(i in seq_along(plabel)) { |
| pkg <- classDefs[[i]]@package |
| cl <- classDefs[[i]]@className |
| si <- supers[[i]] |
| pki <- plabel[[i]] |
| if(identical(si, "ANY") || |
| (identical(cl, si) && identical(pkg, pki))) |
| next |
| cli <- getClassDef(si, package = pki) |
| if(is.null(cli)) return(FALSE) |
| sub <- cli@subclasses[[cl]] |
| if(is.null(sub) || !identical(pkg, sub@package)) |
| return(FALSE) |
| } |
| TRUE |
| } |
| |
| .ambiguousMethodMessage <- function(cond) { |
| selected <- attr(cond, "selected") |
| if(is.null(selected)) {# not properly set up, so just use the message |
| message(cond$message) |
| } |
| else { |
| possible <- attr(cond, "candidates") |
| message(gettextf("Note: method with signature %s chosen for function %s,\n target signature %s.\n %s would also be valid", |
| sQuote(selected), |
| sQuote(attr(cond, "generic")), |
| sQuote(attr(cond, "target")), |
| paste0('"', possible[is.na(match(possible, selected))], '"', |
| collapse=", ")), |
| domain = NA) |
| } |
| } |
| |
| .simpleInheritanceGeneric <- function(fdef) { |
| identical(attr(fdef@signature, "simpleOnly"), TRUE) |
| } |
| |
| .eligibleSuperClasses <- function(contains, simpleOnly) { |
| what <- names(contains) |
| if(!length(what)) |
| what |
| else { |
| eligible <- |
| vapply(contains, |
| if(simpleOnly) |
| function(x) (is.logical(x) && x) || x@simple |
| else # eliminate conditional inheritance |
| function(x) (is.logical(x) && x) || x@simple || isTRUE(body(x@test)), NA) |
| what[eligible] |
| } |
| } |
| |
| .newSignature <- function(classes, names) { |
| ## a simple version to deal with boostrapping stage, used in new() etc |
| n <- min(length(classes), length(names)) |
| i <- seq_len(n) |
| ## a corresponding set of package names |
| ## <FIXME> There should be a "<unknown>" package name instead of "methods" |
| ## but this requires a way to deal with that generally </FIXME> |
| pkgs <- lapply(classes[i], packageSlot) |
| pkgs[vapply(pkgs, is.null, logical(1L))] <- "methods" |
| |
| ## Simplified version ... |
| .asS4(structure(as.character(classes)[i], |
| class = .signatureClassName, |
| names = as.character(names)[i], |
| package = as.character(pkgs) )) |
| } |
| |
| .findNextFromTable <- function(method, f, optional, envir, prev = character()) |
| { |
| fdef <- getGeneric(f, where = envir) |
| env <- environment(fdef) |
| ## target <- method@target |
| n <- get(".SigLength", envir = env) |
| defined <- method@defined |
| m <- length(defined) |
| if(m > n) |
| length(defined) <- n |
| else if(n > m) |
| ## will only really need this to be a signature when the elements |
| ## have package attribute--see .sigLabel |
| defined <- new("signature", fdef, c(defined@.Data, rep("ANY", n-m))) |
| excluded <- c(prev, .sigLabel(defined)) |
| allTable <- .getMethodsTable(fdef, inherited = TRUE) |
| methods <- .findInheritedMethods(defined, fdef, mtable = NULL, |
| table = allTable, |
| excluded = excluded, |
| where = envir) |
| if(length(methods) == 0L) # use default method, maybe recursively. |
| methods <- list(finalDefaultMethod(fdef@default)) #todo: put a label on it? |
| if(length(methods) > 1L) |
| warning(sprintf(ngettext(length(methods), |
| "found %d equally good next method", |
| "found %d equally good next methods"), |
| length(methods)), |
| domain = NA) |
| ## excluded slot is a list, but with methods tables, elements are just labels |
| new("MethodWithNext", method, nextMethod = methods[[1L]], |
| excluded = as.list(excluded)) |
| } |
| |
| |
| ## get the classes of the args |
| |
| .InheritForDispatch <- function(classes, fdef, mtable) { |
| methods <- .findInheritedMethods(classes, fdef, mtable) |
| if(length(methods) == 1L) |
| return(methods[[1L]]) # the method |
| else if(length(methods) == 0L) { |
| cnames <- paste0("\"", vapply(classes, as.character, ""), "\"", |
| collapse = ", ") |
| stop(gettextf("unable to find an inherited method for function %s for signature %s", |
| sQuote(fdef@generic), |
| sQuote(cnames)), |
| domain = NA) |
| } |
| else |
| stop("Internal error in finding inherited methods; didn't return a unique method", domain = NA) |
| } |
| |
| .findMethodForFdef <- function(signature, table, fdef = NULL) { |
| value <- .findMethodInTable(signature, table, fdef) |
| if(is.null(value) && is(fdef, "genericFunction")) { # try without expanding signature |
| fullSig <- .matchSigLength(signature, fdef, environment(fdef), FALSE) |
| if(!identical(fullSig, signature)) |
| value <- .findMethodInTable(signature, table, fdef, FALSE) |
| } |
| value |
| } |
| |
| .findMethodInTable <- function(signature, table, fdef = NULL , expdSig = TRUE) |
| { |
| if(is(fdef, "genericFunction") && expdSig) |
| signature <- .matchSigLength(signature, fdef, environment(fdef), FALSE) |
| label <- .sigLabel(signature) |
| ## allMethods <- objects(table, all.names=TRUE) |
| ## if(match(label, allMethods, nomatch = 0L)) |
| if(!is.null(value <- table[[label]])) { |
| if(is.environment(value)) { |
| pkgs <- names(value) |
| if(length(pkgs) == 1) |
| value <- value[[pkgs]] |
| else if(length(pkgs) == 0) |
| value <- NULL |
| ## else, return the environment indicating multiple possibilities |
| } |
| value |
| } # else, NULL |
| } |
| |
| ## inheritance distances: 0 for the class, 1 for immediate contains, 2 for other contains |
| ## and 3 for ANY |
| .inhDistances <- function(classDef) { |
| contains <- classDef@contains |
| allNames <- unique(names(contains)) # bug allows duplicates in contains |
| dist <- rep(2, length(allNames)) |
| for(i in seq_along(dist)) { |
| ci <- contains[[i]] |
| dist[[i]] <- ci@distance |
| } |
| dist <- c(0, dist, NA) |
| names(dist) <- c(classDef@className, allNames, "ANY") |
| dist |
| } |
| |
| .leastMethodDistance <- function(methods, supersList, classDefs, fromGroup, verbose = FALSE) { |
| n <- length(methods) |
| dist <- rep(0, n) |
| nArg <- length(classDefs) |
| defClasses <- matrix("ANY", nArg, n) |
| for(j in 1L:n) { |
| cl <- methods[[j]]@defined@.Data |
| defClasses[seq_along(cl), j] <- cl |
| } |
| containsDist <- lapply(classDefs, .inhDistances) |
| maxDist <- max(unlist(containsDist), na.rm = TRUE) + 1 |
| if(verbose) { cat("** individual arguments' distances:\n"); print(containsDist) } |
| ## add up the inheritance distances for each argument (row of defClasses) |
| for(i in 1L:nArg) { |
| ihi <- containsDist[[i]] |
| ihi[is.na(ihi)] <- maxDist |
| cli <- defClasses[i,] |
| dist <- dist + ihi[match(cli, names(ihi))] |
| } |
| ## These should be integers, so we do not need to worry about a decimal point |
| if(verbose) cat("** final methods' distances: (", |
| paste(formatC(dist), collapse= ", "), ")\n", sep='') |
| best <- dist == min(dist) |
| ## of the least distance methods, choose direct, rather than group |
| ## methods, unless all the best methods are from group generics |
| if(any(fromGroup[best]) && !all(fromGroup[best])) |
| best <- best & !fromGroup |
| (1:n)[best] |
| } |
| |
| ## currently called exactly once from .findInheritedMethods() : |
| .getBestMethods <- function(methods, supersList, fromGroup, verbose = FALSE) { |
| n <- length(methods) ## >= 2 |
| nArg <- length(supersList)## >= 1 |
| sigs <- matrix("ANY", nArg, n) |
| for(i in 1:n) { |
| sig <- methods[[i]]@defined |
| if(length(sig) < nArg) { # is this still possible? --> show 'verbose' |
| if(verbose) cat(sprintf(" .. method %d: length(sig) = %d < nArg = %d\n", |
| i, length(sig), nArg)) |
| sigs[seq_along(sig), i] <- sig |
| } |
| else |
| sigs[,i] <- sig |
| } |
| if(nArg < 2) { # the easy case |
| return(which.min(match(sigs[1L,], supersList[[1L]]))) |
| } |
| ## else nArg >= 2 |
| pos <- matrix(0L, nArg, n) |
| for(i in 1:nArg) { |
| pos[i,] <- match(sigs[i,], supersList[[i]]) |
| } |
| valid <- colSums(is.na(pos)) == 0L |
| best <- valid |
| dominated <- !valid |
| ## pairwise comparison of columns of pos. Any way to vectorize? |
| seqn <- seq_len(n) |
| for(i in seqn[valid]) { |
| for(j in seqn[-i][valid[-i]]) { |
| diffs <- pos[,j] - pos[,i] |
| if(any(diffs < 0)) { best[i] <- FALSE; if(dominated[i]) break } |
| if(all(diffs <= 0)) { dominated[i] <- TRUE; if(!best[i]) break } |
| } |
| } |
| if(verbose) |
| cat(if(any(best)) paste(" have best ones", |
| paste(format(seqn[best]),collapse=",")) |
| else if(any(dominated)) paste(" can eliminate dominated ones,", |
| paste(format(seqn[dominated]),collapse=",")), |
| "\n") |
| ## a best method is as early in the superclasses as any other on all arguments |
| ## Because the signatures are not duplicated, there can be at most one. |
| if(any(best)) |
| seqn[best] |
| ## eliminate those methods dominated by another |
| else |
| seqn[!dominated] |
| } |
| |
| ## currently called exactly once from .findInheritedMethods() : |
| .disambiguateMethods <- function(target, which, generic, methods, supersList, |
| fromGroup, classDefs, verbose) |
| { |
| ## save full set of possibilities for condition object |
| candidates <- methods[which] |
| note <- character() |
| ## choose based on total generational distance |
| which2 <- .leastMethodDistance(candidates, supersList, classDefs, |
| fromGroup[which]) |
| if(length(which2) < length(which)) { |
| note <- c(sprintf(ngettext(which2, |
| "Selecting %d method of minimum distance", |
| "Selecting %d methods of minimum distance"), |
| which2)) |
| which <- which[which2] |
| } |
| ## if some are group methods, eliminate those |
| if(length(which) > 1 && any(fromGroup[which]) && !all(fromGroup[which])) { |
| which <- which[!fromGroup] |
| note <- c(note, sprintf(ngettext(length(which), |
| "Selecting %d non-group method", |
| "Selecting %d non-group methods"), |
| length(which))) |
| } |
| ## prefer partially direct methods |
| if(length(which) > 1) { |
| direct <- vapply(methods[which], function(x, target) |
| (is(x, "MethodDefinition") && any(target == x@defined)), |
| NA, target = target) |
| if(any(direct) && !all(direct)) { |
| which <- which[direct] |
| note <- c(note, sprintf(ngettext(length(which), |
| "Selecting %d partially exact-matching method", |
| "Selecting %d partially exact-matching methods"), |
| length(which))) |
| } |
| } |
| which <- which[[1L]] |
| if(identical(as.character(generic), "coerce")) |
| return(which) # as() computations not currently consistent w. selection (R 2.15.2) |
| selected <- names(methods)[[which]] |
| ## FIXME (?): This is not shown to the user |
| msg <- sprintf(ngettext(length(candidates), |
| "Choosing method %s from %d ambiguous possibility", |
| "Choosing method %s from %d ambiguous possibilities"), |
| sQuote(selected), length(candidates)) |
| condObject <- simpleCondition(msg) |
| ## would be nice to use an S4 class eventually |
| class(condObject) <- c("ambiguousMethodSelection", class(condObject)) |
| attributes(condObject) <- |
| c(attributes(condObject), |
| list("candidates" = names(candidates), |
| "target" = .sigLabel(target), |
| "selected" = selected, |
| "generic" = generic, |
| "notes" = if(length(note)) paste(note, collapse ="; ") else "")) |
| if(verbose) cat(" .disambiguateM*(): notes =\n\t", |
| attr(condObject, "notes"), "\n") |
| signalCondition(condObject) |
| which |
| } |
| |
| # add objects to the generic function's environment that allow |
| # table-based dispatch of methods |
| .setupMethodsTables <- function(generic, |
| initialize = !exists(".MTable", envir = env, inherits = FALSE)) |
| { |
| env <- environment(generic) |
| if(initialize || !exists(".SigLength", envir = env, inherits = FALSE)) { |
| nsig <- 1 |
| ## check that groups of generics agree on .SigLength; otherwise |
| ## labels won't match |
| for(gp in generic@group) { |
| gpDef <- getGeneric(gp) |
| if(is(gpDef, "genericFunction")) { |
| .getMethodsTable(gpDef) # force initialization |
| nsig <- max(nsig, get(".SigLength", envir = environment(gpDef))) |
| } |
| } |
| assign(".SigLength", nsig, envir = env) |
| } |
| argSyms <- lapply(generic@signature, as.name) |
| assign(".SigArgs", argSyms, envir = env) |
| if(initialize) { |
| mlist <- generic@default # from 2.11.0: method, primitive or NULL, not MethodsList |
| mtable <- .mlistAddToTable(generic, mlist) # by default, adds to an empty table |
| assign(".MTable", mtable, envir = env) |
| } |
| else ## the current .MTable |
| mtable <- getMethodsForDispatch(generic) |
| .resetInheritedMethods(env, mtable) |
| if(is(generic, "groupGenericFunction")) { |
| for(gp in generic@groupMembers) { |
| gpDef <- getGeneric(gp) |
| if(is(gpDef, "genericFunction")) |
| .getMethodsTable(gpDef) # force initialization w. group methods |
| } |
| } |
| NULL |
| } |
| |
| .updateMethodsInTable <- function(generic, where, attach) { |
| fenv <- environment(generic) |
| reset <- identical(attach, "reset") |
| if(is.null(mtable <- fenv$.MTable)) { |
| .setupMethodsTables(generic) |
| mtable <- get(".MTable", envir = fenv) |
| } |
| if(!reset) { |
| env <- as.environment(where) |
| tname <- .TableMetaName(generic@generic, generic@package) |
| if(!is.null(tt <- env[[tname]])) { |
| .mergeMethodsTable(generic, mtable, tt, attach) |
| } |
| ## else used to warn, but the generic may be implicitly required |
| ## by class inheritance, without any explicit methods in this package |
| } |
| if(length(generic@group)) { |
| groups <- as.list(generic@group) |
| generics <- vector("list", length(groups)) |
| for(i in seq_along(groups)) |
| generics[[i]] <- getGeneric(groups[[i]]) |
| .checkGroupSigLength(groups, generics) |
| } |
| if(is(generic, "groupGenericFunction")) { |
| .checkGroupSigLength(list(generic@generic), list(generic)) |
| for(g in getGroupMembers(generic)) |
| .updateMethodsInTable(getGeneric(g), where, attach) |
| } |
| .resetInheritedMethods(fenv, mtable) |
| mtable |
| } |
| |
| .resetInheritedMethods <- function(fenv, mtable) { |
| allObjects <- character() |
| direct <- names(mtable) |
| if(!is.null(allTable <- fenv$.AllMTable)) { |
| ## remove all inherited methods. Note that code (e.g. setMethod) that asigns |
| ## a new method to mtable is responsible for copying it to allTable as well. |
| allObjects <- names(allTable) |
| remove(list = setdiff(allObjects, direct), envir = allTable) |
| } |
| else { |
| allTable <- new.env(TRUE, fenv) |
| assign(".AllMTable", allTable, envir = fenv) |
| } |
| ## check for missing direct objects; usually a non-existent AllMTable? |
| if(any(is.na(match(direct, allObjects)))) { |
| list2env(as.list(mtable, all.names=TRUE), allTable) |
| } |
| for (d in direct) { |
| m <- allTable[[d]] |
| if (is(m, "MethodWithNext")) |
| allTable[[d]] <- as(m, "MethodDefinition") |
| } |
| NULL |
| } |
| |
| ## In the following, consider separate "compute" and "print" functions/methods: |
| ## Wish: alternative to 'classes' allow "wild-card signature", e.g., |
| ## showMethods("coerce", signature = c("dgeMatrix", "*")) |
| .showMethodsTable <- function(generic, includeDefs = FALSE, inherited = FALSE, |
| classes = NULL, showEmpty = TRUE, printTo = stdout()) |
| { |
| cf <- function(...) cat(file = printTo, sep = "", ...) |
| sigString <- function(sig) |
| paste0(names(sig), "=\"", as.character(sig), "\"", collapse = ", ") |
| ## qs <- function(what) paste0('"', what, '"', collapse = ", ") |
| doFun <- function(func, pkg) cf("Function: ", func, " (package ", pkg, ")\n") |
| env <- environment(generic) |
| ## signature <- generic@signature |
| table <- get(if(inherited) ".AllMTable" else ".MTable", envir = env) |
| f <- generic@generic |
| p <- packageSlot(f) |
| if(is.null(p)) p <- "base" |
| deflt <- new("signature", generic, "ANY") |
| labels <- sort(names(table)) |
| if(!is.null(classes) && length(labels) > 0L) { |
| sigL <- strsplit(labels, split = "#") |
| keep <- !vapply(sigL, function(x, y) all(is.na(match(x, y))), NA, y=classes) |
| labels <- labels[keep] |
| } |
| if(length(labels) == 0L) { |
| if(showEmpty) { |
| doFun(f,p) |
| cf("<No methods>\n\n") |
| } |
| return(invisible()) |
| } |
| ## else: non-empty methods list |
| doFun(f,p) |
| for(m in mget(labels, table)) { |
| pkgs <- NULL |
| if(is.environment(m)) { ## duplicate class case -- compare .findMethodInTable() |
| pkgs <- names(m) |
| m <- m[[pkgs[1L]]] |
| } |
| if( is(m, "MethodDefinition")) { |
| t <- m@target |
| if(length(t) == 0L) |
| t <- deflt |
| d <- m@defined |
| if(length(d) == 0L) |
| d <- deflt |
| cf(sigString(t), "\n") |
| if(!identical(t, d)) |
| cf(" (inherited from: ", sigString(d), ")\n") |
| if(!.identC(m@generic, f) && length(m@generic) == 1L && |
| nzchar(m@generic)) |
| cf(" (definition from function \"", m@generic, "\")\n") |
| if(length(pkgs) > 1) |
| cf(" (", length(pkgs), " methods defined for this signature, with different packages)\n") |
| } |
| if(includeDefs && is.function(m)) { |
| if(is(m, "MethodDefinition")) |
| m <- m@.Data |
| cat(deparse(m), sep="\n", "\n", file = printTo) |
| } |
| } |
| cat("\n", file = printTo) |
| } |
| |
| ## temporary switch for tables |
| useMTable <- function(onOff = NA) |
| .Call(C_R_set_method_dispatch, as.logical(onOff)) |
| |
| ## get all the group generic functions, in breadth-first order since |
| ## direct group inheritance is closer than indirect (all existing |
| ## groups are mutually exclusive, but multiple group membership is |
| ## allowed) |
| .getAllGroups <- function(funs) { |
| start <- length(funs) |
| for(i in seq_along(funs)) { |
| groups <- funs[[i]]@group |
| funs <- c(funs, lapply(groups, |
| function(what) { |
| f <- getGeneric(what) |
| if(!is.function(f)) |
| stop("failed to find expected group generic function: ", |
| what) |
| f |
| })) |
| } |
| ## now the next generations recusively |
| if(length(funs) > start) { |
| nmore <- length(funs) - start |
| more <- Recall(funs[(start+1):length(funs)]) |
| ## did we add any groups? |
| if(length(more) > nmore) |
| funs <- c(funs, more[(nmore+1):length(more)]) |
| } |
| funs |
| } |
| |
| .getGroupMethods <- function(labels, generics, found) { |
| methods <- list() |
| for(i in seq_along(generics)) { |
| gen <- generics[[i]] |
| if(!is(gen,"genericFunction")) |
| stop(gettextf("invalid group generic function in search for inherited method (class %s)", |
| dQuote(class(gen))), |
| domain = NA) |
| table <- .getMethodsTable(gen) |
| allMethods <- sort(names(table)) |
| ## TODO: possible for .SigLength to differ between group & |
| ## members. Requires expanding labels to max. length |
| newFound <- rep(FALSE, length(found)) |
| newFound[!found] <- labels[!found] %in% allMethods |
| found <- found | newFound |
| methods[labels[newFound]] <- mget(labels[newFound], table) |
| } |
| methods |
| } |
| |
| .getMethodsTable <- function(fdef, env = environment(fdef), |
| check = TRUE, inherited = FALSE) |
| { |
| name <- if(inherited) ".AllMTable" else ".MTable" |
| if(check && !exists(name, envir = env, inherits = FALSE)) { |
| .setupMethodsTables(fdef, initialize = TRUE) |
| if(!exists(name, envir = env, inherits = FALSE)) |
| stop("invalid methods table request") |
| } |
| get(name, envir = env) |
| } |
| |
| .getGenericSigLength <- function(fdef, env = environment(fdef), check = TRUE) { |
| if(check && !exists(".SigLength", envir = env, inherits = FALSE)) |
| .setupMethodsTables(fdef) |
| get(".SigLength", envir = env) |
| } |
| |
| |
| |
| .checkGroupSigLength <- function(gnames, generics = lapply(gnames, getGeneric)) { |
| funs <- gnames |
| recall <- FALSE |
| for(i in seq_along(gnames)) { |
| what <- gnames[[i]] |
| fdef <- generics[[i]] |
| if(!is(fdef, "groupGenericFunction")) { |
| warning(gettextf("trying to check signature length of group generic '%s', but it is not a group generic", what), |
| domain = NA) |
| next |
| } |
| if(length(fdef@group)) {# push up the check one level |
| gnames[[i]] <- fdef@group |
| generics[[i]] <- lapply(fdef@group, getGeneric) |
| recall <- TRUE |
| next |
| } |
| funs <- c(funs, getGroupMembers(fdef, TRUE, FALSE)) |
| } |
| if(recall) |
| return(Recall(unlist(gnames, FALSE), unlist(generics, FALSE))) |
| funs <- unique(funs) |
| fdefs <- lapply(funs, function(x) { |
| if(is.character(x) && length(x) == 1L) getGeneric(x) |
| else x}) |
| ## now compare the sig lengths |
| sigs <- rep(0,length(funs)) |
| for(i in seq_along(sigs)) { |
| what <- funs[[i]] |
| fdef <- fdefs[[i]] |
| if(is.null(fdef)) |
| next # getGroupMembers returns NULL if member is not defined |
| if(!is(fdef, "genericFunction")) |
| warning(gettextf("trying to check signature length of generic '%s', but it is not a generic function: i = %d, funs = %s, gnames = %s", |
| what, i, paste(unlist(funs), collapse = ", "), |
| paste(as.character(gnames), collapse = ", ")), |
| domain = NA) |
| else { |
| ev <- environment(fdef) |
| if(is.null(sigl <- ev$.SigLength)) { |
| .setupMethodsTables(fdef) |
| sigl <- get(".SigLength", envir = ev) |
| } |
| sigs[i] <- sigl |
| } |
| } |
| n <- max(sigs) |
| reset <- sigs < n & sigs > 0 # all the sigs for defined funs & less than max. |
| if(any(reset)) { |
| funs <- funs[reset] |
| fdefs <- fdefs[reset] |
| for(fdef in fdefs) { |
| .resetSigLength(fdef, n) |
| } |
| } |
| funs |
| } |
| |
| ## a simplified outer of paste |
| outerLabels <- function(labels, new) { |
| ## WARNING: This code incorporates the definition of .sigLabel |
| ## and so must change if that does (e.g. to include package) |
| n <- length(labels) |
| m <- length(new) |
| paste(labels[rep.int(1L:n, rep.int(m,n))], new[rep.int(1L:m,n)], sep ="#") |
| } |
| |
| |
| .matchSigLength <- function(sig, fdef, fenv, reset = FALSE) { |
| nargs <- .getGenericSigLength(fdef, fenv, TRUE) |
| n <- length(sig) |
| pkgs <- packageSlot(sig) |
| if(n < nargs) { |
| more <- nargs - n |
| pkgs <- c(pkgs, rep("methods", more)) |
| sig <- c(as.character(sig), rep("ANY", more)) |
| } |
| else if(n > nargs) { #reset table? |
| if(all(sig[(nargs+1):n] == "ANY")) { |
| length(sig) <- nargs |
| if (!is.null(pkgs)) |
| length(pkgs) <- nargs |
| } else { |
| while(sig[[n]] == "ANY") |
| n <- n-1 |
| if(reset) |
| .resetSigLength(fdef, n) |
| length(sig) <- n |
| if (!is.null(pkgs)) |
| length(pkgs) <- n |
| } |
| } |
| packageSlot(sig) <- pkgs |
| sig |
| } |
| |
| .resetSigLength <- function(fdef, n) { |
| fenv <- environment(fdef) |
| assign(".SigLength", n, envir = fenv) |
| mtable <- .getMethodsTable(fdef, fenv, check = FALSE) |
| signames <- fdef@signature |
| length(signames) <- n |
| .resetTable(mtable, n, signames) |
| .resetInheritedMethods(fenv, mtable) |
| } |
| |
| .TableMetaName <- function(name, package) |
| methodsPackageMetaName("T", paste(name, package, sep=":")) |
| |
| .TableMetaPrefix <- function() |
| methodsPackageMetaName("T","") |
| |
| # regexp for matching table names; semi-general but assumes the |
| # meta pattern starts with "." and has no other special characters |
| .TableMetaPattern <- function() |
| paste0("^[.]",substring(methodsPackageMetaName("T",""),2)) |
| |
| .addToMetaTable <- function(fdef, signature, definition, where, nSig) { |
| return() |
| } |
| |
| ## the real version |
| ..addToMetaTable <- function(fdef, signature, definition, where, |
| nSig = .getGenericSigLength(fdef)) { |
| ## TODO: nSig should be a slot in the table |
| tname <- .TableMetaName(fdef@generic, fdef@package) |
| where <- as.environment(where) |
| if(!is.null(table <- where[[tname]])) { |
| if(length(signature) > nSig) |
| .resetTable(table, length(signature), fdef@signature[seq_along(signature)]) |
| } |
| else { |
| table <- new.env(TRUE, environment(fdef)) |
| assign(tname, table, envir = where) |
| } |
| .cacheMethodInTable(fdef, signature, definition, table) |
| } |
| |
| .removeMethodsMetaTable <- function(generic, where) { |
| ## does not warn if none exists, on the theory that a generic may be created |
| ## but no methods defined to create a table. The use of implicitGeneric's is an example. |
| tname <- .TableMetaName(generic@generic, generic@package) |
| if(exists(tname, where, inherits = FALSE)) |
| rm(list=tname, pos = where) |
| } |
| |
| .getGenericSigArgs <- function(fdef, env = environment(fdef), check = TRUE) { |
| if(check && !exists(".SigLength", envir = env, inherits = FALSE)) |
| .setupMethodsTables(fdef) |
| n <- get(".SigLength", envir = env) |
| args <- get(".SigArgs", envir = env) |
| length(args) <- n |
| args |
| } |
| |
| |
| ## the most simple part of listFromMethods() below; not yet exported |
| tableNames <- function(generic, where, table) { |
| fdef <- getGeneric(generic) |
| if(missing(table)) |
| table <- |
| if(missing(where)) .getMethodsTable(fdef) |
| else get(.TableMetaName(fdef@generic, fdef@package), |
| envir = as.environment(where), inherits = FALSE) |
| names(table) |
| } |
| |
| listFromMethods <- function(generic, where, table) { |
| fdef <- getGeneric(generic) |
| if(missing(table)) |
| table <- |
| if(missing(where)) .getMethodsTable(fdef) |
| else get(.TableMetaName(fdef@generic, fdef@package), |
| envir = as.environment(where), inherits = FALSE) |
| fev <- environment(fdef) |
| nSigArgs <- .getGenericSigLength(fdef, fev) |
| methods <- as.list(table, all.names=TRUE) |
| names <- names(methods) |
| if(nSigArgs > 1) { |
| n <- length(names) |
| sigs <- vector("list", n) |
| namesCon <- textConnection(names) |
| for(i in seq_len(n)) |
| sigs[[i]] <- scan(namesCon, "", sep ="#", nmax = nSigArgs, quiet=TRUE) |
| } |
| else |
| sigs <- as.list(names) |
| new("LinearMethodsList", classes=sigs, methods=methods, |
| arguments = .getGenericSigArgs(fdef, fev), generic = fdef) |
| } |
| |
| .makeMlist1 <- function(arg, objects, j = 1) { |
| .MlistDefunct(".makeMlist1()") |
| mnames <- character(length(objects)) |
| for(i in seq_along(objects)) { |
| what <- objects[[i]] |
| if(is.primitive(what)) |
| sig <- "ANY" |
| else |
| sig <- what@defined |
| mnames[[i]] <- (if(length(sig) < j) "ANY" else sig[[j]]) |
| } |
| names(objects) <- mnames |
| new("MethodsList", argument = arg, methods = objects, allMethods = objects) |
| } |
| |
| .makeMlist2 <- function(args, objects, j = 1) { |
| ## make a list according to argument j, convert these as needed |
| .MlistDefunct(".makeMlist2()") |
| mlists <- list() |
| for(what in objects) { |
| sig <- if(!is.primitive(what)) what@defined # else NULL |
| if(length(sig) <= j) |
| arg1 <- arg2 <- "ANY" |
| else { |
| arg1 <- sig[[j]] |
| arg2 <- sig[[j+1]] |
| } |
| x <- list(what) |
| el <- mlists[[arg1, exact = TRUE]] |
| mlists[[arg1]] <- (if(is.null(el)) x else c(el, x)) |
| } |
| jNext <- j+1 |
| if(jNext < length(args)) |
| for(i in seq_along(mlists)) |
| mlists[[i]] <- .makeMlist2(args, mlists[[i]], jNext) |
| else { |
| arg2 <- as.name(args[[jNext]]) |
| for(i in seq_along(mlists)) |
| mlists[[i]] <- .makeMlist1(arg2, mlists[[i]], jNext) |
| } |
| new("MethodsList", argument = as.name(args[[1L]]), |
| methods = mlists, allMethods = mlists) |
| } |
| |
| .makeMlistFromTable <- function(generic, where = NULL) { |
| .MlistDefunct(".makeMlistFromTable()") |
| if(is.null(where)) { |
| what <- ".MTable" |
| where <- environment(generic) |
| } |
| else { |
| where <- as.environment(where) |
| what <- .TableMetaName(generic@generic, generic@package) |
| } |
| if(exists(what, envir = where, inherits= FALSE)) |
| table <- get(what, envir = where) |
| else |
| table <- new.env() |
| value <- new("MethodsList", argument = as.name(generic@signature[[1]])) |
| allNames <- sort(names(table)) |
| if(length(allNames) == 0L) |
| return(value) |
| argNames <- generic@signature |
| ## USES THE PATTERN OF class#class#.... in the methods tables |
| nargs <- nchar(unique(gsub("[^#]","", allNames)))+1 |
| if(length(nargs) > 1L) { |
| warning("something weird: inconsistent number of args in methods table strings:", |
| paste(nargs,collapse = ", ")," (using the largest value)", |
| domain = NA) |
| nargs <- max(nargs) |
| } |
| length(argNames) <- nargs # the number of args used |
| if(nargs == 1) |
| .makeMlist1(as.name(argNames[[1L]]), mget(allNames, table)) |
| else |
| .makeMlist2(argNames, mget(allNames, table)) |
| } |
| |
| ## assign a methods meta-data table, by default (and usually) a copy of the table |
| ## from the generic function with the initial methods, if any. |
| .assignMethodsTableMetaData <- function(name, generic, where, table) { |
| what <- .TableMetaName(generic@generic, generic@package) |
| if(missing(table)) |
| table <- .copyEnv(.getMethodsTable(generic)) |
| assign(what, table, envir = as.environment(where)) |
| } |
| |
| .getMethodsTableMetaData <- function(generic, where, optional = FALSE) { |
| what <- .TableMetaName(generic@generic, generic@package) |
| if(!is.null(f <- get0(what, envir = where, inherits = FALSE))) |
| f |
| else if(optional) |
| NULL |
| else |
| stop(gettextf("no methods table for generic %s from package %s in package %s", |
| sQuote(generic@generic), |
| sQuote(generic@package), |
| sQuote(getPackageName(where))), |
| domain = NA) |
| } |
| |
| setPackageSlot <- function(x, value) { |
| packageSlot(x) <- value |
| x |
| } |
| |
| .inheritedArgsExpression <- function(target, defined, body) { |
| expr <- substitute({}, list(DUMMY = "")) # bug if you use quote({})--is overwritten!! |
| args <- names(defined) |
| for(i in seq_along(defined)) { |
| ei <- extends(setPackageSlot(target [[i]], packageSlot(target)[[i]]), |
| setPackageSlot(defined[[i]], packageSlot(defined)), |
| fullInfo = TRUE) |
| if(is(ei, "SClassExtension") && !ei@simple) |
| expr[[length(expr) + 1L]] <- |
| substitute(ARG <- as(ARG, DEFINED, strict = FALSE), |
| list(ARG = as.name(args[[i]]), |
| DEFINED = as.character(defined[[i]]))) |
| } |
| if(length(expr) > 1L) { |
| expr[length(expr) + 1L] <- list(body) # body could be NULL! |
| expr |
| } |
| else |
| NULL |
| } |
| |
| testInheritedMethods <- function(f, signatures, test = TRUE, virtual = FALSE, |
| groupMethods = TRUE, where = .GlobalEnv) |
| { |
| ## Function relevantClasses is defined here to set object .undefClasses |
| ## in testInheritedMethods as a marker to warn about undefined subclasses |
| .relevantClasses <- function(classes, excludeVirtual, where, doinheritance) { |
| classDefs <- lapply(classes, getClassDef, where) |
| undefs <- vapply(classDefs, is.null, NA) |
| if(any(undefs)) { |
| .undefClasses <<- unique(c(.undefClasses, classes[undefs])) |
| classes <- classes[!undefs] |
| classDefs <- classDefs[!undefs] |
| } |
| if(doinheritance) { |
| allSubs <- lapply(classDefs, function(what) names(what@subclasses)) |
| allSubs <- unique(unlist(allSubs)) |
| pattern <- sapply(allSubs, .matchSubsPattern, classes, excludeVirtual) |
| ## exclude virtuals |
| if(excludeVirtual) { |
| excl <- nzchar(pattern) |
| pattern <- pattern[excl] |
| allSubs <- allSubs[excl] |
| } |
| if(length(allSubs)>0) |
| allSubs <- sapply(split(allSubs, pattern), `[[`,1) |
| else |
| allSubs <- character() |
| } |
| else |
| allSubs <- character() |
| ## prepend the classes themselves, as appropriate |
| iAny <- match( "ANY", classes, 0) |
| if(iAny > 0) { |
| classes[[iAny]] <- ".Other" # non-virtual placeholder for ANY |
| classDefs[[iAny]] <- getClassDef(".Other") |
| } |
| if(excludeVirtual) |
| classes <- classes[vapply(classDefs, function(def) isFALSE(def@virtual), NA)] |
| unique(c(classes, allSubs)) |
| } |
| ## end of .relevantClasses |
| |
| if(!is(f, "genericFunction")) |
| f <- getGeneric(f) |
| fname <- f@generic |
| if(missing(signatures)) { |
| mdefs <- findMethods(f) |
| mnames <- names(mdefs) |
| sigs <- findMethodSignatures(methods = mdefs) |
| if(groupMethods) { |
| groups <- getGroup(f, recursive = TRUE) |
| for(group in groups) { |
| fg <- getGeneric(group) |
| mg <- findMethods(fg) |
| sigsg <- findMethodSignatures(methods = mg) |
| newSigs <- is.na(match(names(mg), mnames)) |
| mg <- mg[newSigs] |
| mdefs <- c(mdefs, mg[newSigs]) |
| sigs <- rbind(sigs, sigsg[newSigs,]) |
| mnames <- c(mnames, names(mg)[newSigs]) |
| } |
| } |
| if(length(sigs) == 0) |
| return(new("MethodSelectionReport", generic = fname)) |
| ## possible selection of which args to include with inheritance |
| ok <- if(fname %in% c("coerce", "coerce<-")) |
| match(colnames(sigs), "from", 0) > 0 else rep.int(TRUE, ncol(sigs)) |
| for(j in seq_len(ncol(sigs))) { |
| classesj <- unique(sigs[,j]) |
| .undefClasses <- character() |
| subclasses <- .relevantClasses(classesj, !virtual, where, ok[[j]]) |
| nj <- length(subclasses) |
| ## if(nj == 0) { ##FIXME, wrong test |
| ## warning(gettextf("No eligible subclasses for argument '%s' found, so no contribution to analysis", |
| ## colnames(sigs)[[j]]), domain = NA) |
| ## next |
| ## } |
| if(j > 1) { |
| ## replicate all the previous elements of subclasses a la outer |
| subclasses <- rep(subclasses, rep.int(ncomb, nj)) |
| ncomb <- ncomb * nj |
| sigLabels <- paste(rep(sigLabels, times = nj), subclasses, sep = "#") |
| } |
| else { |
| sigLabels <- subclasses |
| ncomb <- nj |
| } |
| |
| if(length(.undefClasses)) { |
| warning(gettextf("undefined classes (%s) will be ignored for argument '%s'", |
| paste0('"',unique(.undefClasses),'"', collapse=", "), |
| colnames(sigs)[[j]]), domain = NA) |
| .undefClasses <- character() |
| } |
| } ## loop on j |
| ## now split the individual labels back into signatures |
| signatures <- strsplit(sigLabels, "#", fixed = TRUE) |
| } ## end of missing(signatures) case |
| else if(is(signatures, "matrix") && typeof(signatures) == "character" |
| && ncol(signatures) <= length(f@signature)) { |
| ## turn signatures back into a list |
| siglist <- vector("list", nrow(signatures)) |
| for(i in seq_len(nrow(signatures))) |
| siglist[[i]] <- signatures[i,] |
| signatures <- siglist |
| } |
| else stop("argument 'signatures' must be a character matrix whose rows are method signatures") |
| ambig_target <- character() |
| ambig_candidates <- list() |
| ambig_selected <- character() |
| ambig_note <- character() |
| if(test) { |
| ## define a handler that accumulates the attributes from the condition object |
| warninghandler <- function(cond) { |
| ambig_target <<- c(ambig_target, attr(cond, "target")) |
| ambig_candidates <<- c(ambig_candidates, list(attr(cond, "candidates"))) |
| ambig_selected <<- c(ambig_selected, attr(cond, "selected")) |
| ambig_note <<- c(ambig_note, attr(cond, "note")) |
| } |
| ambigOpt <- options(ambiguousMethodSelection = warninghandler) |
| on.exit(options(ambigOpt)) |
| doSelect <- function(sig) { |
| x <- selectMethod(f = f, sig, optional = TRUE) |
| if(is(x, "MethodDefinition")) { |
| nsig <- x@defined |
| if(length(nsig) < length(sig)) |
| c(nsig, rep("ANY", length(sig) - length(nsig))) |
| else |
| nsig |
| } |
| else if(is.null(x)) |
| rep_len("<NONE>", length(sig)) |
| else # primitive |
| rep_len("ANY", length(sig)) |
| } |
| signatures <- lapply(signatures, doSelect) |
| } |
| signatures <- sapply(signatures, paste0, collapse = "#") |
| names(signatures) <- sigLabels |
| |
| new("MethodSelectionReport", generic = fname, allSelections = signatures, |
| target = ambig_target, selected = ambig_selected, |
| candidates = ambig_candidates, note = ambig_note) |
| } |
| |
| .matchSubsPattern <- function(what, matchto, excludeVirtual) { |
| def <- getClass(what) |
| if(excludeVirtual & def@virtual) |
| return("") |
| matches <- match(names(def@contains), matchto, 0) |
| matches <- matches[matches>0] |
| paste(matches, collapse=".") |
| } |
| |