Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove depency on env_unlock() #286

Merged
merged 4 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Imports:
desc,
fs,
glue,
lifecycle,
methods,
pkgbuild,
processx,
Expand Down
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# pkgload (development version)

* The `reset` argment of `load_all()` is no longer supported because preserving
the namespace requires unlocking its environment, which is no longer possible
in recent versions of R. It should no longer be necessary as the performance
issues caused by resetting the namespace were resolved a while ago.

* New experimental feature for generating a `compile_commands.json` file after
each `load_all()`. This file is used by LSP servers such as clangd to provide
intellisense features in your native files. To enable it, add this directive
Expand Down
72 changes: 39 additions & 33 deletions R/load.R
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,9 @@
#' be useful for checking for missing exports.
#'
#' @param path Path to a package, or within a package.
#' @param reset clear package environment and reset file cache before loading
#' any pieces of the package. This largely equivalent to running
#' [unload()], however the old namespaces are not completely removed and no
#' `.onUnload()` hooks are called. Use `reset = FALSE` may be faster for
#' large code bases, but is a significantly less accurate approximation.
#' @param reset `r lifecycle::badge("deprecated")` This is no longer supported
#' because preserving the namespace requires unlocking its environment, which
#' is no longer possible in recent versions of R.
#' @param compile If `TRUE` always recompiles the package; if `NA`
#' recompiles if needed (as determined by [pkgbuild::needs_compile()]);
#' if `FALSE`, never recompiles.
Expand Down Expand Up @@ -106,9 +104,6 @@
#' # Running again loads changed files
#' load_all("./")
#'
#' # With reset=TRUE, unload and reload the package for a clean start
#' load_all("./", TRUE)
#'
#' # With export_all=FALSE, only objects listed as exports in NAMESPACE
#' # are exported
#' load_all("./", export_all = FALSE)
Expand All @@ -125,6 +120,13 @@ load_all <- function(path = ".",
quiet = NULL,
recompile = FALSE,
warn_conflicts = TRUE) {
if (!isTRUE(reset)) {
lifecycle::deprecate_warn(
when = "1.3.5",
what = "load_all(reset)",
details = "`reset = FALSE` is no longer supported."
)
}

path <- pkg_path(path)
package <- pkg_name(path)
Expand Down Expand Up @@ -163,31 +165,24 @@ load_all <- function(path = ".",
}

old_methods <- list()
clear_cache()

if (reset) {
clear_cache()

# Remove package from known namespaces. We don't unload it to allow
# safe usage of dangling references.
if (is_loaded(package)) {
patch_colon(package)
# Remove package from known namespaces. We don't unload it to allow
# safe usage of dangling references.
if (is_loaded(package)) {
patch_colon(package)

methods_env <- ns_s3_methods(package)
unregister(package)
methods_env <- ns_s3_methods(package)
unregister(package)

# Save foreign methods after unregistering the package's own
# methods. We'll restore the foreign methods but let the package
# register its own methods again.
old_methods <- as.list(methods_env)
old_methods <- Filter(function(x) is_foreign_method(x, package), old_methods)
}
# Save foreign methods after unregistering the package's own
# methods. We'll restore the foreign methods but let the package
# register its own methods again.
old_methods <- as.list(methods_env)
old_methods <- Filter(function(x) is_foreign_method(x, package), old_methods)
}

if (is_loaded(package)) {
rlang::env_unlock(ns_env(package))
} else {
create_ns_env(path)
}
create_ns_env(path)

out <- list(env = ns_env(package))

Expand Down Expand Up @@ -388,11 +383,24 @@ is_loading <- function(pkg = NULL) {
}
}

# Ensure that calls to `::` resolve to the original unregistered
# namespace
# Ensure that calls to `::` resolve to the original detached namespace. It is
# uncommon to refer to functions in your own package with `::` but it sometimes
# is necessary, e.g. in standalone files.
#
# To enable this, assign `::` in your namespace, e.g.
#
# ```
# on_load(`::` <- base::`::`)
# ```
patch_colon <- function(package) {
ns <- asNamespace(package)
rlang::env_unlock(ns)

if (!env_has(ns, "::")) {
return()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like it would be useful to log this in some way, but if you think it only affects rlang, it's probably fine as is.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only used in rlang AFAIK and even there I think we ran into other issues that prevented us from using :: in some standalone files. So I prefer to keep this feature low-key, and a warning would affect all pkgload users. But you're right that if this feature does end up being needed by someone, they will have a very hard time to discover this fix. We can revisit if someone reports an issue about this.

}

rlang::env_binding_unlock(ns, "::")
on.exit(rlang::env_binding_lock(ns, "::"))

ns[["::"]] <- function(lhs, rhs) {
lhs <- as.character(substitute(lhs))
Expand All @@ -407,6 +415,4 @@ patch_colon <- function(package) {
eval(bquote(base::`::`(.(lhs), .(rhs))), baseenv())
}
}

lockEnvironment(ns)
}
11 changes: 3 additions & 8 deletions man/load_all.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion revdep/failures.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Version: NA
* GitHub: NA
* Source code: https://github.com/cran/zellkonverter
* Number of recursive dependencies: 157
* Number of recursive dependencies: 153

Run `revdepcheck::cloud_details(, "zellkonverter")` for more info

Expand Down
15 changes: 2 additions & 13 deletions tests/testthat/test-load-hooks.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ test_that("hooks called in correct order", {
c("pkg_load", "user_load", "pkg_attach", "user_attach")
)

reset_events()
load_all("testHooks", reset = FALSE)
expect_equal(globalenv()$hooks$events, character())

reset_events()
unload("testHooks")
expect_equal(globalenv()$hooks$events,
Expand Down Expand Up @@ -80,13 +76,6 @@ test_that("onLoad and onAttach", {
expect_equal(the$b, 2)
expect_equal(the$c, 1)

# ===================================================================
# Loading again without reset won't change a, b, and c in the
# namespace env, and also shouldn't trigger onload or onattach. But
# the existing namespace values will be copied over to the package
# environment
load_all("testLoadHooks", reset = FALSE)

# Shouldn't form new environments
expect_identical(nsenv, ns_env("testLoadHooks"))
expect_identical(pkgenv, pkg_env("testLoadHooks"))
Expand All @@ -96,10 +85,10 @@ test_that("onLoad and onAttach", {
expect_equal(the$c, 1)

# ===================================================================
# With reset=TRUE, there should be new package and namespace
# When loading again there should be new package and namespace
# environments, and the values should be the same as the first
# load_all.
load_all("testLoadHooks", reset = TRUE)
load_all("testLoadHooks")
nsenv2 <- ns_env("testLoadHooks")
pkgenv2 <- pkg_env("testLoadHooks")

Expand Down
2 changes: 1 addition & 1 deletion tests/testthat/test-s4-unload.R
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ test_that("loading and reloading s4 classes", {
})

# Loading again shouldn't result in any errors or warnings
expect_no_warning(load_all("testS4union", reset = FALSE) )
expect_no_warning(load_all("testS4union") )

unload("testS4union")
unloadNamespace("stats4") # This was imported by testS4union
Expand Down
Loading