-
Notifications
You must be signed in to change notification settings - Fork 49
Impement S7_user_frame() and S7_generic_call()
#681
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
hadley
wants to merge
11
commits into
main
Choose a base branch
from
frame-helpers
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
3cd3824
Impement `S7_user_frame()` and `S7_generic_call()`
hadley 94fccae
Add a `match` argument
hadley c1725c9
test: cover same-generic calls that are not super dispatches
t-kalinowski 6c0e9c1
Merge commit 'ed21054190f4d6a3deada57fcd27f2be6ba7fb25'
hadley 6ac16aa
Check for presence of `super()`
hadley c85ed46
Merge commit 'b2b4b604df32c776a9a9fca849bf60213a1ead0e'
hadley 528cc91
Use a sentinel marker for safety
hadley 19330b4
test: cover super() passed to another generic
t-kalinowski 71e4ca9
Fix method-call helper frame detection
t-kalinowski 14f4b91
Update R/method-call.R
t-kalinowski 1a29ded
Add `S7_generic_fun()`
hadley File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,146 @@ | ||
| #' Access the generic call and user frame from within a method | ||
| #' | ||
| #' @description | ||
| #' These helpers give a method stable access to two pieces of context that are | ||
| #' otherwise obscured by S7's dispatch machinery: | ||
| #' | ||
| #' * `S7_generic_call()` returns the originating call to the generic. This is | ||
| #' useful as the `call` for an error message, so that the user sees the | ||
| #' generic call (e.g. `foo(1)`) rather than S7's internal dispatch. | ||
| #' | ||
| #' * `S7_user_frame()` returns the frame from which the generic was called. | ||
| #' This is the equivalent of [parent.frame()] in an S3 method, and is | ||
| #' useful if you need non-standard evaluation. | ||
| #' | ||
| #' * `S7_generic_fun()` returns the generic function itself. This is the | ||
| #' equivalent of [sys.function()] in an S3 method, and is useful if you need | ||
| #' to inspect the generic, e.g. to retrieve its name or dispatch arguments. | ||
| #' | ||
| #' All three helpers skip intermediate frames when a method re-dispatches the | ||
| #' same generic to a superclass with [super()], reporting the outermost | ||
| #' user-facing call, its caller, and the originating generic. | ||
| #' | ||
| #' @param match Set to `TRUE` to process with [match.call()] and name all | ||
| #' arguments. | ||
| #' @returns `S7_generic_call()` returns a call; `S7_user_frame()` returns an | ||
| #' environment; `S7_generic_fun()` returns the generic function. All error if | ||
| #' called outside of a method. | ||
| #' @seealso [S7_dispatch()] for how methods are called, and [super()] for | ||
| #' superclass dispatch. | ||
| #' @export | ||
| #' @examples | ||
| #' # S7_generic_call() reports the call to the generic, skipping super(): | ||
| #' foo <- new_generic("foo", "x") | ||
| #' method(foo, class_double) <- function(x) { | ||
| #' S7_generic_call() | ||
| #' } | ||
| #' | ||
| #' Number <- new_class("Number", parent = class_double) | ||
| #' method(foo, Number) <- function(x) { | ||
| #' foo(super(x, class_double)) | ||
| #' } | ||
| #' | ||
| #' foo(Number(1)) | ||
| #' | ||
| #' # S7_user_frame() supplies the enclosing environment for non-standard | ||
| #' # evaluation, so an expression can mix columns of the data with variables | ||
| #' # from where the generic was called, like subset(): | ||
| #' keep_rows <- new_generic("keep_rows", "data") | ||
| #' method(keep_rows, class_data.frame) <- function(data, condition) { | ||
| #' rows <- eval(substitute(condition), data, S7_user_frame()) | ||
| #' data[rows, , drop = FALSE] | ||
| #' } | ||
| #' | ||
| #' threshold <- 4 | ||
| #' df <- data.frame(x = 1:3, y = c(2, 5, 8)) | ||
| #' # `x` and `y` come from the data frame; `threshold` from this frame | ||
| #' keep_rows(df, x + y > threshold) | ||
| #' | ||
| #' # S7_generic_fun() returns the generic itself, e.g. to use its name in a | ||
| #' # message: | ||
| #' bar <- new_generic("bar", "x") | ||
| #' method(bar, class_double) <- function(x) { | ||
| #' generic <- S7_generic_fun() | ||
| #' paste0("Called ", generic@name, "()") | ||
| #' } | ||
| #' bar(1) | ||
| S7_generic_call <- function(match = FALSE) { | ||
| idx <- generic_call_frame() | ||
| call <- sys.call(idx) | ||
| if (isTRUE(match)) { | ||
| call <- match.call(sys.function(idx), call, envir = sys.frame(idx)) | ||
| } | ||
| call | ||
| } | ||
|
|
||
| #' @rdname S7_generic_call | ||
| #' @export | ||
| S7_user_frame <- function() { | ||
| frame <- generic_call_frame() | ||
| sys.frame(sys.parents()[frame]) | ||
| } | ||
|
|
||
| #' @rdname S7_generic_call | ||
| #' @export | ||
| S7_generic_fun <- function() { | ||
| frame <- generic_call_frame() | ||
| sys.function(frame) | ||
| } | ||
|
|
||
| generic_call_frame <- function(call = sys.call(-1L)) { | ||
| parents <- sys.parents() | ||
|
|
||
| method_frame <- active_method_frame(call) | ||
| frame <- parent_generic_frame(method_frame, parents) | ||
| if (is.na(frame)) { | ||
| stop2("Must be called from within a method.", call = call) | ||
| } | ||
|
|
||
| # Walk past same-generic super() re-dispatches. | ||
| repeat { | ||
| if (!is_super_dispatch(frame)) { | ||
| break | ||
| } | ||
|
|
||
| parent <- parent_generic_frame(frame, parents) | ||
| if ( | ||
| is.na(parent) || !identical(sys.function(parent), sys.function(frame)) | ||
| ) { | ||
| break | ||
| } | ||
| frame <- parent | ||
| } | ||
|
|
||
| frame | ||
| } | ||
|
|
||
| active_method_frame <- function(call) { | ||
| for (i in rev(seq_len(sys.nframe() - 1L))) { | ||
| if (inherits(sys.function(i), "S7_method")) { | ||
| return(i) | ||
| } | ||
| if (inherits(sys.function(i), "S7_generic")) { | ||
| break | ||
| } | ||
| } | ||
|
|
||
| stop2("Must be called from within a method.", call = call) | ||
| } | ||
|
|
||
| parent_generic_frame <- function(frame, parents) { | ||
| parent <- parents[[frame]] | ||
| while (parent > 0L) { | ||
| if (inherits(sys.function(parent), "S7_generic")) { | ||
| return(parent) | ||
| } | ||
| parent <- parents[[parent]] | ||
| } | ||
|
|
||
| NA_integer_ | ||
| } | ||
|
|
||
| # S7_dispatch() marks the generic's frame with `_dispatched_super` when it | ||
| # unwraps a super() object. | ||
| is_super_dispatch <- function(i) { | ||
| exists("_dispatched_super", envir = sys.frame(i), inherits = FALSE) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,6 +34,7 @@ reference: | |
| - method_explain | ||
| - S7_class | ||
| - S7_class_desc | ||
| - S7_generic_call | ||
|
|
||
| - title: Packages | ||
| desc: > | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # helpers error when called outside a method | ||
|
|
||
| Code | ||
| S7_generic_call() | ||
| Condition | ||
| Error in `S7_generic_call()`: | ||
| ! Must be called from within a method. | ||
| Code | ||
| S7_user_frame() | ||
| Condition | ||
| Error in `S7_user_frame()`: | ||
| ! Must be called from within a method. | ||
| Code | ||
| S7_generic_fun() | ||
| Condition | ||
| Error in `S7_generic_fun()`: | ||
| ! Must be called from within a method. | ||
|
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I love this approach, but it feels safer than either trying to inspect the generic call for calls or
super(), or evaluating each of the arguments to the generic to see if they have classS7_super.