Skip to content

Commit 70e50e1

Browse files
authored
Merge pull request #364 from shrektan/promise-symbol
Recognize symbols created by delayedAssign / assign / makeActiveBinding
2 parents 9344721 + d15771e commit 70e50e1

File tree

3 files changed

+105
-7
lines changed

3 files changed

+105
-7
lines changed

NEWS.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# languageserver 0.3.10
2+
3+
- Recongize promise and active binding symbols (#362)
4+
15
# languageserver 0.3.9
26

37
- skip tests on solaris
@@ -7,7 +11,7 @@
711
- When closing a file, "Problems" should be removed (#348)
812
- Implement renameProvider (#337)
913
- Hover on symbol in a function with functional argument causes parse error (#345)
10-
Hover on non-function symbol in other document should show definition and - documentation (#343)
14+
- Hover on non-function symbol in other document should show definition and - documentation (#343)
1115
- Check if symbol on rhs of assignment in definition (#341)
1216
- Implement referencesProvider (#336)
1317
- Add comment of notice above temp code of definition (#353)

R/document.R

+63-6
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,24 @@ parse_expr <- function(content, expr, env, level = 0L, srcref = attr(expr, "srcr
181181
if (length(expr) == 0L || is.symbol(expr)) {
182182
return(env)
183183
}
184+
# We should handle base function specially as users may use base::fun form
185+
# The reason that we only take care of `base` (not `utils`) is that only `base` calls can generate symbols
186+
# Check if the lang is in base::fun form
187+
is_base_call <- function(x) {
188+
length(x) == 3L && as.character(x[[1L]]) %in% c("::", ":::") && as.character(x[[2L]]) == "base"
189+
}
190+
# Be able to handle `pkg::name` case (note `::` is a function)
191+
is_symbol <- function(x) {
192+
is.symbol(x) || is_base_call(x)
193+
}
194+
# Handle `base` function specically by removing the `base::` prefix
195+
fun_string <- function(x) {
196+
if (is_base_call(x)) as.character(x[[3L]]) else as.character(x)
197+
}
184198
for (i in seq_along(expr)) {
185199
e <- expr[[i]]
186-
if (missing(e) || !is.call(e) || !is.symbol(e[[1L]])) next
187-
f <- as.character(e[[1L]])
200+
if (missing(e) || !is.call(e) || !is_symbol(e[[1L]])) next
201+
f <- fun_string(e[[1L]])
188202
cur_srcref <- if (level == 0L) srcref[[i]] else srcref
189203
if (f %in% c("{", "(")) {
190204
Recall(content, e[-1L], env, level + 1L, cur_srcref)
@@ -204,10 +218,53 @@ parse_expr <- function(content, expr, env, level = 0L, srcref = attr(expr, "srcr
204218
Recall(content, e[[3L]], env, level + 1L, cur_srcref)
205219
} else if (f == "repeat") {
206220
Recall(content, e[[2L]], env, level + 1L, cur_srcref)
207-
} else if (f %in% c("<-", "=") && length(e) == 3L && is.symbol(e[[2L]])) {
208-
symbol <- as.character(e[[2L]])
209-
value <- e[[3L]]
210-
type <- get_expr_type(value)
221+
} else if (f %in% c("<-", "=", "delayedAssign", "makeActiveBinding", "assign")) {
222+
# to see the pos/env/assign.env of assigning functions is set or not
223+
# if unset, it means using the default value, which is top-level
224+
# if set, we should compare to a vector of known "top-level" candidates
225+
is_top_level <- function(arg_env, ...) {
226+
if (is.null(arg_env)) return(TRUE)
227+
default <- list(
228+
quote(parent.frame(1)), quote(parent.frame(1L)),
229+
quote(environment()),
230+
quote(.GlobalEnv), quote(globalenv())
231+
)
232+
extra <- substitute(list(...))[-1L]
233+
top_level_envs <- c(default, as.list(extra))
234+
any(vapply(top_level_envs, identical, x = arg_env, FUN.VALUE = logical(1L)))
235+
}
236+
237+
type <- NULL
238+
239+
if (f %in% c("<-", "=")) {
240+
if (length(e) != 3L || !is.symbol(e[[2L]])) next
241+
symbol <- as.character(e[[2L]])
242+
value <- e[[3L]]
243+
} else if (f == "delayedAssign") {
244+
call <- match.call(base::delayedAssign, as.call(e))
245+
if (!is.character(call$x)) next
246+
if (!is_top_level(call$assign.env)) next
247+
symbol <- call$x
248+
value <- call$value
249+
} else if (f == "assign") {
250+
call <- match.call(base::assign, as.call(e))
251+
if (!is.character(call$x)) next
252+
if (!is_top_level(call$pos, -1L, -1)) next # -1 is the default
253+
if (!is_top_level(call$envir)) next
254+
symbol <- call$x
255+
value <- call$value
256+
} else if (f == "makeActiveBinding") {
257+
call <- match.call(base::makeActiveBinding, as.call(e))
258+
if (!is.character(call$sym)) next
259+
if (!is_top_level(call$env)) next
260+
symbol <- call$sym
261+
value <- call$fun
262+
type <- "variable"
263+
}
264+
265+
if (is.null(type)) {
266+
type <- get_expr_type(value)
267+
}
211268

212269
env$objects <- c(env$objects, symbol)
213270

tests/testthat/test-symbol.R

+37
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,43 @@ test_that("Document Symbol works", {
3838
)
3939
})
4040

41+
test_that("Recognize symbols created by delayedAssign()/assign()/makeActiveBinding()", {
42+
skip_on_cran()
43+
client <- language_client()
44+
45+
defn_file <- withr::local_tempfile(fileext = ".R")
46+
writeLines(c(
47+
"delayedAssign('d1', 1)",
48+
"delayedAssign(value = function() 2, x = 'd2')",
49+
"base::delayedAssign(value = '3', 'd3')",
50+
"delayedAssign(('d4'), 4)",
51+
"delayedAssign('d5', 5, assign.env = globalenv())",
52+
"delayedAssign('d6', 6, assign.env = emptyenv())",
53+
"delayedAssign('d7', 7, assign.env = parent.frame(1))",
54+
"makeActiveBinding('a1', function() 1, environment())",
55+
"makeActiveBinding(function() '2', sym = 'a2')",
56+
"base::makeActiveBinding(",
57+
" fun = function() stop('3'),",
58+
" sym = 'a3'",
59+
")",
60+
"makeActiveBinding(('a4'), function() 4, environment())",
61+
"makeActiveBinding('a5', function() 5, .GlobalEnv)",
62+
"makeActiveBinding('a6', function() 6, new.env())",
63+
"assign(value = '1', x = 'assign1')",
64+
"assign('assign2', 2, pos = -1L)",
65+
"assign('assign3', 3, pos = environment())",
66+
"assign('assign4', 4, pos = new.env())"
67+
), defn_file)
68+
69+
client %>% did_save(defn_file)
70+
result <- client %>% respond_document_symbol(defn_file)
71+
72+
expect_setequal(
73+
result %>% map_chr(~ .$name),
74+
c("d1", "d2", "d3", "d5", "d7", "a1", "a2", "a3", "a5", "assign1", "assign2", "assign3")
75+
)
76+
})
77+
4178
test_that("Document section symbol works", {
4279
skip_on_cran()
4380
client <- language_client(capabilities = list(

0 commit comments

Comments
 (0)