5

I want to create a function check_sym that takes another function f as argument and checks whether inside f a specific function is used.

Specifically I want to check if inside f non-standard evaluation in the form of !! sym is used.

I can do this with a simple trick that turns a functions body into a character string and then uses regex to check for "!!sym\\(".

library(dplyr)
library(purrr)
library(rlang)

check_sym <- function(f) {
  f <- as.list(body(f))
  f <- unlist(as.character(f))
  purrr::some(f, function(x) grepl("!!sym\\(", x))
}

foo <- function(df, x) { 
  select(df, !! sym(x))
}

check_sym(foo)
#> [1] TRUE

Created on 2020-02-16 by the reprex package (v0.3.0)

However, while this is possible, I am looking for a way that does not rely on character strings and regex, but rather ideally some method which looks inside the function and "sees" all function calls at a deeper level, which would be more reliable.

Any ideas appreciated.

Final solution based on accepted answer:

Based on MrFlick's answer below my actual solution is the following:

I define check_syms as:

check_sym <- function(f) {
  grepl("!!sym", paste(all.names(body(f)), collapse = ""))
}

It correctly identifies functions which make a function call to "!! sym" compared to functions that only call for example paste0("!!sym").

foo <- function(df, x) { 
  select(df, !! sym(x))
}

test_f <- function(x) {
  print(paste0("!!sym", x))
}

check_sym(foo)
#> [1] TRUE

check_sym(test_f)
#> [1] FALSE
TimTeaFan
  • 17,549
  • 4
  • 18
  • 39

2 Answers2

5

In base R, we can capture the output of body into a vector of strings and use grepl

check_sym <- function(f)  any(grepl('!!sym(', capture.output(body(f)), fixed = TRUE))

check_sym(foo)
# [1] TRUE 


some_fct <- function(x) print("sym")
check_sym(some_fct)
#[1] FALSE
akrun
  • 874,273
  • 37
  • 540
  • 662
  • Thanks for your answer. It only uses `base` R and is much less verbose. However, it still relies on regex and cannot "see" the difference between a real function call to `sym()` and the string `"sym"`. So this function `some_fct <- function(x) print("sym")` would also evaluate to `TRUE`. – TimTeaFan Feb 16 '20 at 22:18
  • @TimTeaFan updated the post, but not sure whether there are conditions that cause it to. fail – akrun Feb 16 '20 at 22:21
3

You could use the all.names function. It pulls out the names of all the variables/function defined inside the function

all.names(body(foo))
# [1] "{"      "select" "df"     "!"      "!"      "sym"   
# [7] "x"

Part of the problem with your specific example is that !!sym isn't really a simple variable. It's more like a call to !(!(sym)) to the parser. The !! stuff really isn't a special operator to the R parser, it's just two calls to the negation operator which rlang re-interprets via non-standard evaluation.

MrFlick
  • 195,160
  • 17
  • 277
  • 295
  • Thanks! This is more or less what I was looking for. Applying a regex on the output of `all.names` ensures that actually a function with this name was called. – TimTeaFan Feb 18 '20 at 09:54