diff --git a/ChangeLog b/ChangeLog index 9759112b5..428e2f447 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2026-10-29 Kevin Ushey + + * inst/include/Rcpp/Module.h (RCPP_MODULE): Run the module body only the + first time the boot function is called, since it adds to the static + module and every Module() call boots the module again, which duplicated + constructors and methods (#1527) + * inst/tinytest/test_module.R: Test loading a module repeatedly + 2026-10-29 Kevin Ushey * inst/include/Rcpp/Module.h (RCPP_MODULE): Wrap the module boot diff --git a/inst/include/Rcpp/Module.h b/inst/include/Rcpp/Module.h index 7f5ff4356..05059f345 100644 --- a/inst/include/Rcpp/Module.h +++ b/inst/include/Rcpp/Module.h @@ -630,17 +630,23 @@ namespace Rcpp { #define RCPP_MODULE_BOOT(name) _rcpp_module_boot_##name // the boot function is called from R via .Call(), so exceptions thrown by -// the module body must be turned into R errors -#define RCPP_MODULE(name) \ - void _rcpp_module_##name##_init() ; \ - static Rcpp::Module _rcpp_module_##name( # name ) ; \ - extern "C" SEXP _rcpp_module_boot_##name(){ \ - BEGIN_RCPP \ - Rcpp::internal::ModuleScope _rcpp_module_scope( & _rcpp_module_##name ) ; \ - _rcpp_module_##name##_init( ) ; \ - return Rcpp::XPtr( & _rcpp_module_##name , false ) ; \ - END_RCPP \ - } \ +// the module body must be turned into R errors; it runs every time the +// module is loaded, but the body adds to the static module, so it may only +// run once +#define RCPP_MODULE(name) \ + void _rcpp_module_##name##_init() ; \ + static Rcpp::Module _rcpp_module_##name( # name ) ; \ + static bool _rcpp_module_##name##_initialized = false ; \ + extern "C" SEXP _rcpp_module_boot_##name(){ \ + BEGIN_RCPP \ + if( ! _rcpp_module_##name##_initialized ){ \ + Rcpp::internal::ModuleScope _rcpp_module_scope( & _rcpp_module_##name ) ; \ + _rcpp_module_##name##_init( ) ; \ + _rcpp_module_##name##_initialized = true ; \ + } \ + return Rcpp::XPtr( & _rcpp_module_##name , false ) ; \ + END_RCPP \ + } \ void _rcpp_module_##name##_init() // silly little dance to suppress a 'defined but not used variable' warning diff --git a/inst/tinytest/test_module.R b/inst/tinytest/test_module.R index 5b7648377..162d4d53f 100644 --- a/inst/tinytest/test_module.R +++ b/inst/tinytest/test_module.R @@ -128,3 +128,13 @@ gctorture(TRUE) res <- gadget$value() gctorture(FALSE) expect_identical( res, c(1, 2, 3) ) + +## loading a module again must not re-run its body, which used to add +## every constructor and method to the static module once more (#1527); +## this redefines the module's classes, so it comes last +is_demo_module <- function(dll) is.loaded("_rcpp_module_boot_demoModule", PACKAGE = dll[["name"]]) +dll <- Filter(is_demo_module, getLoadedDLLs())[[1L]] +mod <- Module( "demoModule", PACKAGE = dll, mustStart = TRUE ) +mod <- Module( "demoModule", PACKAGE = dll, mustStart = TRUE ) +expect_equal( length( mod$ModuleWorld@constructors ), 1L ) +expect_equal( length( mod$ModuleWorld@methods$greet@nargs ), 1L )