rr-packager-s3

Why is my method as.character.haven_labelled() not working in my package?


I am trying to handle classed vectors in my package without importing another package to do it. I have written an as.character method for class haven_labelled. It is for internal use only, so I'm not exporting it:

as.character.haven_labelled <- function(x, ...) {
    as.character.default(x)
}

I have this vector for testing in one of my exported data sets:

> class(mtcars_lab$vs)
[1] "haven_labelled" "vctrs_vctr"     "double" 

But when I load the package and pass the vector to as.character, R dispatches to as.character.vctrs_vctr instead of my method as.character.haven_labelled, resulting in an error:

> devtools::load_all(".")
ℹ Loading sift
> as.character(mtcars_lab$vs)
Error in `as.character()`:
! Can't convert `x` <haven_labelled> to <character>.
Run `rlang::last_error()` to see where the error occurred.
> rlang::last_error()
<error/vctrs_error_cast>
Error in `as.character()`:
! Can't convert `x` <haven_labelled> to <character>.
---
Backtrace:
 1. base::as.character(mtcars_lab$vs)
 2. vctrs:::as.character.vctrs_vctr(mtcars_lab$vs)
Run `rlang::last_trace()` to see the full context.
> rlang::last_trace()
<error/vctrs_error_cast>
Error in `as.character()`:
! Can't convert `x` <haven_labelled> to <character>.
---
Backtrace:
     ▆
  1. ├─base::as.character(mtcars_lab$vs)
  2. ├─vctrs:::as.character.vctrs_vctr(mtcars_lab$vs)
  3. │ └─vctrs::vec_cast(x, character())
  4. └─vctrs (local) `<fn>`()
  5.   └─vctrs::vec_default_cast(...)
  6.     ├─base::withRestarts(...)
  7.     │ └─base (local) withOneRestart(expr, restarts[[1L]])
  8.     │   └─base (local) doWithOneRestart(return(expr), restart)
  9.     └─vctrs::stop_incompatible_cast(...)
 10.       └─vctrs::stop_incompatible_type(...)
 11.         └─vctrs:::stop_incompatible(...)
 12.           └─vctrs:::stop_vctrs(...)
 13.             └─rlang::abort(message, class = c(class, "vctrs_error"), ..., call = vctrs_error_call(call))

Solution

  • Methods need to be exported into the namespace through Roxygen. For internal-only and undocumented methods, it's adequate to use:

    #' @exportS3Method as.character haven_labelled
    as.character.haven_labelled <- function(x, ...) {
        as.character.default(x)
    }
    
    #' @exportS3Method as.integer haven_labelled
    as.integer.haven_labelled <- function(x, ...) {
        as.integer(as.character.haven_labelled(x))
    }
    

    I found that using plain @export sometimes did not add the right entry to NAMESPACE, like it would create an entry for export(as.character.haven_labelled) but not for S3method(as.character,haven_labelled). Specifying the @exportS3Method is reliable.