imputesrcref imputes transparent srcref metadata for injected brace calls ({) in R function ASTs.
In R, a srcref is source-location metadata (line/column ranges) attached to
parsed objects when source retention is enabled (for example with
options(keep.source = TRUE)).
This package solves a finer-grained mapping problem for control-flow
expressions written without braces (for example if (x) f() else g()) and
for unbraced function-call arguments. It adds transparent wrappers and imputes
srcrefs from parse data so individual components can be mapped to source
precisely.
It uses the parser token tables getParseData for it.
Given code like:
if (x && y) f() else g()
impute_srcrefs() rewrites target positions to:
if ({x} && {y}) {f()} else {g()}
and:
g({x+1}, {f({y+1})})
and assigns srcrefs to injected { calls using parse-data-derived source spans.
impute_srcrefs(fn)
source_impute_srcrefs(file, envir = parent.frame(), ...)
impute_package_srcrefs(package, include_internal = TRUE, ...)
srcref metadata (the default for source installs); cached parse
data is no longer required.get_impute_blacklist(include_default = TRUE)
set_impute_blacklist(functions, append = TRUE)
reset_impute_blacklist()
impute_package_srcrefs() requires each function to carry a srcref
attribute. Source installs retain this by default. Binary installs often do
too, but if srcref is missing the function will be skipped (failed[i] == "no srcref"). To force retention explicitly, install from source with:
install.packages("<package>", INSTALL_opts = "--with-keep.source")
Cached parse data (--with-keep.parse.data) is no longer required —
impute_srcrefs() re-parses from the function's source lines when no parse
data is attached.
For functions that truly lack srcref (e.g. some generated closures),
options(imputesrcref.allow_deparse_fallback = TRUE) will impute against a
deparsed copy of the function instead, at the cost of source line numbers
shifting to the deparsed layout.
options(keep.source = TRUE)
f <- eval(parse(text = "function(x, y) if (x && y) f() else g()", keep.source = TRUE)[[1]])
g <- impute_srcrefs(f)
g
env <- new.env(parent = baseenv())
res <- source_impute_srcrefs("path/to/file.R", envir = env)
res$functions
res <- impute_package_srcrefs("stringr", include_internal = TRUE)
res$patched_count
head(res$failed[!is.na(res$failed)])
Returned fields are:
packagefn_namesfailed (NA means successfully patched; otherwise a short reason
such as "no srcref" or an error message)patched_countGeneric call wrapping excludes SPECIALSXP primitive names by default (from builtins()).
Add your own exclusions with:
set_impute_blacklist(c("str_c", "paste0"))
Inspect active values:
head(get_impute_blacklist())
get_impute_blacklist(include_default = FALSE)
impute_srcrefs() requires function srcref metadata by default.
For functions created without srcrefs, opt into deparse-based fallback explicitly:
options(imputesrcref.allow_deparse_fallback = TRUE)
Snapshot tests compare generated output against committed .out golden files.
They include regression coverage for:
srcref line mappings (sr[7:8] vs sr[1:3])function())
Run tests in compare mode:Rscript -e "testthat::test_dir('tests/testthat', load_package = 'source')"
Refresh snapshots:
UPDATE_SNAPSHOTS=1 Rscript -e "testthat::test_dir('tests/testthat', load_package = 'source')"
By default, mismatches fail. With UPDATE_SNAPSHOTS=1, the snapshot file is rewritten.
tests/testthat/test-package-srcref-imputation.R runs whole-package srcref
imputation over a corpus of popular packages (data.table, dplyr, fs,
ggplot2, glue, jsonlite, stringr, zoo). For each it asserts that no
function fails for an unexpected reason and that a sample of patched functions
is idempotent and srcref-text-consistent (the line-accuracy guarantee).
These tests are slow and require the corpus packages to be installed with
srcref retention, so they are gated behind FULL_TEST=1 and skip gracefully
when a package is absent or was installed without srcref.
The Makefile provides targets for this. The corpus is installed into a
separate library (CORPUS_LIB, default ~/Rlib_test) so your main library is
left untouched:
make corpus-install # install the 8 corpus packages with srcref (run once)
make test-full # run the full suite including the corpus tests
Use a different library with make test-full CORPUS_LIB=/path/to/lib. Or run
it directly:
FULL_TEST=1 Rscript -e \
".libPaths(c('~/Rlib_test', .libPaths())); testthat::test_file('tests/testthat/test-package-srcref-imputation.R')"
The package's internals are implemented in C and invoked from thin R wrappers
via .Call. Building from source requires a C toolchain (Rtools on
Windows, the standard R CMD INSTALL toolchain on macOS / Linux). No
external libraries are needed.
This package was inspired in part by covr's parse-data handling approach.