Let's run our prior example with more supporting data (pre-patch). We use a single `evalq` so that the frames we will inspect now are consistent: f <- function() { c(list(sys.parent(1)), evalq( list( sys.parent(1), (function() sys.parent(2))(), (function() sys.parent(1))(), status=list( sys.funs=sapply(sys.calls(), '[[', 1), sys.frames=sys.frames(), sys.parents=sys.parents() ) ) ) ) } res <- f() First, the output of the various `sys.parent` which is unchanged: str(res[1:4]) ## 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))()) And the additional data we recorded: str(res[['status']]) ## List of 3 ## $ sys.funs :List of 3 ## ..$ : symbol f ## ..$ : symbol evalq ## ..$ : symbol evalq ## $ sys.frames :Dotted pair list of 3 ## ..$ : # <<< Original ## ..$ : ## ..$ : # <<< Duplicate ## $ sys.parents: int [1:3] 0 1 2 Notice how `f`'s evaluation environment (<0x7f962b9c78f0>) shows up twice, first as itself at position 1 in the frame stack, and then again at position 3 because we `evalq`ed in that environment. The reason `(f() sys.parent(1))()` returns the evaluation environment of `f`, frame #1, and by extension `(f() sys.parent(2))()` returns frame #0 (i.e. global env) is because `R_sysparent` looks for every frame (`cptr->cloenv`) in the context stack that matches the `cptr->sysparent` frame, **and** returns the oldest one[1]. The `cptr->sysparent` for the anonymous function in `(f() sys.parent(1))` is frame #3 (<0x7f962b9c78f0>), but `sys.parent` finds the earlier reference to it at position #1, and returns that. The `sysparent` for `sys.parent(1)` is instead frame #2 (<0x7f962b9c7bc8>), the `evalq` closure evaluation environment. Since that is unique `sys.parent` returns that frame number. That this happens seems like an unfortunate implementation detail (e.g. it would not happen if `evalq` where a special). If that frame were not eligible to be returned there would be no inconsistency. Are there situations where retrieving that particular frame is desirable? I'm having a hard time thinking of any. If we use a new frame for the `envir` of `evalq` we get: f <- function() { e <- new.env() list( sys.parent(1), evalq(sys.parent(1), e), evalq((function() sys.parent(2))(), e), evalq((function() sys.parent(1))(), e) ) } res <- f() str(res) ## List of 4 ## $ : int 0 # sys.parent(1) ## $ : int 2 # evalq(sys.parent(1), e) ## $ : int 2 # evalq((function() sys.parent(2))(), e) ## $ : int 3 # evalq((function() sys.parent(1))(), e) At least the last three are now internally consistent, but two of them point to the `evalq` closure frame, which is not particularly useful. With the patch applied we get: ## List of 4 ## $ : int 0 # sys.parent(1) ## $ : int 0 # evalq(sys.parent(1), e) ## $ : int 0 # evalq((function() sys.parent(2))(), e) ## $ : int 3 # evalq((function() sys.parent(1))(), e) The last is the new environment we created. This makes sense as that is the environment that `(function() sys.parent(1))` is called in. We'll run a few more tests with and without the patch applied. First the original example, but with an additional layer added to remove the global environment from the results as that is the fallback frame which could be hiding bugs: 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))()) ) } g <- function() f() res <- g() str(res) ## R.4.0.1 (beta) With Patch ## List of 4 List of 4 ## $ : int 1 $ : int 1 ## $ : int 3 $ : int 1 ## $ : int 1 $ : int 1 ## $ : int 2 $ : int 2 Now explicitly setting an evaluation environment: f <- function() { e <- new.env() list( sys.parent(1), evalq(sys.parent(1), e), evalq((function() sys.parent(2))(), e), evalq((function() sys.parent(1))(), e) ) } res <- g() str(res) ## R.4.0.1 (beta) With Patch ## List of 4 List of 4 ## $ : int 1 $ : int 1 ## $ : int 3 $ : int 1 ## $ : int 3 $ : int 1 ## $ : int 4 $ : int 4 Setting an evaluation environment that already exists in the context stack. This triggers the `sys.parent` behavior of finding the earliest instance of the `cptr->sysparent` frame in the stack: f <- function(e) { list( sys.parent(1), evalq(sys.parent(1), e), evalq((function() sys.parent(2))(), e), evalq((function() sys.parent(1))(), e) ) } g <- function(e) f(e) h <- function() g(environment()) i <- function() h() res <- i() str(res) ## R.4.0.1 (beta) With Patch ## List of 4 List of 4 ## $ : int 3 $ : int 3 ## $ : int 5 $ : int 3 ## $ : int 1 $ : int 1 ## $ : int 2 $ : int 2 f <- function(e) { list( sys.parent(1), evalq(sys.parent(1), e), evalq((function() sys.parent(2))(), e), evalq((function() sys.parent(1))(), e), evalq(sys.frames(), e) ) } g <- function(e) f(e) h <- function() g(environment()) i <- function() h() res <- i() The seemingly surprising one here is that `evalq((function() sys.parent(1))(), e)` returns an earlier frame than the first two. This happens because the `sys.parent(1)` inside the anonymous function resolves to `e`, and `e` shows up both as the `evalq` `envir` and as the frame of `h` (frame #2). Again, `sys.parent` returns the oldest frame that matches the `cptr->sysparent` of the frame it is evaluated in, so it returns frame #2 instead of the frame set by `do_eval`. The first and second expressions return the calling frame of `f`, which is `g`'s evaluation environment. Since that environment is unique on the stack `sys.parent` stops there. The patch I propose prevents `evalq` (and `eval`) from affecting what the calling frame is. This makes sense to me because `evalq` is not a function call. Internally though R explicitly sets function contexts for `evalq`, as demonstrated by the fact that it's possible to set `on.exit` and `return` that don't affect the environment the `evalq` call is evaluated in. While it is useful to be able to set `on.exit` on e.g. a `local` call, this is an undocumented implementation. Given that this is undocumented it doesn't seem sufficient to require that `evalq` and `eval` affect the calling frame. [1]: https://github.com/wch/r-source/blob/tags/R-4-0-0/src/main/context.c#L433