Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
2026-10-29 Kevin Ushey <kevinushey@gmail.com>

* 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 <kevinushey@gmail.com>

* inst/include/Rcpp/Module.h (RCPP_MODULE): Wrap the module boot
Expand Down
28 changes: 17 additions & 11 deletions inst/include/Rcpp/Module.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>( & _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>( & _rcpp_module_##name , false ) ; \
END_RCPP \
} \
void _rcpp_module_##name##_init()

// silly little dance to suppress a 'defined but not used variable' warning
Expand Down
10 changes: 10 additions & 0 deletions inst/tinytest/test_module.R
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
Loading