Overview

This package was developed due to behavior of match.call in some very specific corner cases:

This issue cropped up when trying to match calls that happened earlier in the dynamic call stack to the call to match.call (e.g. the grand-parent call).

An Example of the Problem

fun0 <- function(...) {
  fun_gpar <- function(b, ...) {
    fun_par <- function(a)
      match.call(fun_gpar, sys.call(sys.parent()), expand.dots=FALSE)
    fun_par()
  }
  fun_gpar(...)
}
fun0(999, 2 + 2, 3 * pi(), 4)

Notice how:

Also, note that sys.call(sys.parent()) is not the same as the default value for argument call of match.call even though it looks like it is. The difference is in the example above, the expression is evaluated in the parent context, whereas the default call value is evaluated within match.call's context.

Source of Problem

The reason match.call doesn't work properly in the aforementioned circumstances is that match.call always uses the lexical stack of the function that invokes match.call to substitute the ... argument, irrespective of where the call you are attempting to match is issued. In our simple example, match.call will be matching the dots in the lexical parent of fun_par, instead of in the calling frame of fun_gpar. In this case, match.call is matching to the dots in fun_gpar <- function(b, ...) { on the second line instead of the dots in fun_gpar(...) on the 7th line. These look similar, but are really completely different.

This mismatch causes the problems described above. Since fun_gpar has a formal argument b in addition to ..., when we invoke fun_gpar(...) the actual ... argument inside fun_gpar will be the original ... argument passed to fun_gpar, less the explicit formal arguments that are matched. In this case, b positionally matches the 999, so the ... inside fun_gpar no longer contains 999, but match.call uses this 999-less ... to match the fun_gpar(...) call, not the original ... in the dynamic parent frame parent of fun_gpar.

This behavior is not unreasonable since match.call doesn't provide any way to give it the correct frame for substituting the dots in.

In addition, when substituting dots, R will replace any non-constant values with ..X, etc. Not entirely clear why this happens, but it is done in this following snippet of code in subDots at lines 1258-1268 in src/main/unique.c (R 3.0.2):

for(a = dots, b = rval, i = 1; i <= len; a = CDR(a), b = CDR(b), i++) {
  snprintf(tbuf, 10, "..%d",i);
  SET_TAG(b, TAG(a));
  t = CAR(a);
  while (TYPEOF(t) == PROMSXP)
      t = PREXPR(t);
  if( isSymbol(t) || isLanguage(t) )
      SETCAR(b, mkSYMSXP(mkChar(tbuf), R_UnboundValue));   // <--- HERE
  else
      SETCAR(b, t);
}

More Examples

Scenario Summary

Here is a comprehensive list of the scenarios that we have found to cause problems with match.call:

  1. fun_gpar must be a LEXICAL parent of fun_par otherwise we get error "... used in a situation where it does not exist"; this demonstrates that match.call descends through the lexical stack, not the dynamic stack.
  2. If fun_par has a ... in its formal definition, then none of the ... arguments of fun_gpar are captured if the call to fun_gpar doesn't also include the ... arguments. This is because we redefine ... to be the arguments to fun_par, but then our call to fun_par doesn't have any arguments, so the lexically earliest set of dots is empty.
  3. If fun_par doesn't have any formals, then match.call will grab the ... values, but with limitations: a. unnamed arguments in ... will be consumed by the named formals of fun_gpar to the extent those are not otherwise matched, but these consumed arguments will not show up in the output of match.call b. If the call to fun_gpar involves expressions or symbols, these are replaced with ..1, ..2, etc. instead of being captured properly

See below for actual code examples

Scenario 1:

fun1 <- function(a, ...) fun_gpar(a, ...)
fun_gpar <- function(b, ...) fun_par()
fun_par <- function() match.call(fun_gpar, sys.call(sys.parent()), expand.dots=F)

fun1(3, "test", x=45, zest="lemon")

Scenario 2:

fun2 <- function(a, ...) {
  fun_gpar <- function(b, ...) {
    fun_par <- function(...) match.call(fun_gpar, sys.call(sys.parent()), expand.dots=F)
    fun_par()
  }
  fun_gpar(a, ...)
}
fun2(3, "test", x=45, zest="lemon")

Scenario 3.a:

fun3 <- function(a, ...) {
  fun_gpar <- function(b, c, d, ...) {
    fun_par <- function() match.call(fun_gpar, sys.call(sys.parent()), expand.dots=F)
    fun_par()
  }
  fun_gpar(a, ...)
}
fun3(3, "test", 59, x=45, zest="lemon", 58)

We lost "test" and "59", because ... was reduced by the formals otherwise not matched (b was directly matched by a), and after reduction, it was used to fill the call.

Scenario 3.b:

fun3(3, "test", 59, x=45, zest="lemon", (58), (60))

Expressions are returned as their position in dots, since we lost the first two elements we're left with third and fourth.



brodieG/matchcall documentation built on May 13, 2019, 7:46 a.m.