diff --git a/NEWS.md b/NEWS.md index bd78b63997..53bb9c9963 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # CHANGES IN knitr VERSION 1.17 (unreleased) +## MAJOR CHANGES + +- the automatic detection of chunk dependencies (via the chunk option `autodep = TRUE`) is more conservative by default now; chunk B depends on chunk A if _any_ variables in B are created in A, no matter if these variables are local or global in B; you can use the chunk option `cache.globals` to manually provide a vector of variable names that should be considered "global" to avoid the dependency when local variables in B are also found in A (thanks, @knokknok, #1403) + ## BUG FIXES - when a table only has one column, `kable()` does not work in R Markdown documents (thanks, @expersso, https://github.com/yihui/printr/issues/31) diff --git a/R/block.R b/R/block.R index 65ceaa48fe..a5982a28a8 100644 --- a/R/block.R +++ b/R/block.R @@ -259,7 +259,11 @@ block_exec = function(options) { # make sure all objects to be saved exist in env objs = intersect(c(objs, obj.new), ls(env, all.names = TRUE)) if (options$autodep) { - cache$objects(objs, code, options$label, options$cache.path) + # you shall manually specify global object names if find_symbols() is not reliable + cache$objects( + objs, options$cache.globals %n% find_symbols(code), options$label, + options$cache.path + ) dep_auto() } if (options$cache < 3) { diff --git a/R/cache.R b/R/cache.R index e6d3a42ed9..a9db786bcd 100644 --- a/R/cache.R +++ b/R/cache.R @@ -48,10 +48,9 @@ new_cache = function() { } else lines = x writeLines(lines, con = path) } - cache_objects = function(keys, code, label, path) { + cache_objects = function(keys, globals, label, path) { save_objects(keys, label, valid_path(path, '__objects')) - # find globals in code; may not be reliable - save_objects(find_globals(code), label, valid_path(path, '__globals')) + save_objects(globals, label, valid_path(path, '__globals')) } cache_load = function(hash, lazy = TRUE) { @@ -116,6 +115,14 @@ known_globals = c( '{', '[', '(', ':', '<-', '=', '+', '-', '*', '/', '%%', '%/%', '%*%', '%o%', '%in%' ) +# analyze code and find out all possible variables (not necessarily global variables) +find_symbols = function(code) { + if (is.null(code) || length(p <- parse(text = code, keep.source = TRUE)) == 0) return() + p = getParseData(p) + p = p[p$terminal & p$token %in% c('SYMBOL', 'SYMBOL_FUNCTION_CALL', 'SPECIAL'), ] + unique(p$text) +} + # a variable name to store the metadata object from code chunks cache_meta_name = function(hash) sprintf('.%s_meta', hash) # a variable name to store the text output of code chunks diff --git a/tests/testit/test-cache.R b/tests/testit/test-cache.R index 36a557071a..372f09ae3a 100644 --- a/tests/testit/test-cache.R +++ b/tests/testit/test-cache.R @@ -13,6 +13,11 @@ assert( identical(find_globals(c('a=1%*%1%o%2 %in% d', 'b=d%%10+3%/%2-z[1:3]')), c('d', 'z')) ) +assert( + 'find_symbols() identifies all symbols', + find_symbols('x = x + 1; rnorm(1, std = z)') %==% c('x', 'rnorm', 'z') +) + knit_lazy = function(lazy = TRUE) { in_dir(tempdir(), { txt = c(sprintf('```{r test, cache=TRUE, cache.lazy=%s}', lazy),