tl;dr:
Given the following code:
f1 <- function() {
1
}
f2 <- function() {
f1 <- 10
2
}
Is it possible to write a static analysis tool that can analyse the code and determine that the function f1()
and the variable f1
are different objects? I’m hoping to find a solution that can be incorporated into an R package, i.e. written in R or C/C++.
background
This is related to an old issue in a package I’m developing, see lewinfox/foodwebr/issues/2. The aim of the package is to create function dependency graphs for functions in a given environment. The current implementation creates an incorrect edge between f2()
and f1()
because it can’t work out that f1
the variable is not the same as f1()
the function.
This is one of a few attempts at this problem:
- Generating a Call Graph in R
- https://cran.r-project.org/package=mvbutils (my current implementation is stolen from this)
- https://github.com/duncantl/CodeDepends (tried reading the source code and got lost, there is a deprecation notice that points to a package that is not on CRAN)
- https://github.com/edpeyton/pkgdepR (only works with functions in packages, as far as I can tell)
- https://github.com/r-lib/lobstr (Seems like it ought to be able to do exactly what I need, I just can’t work out how to get there…)
My (mvbutils) current implementation works by converting the body()
of the function to text and matching symbols to known function names. This works 99% of the time (citation needed) but falls down in the example above.
It feels like this is a very solvable problem given R’s code introspection abilities, but I can’t figure it out. If anyone knows about this and can point me in the right direction I would be very grateful!
This reproduces the problem:
install.packages("remotes")
remotes::install_github("lewinfox/foodwebr")
library(foodwebr)
f1 <- function() {
1
}
f2 <- function() {
f1 <- 10 # This variable `f1` will be confused with the function `f1()`
2
}
# The foodweb mistakenly believes that function `f2()` calls function `f1()`
foodweb()
#> # A `foodweb`: 2 vertices and 1 edge
#> digraph 'foodweb' {
#> f1()
#> f2() -> { f1() }
#> }
The output of the foodweb()
call should be
#> # A `foodweb`: 2 vertices and 0 edges
#> digraph 'foodweb' {
#> f1()
#> f2()
#> }