blob: 1bd61aa3ce7763f991d4a89692ab824d34287743 [file] [log] [blame]
# File src/library/methods/R/BasicClasses.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/
## a few class name definitions needed elsewhere
.anyClassName <- structure("ANY", package = "methods")
.signatureClassName <- structure("signature", package = "methods")
.InitBasicClasses <- function(envir)
{
## setClass won't allow redefining basic classes,
## so make the list of these empty for now.
assign(".BasicClasses", character(), envir)
## hide some functions that would break because the basic
## classes are not yet defined
real.reconcileP <- reconcilePropertiesAndPrototype
assign("reconcilePropertiesAndPrototype",
function(name, properties, prototype, extends, where) {
list(properties=properties, prototype = prototype, extends = extends)
}, envir)
clList <- c("VIRTUAL", "ANY", "vector", "missing")
for(.class in clList)
setClass(.class, where = envir)
## Now some pseudo-classes in base, marked specially for new()
## "numeric" is the class returned by class() for double vectors
vClasses <- c("logical", "numeric", "character",
"double",
"complex", "integer", "raw",
"expression", "list")
for(.class in vClasses) {
.setBaseClass(.class, prototype = newBasic(.class), where = envir)
}
.setBaseClass("expression", prototype = expression(), where = envir)
clList <- c(clList, vClasses)
nullF <- function()NULL; environment(nullF) <- .GlobalEnv
nullF <- utils::removeSource(nullF)
attr(nullF, "source") <- NULL
.setBaseClass("function", prototype = nullF, where = envir); clList <- c(clList, "function")
setClass("language", where = envir); clList <- c(clList, "language")
.setBaseClass("environment", prototype = new.env(), where = envir); clList <- c(clList, "environment")
.setBaseClass("externalptr", prototype = .newExternalptr(), where = envir); clList <- c(clList, "externalptr")
.setBaseClass("builtin", prototype = `<-`, where = envir); clList <- c(clList, "builtin")
.setBaseClass("special", prototype = `if`, where = envir); clList <- c(clList, "special")
## S4, S3 are basic classes that are used to define methods related to being S4, S3 object
for(cl in c("S4", "S3")) {
tmp <- newClassRepresentation(className=cl, prototype = defaultPrototype(), virtual=TRUE, package = "methods")
assignClassDef(cl, tmp, where = envir); clList <- c(clList, cl)
}
## NULL is weird in that it has NULL as a prototype, but is not virtual
tmp <- newClassRepresentation(className="NULL", prototype = NULL, virtual=FALSE, package = "methods")
assignClassDef("NULL", tmp, where = envir); clList <- c(clList, "NULL")
## the pseudo-NULL used to store NULL as a slot
## must match the C code in attrib.c (would be better to use that
## code to create .pseudoNULL)
assign(".pseudoNULL", as.name("\001NULL\001"), envir = envir)
setClass("structure", where = envir); clList <- c(clList, "structure")
setClass("nonStructure", where = envir); #NOT a basic class
stClasses <- c("matrix", "array") # classes that have attributes, but no class attr.
for(.class in stClasses) {
.setBaseClass(.class, prototype = newBasic(.class), where = envir)
}
## "ts" will be defined below as an S3 class, but it is still
## included in .BasicClasses, to allow its coerce() method to use
## as.ts(). This decision may be revisited.
clList <- c(clList, stClasses, "ts")
assign(".BasicClasses", clList, envir)
## Now we can define the SClassExtension class and use it to instantiate some
## is() relations.
.InitExtensions(envir)
for(.class in vClasses)
setIs(.class, "vector", where = envir)
## The one place where "double" and "numeric" currently differ:
setIs("integer", "double", where = envir,
coerce = .gblEnv(function(object) as.double(object)),
replace = .gblEnv(function(from, value) { class(value) <- "integer" ; value }))
setIs("integer", "numeric", where = envir)
setIs("double", "numeric", where = envir)
setIs("structure", "vector", coerce = .gblEnv(function(object) as.vector(object)),
replace = .gblEnv(function(from, to, value) {
attributes(value) <- attributes(from)
value
}),
where = envir)
setIs("array", "structure", where = envir)
setIs("matrix", "array", where = envir)
### Rather want a simple setAs("array", "matrix", ..) method..
## setIs("array", "matrix", test = .gblEnv(function(object) length(dim(object)) == 2),
## replace = .gblEnv(function(from, to, value) {
## if(is(value, "matrix"))
## value
## else
## stop("replacement value is not a matrix")
## }),
## where = envir)
## Some class definitions extending "language", delayed to here so
## setIs will work.
.setBaseClass("name", "language", prototype = as.name("<UNDEFINED>"), where = envir); clList <- c(clList, "name")
.setBaseClass("call", "language", prototype = quote("<undef>"()), where = envir); clList <- c(clList, "call")
.setBaseClass("{", "language", prototype = quote({}), where = envir); clList <- c(clList, "{")
.setBaseClass("if", "language", prototype = quote(if(NA) TRUE else FALSE), where = envir); clList <- c(clList, "if")
.setBaseClass("<-", "language", prototype = quote("<undef>"<-NULL), where = envir); clList <- c(clList, "<-")
.setBaseClass("for", "language", prototype = quote(for(NAME in logical()) NULL), where = envir); clList <- c(clList, "for")
.setBaseClass("while", "language", prototype = quote(while(FALSE) NULL), where = envir); clList <- c(clList, "while")
.setBaseClass("repeat", "language", prototype = quote(repeat{break}), where = envir); clList <- c(clList, "repeat")
.setBaseClass("(", "language", prototype = quote((NULL)), where = envir); clList <- c(clList, "(")
## a virtual class used to allow NULL as an indicator that a possible function
## is not supplied (used, e.g., for the validity slot in classRepresentation
setClass("OptionalFunction", where = envir)
setIs("function", "OptionalFunction", where = envir)
setIs("NULL", "OptionalFunction")
assign(".BasicClasses", clList, envir)
assign(".SealedClasses", clList, envir)
## restore the true definition of the hidden functions
assign("reconcilePropertiesAndPrototype", real.reconcileP, envir)
}
.InitS3Classes <- function(envir) {
## create a virtual class from which all S3 classes will inherit the .S3Class slot
setClass("oldClass", representation(.S3Class = "character"),
contains = "VIRTUAL", prototype = prototype(.S3Class = character()),
where = envir)
## call setOldClass on some known old-style classes. Ideally this would be done
## in the code that uses the classes, but that code doesn't know about the methods
## package.
## Two steps; first, those classes with a known prototype. These
## can be non-Virtual
clList <- get(".SealedClasses", envir = envir)
for(i in seq_along(.OldClassesPrototypes)) {
el <- .OldClassesPrototypes[[i]]
if(is.list(el) && length(el) > 1)
setOldClass(el[[1L]], prototype = el[[2L]], where = envir)
else
warning(gettextf("OOPS: something wrong with '.OldClassesPrototypes[[%d]]'", i),
domain = NA)
}
setGeneric("slotsFromS3", where = envir)
## the method for "oldClass" is really a constant, just hard to express that way
setMethod("slotsFromS3", "oldClass", function(object) getClass("oldClass")@slots,
where = envir)
setClass("ts", contains = "structure", representation(tsp = "numeric"),
prototype = prototype(NA, tsp = rep(1,3)), where = envir)
setOldClass("ts", S4Class = "ts", where = envir)
setClass("mts", contains=c("matrix", "ts"), prototype =
prototype(matrix(NA,1,1), tsp = rep(1,3), .S3Class = c("mts", "ts")))
.init_ts <- function(.Object, ...) {
if(nargs() < 2) # guaranteed to be called with .Object from new
return(.Object)
args <- list(...)
argnames <- names(args)
slotnames <- if(is.null(argnames)) FALSE else {
nzchar(argnames) & is.na(match(argnames, .tsArgNames)) }
if(any(slotnames)) {
value <- do.call(stats::ts, args[!slotnames])
.mergeAttrs(value, .Object, args[slotnames])
}
else
.mergeAttrs(stats::ts(...), .Object)
}
setMethod("initialize", "ts", .init_ts, where = envir)
setMethod("initialize", "mts", .init_ts, where = envir) #else, it's ambiguous
## the following mimics settings for other basic classes ("ts" was
## not defined at the time these are done).
setMethod("coerce", c("ANY", "ts"), function (from, to, strict = TRUE)
{
value <- as.ts(from)
if(strict) {
attrs <- attributes(value)
if(length(attrs) > 2)
attributes(value) <- attrs[c("class", "tsp")]
value <- .asS4(value)
}
value
},
where = envir)
setClass("factor", contains = "integer", representation(levels = "character"),
validity = base::.valid.factor, where = envir)
setOldClass("factor", S4Class = "factor", where = envir)
setClass("ordered", contains = "factor", where = envir)
setOldClass("ordered", S4Class = "ordered", where = envir)
if(!isGeneric("show", envir))
setGeneric("show", where = envir, simpleInheritanceOnly = TRUE)
setMethod("show", "oldClass", function(object) {
if(!isS4(object)) {
print(object)
return(invisible())
}
cl <- as.character(class(object))
S3Class <- object@.S3Class
if(length(S3Class)) S3Class <- S3Class[[1L]]
else S3Class <- "oldClass" # or error?
cat("Object of class \"", cl, "\"\n", sep = "")
print(S3Part(object, strictS3 = TRUE))
otherSlots <- slotNames(cl)
S3slots <- slotNames(S3Class)
otherSlots <- otherSlots[is.na(match(otherSlots, S3slots))]
for(what in otherSlots) {
cat('Slot "', what, '":\n', sep = "")
show(slot(object, what))
cat("\n")
}
NULL
}, where = envir)
.initS3 <- function(.Object, ...) {
if(nargs() < 2)
return(.Object)
Class <- class(.Object)
ClassDef <- getClass(Class)
S3Class <- attr(ClassDef@prototype, ".S3Class")
if(is.null(S3Class)) # not a class set up by setOldClass()
return(callNextMethod())
S3ClassP <- S3Class[[1L]]
args <- list(...)
## separate the slots, superclass objects
snames <- allNames(args)
which <- nzchar(snames)
elements <- args[which]
supers <- args[!which]
thisExtends <- names(ClassDef@contains)
slotDefs <- ClassDef@slots
dataPart <- slotDefs[[".Data"]]
if(is.null(dataPart))
dataPart <- "missing" # nothing will extend this => no data part args allowed
for(i in rev(seq_along(supers))) {
obj <- supers[[i]]
Classi <- class(obj)
defi <- getClassDef(Classi)
if(is.null(defi))
stop(gettextf(
"unnamed argument to initialize() for S3 class must have a class definition; %s does not",
dQuote(Classi)),
domain = NA)
if(is(obj, S3ClassP)) {
## eligible to be the S3 part; merge other slots from prototype;
## obj then becomes the object, with its original class as the S3Class
if(is.null(attr(obj, ".S3Class"))) # must be an S3 object; use its own class
attr(obj, ".S3Class") <- Classi
.Object <- .asS4(.mergeAttrs(obj, .Object))
}
else if(is(obj, dataPart)) {
## the S3Class stays from the prototype
.Object <- .mergeAttrs(obj, .Object)
}
else stop(gettextf(
"unnamed argument must extend either the S3 class or the class of the data part; not true of class %s",
dQuote(Classi)), domain = NA)
}
## named slots are done as in the default method, which will also call validObject()
if(length(elements)>0) {
elements <- c(list(.Object), elements)
.Object <- do.call(`callNextMethod`, elements)
}
else
validObject(.Object)
.Object
}
setMethod("initialize", "oldClass", .initS3, where = envir)
## Next, miscellaneous S3 classes.
for(cl in .OldClassesList)
setOldClass(cl, where = envir)
## special mess for "maov"; see comment in .OldClassesList
setIs("maov", "aov")
setClassUnion("data.frameRowLabels", c("character", "integer"), where = envir)
setClass("data.frame",
representation(names = "character", row.names = "data.frameRowLabels"),
contains = "list", prototype = unclass(data.frame()), where = envir) # the S4 version
setOldClass("data.frame", S4Class = "data.frame", where = envir)
## the S3 methods for $<-, [[<- and [<- do some stupid things to class()
## This buffers the effect from S4 classes
setMethod("$<-", "data.frame", where = envir,
function(x, name, value) {
S3Part(x) <- `$<-.data.frame`(S3Part(x, TRUE), name, value)
x
})
callBracketReplaceGeneric <- function() {
call <- sys.call(sys.parent())
which.ij <- if (length(call) > 4L) 3:4 else 3L
ij <- as.list(call[which.ij])
present <- logical(length(ij))
for (a in seq_along(ij)) {
arg <- ij[[a]]
present[a] <- !missing(arg)
}
ij[present] <- head(c(quote(i), quote(j)), length(ij))[present]
call <- as.call(c(call[[1L]], quote(x3), ij, quote(...),
value=quote(value)))
eval(call, parent.frame())
}
setMethod("[<-", "data.frame", where = envir,
function (x, i, j, ..., value) {
x3 <- S3Part(x, TRUE)
S3Part(x) <- callBracketReplaceGeneric()
x
})
setMethod("[[<-", "data.frame", where = envir,
function (x, i, j, ..., value) {
x3 <- S3Part(x, TRUE)
S3Part(x) <- callBracketReplaceGeneric()
x
})
## methods to go from S4 to S3; first, using registered class; second, general S4 object
setMethod("coerce", c("oldClass", "S3"), function (from, to, strict = TRUE)
{
from <- .notS4(from) # not needed? ensures that class() can return >1 string
cl <- class(from)
cl1 <- .class1(from)
classDef <- getClassDef(cl1)
S3Class <- attr(classDef@prototype, ".S3Class")
if(length(S3Class) > length(cl)) #add S3 inheritance
attr(from, "class") <- S3Class
from
},
where = envir)
setMethod("coerce", c("ANY", "S3"), function (from, to, strict = TRUE)
{
switch(typeof(from),
S4 =
stop(gettextf("class %s does not have an S3 data part, and so is of type \"S4\"; no S3 equivalent",
dQuote(class(from))),
domain = NA),
.notS4(from) )
},
where = envir)
setMethod("coerce", c("ANY", "S4"), function (from, to, strict = TRUE)
{
if(isS4(from)) {
value <- from
}
else {
cl <- .class1(from)
classDef <- getClass(cl)
if(isTRUE(classDef@virtual))
stop(gettextf("class %s is VIRTUAL; not meaningful to create an S4 object from this class",
dQuote(cl)),
domain = NA)
pr <- classDef@prototype
value <- new(cl)
slots <- classDef@slots
if(match(".Data", names(slots), 0L) > 0L) {
data <- unclass(from)
if(!is(data, slots[[".Data"]]))
stop(gettextf("object must be a valid data part for class %s; not true of type %s",
dQuote(cl), dQuote(class(data))),
domain = NA)
value@.Data <- unclass(from)
}
## copy attributes: Note that this copies non-slots as well
## but checks the slots for validity
anames <- names(attributes(from))
isSlot <- anames %in% names(slots)
for(i in seq_along(anames)) {
what <- anames[[i]]
if(isSlot[[i]])
slot(value, what) <- attr(from, what)
else
attr(value, what) <- attr(from, what)
}
}
if(strict)
## validate. If we created S4 object, slots were tested; else, not
## so complete= is set accordingly.
validObject(value, complete = isS4(from))
value
})
assign(".SealedClasses", c(clList,unique(unlist(.OldClassesList))), envir)
}
### create a class definition for one of the pseudo-classes in base
### The class name does _not_ have a package attribute, which signals
### the C coded for new() to return an object w/o explicit class
### attribute, to be consistent with older R code
.setBaseClass <- function(cl, ..., where) {
setClass(cl, ..., where = where)
def <- getClassDef(cl, where)
def@className <- as.character(def@className)
def@prototype <- .notS4(def@prototype)
assignClassDef(cl, def, where = where)
}
.tsArgNames <- names(formals(stats::ts))
### The following methods are now activated
### via the last line of the function .InitMethodDefinitions in ./MethodsListClass.R
###
### Tradeoff between intuition of users that
### new("matrix", ...) should be like matrix(...) vs consistency of new().
### Relevant when new class has basic class as its data part.
.InitBasicClassMethods <- function(where) {
## methods to initialize "informal" classes by using the
## functions of the same name.
## These methods are designed to be inherited or extended
initMatrix <- function(.Object, data = NA, nrow = 1, ncol = 1,
byrow = FALSE, dimnames = NULL, ...) {
na <- nargs()
if(length(dots <- list(...)) && ".Data" %in% names(dots)) {
if(na == 2)
.Object <- .mergeAttrs(dots$.Data, .Object)
else {
dat <- dots$.Data
dots <- dots[names(dots) != ".Data"]
if(na == 2 + length(dots)) {
.Object <- .mergeAttrs(as.matrix(dat), .Object, dots)
}
else
stop("cannot specify matrix() arguments when specifying '.Data'")
}
}
else if(is.matrix(data) && na == 2 + length(dots))
.Object <- .mergeAttrs(data, .Object, dots)
else {
if (missing(nrow))
nrow <- ceiling(length(data)/ncol)
else if (missing(ncol))
ncol <- ceiling(length(data)/nrow)
value <- matrix(data, nrow, ncol, byrow, dimnames)
.Object <- .mergeAttrs(value, .Object, dots)
}
validObject(.Object)
.Object
}
.matrixExtends <- unique(c("matrix", names(getClass("matrix")@contains)))
setMethod("initialize", "matrix", where = where,
function(.Object, ...) {
if(nargs() < 2) # guaranteed to be called with .Object from new
return(.Object)
else {
if(isMixin(getClass(class(.Object)))) # other superclasses
callNextMethod()
else
initMatrix(.Object, ...)
}
}
)
initArray <- function(.Object, data = NA, dim = length(data),
dimnames = NULL, ...) {
na <- nargs()
if(length(dots <- list(...)) && ".Data" %in% names(dots)) {
if(na == 2)
.Object <- .mergeAttrs(dots$.Data, .Object)
else {
dat <- dots$.Data
dots <- dots[names(dots) != ".Data"]
if(na == 2 + length(dots)) {
.Object <- .mergeAttrs(as.array(dat), .Object, dots)
}
else
stop("cannot specify array() arguments when specifying '.Data'")
}
}
else if(is.array(data) && na == 2 + length(dots))
.Object <- .mergeAttrs(data, .Object, dots)
else {
value <- array(data, dim, dimnames)
.Object <- .mergeAttrs(value, .Object, dots)
}
validObject(.Object)
.Object
}
.arrayExtends <- unique(c("array", names(getClass("array")@contains)))
setMethod("initialize", "array", where = where,
function(.Object, ...) {
if(nargs() < 2) # guaranteed to be called with .Object from new
.Object
else {
if(isMixin(getClass(class(.Object)))) # other superclasses
callNextMethod()
else
initArray(.Object, ...)
}
}
)
## following should not be needed if data_class2 returns "array",...
## setMethod("[", # a method to avoid invalid objects from an S4 class
## signature(x = "array"), where = where,
## function (x, i, j, ..., drop = TRUE)
## {
## value <- callNextMethod()
## if(is(value, class(x)))
## value@.Data
## else
## value
## })
}
## .OldClassesList is a purely heuristic list of known old-style classes, with emphasis
## on old-style class inheritiance. Used in .InitBasicClasses to call setOldClass for
## each known class pattern.
## .OldClassesPrototypes is a list of S3 classes for which prototype
## objects are known & reasonable.
## Its classes should not reappear in .OldClassesList (as these become VIRTUAL)
## and will have been initialized first in .InitBasicClasses().
## NB: the methods package will NOT set up prototypes for S3 classes
## except those in package base and for "ts" and "formula"
## (and would rather not do those either).
## Ideally, the package that owns the S3 class should have code to call
## setOldClass in its initialization.
.OldClassesPrototypes <-
list(
list("data.frame", data.frame(), "data.frame"),
list("factor", factor()),
list("table", table(factor())),
list("summary.table", summary.table(table(factor())))
, list("ts", stats::ts())
, list("formula", stats::formula())
)
.OldClassesList <-
list(
c("anova", "data.frame"),
c("mlm", "lm"),
c("aov", "lm"),
## note: definition of "maov" below differs from the
## current S3 attribute, which has an inconsistent combination
## of "aov" and "mlm" (version 2.12 devel, rev. 51984)
c("maov", "mlm", "lm"),
c("POSIXct", "POSIXt"),
c("POSIXlt", "POSIXt"),
"Date",
"dump.frames",
c("glm.null", "glm", "lm"),
c("anova.glm.null", "anova.glm"),
"hsearch",
"integrate",
"packageInfo",
"libraryIQR",
"packageIQR",
"mtable",
c("summaryDefault","table"),
"recordedplot",
"socket",
"packageIQR",
"density",
"logLik",
"rle"
)
.InitSpecialTypesAndClasses <- function(where) {
if(is.null(S3table <- where$.S3MethodsClasses)) {
S3table <- new.env()
assign(".S3MethodsClasses", S3table, envir = where)
}
specialClasses <- .indirectAbnormalClasses
specialTypes <- .AbnormalTypes # only part matching classes used
for(i in seq_along(specialClasses)) {
cl <- specialTypes[[i]]
ncl <- specialClasses[[i]]
setClass(ncl, representation(.xData = cl), where = where)
setIs(ncl, cl, coerce = function(from) from@.xData,
replace = function(from, value){ from@.xData <- value; from},
where = where)
## these classes need explicit coercion for S3 methods
assign(cl, getClass(cl, where), envir = S3table)
}
## a few other special classes
setClass("namedList", representation(names = "character"),
contains = "list", where = where)
if(!isGeneric("show", where))
setGeneric("show", where = where, simpleInheritanceOnly = TRUE)
setMethod("show", "namedList", function(object) {
cat("An object of class ", dQuote(class(object)), "\n")
print(structure(object@.Data, names=object@names))
showExtraSlots(object, getClass("namedList"))
})
setClass("listOfMethods", representation(arguments = "character",
signatures = "list", generic = "genericFunction"),
contains = "namedList",
where = where)
specialClasses <- c(specialClasses, "namedList", "listOfMethods")
assign(".SealedClasses", c(get(".SealedClasses", where), specialClasses), where)
setMethod("initialize", ".environment", # for simple subclasses of "environment"
function(.Object, ...) {
args <- list(...)
objs <- names(args)
hasEnvArg <- length(args) && !all(nzchar(objs))
if(hasEnvArg) {
ii <- seq_along(args)[!nzchar(objs)]
i <- integer()
for(iii in ii) {
if(is(args[[iii]], "environment"))
i <- c(i, iii)
}
if(length(i)>1)
stop("cannot have more than one unnamed argument as environment")
if(length(i) == 1) {
selfEnv <- args[[i]]
args <- args[-i]
objs <- objs[-i]
if(!is(selfEnv, "environment"))
stop("unnamed argument to new() must be an environment for the new object")
selfEnv <- as.environment(selfEnv)
}
## else, no environment superclasses
else
selfEnv <- new.env()
}
else
selfEnv <- new.env()
if(length(objs)) {
## don't assign locally named slots of subclasses
ClassDef <- getClass(class(.Object))
slots <- slotNames(ClassDef)
localObjs <- is.na(match(objs, slots))
if(any(localObjs)) {
for(what in objs[localObjs])
selfEnv[[what]] <- args[[what]]
objs <- objs[!localObjs]
args <- args[!localObjs]
}
}
.Object@.xData <- selfEnv
if(length(objs)) # call next method with remaining args
.Object <- do.call(callNextMethod, c(.Object, args))
.Object
}, where = where)
}