Context
I develop a mutation testing package for R (mutator) and it uses pkgload to quickly reload the package for each of the test suite runs it has to perform (can be 1000s of them). I have system tests that compare results between running a test suite with pkgload or running it with R CMD INSTALL (so installing the package each time) and they caught a discrepancy for package nanotime (0.3.15) on S4 dispatches.
Its tinytest suite calls seq(<nanoival>, by = <nanoperiod>, length.out = ...), which under load_all() falls to seq.default and then hits nanotime's deliberately-disabled + for those types:
binary '+' is not defined for 'nanoival' and 'nanoperiod' objects; instead use 'plus(e1, e2, tz)'
Summary
When a package defines an S4 method on a base generic that dispatches on ... (the clearest example is seq, whose implicit generic has signature "..."), the method is registered correctly under pkgload::load_all() (existsMethod() / selectMethod() find it and it runs when invoked directly), but normal dispatch does not reach it as soon as the call has more than one argument. R silently falls through to the internal default (seq.default).
The exact same package installed (R CMD INSTALL + library()) dispatches correctly for any number of arguments. Hence, a load_all()/methods setup issue, not a problem with the package.
seq(x) (single argument): S4 method is dispatched
seq(x, by = 1L, length.out = 3) (extra args): falls to seq.default (only under load_all())
Minimal reprex
# Build a tiny package: one S4 class with an S4 method on `seq`.
d <- file.path(tempfile("s4seq_"), "s4seqpkg")
dir.create(file.path(d, "R"), recursive = TRUE)
writeLines(
"Package: s4seqpkg\nVersion: 0.1.0\nTitle: T\nDescription: d.\nAuthor: A\nLicense: MIT\nImports: methods",
file.path(d, "DESCRIPTION")
)
writeLines(
c("import(methods)", "exportClasses(myc)", "exportMethods(seq)"),
file.path(d, "NAMESPACE")
)
writeLines(
c(
"setClass('myc', contains = 'numeric')",
"setMethod('seq', 'myc', function(from, ...) 99L)"
),
file.path(d, "R", "x.R")
)
## --- via pkgload::load_all() ---------------------------------------------
pkgload::load_all(d, quiet = TRUE)
b <- new("myc", 1)
seq(b) #> [1] 99 (S4 method dispatched)
seq(b, by = 1L, length.out = 3) #> [1] 1 2 3 (WRONG: seq.default, S4 method skipped)
# The method IS registered and works when invoked explicitly:
existsMethod("seq", "myc") #> [1] TRUE
selectMethod("seq", "myc")(b, by = 1L, length.out = 3) #> [1] 99
Now the same package installed dispatches correctly:
lib <- tempfile("lib_"); dir.create(lib)
system2(file.path(R.home("bin"), "R"),
c("CMD", "INSTALL", paste0("--library=", lib), d),
stdout = FALSE, stderr = FALSE)
# In a fresh session:
.libPaths(c(lib, .libPaths()))
library(s4seqpkg)
b <- new("myc", 1)
seq(b) #> [1] 99
seq(b, by = 1L, length.out = 3) #> [1] 99 (correct: S4 method dispatched)
Some notes
- The method is present after
load_all(): isGeneric("seq") is TRUE, existsMethod("seq", "myc") is TRUE, find("seq") shows the package's
standardGeneric ahead of base, and selectMethod(...)() returns the right answer. Only implicit dispatch through a bare seq(...) call misses it, and only once extra arguments are present.
- It is specific to generics that dispatch on
.... S4 methods on generics that dispatch on a named formal work fine under load_all() , for instance, setMethod("mean", "myc", ...) (mean(x, ...)) dispatches correctly.
- It is not about primitives: S4 methods on primitive group generics such as
+/Arith dispatch correctly under load_all() (C-level dispatch). This rather happens with internal generics such as seq (function (...) UseMethod("seq")).
- Not fixed by any of:
methods::cacheMetaData(ns, TRUE), methods::setGeneric("seq"), or load_all(export_all = FALSE).
Environment
- pkgload 1.5.3
- R 4.6.1 (2026-06-24)
- Platform: Linux (x86_64)
methods (base)
Related issues
Might be related to #45
Context
I develop a mutation testing package for R (mutator) and it uses
pkgloadto quickly reload the package for each of the test suite runs it has to perform (can be 1000s of them). I have system tests that compare results between running a test suite withpkgloador running it withR CMD INSTALL(so installing the package each time) and they caught a discrepancy for packagenanotime(0.3.15) on S4 dispatches.Its tinytest suite calls
seq(<nanoival>, by = <nanoperiod>, length.out = ...), which underload_all()falls toseq.defaultand then hits nanotime's deliberately-disabled+for those types:Summary
When a package defines an S4 method on a base generic that dispatches on
...(the clearest example isseq, whose implicit generic has signature"..."), the method is registered correctly underpkgload::load_all()(existsMethod()/selectMethod()find it and it runs when invoked directly), but normal dispatch does not reach it as soon as the call has more than one argument. R silently falls through to the internal default (seq.default).The exact same package installed (
R CMD INSTALL+library()) dispatches correctly for any number of arguments. Hence, aload_all()/methodssetup issue, not a problem with the package.seq(x)(single argument): S4 method is dispatchedseq(x, by = 1L, length.out = 3)(extra args): falls toseq.default(only underload_all())Minimal reprex
Now the same package installed dispatches correctly:
Some notes
load_all():isGeneric("seq")isTRUE,existsMethod("seq", "myc")isTRUE,find("seq")shows the package'sstandardGenericahead ofbase, andselectMethod(...)()returns the right answer. Only implicit dispatch through a bareseq(...)call misses it, and only once extra arguments are present..... S4 methods on generics that dispatch on a named formal work fine underload_all(), for instance,setMethod("mean", "myc", ...)(mean(x, ...)) dispatches correctly.+/Arithdispatch correctly underload_all()(C-level dispatch). This rather happens with internal generics such asseq(function (...) UseMethod("seq")).methods::cacheMetaData(ns, TRUE),methods::setGeneric("seq"), orload_all(export_all = FALSE).Environment
methods(base)Related issues
Might be related to #45