Skip to content

Commit e36a0b0

Browse files
committed
Give up on substitute() for dispatch args
1 parent d1caf49 commit e36a0b0

3 files changed

Lines changed: 15 additions & 26 deletions

File tree

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# S7 (development version)
22

3+
* Calling `substitute()` on a dispatched argument inside a method now returns the argument's value rather than its original expression, since dispatched arguments are evaluated before the method is called. `substitute()` continues to return the original expression for non-dispatched arguments.
34
* Errors thrown by S7 now report the function where they occurred, making it easier to track down the source of a problem (#646).
45
* `class_POSIXct` uses the `tzone` attribute (not `tz`), and allows it to be absent (#401).
56
* Base type wrappers like `class_integer` now define their constructor and validator in the S7 namespace. (#553).

src/method-dispatch.c

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,7 @@ SEXP method_call_(SEXP call_, SEXP op_, SEXP args_, SEXP env_) {
178178
SEXP mcall = PROTECT(Rf_lcons(R_NilValue, R_NilValue));
179179
SEXP mcall_tail = mcall;
180180

181-
PROTECT_INDEX arg_pi, val_pi;
182-
PROTECT_WITH_INDEX(R_NilValue, &arg_pi); // unnecessary, for rchk only
181+
PROTECT_INDEX val_pi;
183182
PROTECT_WITH_INDEX(R_NilValue, &val_pi); // unnecessary, for rchk only
184183

185184
// For each of the arguments to the generic
@@ -189,8 +188,6 @@ SEXP method_call_(SEXP call_, SEXP op_, SEXP args_, SEXP env_) {
189188

190189
if (i < n_dispatch) {
191190

192-
SEXP arg = Rf_findVarInFrame(envir, name);
193-
194191
SETCADR(missing_call, name);
195192
int is_missing = Rf_asLogical(Rf_eval(missing_call, envir));
196193

@@ -201,37 +198,25 @@ SEXP method_call_(SEXP call_, SEXP op_, SEXP args_, SEXP env_) {
201198

202199
} else { // arg not missing, is a PROMSXP
203200

204-
// Force the promise so we can look up its class.
205-
// However, we preserve and pass along the promise itself so that
206-
// methods can still call substitute()
207-
// Instead of Rf_eval(arg, R_EmptyEnv), we do Rf_eval(name, envir), so that
208-
// - if TYPEOF(arg) == LANGSXP or SYMSXP, arg doesn't need to be enquoted and
209-
// - if TYPEOF(arg) == PROMSXP, arg is updated in place.
210-
REPROTECT(arg, arg_pi); // unnecessary, for rchk only
201+
// Force the promise so we can look up its class, then pass along the
202+
// forced value (enquoted if necessary so it isn't re-evaluated). We
203+
// don't pass along the promise itself, so substitute() in a method
204+
// sees the value of a dispatched argument, not its original call.
211205
SEXP val = Rf_eval(name, envir);
212206
REPROTECT(val, val_pi); // unnecessary, for rchk only
213207

214208
if (Rf_inherits(val, "S7_super")) {
215209

216-
217210
// Put the super() stored value into the method call.
218-
// Note: This means we don't pass along the arg PROMSXP, meaning that
219-
// substitute() in methods does not retrieve the `super()` call.
220-
// If we wanted substitute() to work here too, we could do:
221-
// if (TYPEOF(arg) == PROMSXP) { SET_PRVALUE(arg, true_val); } else { arg = true_val; }
222-
SEXP arg = VECTOR_ELT(val, 0); // true_val used for dispatch
223-
APPEND_NODE(mcall_tail, name, arg);
211+
SEXP true_val = VECTOR_ELT(val, 0); // true_val used for dispatch
212+
APPEND_NODE(mcall_tail, name, maybe_enquote(true_val));
224213

225214
// Put the super() stored class dispatch vector into dispatch_classes
226215
SET_VECTOR_ELT(dispatch_classes, i, VECTOR_ELT(val, 1));
227216

228217
} else { // val is not a S7_super, a regular value
229218

230-
// The PROMSXP arg will have been updated in place by Rf_eval() above.
231-
// Add to arguments of method call
232-
APPEND_NODE(mcall_tail, name, arg);
233-
234-
// Determine class string to use for method look up
219+
APPEND_NODE(mcall_tail, name, maybe_enquote(val));
235220
SET_VECTOR_ELT(dispatch_classes, i, S7_obj_dispatch(val));
236221
}
237222
}
@@ -268,6 +253,6 @@ SEXP method_call_(SEXP call_, SEXP op_, SEXP args_, SEXP env_) {
268253
SETCAR(mcall, method_name);
269254

270255
SEXP out = Rf_eval(mcall, envir);
271-
UNPROTECT(4);
256+
UNPROTECT(3);
272257
return out;
273258
}

tests/testthat/test-method-dispatch.R

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,13 @@ describe("multiple dispatch", {
6262
})
6363

6464

65-
test_that("can substitute() args", {
65+
test_that("can substitute() non-dispatch args", {
6666
foo <- new_generic("foo", "x", function(x, ..., z = 1) S7_dispatch())
67+
68+
# Dispatched args are forced before the method is called, so substitute()
69+
# sees their value rather than the original expression.
6770
method(foo, class_character) <- function(x, ..., z = 1) substitute(x)
68-
expect_equal(foo(letters), quote(letters))
71+
expect_equal(foo(letters), letters)
6972

7073
suppressMessages(
7174
method(foo, class_character) <- function(x, ..., z = 1, y) substitute(y)

0 commit comments

Comments
 (0)