[Rd] eval and Calling Frames

brodie gaslam brod|e@g@@|@m @end|ng |rom y@hoo@com
Mon Jun 1 03:13:40 CEST 2020


 I ran into an interesting issue with `evalq` (and also
`eval(quote(...))`):

     f <- function() {
       list(
         sys.parent(1),
         evalq(sys.parent(1)),
         evalq((function() sys.parent(2))()),  # add an anon fun layer
         evalq((function() sys.parent(1))())
       )
     }
     res <- f()
     str(res)
     ## List of 4
     ##  $ : int 0         # sys.parent(1)
     ##  $ : int 2         # evalq(sys.parent(1))
     ##  $ : int 0         # evalq((function() sys.parent(2))())
     ##  $ : int 1         # evalq((function() sys.parent(1))())

In order of least to most surprising:

1. `sys.parent(1)` and `evalq(sys.parent(1))` are not the same
2. `evalq(sys.parent(1))` and `evalq((function() sys.parent(2))())`
   are not the same
3. `evalq((function() sys.parent(1))())` returns a lower frame number
   than `evalq(sys.parent(1))`

The root cause of this is that the `evalq` **closure** sets a context,
but then the C-level `do_eval` it invokes sets another one[1] with the
new `evalq` context as the calling frame (`cptr->sysparent`)[2].  This
then interacts with how `sys.parent` resolves parents when a target
frame appears more than once in the context stack.  `sys.parent`
returns the oldest context that matches[3], and in this case `f`'s
frame appears twice because `evalq` adds it via `do_eval`.

One option is to change what `sysparent` of the `evalq` `envir`.
For example, if we set it to be the same as it would be for commands
outside the `evalq` we get:

     str(res)
     ## List of 4
     ##  $ : int 0         # sys.parent(1)
     ##  $ : int 0         # evalq(sys.parent(1))
     ##  $ : int 0         # evalq((function() sys.parent(2))())
     ##  $ : int 1         # evalq((function() sys.parent(1))())

There is precedent for doing this in S3 generics and their methods
where method `sysparent` is set to be that of the generic.  Now
`evalq` no longer interferes with the resolution of calling frames.
It seems reasonable to set evaluation environments without affecting
what the calling frame is. Indeed that happens when we do something like
`environment(fun) <- blah` as the calling frame is unaffected when `fun` is
invoked.

I attach a patch that implements this change.  The patch is a
hack-job intended solely for illustrative purposes, though it does
pass `make check-all` on a current version of r-devel.  I also ran the
`rlang` tests as those probably push the envelope in this area.  There
only one failed with 2,613 passing.  The failed one is for a
deprecated function that was specifically checking for the repeated
`evalq` contexts[7].

I also attach a document with additional examples and commentary for
those interested.

Best,

Brodie.

PS: for a loosely related issue see #15531[8].

[1]: https://github.com/wch/r-source/blob/tags/R-4-0-0/src/main/eval.c#L3329
[2]: https://github.com/wch/r-source/blob/tags/R-4-0-0/src/main/context.c#L260
[3]: https://github.com/wch/r-source/blob/tags/R-4-0-0/src/main/context.c#L433
[4]: https://github.com/wch/r-source/blob/tags/R-4-0-0/src/main/eval.c#L1815
[5]: https://cran.r-project.org/doc/manuals/r-devel/R-ints.html#Contexts
[6]: https://bugs.r-project.org/bugzilla/show_bug.cgi?id=15531
[7]: https://github.com/r-lib/rlang/blob/v0.4.6/tests/testthat/test-retired.R#L437
[8]: https://bugs.r-project.org/bugzilla/show_bug.cgi?id=15531



-------------- next part --------------
An embedded and charset-unspecified text was scrubbed...
Name: patch.txt
URL: <https://stat.ethz.ch/pipermail/r-devel/attachments/20200601/bd8d8d21/attachment.txt>

-------------- next part --------------
An embedded and charset-unspecified text was scrubbed...
Name: details.txt
URL: <https://stat.ethz.ch/pipermail/r-devel/attachments/20200601/bd8d8d21/attachment-0001.txt>


More information about the R-devel mailing list