stackcallr inspects the call tree of an R function without running it. Point it at a function name and it statically resolves every function that function calls, the functions those call, and so on, including S3 methods reached through UseMethod() dispatch.
Installation
stackcallr isn’t on CRAN. Install the development version from GitHub:
# install.packages("pak")
pak::pak("mjfrigaard/stackcallr")Usage
call_stack() takes the name of a function as a string, bare or namespaced, and returns the tree of functions it calls. The example below stops two levels down:
library(stackcallr)
call_stack("stats::sd", max_depth = 2)
#> █─sd
#> ├─as.double
#> ├─█─is.factor
#> │ └─inherits
#> ├─█─is.vector
#> │ └─.Internal
#> ├─sqrt
#> └─█─var
#> ├─.Call
#> ├─as.matrix
#> ├─c
#> ├─is.atomic
#> ├─is.data.frame
#> ├─is.na
#> ├─is.null
#> ├─missing
#> ├─pmatch
#> ├─stop
#> └─stopifnotFor a generic, call_stack() resolves UseMethod() dispatch into the actual registered methods, rather than stopping at UseMethod itself. Every mean.* node below is a real method, ready to be expanded further with a larger max_depth:
call_stack("mean", max_depth = 1)
#> █─mean
#> ├─mean.Date
#> ├─mean.default
#> ├─mean.difftime
#> ├─mean.POSIXct
#> ├─mean.POSIXlt
#> └─mean.quosureRecursion depth is controlled with max_depth (default Inf, which follows every call down to primitives or a detected cycle).
Shiny module trees
call_tree_dir() and call_tree_app() build the same kind of tree for a Shiny app, either from a directory of .R files on disk or from a live shiny.appobj. Both take a modules_only argument that prunes the tree down to functions calling NS()/moduleServer(), splicing out plain wrapper functions so real modules underneath them stay visible:
app_dir <- tempfile("app")
dir.create(app_dir)
writeLines(
'launch <- function() { shinyApp(ui = app_ui(), server = app_server) }',
file.path(app_dir, "launch.R")
)
writeLines('app_ui <- function() { mod_a_ui("a") }', file.path(app_dir, "app_ui.R"))
writeLines(
'app_server <- function(input, output, session) { mod_a_server("a") }',
file.path(app_dir, "app_server.R")
)
writeLines('mod_a_ui <- function(id) { ns <- NS(id) }', file.path(app_dir, "mod_a_ui.R"))
writeLines(
'mod_a_server <- function(id) { moduleServer(id, function(input, output, session) {}) }',
file.path(app_dir, "mod_a_server.R")
)
call_tree_dir(app_dir, root = "launch", modules_only = TRUE)
#> █─launch
#> ├─mod_a_ui
#> └─mod_a_serverNeither function requires shiny to be installed for stackcallr itself to work; shiny is in Suggests, needed only if you’re building a shiny.appobj to pass in.
Functions
-
call_stack(fn, max_depth = Inf)— the exported entry point. Resolvesfnto a function, then recursively walks its body viacodetools::findGlobals(), following S3 dispatch where it findsUseMethod()calls. -
call_tree_dir(path, root, modules_only = FALSE)— builds the call tree of every function defined in a directory of.Rfiles, parsed without loading or running any of it. -
call_tree_app(app, app_fun, ui_fun, server_fun, modules_only = FALSE)— builds the call tree of a running Shiny app by inspecting ashiny.appobj’s server function and its enclosing environment. -
print.call_stack()— renders the tree returned by any of the three, in the style oflobstr::ast().
See vignette("stackcallr") for how call_stack() resolves a function, and vignette("shiny-modules") for call_tree_dir() and call_tree_app().