Skip to content

PRL-PRG/imputesrcref

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

imputesrcref

imputesrcref imputes transparent srcref metadata for injected brace calls ({) in R function ASTs.

srcref primer

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.

What it does

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.

Exported API

  • impute_srcrefs(fn)
    • Impute missing control-flow/function-call braces and transparent srcrefs for one function.
  • source_impute_srcrefs(file, envir = parent.frame(), ...)
    • Source an R file and patch all changed/new functions in the target environment.
  • impute_package_srcrefs(package, include_internal = TRUE, ...)
    • Patch package namespace functions. Works for any package whose functions retain srcref metadata (the default for source installs); cached parse data is no longer required.
  • get_impute_blacklist(include_default = TRUE)
    • Inspect call names excluded from generic argument wrapping.
  • set_impute_blacklist(functions, append = TRUE)
    • Add or replace user blacklist entries.
  • reset_impute_blacklist()
    • Clear user blacklist entries.

Installed packages: srcref retention

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.

Usage

Patch functions

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

Source a file and patch all loaded functions

env <- new.env(parent = baseenv())
res <- source_impute_srcrefs("path/to/file.R", envir = env)
res$functions

Patch an installed package

res <- impute_package_srcrefs("stringr", include_internal = TRUE)
res$patched_count
head(res$failed[!is.na(res$failed)])

Returned fields are:

  • package
  • fn_names
  • failed (NA means successfully patched; otherwise a short reason such as "no srcref" or an error message)
  • patched_count

Blacklist API

Generic 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)

Functions without srcref metadata

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)

Tests

Snapshot tests compare generated output against committed .out golden files. They include regression coverage for:

  • package-style srcref line mappings (sr[7:8] vs sr[1:3])
  • zero-formals functions (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.

Package corpus tests

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')"

Implementation

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.

Acknowledgements

This package was inspired in part by covr's parse-data handling approach.

About

No description, website, or topics provided.

Resources

License

Unknown, MIT licenses found

Licenses found

Unknown
LICENSE
MIT
LICENSE.md

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors