ellipsis/0000755000176200001440000000000013541234442012100 5ustar liggesusersellipsis/NAMESPACE0000644000176200001440000000034413536250033013316 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method(safe_median,numeric) export(check_dots_empty) export(check_dots_unnamed) export(check_dots_used) export(safe_median) import(rlang) useDynLib(ellipsis, .registration = TRUE) ellipsis/README.md0000644000176200001440000000474413536242052013370 0ustar liggesusers # ellipsis [![Lifecycle: maturing](https://img.shields.io/badge/lifecycle-maturing-blue.svg)](https://www.tidyverse.org/lifecycle/#maturing) [![CRAN status](https://www.r-pkg.org/badges/version/ellipsis)](https://cran.r-project.org/package=ellipsis) [![Travis build status](https://travis-ci.org/r-lib/ellipsis.svg?branch=master)](https://travis-ci.org/r-lib/ellipsis) [![Codecov test coverage](https://codecov.io/gh/r-lib/ellipsis/branch/master/graph/badge.svg)](https://codecov.io/gh/r-lib/ellipsis?branch=master) Adding `...` to a function is a powerful technique because it allows you to accept any number of additional arguments. Unfortunately it comes with a big downside: any misspelled or extraneous arguments will be silently ignored. This package provides tools for making `...` safer: - `check_dots_used()` errors if any components of `...` are not evaluated. This allows an S3 generic to state that it expects every input to be evaluated. - `check_dots_unnamed()` errors if any components of `...` are named. This allows you to collect arbitrary unnamed arguments, warning if the user misspells a named argument. - `check_dots_empty()` errors if `...` is used. This allows you to use `...` to force the user to supply full argument names, while still warning if an argument name is misspelled. Thanks to [Jenny Bryan](http://github.com/jennybc) for the idea, and [Lionel Henry](http://github.com/lionel-) for the heart of the implementation. ## Installation Install the released version from CRAN: ``` r install.packages("ellipsis") ``` Or the development version from GitHub: ``` r devtools::install_github("r-lib/ellipsis") ``` ## Example `mean()` is a little dangerous because you might expect it to work like `sum()`: ``` r sum(1, 2, 3, 4) #> [1] 10 mean(1, 2, 3, 4) #> [1] 1 ``` This silently returns the incorrect result because `mean()` has arguments `x` and `...`. The `...` silently swallows up the additional arguments. We can use `ellipsis::check_dots_used()` to check that every input to `...` is actually used: ``` r safe_mean <- function(x, ..., trim = 0, na.rm = FALSE) { ellipsis::check_dots_used() mean(x, ..., trim = trim, na.rm = na.rm) } safe_mean(1, 2, 3, 4) #> Error: 3 components of `...` were not used. #> #> We detected these problematic arguments: #> * `..1` #> * `..2` #> * `..3` #> #> Did you misspecify an argument? ``` ellipsis/man/0000755000176200001440000000000013541226740012655 5ustar liggesusersellipsis/man/ellipsis-package.Rd0000644000176200001440000000153013541226740016360 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/ellipsis-package.R \docType{package} \name{ellipsis-package} \alias{ellipsis} \alias{ellipsis-package} \title{ellipsis: Tools for Working with ...} \description{ The ellipsis is a powerful tool for extending functions. Unfortunately this power comes at a cost: misspelled arguments will be silently ignored. The ellipsis package provides a collection of functions to catch problems and alert the user. } \seealso{ Useful links: \itemize{ \item \url{https://ellipsis.r-lib.org} \item \url{https://github.com/r-lib/ellipsis} \item Report bugs at \url{https://github.com/r-lib/ellipsis/issues} } } \author{ \strong{Maintainer}: Hadley Wickham \email{hadley@rstudio.com} Other contributors: \itemize{ \item RStudio [copyright holder] } } \keyword{internal} ellipsis/man/safe_median.Rd0000644000176200001440000000140113536242052015371 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/safe.R \name{safe_median} \alias{safe_median} \alias{safe_median.numeric} \title{Safe version of median} \usage{ safe_median(x, ...) \method{safe_median}{numeric}(x, ..., na.rm = TRUE) } \arguments{ \item{x}{Numeric vector} \item{...}{Additional arguments passed on to methods.} \item{na.rm}{For numeric method, should missing values be removed?} } \description{ \code{safe_median()} works \code{\link[stats:median]{stats::median()}} but warns if some elements of \code{...} are never used. } \examples{ x <- c(1:10, NA) safe_median(x, na.rm = TRUE) median(x, na.rm = TRUE) try(median(x, na.mr = TRUE)) try(safe_median(x, na.mr = TRUE)) try(median(1, 2, 3)) try(safe_median(1, 2, 3)) } ellipsis/man/dots_empty.Rd0000644000176200001440000000062213536242052015331 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/dots.R \name{dots_empty} \alias{dots_empty} \title{Helper for consistent documentation} \arguments{ \item{...}{These dots are for future extensions and must be empty.} } \description{ Use \code{@inheritParams ellipsis::dots_empty} in your package to consistently document an unused \code{...} argument. } \keyword{internal} ellipsis/man/check_dots_unnamed.Rd0000644000176200001440000000141713541226740016764 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/check.R \name{check_dots_unnamed} \alias{check_dots_unnamed} \title{Check that all dots are unnamed} \usage{ check_dots_unnamed(env = parent.frame(), action = abort) } \arguments{ \item{env}{Environment in which to look for \code{...}.} \item{action}{The action to take when the dots have not been used. One of \code{\link[rlang:abort]{rlang::abort()}}, \code{\link[rlang:warn]{rlang::warn()}}, \code{\link[rlang:inform]{rlang::inform()}} or \code{\link[rlang:signal]{rlang::signal()}}.} } \description{ Named arguments in ... are often a sign of misspelled argument names. } \examples{ f <- function(..., foofy = 8) { check_dots_unnamed() c(...) } f(1, 2, 3, foofy = 4) try(f(1, 2, 3, foof = 4)) } ellipsis/man/check_dots_used.Rd0000644000176200001440000000210113541226740016264 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/check.R \name{check_dots_used} \alias{check_dots_used} \title{Check that all dots have been used} \usage{ check_dots_used(env = parent.frame(), action = abort) } \arguments{ \item{env}{Environment in which to look for \code{...} and to set up handler.} \item{action}{The action to take when the dots have not been used. One of \code{\link[rlang:abort]{rlang::abort()}}, \code{\link[rlang:warn]{rlang::warn()}}, \code{\link[rlang:inform]{rlang::inform()}} or \code{\link[rlang:signal]{rlang::signal()}}.} } \description{ Automatically sets exit handler to run when function terminates, checking that all elements of \code{...} have been evaluated. If you use \code{\link[=on.exit]{on.exit()}} elsewhere in your function, make sure to use \code{add = TRUE} so that you don't override the handler set up by \code{check_dots_used()}. } \examples{ f <- function(...) { check_dots_used() g(...) } g <- function(x, y, ...) { x + y } f(x = 1, y = 2) try(f(x = 1, y = 2, z = 3)) try(f(x = 1, y = 2, 3, 4, 5)) } ellipsis/man/check_dots_empty.Rd0000644000176200001440000000151113541226740016466 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/check.R \name{check_dots_empty} \alias{check_dots_empty} \title{Check that dots are unused} \usage{ check_dots_empty(env = parent.frame(), action = abort) } \arguments{ \item{env}{Environment in which to look for \code{...}.} \item{action}{The action to take when the dots have not been used. One of \code{\link[rlang:abort]{rlang::abort()}}, \code{\link[rlang:warn]{rlang::warn()}}, \code{\link[rlang:inform]{rlang::inform()}} or \code{\link[rlang:signal]{rlang::signal()}}.} } \description{ Sometimes you just want to use \code{...} to force your users to fully name the details arguments. This function warns if \code{...} is not empty. } \examples{ f <- function(x, ..., foofy = 8) { check_dots_empty() x + foofy } try(f(1, foof = 4)) f(1, foofy = 4) } ellipsis/DESCRIPTION0000644000176200001440000000167213541234442013614 0ustar liggesusersPackage: ellipsis Version: 0.3.0 Title: Tools for Working with ... Description: The ellipsis is a powerful tool for extending functions. Unfortunately this power comes at a cost: misspelled arguments will be silently ignored. The ellipsis package provides a collection of functions to catch problems and alert the user. Authors@R: c( person("Hadley", "Wickham", , "hadley@rstudio.com", role = c("aut", "cre")), person("RStudio", role = "cph") ) License: GPL-3 Encoding: UTF-8 LazyData: true RoxygenNote: 6.1.1 URL: https://ellipsis.r-lib.org, https://github.com/r-lib/ellipsis BugReports: https://github.com/r-lib/ellipsis/issues Depends: R (>= 3.2) Imports: rlang (>= 0.3.0) Suggests: covr, testthat NeedsCompilation: yes Packaged: 2019-09-20 20:15:40 UTC; jhester Author: Hadley Wickham [aut, cre], RStudio [cph] Maintainer: Hadley Wickham Repository: CRAN Date/Publication: 2019-09-20 20:40:02 UTC ellipsis/tests/0000755000176200001440000000000013536242052013242 5ustar liggesusersellipsis/tests/testthat/0000755000176200001440000000000013541234442015102 5ustar liggesusersellipsis/tests/testthat/test-check.R0000644000176200001440000000353413541226740017266 0ustar liggesuserscontext("test-check") test_that("error if dots not used", { f <- function(x, y, ...) { check_dots_used() x + y } expect_error(f(1, 2), NA) expect_error(f(1, 2, 3), class = "rlib_error_dots_unused") }) test_that("error if dots not used by another function", { g <- function(a = 1, b = 1, ...) { a + b } f <- function(x = 1, ...) { check_dots_used() x * g(...) } expect_error(f(x = 10, a = 1), NA) expect_error(f(x = 10, c = 3), class = "rlib_error_dots_unused") }) test_that("error if dots named", { f <- function(..., xyz = 1) { check_dots_unnamed() } expect_error(f(xyz = 1), NA) expect_error(f(1, 2, 3), NA) expect_error(f(1, 2, 3, xyz = 4), NA) expect_error(f(1, 2, 3, xy = 4), class = "rlib_error_dots_named") }) test_that("error if if dots not empty", { f <- function(..., xyz = 1) { check_dots_empty() } expect_error(f(xyz = 1), NA) expect_error(f(xy = 4), class = "rlib_error_dots_nonempty") }) test_that("can control the action", { f <- function(action, check, ..., xyz = 1) { check(action = action) } expect_error(f(abort, check_dots_used, xy = 4), class = "rlib_error_dots_unused") expect_warning(f(warn, check_dots_used, xy = 4), class = "rlib_error_dots_unused") expect_message(f(inform, check_dots_used, xy = 4), class = "rlib_error_dots_unused") expect_error(f(abort, check_dots_unnamed, xy = 4), class = "rlib_error_dots_named") expect_warning(f(warn, check_dots_unnamed, xy = 4), class = "rlib_error_dots_named") expect_message(f(inform, check_dots_unnamed, xy = 4), class = "rlib_error_dots_named") expect_error(f(abort, check_dots_empty, xy = 4), class = "rlib_error_dots_nonempty") expect_warning(f(warn, check_dots_empty, xy = 4), class = "rlib_error_dots_nonempty") expect_message(f(inform, check_dots_empty, xy = 4), class = "rlib_error_dots_nonempty") }) ellipsis/tests/testthat/test-safe.R0000644000176200001440000000034113541226740017120 0ustar liggesuserscontext("test-safe") test_that("warn if unused dots", { expect_error(safe_median(1:10), NA) expect_error(safe_median(1:10, na.rm = TRUE), NA) expect_error(safe_median(1:10, y = 1), class = "rlib_error_dots_unused") }) ellipsis/tests/testthat/test-dots.R0000644000176200001440000000115613536242052017156 0ustar liggesuserscontext("test-dots") capture_dots <- function(..., auto_name = TRUE) dots(auto_name = auto_name) test_that("errors with bad inputs", { expect_error(dots(), "No ... found") expect_error(dots(1), "not an environment") }) test_that("no dots yields empty list", { expect_equal(capture_dots(), list()) }) test_that("captures names if present", { expect_named(capture_dots(x = 1, y = 2), c("x", "y")) }) test_that("constructs names if absent", { expect_named(capture_dots(1, 2), c("..1", "..2")) }) test_that("unless auto_name = FALSE", { expect_named(capture_dots(x = 1, 2, auto_name = FALSE), c("x", NA)) }) ellipsis/tests/testthat.R0000644000176200001440000000007413536242052015226 0ustar liggesuserslibrary(testthat) library(ellipsis) test_check("ellipsis") ellipsis/src/0000755000176200001440000000000013541231554012670 5ustar liggesusersellipsis/src/init.c0000644000176200001440000000134213536242052013776 0ustar liggesusers#include #include #include // for NULL #include /* .Call calls */ extern SEXP ellipsis_promise_forced(SEXP); extern SEXP ellipsis_dots(SEXP, SEXP); extern SEXP ellipsis_eval_bare(SEXP, SEXP); extern SEXP ellipsis_dots_used(SEXP); static const R_CallMethodDef CallEntries[] = { {"ellipsis_dots", (DL_FUNC) &ellipsis_dots, 2}, {"ellipsis_promise_forced", (DL_FUNC) &ellipsis_promise_forced, 1}, {"ellipsis_eval_bare", (DL_FUNC) &ellipsis_eval_bare, 2}, {"ellipsis_dots_used", (DL_FUNC) &ellipsis_dots_used, 1}, {NULL, NULL, 0} }; void R_init_ellipsis(DllInfo *dll) { R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); } ellipsis/src/dots.c0000644000176200001440000000377213536242052014015 0ustar liggesusers#define R_NO_REMAP #define USE_RINTERNALS #include #include #include #include static SEXP find_dots(SEXP env) { if (TYPEOF(env) != ENVSXP) { Rf_errorcall(R_NilValue, "`env` is a not an environment"); } SEXP dots = PROTECT(Rf_findVarInFrame3(env, R_DotsSymbol, TRUE)); if (dots == R_UnboundValue) { Rf_errorcall(R_NilValue, "No ... found"); } UNPROTECT(1); return dots; } SEXP ellipsis_dots(SEXP env, SEXP auto_name_) { int auto_name = Rf_asLogical(auto_name_); SEXP dots = PROTECT(find_dots(env)); // Empty dots if (dots == R_MissingArg) { UNPROTECT(1); return Rf_allocVector(VECSXP, 0); } R_len_t n = Rf_length(dots); SEXP out = PROTECT(Rf_allocVector(VECSXP, n)); SEXP names = PROTECT(Rf_allocVector(STRSXP, n)); Rf_setAttrib(out, R_NamesSymbol, names); for (R_len_t i = 0; i < n; ++i) { SET_VECTOR_ELT(out, i, CAR(dots)); SEXP name = TAG(dots); if (TYPEOF(name) == SYMSXP) { SET_STRING_ELT(names, i, PRINTNAME(name)); } else { if (auto_name) { char buffer[20]; snprintf(buffer, 20, "..%i", i + 1); SET_STRING_ELT(names, i, Rf_mkChar(buffer)); } else { SET_STRING_ELT(names, i, NA_STRING); } } dots = CDR(dots); } UNPROTECT(3); return out; } static bool promise_forced(SEXP x) { if (TYPEOF(x) != PROMSXP) { return true; } else { return PRVALUE(x) != R_UnboundValue; } } SEXP ellipsis_promise_forced(SEXP x) { return Rf_ScalarLogical(promise_forced(x)); } SEXP ellipsis_dots_used(SEXP env) { SEXP dots = PROTECT(find_dots(env)); if (dots == R_MissingArg) { UNPROTECT(1); return Rf_ScalarLogical(true); } while (dots != R_NilValue) { SEXP elt = CAR(dots); if (!promise_forced(elt)) { UNPROTECT(1); return Rf_ScalarLogical(false); } dots = CDR(dots); } UNPROTECT(1); return Rf_ScalarLogical(true); } SEXP ellipsis_eval_bare(SEXP expr, SEXP env) { return Rf_eval(expr, env); } ellipsis/R/0000755000176200001440000000000013541226740012303 5ustar liggesusersellipsis/R/check.R0000644000176200001440000000657613541226740013521 0ustar liggesusers#' Check that all dots have been used #' #' Automatically sets exit handler to run when function terminates, checking #' that all elements of `...` have been evaluated. If you use [on.exit()] #' elsewhere in your function, make sure to use `add = TRUE` so that you #' don't override the handler set up by `check_dots_used()`. #' #' @param action The action to take when the dots have not been used. One of #' [rlang::abort()], [rlang::warn()], [rlang::inform()] or [rlang::signal()]. #' @param env Environment in which to look for `...` and to set up handler. #' @export #' @examples #' f <- function(...) { #' check_dots_used() #' g(...) #' } #' #' g <- function(x, y, ...) { #' x + y #' } #' f(x = 1, y = 2) #' #' try(f(x = 1, y = 2, z = 3)) #' try(f(x = 1, y = 2, 3, 4, 5)) check_dots_used <- function(env = parent.frame(), action = abort) { eval_bare(exit_handler(action), env) invisible() } check_dots <- function(env = parent.frame(), action) { if (.Call(ellipsis_dots_used, env)) { return(invisible()) } proms <- dots(env) used <- vapply(proms, promise_forced, logical(1)) unused <- names(proms)[!used] action_dots( action = action, message = paste0(length(unused), " components of `...` were not used."), dot_names = unused, .subclass = "rlib_error_dots_unused", ) } exit_handler <- function(action) bquote( on.exit({ .(check_dots)(environment(), .(action)) }, add = TRUE), list(check_dots = check_dots, action = action) ) #' Check that all dots are unnamed #' #' Named arguments in ... are often a sign of misspelled argument names. #' #' @inheritParams check_dots_used #' @param env Environment in which to look for `...`. #' @export #' @examples #' f <- function(..., foofy = 8) { #' check_dots_unnamed() #' c(...) #' } #' #' f(1, 2, 3, foofy = 4) #' try(f(1, 2, 3, foof = 4)) check_dots_unnamed <- function(env = parent.frame(), action = abort) { proms <- dots(env, auto_name = FALSE) if (length(proms) == 0) { return() } unnamed <- is.na(names(proms)) if (all(unnamed)) { return(invisible()) } named <- names(proms)[!unnamed] action_dots( action = action, message = paste0(length(named), " components of `...` had unexpected names."), dot_names = named, .subclass = "rlib_error_dots_named", ) } #' Check that dots are unused #' #' Sometimes you just want to use `...` to force your users to fully name #' the details arguments. This function warns if `...` is not empty. #' #' @inheritParams check_dots_used #' @param env Environment in which to look for `...`. #' @export #' @examples #' f <- function(x, ..., foofy = 8) { #' check_dots_empty() #' x + foofy #' } #' #' try(f(1, foof = 4)) #' f(1, foofy = 4) check_dots_empty <- function(env = parent.frame(), action = abort) { dots <- dots(env) if (length(dots) == 0) { return() } action_dots( action = action, message = "`...` is not empty.", dot_names = names(dots), note = "These dots only exist to allow future extensions and should be empty.", .subclass = "rlib_error_dots_nonempty" ) } action_dots <- function(action, message, dot_names, note = NULL, .subclass = NULL, ...) { message <- paste_line( message, "", "We detected these problematic arguments:", paste0("* `", dot_names, "`"), "", note, "Did you misspecify an argument?" ) action(message, .subclass = c(.subclass, "rlib_error_dots"), ...) } ellipsis/R/utils.R0000644000176200001440000000010213536242052013555 0ustar liggesusers paste_line <- function(...) { paste(c(...), collapse = "\n") } ellipsis/R/ellipsis-package.R0000644000176200001440000000006213536242052015637 0ustar liggesusers#' @keywords internal #' @import rlang "_PACKAGE" ellipsis/R/safe.R0000644000176200001440000000132513536242052013343 0ustar liggesusers#' Safe version of median #' #' `safe_median()` works [stats::median()] but warns if some elements of `...` #' are never used. #' #' @param x Numeric vector #' @param ... Additional arguments passed on to methods. #' @param na.rm For numeric method, should missing values be removed? #' @export #' @examples #' x <- c(1:10, NA) #' safe_median(x, na.rm = TRUE) #' median(x, na.rm = TRUE) #' #' try(median(x, na.mr = TRUE)) #' try(safe_median(x, na.mr = TRUE)) #' #' try(median(1, 2, 3)) #' try(safe_median(1, 2, 3)) safe_median <- function(x, ...) { check_dots_used() UseMethod("safe_median") } #' @export #' @rdname safe_median safe_median.numeric <- function(x, ..., na.rm = TRUE) { stats::median(x, na.rm = na.rm) } ellipsis/R/dots.R0000644000176200001440000000107313536242052013376 0ustar liggesusers#' Helper for consistent documentation #' #' Use `@inheritParams ellipsis::dots_empty` in your package #' to consistently document an unused `...` argument. #' #' @param ... These dots are for future extensions and must be empty. #' @name dots_empty #' @keywords internal NULL #' @useDynLib ellipsis, .registration = TRUE dots <- function(env = parent.frame(), auto_name = TRUE) { .Call(ellipsis_dots, env, auto_name) } promise_forced <- function(x) { .Call(ellipsis_promise_forced, x) } eval_bare <- function(expr, env) { .Call(ellipsis_eval_bare, expr, env) } ellipsis/NEWS.md0000644000176200001440000000174413541231460013201 0ustar liggesusers# ellipsis 0.3.0 * `check_dots_used()`, `check_dots_unnamed()`, and `check_dots_empty()` gain an `action` argument, to specify if they should error, warn, message or signal when the dots meet the condition. # ellipsis 0.2.0 ellipsis has officially graduated from experimental to maturing in the package lifecycle. * The main change of this release is that `check_()` functions now throw custom errors, rather than warnings. * `check_` functions have been optimised for the most common case of no problems. This means that you use it in more places without worrying about the performance cost. * New `check_dots_empty()` that checks that `...` is empty (#11). * Improved error message suggesting that you check for mispecified argument names. # ellipsis 0.1.0 * New `check_dots_unnamed()` that checks that all components of `...` are unnamed (#7). * Fix a bug that caused `check_dots_used()` to emit many false positives (#8) # ellipsis 0.0.2 * Fix a `PROTECT`ion error ellipsis/MD50000644000176200001440000000205413541234442012411 0ustar liggesusersc5f68d2f5ac9b299baeb22533fd647e8 *DESCRIPTION deb6ceef8d8275f3c277fa8e6e486688 *NAMESPACE 7580a2fc819653efefa54f500cea8f2f *NEWS.md e210d78a14fa29d6eb3e4b8e25a04513 *R/check.R 89d6e3a696c39a10d3eb7c4e213af1b8 *R/dots.R 0f95d2e1d6ec424ddfbffff4a4c3219d *R/ellipsis-package.R 3321d65ecd14911f5e0c6b5e897d90e5 *R/safe.R 4b9d33407534c5d3d09eae3e63470800 *R/utils.R 7f79997eaee749a76c2f4c62bb0471cf *README.md 035035faa03b516347b501dd2b0112e9 *man/check_dots_empty.Rd bbf86474c189c6a3b838f4585d45bedd *man/check_dots_unnamed.Rd 0e8518ea4a32cd40c09745d8355fc361 *man/check_dots_used.Rd 81a2303f579958de76a4abd349f3151f *man/dots_empty.Rd 21b77e043a0192ecee3e7600ffb4cd1c *man/ellipsis-package.Rd 2bfb7193d77664dce5eda8f79380925f *man/safe_median.Rd f1a51ed79b78c9c27f8faa4d79ff60d6 *src/dots.c bc81ff628a54a830af9295c7e3d56041 *src/init.c d03f7b327e2ac7a03a1a7b095a590e62 *tests/testthat.R 7ec2cf883faab777bdc554a024c8c44a *tests/testthat/test-check.R 9fb33f388a456f8b6fd96d0e34a4209d *tests/testthat/test-dots.R 01a0faa5ace36debf2203da07380e4ae *tests/testthat/test-safe.R