diff --git a/DESCRIPTION b/DESCRIPTION index a3866ed..78b6f8c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -12,6 +12,7 @@ Imports: methods Suggests: pkgload, + S7, testthat (>= 3.0.0) Config/testthat/edition: 3 Depends: R (>= 4.1) diff --git a/R/find-s7.R b/R/find-s7.R new file mode 100644 index 0000000..88b681b --- /dev/null +++ b/R/find-s7.R @@ -0,0 +1,52 @@ +is_s7_generic <- function(x) { + fn <- tryCatch(match.fun(x), error = function(e) NULL) + inherits(fn, "S7_generic") +} + +# Eventually use something from S7: https://github.com/RConsortium/S7/issues/597 +methods_find_s7 <- function(generic, name) { + methods_env <- attr(generic, "methods") + methods <- s7_walk_methods(methods_env) + + if (length(methods) == 0) { + return(data.frame( + method = character(), + class = character(), + package = character(), + topic = character(), + visible = logical(), + source = character(), + )) + } + + class <- vapply(methods, \(m) paste(m$classes, collapse = ","), character(1)) + + # S7 method topic aliases follow S4 convention: generic,class-method. + method <- paste0(name, ",", class, "-method") + package <- vapply(methods, \(m) fn_package(m$method), character(1)) + topic <- help_topic(method, package) + + data.frame( + method = method, + class = class, + package = package, + topic = topic, + visible = rep(TRUE, length(methods)), + source = rep(NA_character_, length(methods)) + ) +} + +# Recursively walk S7 method table (nested environments) to extract all methods +s7_walk_methods <- function(env, signature = character()) { + result <- list() + for (nm in sort(ls(env))) { + val <- env[[nm]] + if (is.environment(val)) { + result <- c(result, s7_walk_methods(val, c(signature, nm))) + } else { + method <- list(classes = c(signature, nm), method = val) + result <- c(result, list(method)) + } + } + result +} diff --git a/R/find.R b/R/find.R index 1b16360..7c76176 100644 --- a/R/find.R +++ b/R/find.R @@ -1,5 +1,9 @@ # Modified from sloop::methods_generic methods_find <- function(x) { + if (is_s7_generic(x)) { + return(methods_find_s7(match.fun(x), x)) + } + info <- attr(utils::methods(x), "info") if (nrow(info) == 0) { @@ -78,19 +82,11 @@ lookup_package <- function(generic, class, is_s4) { fn <- utils::getS3method(generic, class, optional = TRUE) } - # Not found if (is.null(fn)) { return(NA_character_) } - pkg <- utils::packageName(environment(fn)) - - # Function method found, but in a non-package environment - if (is.null(pkg)) { - return(NA_character_) - } - - pkg + fn_package(fn) } pkgs <- mapply(lookup_single_package, generic, class, is_s4, SIMPLIFY = FALSE) diff --git a/R/utils.R b/R/utils.R index e6409ef..67906b2 100644 --- a/R/utils.R +++ b/R/utils.R @@ -13,7 +13,12 @@ find_package <- function(x) { if (is.null(fn)) { return(NULL) } - utils::packageName(environment(fn)) + fn_package(fn) +} + +fn_package <- function(fn) { + pkg <- utils::packageName(environment(fn)) + if (is.null(pkg)) NA_character_ else pkg } last <- function(x, n = 0) { diff --git a/tests/testthat/_snaps/find-s7.md b/tests/testthat/_snaps/find-s7.md new file mode 100644 index 0000000..2cd4b90 --- /dev/null +++ b/tests/testthat/_snaps/find-s7.md @@ -0,0 +1,20 @@ +# S7 methods_list output + + Code + cat(methods_list("s7_method")) + Output + \itemize{ + \item \code{\link[=s7-method-2]{character}} + \item \code{integer} + } + +# S7 multi-dispatch methods_list output + + Code + cat(methods_list("s7_multi")) + Output + \itemize{ + \item \code{\link[=s7-multi-2]{character,integer}} + \item \code{integer,character} + } + diff --git a/tests/testthat/test-find-s7.R b/tests/testthat/test-find-s7.R new file mode 100644 index 0000000..6a31fa4 --- /dev/null +++ b/tests/testthat/test-find-s7.R @@ -0,0 +1,25 @@ +test_that("methods_find finds S7 methods", { + local_load_all("testS7Docs") + result <- methods_find("s7_method") + expect_equal(result$class, c("character", "integer")) + expect_equal(result$package, rep("testS7Docs", 2)) + expect_equal(result$topic, c("s7-method-2", "s7_method")) +}) + +test_that("methods_find finds S7 multi-dispatch methods", { + local_load_all("testS7Docs") + result <- methods_find("s7_multi") + expect_equal(result$class, c("character,integer", "integer,character")) + expect_equal(result$package, rep("testS7Docs", 2)) + expect_equal(result$topic, c("s7-multi-2", "s7_multi")) +}) + +test_that("S7 methods_list output", { + local_load_all("testS7Docs") + expect_snapshot(cat(methods_list("s7_method"))) +}) + +test_that("S7 multi-dispatch methods_list output", { + local_load_all("testS7Docs") + expect_snapshot(cat(methods_list("s7_multi"))) +}) diff --git a/tests/testthat/testS7Docs/DESCRIPTION b/tests/testthat/testS7Docs/DESCRIPTION new file mode 100644 index 0000000..7065a39 --- /dev/null +++ b/tests/testthat/testS7Docs/DESCRIPTION @@ -0,0 +1,10 @@ +Package: testS7Docs +Title: Test package for S7 generics +License: GPL-2 +Description: Test package for S7 generics. +Author: Hadley +Maintainer: Hadley +Version: 0.1 +Imports: S7 +Config/roxygen2/version: 7.3.3.9000 +Encoding: UTF-8 diff --git a/tests/testthat/testS7Docs/NAMESPACE b/tests/testthat/testS7Docs/NAMESPACE new file mode 100644 index 0000000..720b262 --- /dev/null +++ b/tests/testthat/testS7Docs/NAMESPACE @@ -0,0 +1,4 @@ +# Generated by roxygen2: do not edit by hand + +export(s7_method) +export(s7_multi) diff --git a/tests/testthat/testS7Docs/R/a.R b/tests/testthat/testS7Docs/R/a.R new file mode 100644 index 0000000..5978c90 --- /dev/null +++ b/tests/testthat/testS7Docs/R/a.R @@ -0,0 +1,43 @@ +#' An S7 generic +#' +#' @param x,y A parameter +#' @export +s7_method <- S7::new_generic("s7_method", "x") + +#' @rdname s7_method +S7::method(s7_method, S7::class_integer) <- function(x, ...) x + +#' S7 character method +#' +#' @rdname s7-method-2 +S7::method(s7_method, S7::class_character) <- function(x, ...) x + +#' An S7 multi-dispatch generic +#' +#' @param x,y A parameter +#' @export +s7_multi <- S7::new_generic("s7_multi", c("x", "y")) + +#' @rdname s7_multi +S7::method(s7_multi, list(S7::class_integer, S7::class_character)) <- function( + x, + y, + ... +) { + x +} + +#' S7 multi-dispatch method +#' +#' @rdname s7-multi-2 +S7::method(s7_multi, list(S7::class_character, S7::class_integer)) <- function( + x, + y, + ... +) { + y +} + +.onLoad <- function(...) { + S7::methods_register() +} diff --git a/tests/testthat/testS7Docs/man/s7-method-2.Rd b/tests/testthat/testS7Docs/man/s7-method-2.Rd new file mode 100644 index 0000000..b67b5da --- /dev/null +++ b/tests/testthat/testS7Docs/man/s7-method-2.Rd @@ -0,0 +1,12 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/a.R +\name{s7_method,character-method} +\alias{s7_method,character-method} +\title{S7 character method} +\usage{ +## S7 method for class +s7_method(x, ...) +} +\description{ +S7 character method +} diff --git a/tests/testthat/testS7Docs/man/s7-multi-2.Rd b/tests/testthat/testS7Docs/man/s7-multi-2.Rd new file mode 100644 index 0000000..7d2aa30 --- /dev/null +++ b/tests/testthat/testS7Docs/man/s7-multi-2.Rd @@ -0,0 +1,12 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/a.R +\name{s7_multi,character,integer-method} +\alias{s7_multi,character,integer-method} +\title{S7 multi-dispatch method} +\usage{ +## S7 method for classes , +s7_multi(x, y, ...) +} +\description{ +S7 multi-dispatch method +} diff --git a/tests/testthat/testS7Docs/man/s7_method.Rd b/tests/testthat/testS7Docs/man/s7_method.Rd new file mode 100644 index 0000000..cfaa99f --- /dev/null +++ b/tests/testthat/testS7Docs/man/s7_method.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/a.R +\name{s7_method} +\alias{s7_method} +\alias{s7_method,integer-method} +\title{An S7 generic} +\usage{ +s7_method(x, ...) + +## S7 method for class +s7_method(x, ...) +} +\arguments{ +\item{x, y}{A parameter} +} +\description{ +An S7 generic +} diff --git a/tests/testthat/testS7Docs/man/s7_multi.Rd b/tests/testthat/testS7Docs/man/s7_multi.Rd new file mode 100644 index 0000000..8ae35d7 --- /dev/null +++ b/tests/testthat/testS7Docs/man/s7_multi.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/a.R +\name{s7_multi} +\alias{s7_multi} +\alias{s7_multi,integer,character-method} +\title{An S7 multi-dispatch generic} +\usage{ +s7_multi(x, y, ...) + +## S7 method for classes , +s7_multi(x, y, ...) +} +\arguments{ +\item{x, y}{A parameter} +} +\description{ +An S7 multi-dispatch generic +}