Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion R/class.R
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ new_object <- function(.parent, ...) {
stop(msg)
}

args <- list(...)
args <- list2(...)
nms <- names(args)

# TODO: Some type checking on `.parent`?
Expand Down
3 changes: 3 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ modify_list <- function (x, new_vals) {
x
}

list2 <- function(...)
.Call(collect_dots_skip_missing_, environment(), substitute(list(...)))


# For older versions of R ----------------------------------------------------
deparse1 <- function(expr, collapse = " ", width.cutoff = 500L, ...) {
Expand Down
2 changes: 2 additions & 0 deletions src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ extern SEXP S7_class_(SEXP, SEXP);
extern SEXP S7_object_(void);
extern SEXP prop_(SEXP, SEXP);
extern SEXP prop_set_(SEXP, SEXP, SEXP, SEXP);
extern SEXP collect_dots_skip_missing_(SEXP, SEXP);

static const R_CallMethodDef CallEntries[] = {
{"method_", (DL_FUNC) &method_, 4},
{"method_call_", (DL_FUNC) &method_call_, 3},
{"S7_object_", (DL_FUNC) &S7_object_, 0},
{"prop_", (DL_FUNC) &prop_, 2},
{"prop_set_", (DL_FUNC) &prop_set_, 4},
{"collect_dots_skip_missing_", (DL_FUNC) &collect_dots_skip_missing_, 2},
{NULL, NULL, 0}
};

Expand Down
56 changes: 56 additions & 0 deletions src/object.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#define R_NO_REMAP
#include <R.h>
#include <Rinternals.h>

SEXP collect_dots_skip_missing_(SEXP env, SEXP list_dddExprs_call) {
// This function is equivalent to `base::list(...)`, except it
// silently skips missing arguments. Ideally we could iterate
// over the DOTSXP list of promises directly, but there is currently
// no non-"non-API" way to do this. So we use `base::missing(..i)` to
// test for missingness, and use `substitute(list(...))` to get the
// promise expressions.
static SEXP missing_call = NULL;
if (missing_call == NULL) {
SEXP missing_fun = Rf_eval(Rf_install("missing"), R_BaseEnv);
missing_call = Rf_lang2(missing_fun, R_NilValue);
R_PreserveObject(missing_call);
}
// 14 = 2 for ".." + up to 10 digit number + '\0' + 1 extra for safety
static char ddi_buf[14] = "..";
static char *i_buf = ddi_buf + 2;
ddi_buf[13] = '\0'; // Technically not necessary, but just to be safe

PROTECT_INDEX pi;
PROTECT_WITH_INDEX(R_NilValue, &pi);

{
unsigned int i = 1;
SEXP prev_node = list_dddExprs_call;
SEXP ddExpr_node = CDR(list_dddExprs_call);
for (; ddExpr_node != R_NilValue; i++) {
snprintf(i_buf, sizeof(ddi_buf) - 2, "%u", i);
SEXP ddSym = Rf_install(ddi_buf);
SETCADR(missing_call, ddSym);
SEXP is_missing = Rf_eval(missing_call, env);
REPROTECT(is_missing, pi);

if (Rf_asLogical(is_missing)) {
ddExpr_node = CDR(ddExpr_node);
SETCDR(prev_node, ddExpr_node);
} else {
if (TAG(ddExpr_node) == R_NilValue) {
SEXP val_expr = CAR(ddExpr_node);
if (TYPEOF(val_expr) == SYMSXP) {
SET_TAG(ddExpr_node, val_expr);
}
}
SETCAR(ddExpr_node, ddSym);
prev_node = ddExpr_node;
ddExpr_node = CDR(ddExpr_node);
}
}
}

UNPROTECT(1); // is_missing
return Rf_eval(list_dddExprs_call, env);
}
4 changes: 2 additions & 2 deletions tests/testthat/test-constructor.R
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,8 @@ test_that("can create constructors with missing or lazy defaults", {
birthdate = Sys.Date()
))) # no age

expect_error(Person(), 'argument "first_name" is missing, with no default')
expect_error(Person("Alice"), 'argument "last_name" is missing, with no default')
expect_error(Person(), "@first_name")
expect_error(Person("Alice"), "@last_name")

p <- Person("Alice", ,"Smith")

Expand Down