Nicole White
Nicole White

Reputation: 7800

How to override exported function from R package listed in Imports

My package's DESCRIPTION file has httr in its Imports directive:

Imports:
    httr (>= 1.1.0),
    jsonlite,
    rstudioapi

httr exports an S3method for length.path.

S3method(length,path)

And it's defined as:

#' @export
length.path <- function(x) file.info(x)$size

In my package I have objects that I assign the class "path" to. Every time I assign the class "path" to any object, regardless of whether or not I ever call length() on the object, this is printed to stdout:

Error in file.info(x) : invalid filename argument

Here's some reproducible code everyone can run:

> sessionInfo()
R version 3.3.1 (2016-06-21)
Platform: x86_64-apple-darwin13.4.0 (64-bit)
Running under: OS X 10.11.5 (El Capitan)

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
[1] tools_3.3.1

> thing = 1:5
> class(thing) = 'path'

> requireNamespace('httr')
Loading required namespace: httr

> sessionInfo()
R version 3.3.1 (2016-06-21)
Platform: x86_64-apple-darwin13.4.0 (64-bit)
Running under: OS X 10.11.5 (El Capitan)

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
[1] httr_1.2.1  R6_2.1.2    tools_3.3.1

> thing = 1:5
> class(thing) = 'path'
Error in file.info(x) : invalid filename argument

I've tried catching it in a try but that doesn't work:

set_class = function(obj, c) {
  class(obj) = c
  return(obj)
}

thing = 1:5
thing = try(set_class(thing, 'path'), silent=TRUE)

Yields:

Error in file.info(x) : invalid filename argument

I've tried assignInNamespace to override the function:

base_length = function(obj) {
  return(base::length(obj))
}

assignInNamespace('length.path', base_length, 'httr')
thing = 1:5
class(thing) = 'path'

But I get Error: evaluation nested too deeply: infinite recursion / options(expressions=)?

When I use httr functions in my package I use them with httr::function so I'm not sure how this length.path function is leaking into my namespace and overriding the base length function. I've also tried explicit @importFrom httr function for each function that I use instead of using httr::function but that doesn't work either.

I also found this:

https://support.bioconductor.org/p/79059/

But the solution seemed to be to edit the source code of httr, which I can't do since my package imports it. How can I get around this?

Upvotes: 6

Views: 719

Answers (2)

Nicole White
Nicole White

Reputation: 7800

The solution for me was to add an .onLoad function. Thanks to AEF's answer for the unclass idea.

#' @importFrom utils assignInNamespace
.onLoad = function(libname, pkgname) {
  length.path = function(obj) {
    return(length(unclass(obj)))
  }

  assignInNamespace('length.path', length.path, 'httr')
}

It raises a NOTE in R CMD check --as-cran but I'm hoping to get away with it.

Upvotes: 0

AEF
AEF

Reputation: 5670

One possibility could be to create a function length.path() in your own package. If the base type of your path-objects is compatible with base::length() you could just unclass it to avoid infinite recursion:

length.path <- function(x) length(unclass(x))

However, this is potentially slow compared to a direct call to base::length() because it copies the object and needs method dispatch two times.

Upvotes: 1

Related Questions