diff --git a/DESCRIPTION b/DESCRIPTION index 0ff364b76..821a1aa29 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -101,6 +101,7 @@ Collate: 'dbplyr.R' 'explain.R' 'ident.R' + 'import-standalone-s3-register.R' 'join-by-compat.R' 'join-cols-compat.R' 'lazy-join-query.R' diff --git a/NAMESPACE b/NAMESPACE index cbe99836b..9d2be4db6 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -111,8 +111,8 @@ S3method(dim,tbl_lazy) S3method(dimnames,tbl_lazy) S3method(distinct,tbl_lazy) S3method(do,tbl_sql) -S3method(dplyr::filter,tbl_lazy) S3method(dplyr::group_by_drop_default,tbl_lazy) +S3method(dplyr::intersect,tbl_lazy) S3method(dplyr::setdiff,OraConnection) S3method(dplyr::setdiff,tbl_Oracle) S3method(dplyr::setdiff,tbl_lazy) diff --git a/R/import-standalone-s3-register.R b/R/import-standalone-s3-register.R new file mode 100644 index 000000000..d165e18c9 --- /dev/null +++ b/R/import-standalone-s3-register.R @@ -0,0 +1,187 @@ +# Standalone file: do not edit by hand +# Source: +# ---------------------------------------------------------------------- +# +# --- +# repo: r-lib/rlang +# file: standalone-s3-register.R +# last-updated: 2022-08-29 +# license: https://unlicense.org +# --- +# +# nocov start + +#' Register a method for a suggested dependency +#' +#' Generally, the recommended way to register an S3 method is to use the +#' `S3Method()` namespace directive (often generated automatically by the +#' `@export` roxygen2 tag). However, this technique requires that the generic +#' be in an imported package, and sometimes you want to suggest a package, +#' and only provide a method when that package is loaded. `s3_register()` +#' can be called from your package's `.onLoad()` to dynamically register +#' a method only if the generic's package is loaded. +#' +#' For R 3.5.0 and later, `s3_register()` is also useful when demonstrating +#' class creation in a vignette, since method lookup no longer always involves +#' the lexical scope. For R 3.6.0 and later, you can achieve a similar effect +#' by using "delayed method registration", i.e. placing the following in your +#' `NAMESPACE` file: +#' +#' ``` +#' if (getRversion() >= "3.6.0") { +#' S3method(package::generic, class) +#' } +#' ``` +#' +#' @section Usage in other packages: +#' To avoid taking a dependency on vctrs, you copy the source of +#' [`s3_register()`](https://github.com/r-lib/rlang/blob/main/R/standalone-s3-register.R) +#' into your own package. It is licensed under the permissive +#' [unlicense](https://choosealicense.com/licenses/unlicense/) to make it +#' crystal clear that we're happy for you to do this. There's no need to include +#' the license or even credit us when using this function. +#' +#' @param generic Name of the generic in the form `"pkg::generic"`. +#' @param class Name of the class +#' @param method Optionally, the implementation of the method. By default, +#' this will be found by looking for a function called `generic.class` +#' in the package environment. +#' @examples +#' # A typical use case is to dynamically register tibble/pillar methods +#' # for your class. That way you avoid creating a hard dependency on packages +#' # that are not essential, while still providing finer control over +#' # printing when they are used. +#' +#' .onLoad <- function(...) { +#' s3_register("pillar::pillar_shaft", "vctrs_vctr") +#' s3_register("tibble::type_sum", "vctrs_vctr") +#' } +#' @keywords internal +#' @noRd +s3_register <- function(generic, class, method = NULL) { + stopifnot(is.character(generic), length(generic) == 1) + stopifnot(is.character(class), length(class) == 1) + + pieces <- strsplit(generic, "::")[[1]] + stopifnot(length(pieces) == 2) + package <- pieces[[1]] + generic <- pieces[[2]] + + caller <- parent.frame() + + get_method_env <- function() { + top <- topenv(caller) + if (isNamespace(top)) { + asNamespace(environmentName(top)) + } else { + caller + } + } + get_method <- function(method) { + if (is.null(method)) { + get(paste0(generic, ".", class), envir = get_method_env()) + } else { + method + } + } + + register <- function(...) { + envir <- asNamespace(package) + + # Refresh the method each time, it might have been updated by + # `devtools::load_all()` + method_fn <- get_method(method) + stopifnot(is.function(method_fn)) + + + # Only register if generic can be accessed + if (exists(generic, envir)) { + registerS3method(generic, class, method_fn, envir = envir) + } else if (identical(Sys.getenv("NOT_CRAN"), "true")) { + warn <- .rlang_s3_register_compat("warn") + + warn(c( + sprintf( + "Can't find generic `%s` in package %s to register S3 method.", + generic, + package + ), + "i" = "This message is only shown to developers using devtools.", + "i" = sprintf("Do you need to update %s to the latest version?", package) + )) + } + } + + # Always register hook in case package is later unloaded & reloaded + setHook(packageEvent(package, "onLoad"), function(...) { + register() + }) + + # For compatibility with R < 4.1.0 where base isn't locked + is_sealed <- function(pkg) { + identical(pkg, "base") || environmentIsLocked(asNamespace(pkg)) + } + + # Avoid registration failures during loading (pkgload or regular). + # Check that environment is locked because the registering package + # might be a dependency of the package that exports the generic. In + # that case, the exports (and the generic) might not be populated + # yet (#1225). + if (isNamespaceLoaded(package) && is_sealed(package)) { + register() + } + + invisible() +} + +.rlang_s3_register_compat <- function(fn, try_rlang = TRUE) { + # Compats that behave the same independently of rlang's presence + out <- switch( + fn, + is_installed = return(function(pkg) requireNamespace(pkg, quietly = TRUE)) + ) + + # Only use rlang if it is fully loaded (#1482) + if (try_rlang && + requireNamespace("rlang", quietly = TRUE) && + environmentIsLocked(asNamespace("rlang"))) { + switch( + fn, + is_interactive = return(rlang::is_interactive) + ) + + # Make sure rlang knows about "x" and "i" bullets + if (utils::packageVersion("rlang") >= "0.4.2") { + switch( + fn, + abort = return(rlang::abort), + warn = return((rlang::warn)), + inform = return(rlang::inform) + ) + } + } + + # Fall back to base compats + + is_interactive_compat <- function() { + opt <- getOption("rlang_interactive") + if (!is.null(opt)) { + opt + } else { + interactive() + } + } + + format_msg <- function(x) paste(x, collapse = "\n") + switch( + fn, + is_interactive = return(is_interactive_compat), + abort = return(function(msg) stop(format_msg(msg), call. = FALSE)), + warn = return(function(msg) warning(format_msg(msg), call. = FALSE)), + inform = return(function(msg) message(format_msg(msg))) + ) + + stop(sprintf("Internal error in rlang shims: Unknown function `%s()`.", fn)) +} + +# nocov end diff --git a/R/verb-filter.R b/R/verb-filter.R index ba2e43ed3..a3520877a 100644 --- a/R/verb-filter.R +++ b/R/verb-filter.R @@ -14,7 +14,7 @@ #' db %>% filter(x < 5) %>% show_query() #' db %>% filter(is.na(x)) %>% show_query() #' @importFrom dplyr filter -#' @exportS3Method dplyr::filter +# Registered onLoad filter.tbl_lazy <- function(.data, ..., .by = NULL, .preserve = FALSE) { check_unsupported_arg(.preserve, FALSE) check_filter(...) diff --git a/R/zzz.R b/R/zzz.R index a043cc048..98f182ebe 100644 --- a/R/zzz.R +++ b/R/zzz.R @@ -1,5 +1,8 @@ # nocov start .onLoad <- function(...) { + # lazy S3method() directive for this case only works in 4.3 and later + s3_register("dplyr::filter", "tbl_lazy") + methods::setOldClass(c("ident_q", "ident", "character"), ident_q()) methods::setOldClass(c("ident", "character"), ident()) methods::setOldClass(c("sql", "character"), sql())