blob: dd6bacb9a2cf28245a7596011aeea2344279565b [file] [log] [blame]
# File src/library/methods/R/is.R
# Part of the R package, https://www.R-project.org
#
# Copyright (C) 1995-2019 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/
is <- function(object, class2)
{
# With two arguments, tests whether `object' can be treated as from `class2'.
#
# With one argument, returns all the super-classes of this object's class.
class1 <- class(object)
S3Case <- length(class1) > 1L
if(S3Case)
class1 <- class1[[1L]]
if(missing(class2))
return(extends(class1))
class1Def <- getClassDef(class1)
if(is.null(class1Def)) # an unregistered S3 class
return(inherits(object, class2))
if(is.character(class2)) {
class2Def <- getClassDef(class2, .classDefEnv(class1Def),
if (!is.null(package <- packageSlot(class2)))
package
else getPackageName(topenv(parent.frame())))
}
else {
class2Def <- class2
class2 <- class2Def@ className
}
## S3 inheritance is applied if the object is not S4 and class2 is either
## a basic class or an S3 class (registered or not)
S3Case <- S3Case || (is.object(object) && !isS4(object))
S3Case <- S3Case && (is.null(class2Def) || class2 %in% .BasicClasses ||
extends(class2Def, "oldClass"))
if(S3Case)
inherits(object, class2)
else if(.identC(class1, class2) || .identC(class2, "ANY"))
TRUE
else { ## look for class1 in the known subclasses of class2
if(!is.null(contained <- class1Def@contains[[class2]]))
contained@simple || contained@test(object)
else if (is.null(class2Def))
FALSE
else if(!.identC(class(class2Def), "classRepresentation") &&
isClassUnion(class2Def))
any(c(class1, names(class1Def@contains)) %in%
names(class2Def@subclasses))
else {
ext <- class2Def@subclasses[[class1]]
!is.null(ext) && (ext@simple || ext@test(object))
}
}
}
extends <-
## Does the first class extend the second class?
## Returns `maybe' if the extension includes a non-trivial test.
function(class1, class2, maybe = TRUE, fullInfo = FALSE)
{
if(is.character(class1)) {
if(length(class1) > 1L)
class1 <- class1[[1L]]
classDef1 <- getClassDef(class1)
} else if(is(class1, "classRepresentation")) {
classDef1 <- class1
class1 <- classDef1@className
}
else
stop("'class1' must be the name of a class or a class definition")
if(missing(class2)) {
if(is.null(classDef1))
return(class1)
ext <- classDef1@contains
if(!isTRUE(maybe) && length(ext) > 0)
{
noTest <- vapply(ext, function(obj)isTRUE(body(obj@test)), NA)
ext <- ext[noTest]
}
if(fullInfo) {
ext[[class1]] <- TRUE
return(ext)
}
else
return(c(class1,names(ext)))
}
value <- NULL
if(is.character(class2) && length(class2) == 1L) { ## fast first checks
## the [[1L]] below handles old-style classes & throws away package attributes
if(.identC(class1[[1L]], class2) || .identC(class2, "ANY"))
return(TRUE)
if(!is.null(classDef1) && class2 %in% names(classDef1@contains))
value <- classDef1@contains[[class2]]
else
classDef2 <- getClassDef(class2)
}
else if(is(class2, "classRepresentation")) {
classDef2 <- class2
class2 <- class2@className
}
else
stop("'class2' must be the name of a class or a class definition")
if(is.null(value))
value <- possibleExtends(class1, class2, classDef1, classDef2)
if(fullInfo)
value
else if(is.logical(value))
value
else if(value@simple || isTRUE(body(value@test)))
TRUE
else
maybe
}
.specialVirtual <- c("oldClass")
setIs <-
## Defines class1 to be an extension of class2.
## The relationship can be conditional, if a function is supplied as the `test'
## argument. If a function is supplied as the `coerce' argument, this function will
## be applied to any `class1' object in order to turn it into a `class2' object.
##
## Extension may imply that a `class1' object contains a `class2' object. The default
## sense of containing is that all the slots of the simpler class are found in the
## more elaborate one. If the `replace' argument is supplied as an S replacement
## function, this function will be used to implement `as(obj, class2) <- value'.
function(class1, class2, test = NULL, coerce = NULL,
replace = NULL, by = character(), where = topenv(parent.frame()),
classDef = getClass(class1, TRUE, where = where), extensionObject = NULL, doComplete = TRUE)
{
## class2 should exist
where <- as.environment(where)
classDef2 <- getClassDef(class2, where)
if(is.null(classDef2))
stop(gettextf("class %s has no visible definition from package or environment %s",
dQuote(class2),
sQuote(getPackageName(where))),
domain = NA)
## check some requirements:
## One of the classes must be on the target environment (so that the relation can
## be retained by saving the corresponding image)
m1 <- classMetaName(class1)
local1 <- exists(m1, where, inherits = FALSE) &&
!(classDef@sealed || bindingIsLocked(m1, where))
if(!local1) {
m2 <- classMetaName(class2)
local2 <- exists(m2, where, inherits = FALSE) &&
!(classDef2@sealed || bindingIsLocked(m2, where))
if(!local2)
stop(gettextf(
"cannot create a 'setIs' relation when neither of the classes (%s and %s) is local and modifiable in this package",
dQuote(class1),
dQuote(class2)),
domain = NA)
}
if(classDef@sealed && !isClassUnion(classDef2))
stop(gettextf("class %s is sealed; new superclasses can not be defined, except by 'setClassUnion'",
dQuote(class1)),
domain = NA)
prevIs <- !identical(possibleExtends(class1, class2,classDef, classDef2),
FALSE) # used in checking for previous coerce
obj <- if(is.null(extensionObject))
makeExtends(class1, class2, coerce, test, replace, by,
classDef1 = classDef, classDef2 = classDef2,
package = getPackageName(where))
else
extensionObject
## revise the superclass/subclass info in the stored class definition
ok <- .validExtends(class1, class2, classDef, classDef2, obj@simple)
if(!isTRUE(ok))
stop(ok)
where2 <- .findOrCopyClass(class2, classDef2, where, "subclass")
classDef2@subclasses[[class1]] <- obj
if(doComplete)
classDef2@subclasses <- completeSubclasses(classDef2, class1, obj, where)
## try to provide a valid prototype for virtual classes
if(classDef2@virtual && is.na(match(class2, .specialVirtual))) {
## For simplicity, we prefer NULL prototype if "NULL"
## is a subclass of a virtual class; otherwise the
## prototype is an element of class1 or its prototype if VIRTUAL
if(extends(classDef, "NULL"))
classDef2@prototype <- NULL
else if(is.null(classDef2@prototype)
&& is.na(match("NULL", names(classDef2@subclasses)))) {
classDef2@prototype <-
if(classDef@virtual)
classDef@prototype
else # new(), but without intialize(), which may require an arg.
.Call(C_new_object, classDef)
}
}
assignClassDef(class2, classDef2, where2, TRUE)
.removePreviousCoerce(class1, class2, where, prevIs)
where1 <- .findOrCopyClass(class1, classDef, where, "superClass")
## insert the direct contains information in a valid spot
.newDirectSuperclass(classDef@contains, class2, names(classDef2@contains)) <- obj
if(doComplete) {
classDef@contains <- completeExtends(classDef, class2, obj, where = where)
## is needed at least during byte compilation of 'methods' itself :
if(!is(classDef, "ClassUnionRepresentation")) #unions are handled in assignClassDef
.checkSubclasses(class1, classDef, class2, classDef2, where1, where2)
}
assignClassDef(class1, classDef, where1, TRUE)
invisible(classDef)
}
.findOrCopyClass <- function(class, classDef, where, purpose) {
whereIs <- findClass(classDef, where)
if(length(whereIs))
whereIs[[1L]]
else {
if(purpose != "subclass")
warning(gettextf("class %s is defined (with package slot %s) but no metadata object found to revise %s information---not exported? Making a copy in package %s",
.dQ(class), sQuote(classDef@package), purpose,
sQuote(getPackageName(where, FALSE))),
call. = FALSE, domain = NA)
where
}
}
.validExtends <- function(class1, class2, classDef1, classDef2, slotTests) {
.msg <- function(class1, class2)
gettextf("class %s cannot extend class %s",
dQuote(class1),
dQuote(class2))
if((is.null(classDef1) || is.null(classDef2)) &&
!(isVirtualClass(class1) && isVirtualClass(class2)))
return(c(.msg(class1, class2), ": ",
gettext("both classes must be defined")))
if(slotTests) {
slots2 <- classDef2@slots
if(length(slots2)) {
n2 <- names(slots2)
slots1 <- classDef1@slots
n1 <- names(slots1)
if(any(is.na(match(n2, n1))))
return(c(.msg(class1, class2), ": ",
sprintf(ngettext(sum(is.na(match(n2, n1))),
"class %s is missing slot from class %s (%s), and no coerce method was supplied",
"class %s is missing slots from class %s (%s), and no coerce method was supplied"),
dQuote(class1),
dQuote(class2),
paste(n2[is.na(match(n2, n1))], collapse = ", "))))
bad <- character()
for(what in n2)
if(!extends(slots1[[what]], slots2[[what]]))
bad <- c(bad, what)
if(length(bad))
return(c(.msg(class1, class2), ": ",
sprintf(ngettext(length(bad),
"slot in class %s must extend corresponding slot in class %s: fails for %s",
"slots in class %s must extend corresponding slots in class %s: fails for %s"),
dQuote(class1),
dQuote(class2),
paste(bad, collapse = ", "))))
}
}
TRUE
}
".newDirectSuperclass<-" <- function(contains, class2, superclasses2, value) {
superclasses <- names(contains)
if(length(superclasses2) == 0 || length(superclasses) == 0 ||
all(is.na(match(superclasses2, superclasses))))
contains[[class2]] <- value
else {
sq <- seq_along(superclasses)
before <- (sq[match(superclasses, superclasses2,0)>0])[[1]]
contains <- c(contains[sq < before], value, contains[sq >= before])
superclasses <- c(superclasses[sq < before], class2, superclasses[sq >= before])
names(contains) <- superclasses
}
contains
}