Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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
4 changes: 2 additions & 2 deletions crates/ark/src/lsp/completions/completion_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

use std::fs::DirEntry;

use harp::environment::r_env_parent;
use harp::r_symbol;
use harp::syntax::is_valid_symbol;
use harp::syntax::sym_quote;
Expand All @@ -19,7 +20,6 @@ use harp::utils::r_typeof;
use libr::R_UnboundValue;
use libr::Rf_findVarInFrame;
use libr::Rf_isFunction;
use libr::ENCLOS;
use libr::PROMSXP;
use libr::PRVALUE;
use libr::SEXP;
Expand Down Expand Up @@ -480,7 +480,7 @@ pub(super) unsafe fn completion_item_from_namespace(
};

// Otherwise, try the imports environment.
let imports = ENCLOS(namespace);
let imports = r_env_parent(namespace);
let error_imports = match completion_item_from_symbol(
name,
imports,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//
//

use harp::environment::r_env_parent;
use harp::exec::RFunction;
use harp::exec::RFunctionExt;
use harp::utils::r_env_is_pkg_env;
Expand All @@ -14,7 +15,6 @@ use harp::vector::Vector;
use harp::RObject;
use libr::R_EmptyEnv;
use libr::R_lsInternal;
use libr::ENCLOS;
use tower_lsp::lsp_types::CompletionItem;

use crate::console;
Expand Down Expand Up @@ -113,7 +113,7 @@ fn completions_from_search_path(
}

// Get the next environment.
env = ENCLOS(env);
env = r_env_parent(env);
}

// Include installed packages as well.
Expand Down
28 changes: 14 additions & 14 deletions crates/ark/src/modules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
//

use anyhow::anyhow;
use harp::environment::Environment;
use harp::environment::R_ENVS;
use harp::exec::RFunction;
use harp::exec::RFunctionExt;
Expand Down Expand Up @@ -92,12 +91,6 @@ pub fn initialize() -> anyhow::Result<RObject> {
Ok(harp::source_str_in(source, namespace.sexp)?)
})?;

// Lock the environment. It will be unlocked automatically when updating.
// Needs to happen after the `r_source_in()` above. We don't lock the
// bindings to make it easy to make updates by `source()`ing inside the
// temporarily unlocked environment.
Environment::view(namespace.sexp).lock(false);

// Load the positron and rstudio namespaces and their exported functions
for file in PositronModuleAsset::iter() {
source_asset::<PositronModuleAsset>(&file, "import_positron", namespace.sexp)?;
Expand Down Expand Up @@ -158,6 +151,11 @@ pub fn initialize() -> anyhow::Result<RObject> {
let init = RFunction::from("initialize_errors");
unsafe { libr::Rf_eval(init.call.build().sexp, namespace.sexp) };

// Lock all module environments now that loading is complete. In debug
// builds we skip the lock so modules can be hot-reloaded by the watcher.
#[cfg(not(debug_assertions))]
RFunction::from("lock_environments").call_in(namespace.sexp)?;

return Ok(namespace);
}

Expand Down Expand Up @@ -328,18 +326,18 @@ pub extern "C-unwind" fn ark_log_error(msg: SEXP) -> harp::error::Result<SEXP> {
#[cfg(test)]
mod tests {
use harp::environment::Environment;
use libr::CLOENV;
use harp::fn_env;

use crate::r_task;

fn get_namespace(exports: Environment, fun: &str) -> Environment {
let fun = exports.find(fun).unwrap();
let ns = unsafe { CLOENV(fun) };
let ns = fn_env(fun);
Environment::view(ns)
}

#[test]
fn test_environments_are_locked() {
fn test_environments_are_not_locked_in_debug() {
r_task(|| {
let positron_exports =
harp::parse_eval_base("as.environment('tools:positron')").unwrap();
Expand All @@ -348,14 +346,16 @@ mod tests {
let positron_exports = Environment::new(positron_exports);
let rstudio_exports = Environment::new(rstudio_exports);

assert!(positron_exports.is_locked());
assert!(rstudio_exports.is_locked());
// Environments are only locked in release builds. In debug
// builds they stay unlocked for hot-reloading.
assert!(!positron_exports.is_locked());
assert!(!rstudio_exports.is_locked());

let positron_ns = get_namespace(positron_exports, ".ps.ark.version");
let rstudio_ns = get_namespace(rstudio_exports, ".rs.api.versionInfo");

assert!(positron_ns.is_locked());
assert!(rstudio_ns.is_locked());
assert!(!positron_ns.is_locked());
assert!(!rstudio_ns.is_locked());
})
}
}
31 changes: 7 additions & 24 deletions crates/ark/src/modules/positron/init.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@ import_positron <- function(exprs) {

# Namespace is created by the sourcer of this file
ns <- parent.env(environment())
local_unlock(ns)

source(exprs = exprs, local = ns)
export(exprs, from = ns, to = as.environment("tools:positron"))
}

import_positron_path <- function(path) {
ns <- parent.env(environment())
local_unlock(ns)

source(path, local = ns)
export_path(path, from = ns, to = as.environment("tools:positron"))
Expand All @@ -32,14 +30,9 @@ init_positron <- function() {

# Create environment for functions exported on the search path
attach(list(), name = "tools:positron")

# Lock it, we'll unlock when updating
lockEnvironment(as.environment("tools:positron"))
}

export <- function(exprs, from, to) {
local_unlock(to)

for (name in exported_names(exprs)) {
to[[name]] <- from[[name]]
}
Expand Down Expand Up @@ -79,15 +72,13 @@ import_rstudio <- function(exprs) {
init_rstudio()

env <- rstudio_ns()
local_unlock(env)

source(exprs = exprs, local = env)
export(exprs, from = env, to = as.environment("tools:rstudio"))
}

import_rstudio_path <- function(path) {
env <- rstudio_ns()
local_unlock(env)

source(path, local = env)
export_path(path, from = env, to = as.environment("tools:rstudio"))
Expand All @@ -109,10 +100,6 @@ init_rstudio <- function() {
# the modules file again.
attach(list(.__rstudio_ns__. = rstudio_ns), name = "tools:rstudio")

# Lock environments, we'll unlock them before updating
lockEnvironment(rstudio_ns)
lockEnvironment(as.environment("tools:rstudio"))

# Override `rstudioapi::isAvailable()` so it thinks it's running under RStudio
setHook(
packageEvent("rstudioapi", "onLoad"),
Expand All @@ -139,10 +126,6 @@ rstudio_ns <- function() {
.Call(.NAME, ..., PACKAGE = "(embedding)")
}

env_unlock <- function(env) {
.ps.Call("ark_env_unlock", env)
}

defer <- function(expr, envir = parent.frame(), after = FALSE) {
thunk <- as.call(list(function() expr))
do.call(
Expand All @@ -152,13 +135,6 @@ defer <- function(expr, envir = parent.frame(), after = FALSE) {
)
}

local_unlock <- function(env, frame = parent.frame()) {
if (environmentIsLocked(env)) {
env_unlock(env)
defer(lockEnvironment(env), envir = frame)
}
}

# Singleton for cached objects. Only create it if it doesn't exist because
# `init.R` might be sourced multiple times.
if (!exists("the", inherits = FALSE)) {
Expand All @@ -167,6 +143,13 @@ if (!exists("the", inherits = FALSE)) {
the$cli_version <- NULL
}

lock_environments <- function() {
ns <- parent.env(environment())
lockEnvironment(ns)
lockEnvironment(as.environment("tools:positron"))
lockEnvironment(rstudio_ns())
lockEnvironment(as.environment("tools:rstudio"))
}

# `initialize_errors()` is called separately in an unsafe context because it
# doesn't support being called with condition handlers on the stack
Expand Down
36 changes: 24 additions & 12 deletions crates/ark/src/srcref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ async fn ns_populate_srcref_without_vdoc_insertion(

for b in ns.iter().filter_map(Result::ok) {
span.in_scope(|| {
match generate_source(&b, vdoc.len(), &uri) {
match generate_source(&b, ns.inner.sexp, vdoc.len(), &uri) {
Ok(Some(mut lines)) => {
n_ok = n_ok + 1;

Expand Down Expand Up @@ -122,6 +122,7 @@ fn ark_ns_uri(ns_name: &str) -> String {
#[tracing::instrument(level = "trace", skip_all, fields(name = %binding.name))]
fn generate_source(
binding: &Binding,
ns_env: SEXP,
line: usize,
uri: &String,
) -> anyhow::Result<Option<Vec<String>>> {
Expand Down Expand Up @@ -160,10 +161,8 @@ fn generate_source(
// Inject source references in functions. This is slightly unsafe but we
// couldn't think of a dire failure mode.
unsafe {
// First replace the body which contains expressions tagged with srcrefs
// such as calls to `{`. Compiled functions are a little more tricky.
Comment on lines -163 to -164
Copy link
Contributor

Choose a reason for hiding this comment

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

Outdated? Dropped?

let body = harp::fn_body(old.sexp);

let body = BODY(old.sexp);
if r_typeof(body) == BCODESXP {
// This is a compiled function. We could recompile the fresh
// function we just created but the compiler is very slow. Instead,
Expand All @@ -179,16 +178,29 @@ fn generate_source(
// Inject new body instrumented with source references
SET_VECTOR_ELT(consts, 0, R_ClosureExpr(new));
}

harp::attrib_poke(
old.sexp,
r_symbol!("srcref"),
harp::attrib_get(new, r_symbol!("srcref")),
);
} else {
SET_BODY(old.sexp, BODY(new));
let new_body = harp::fn_body(new);
let out = RObject::new(harp::new_function(
harp::fn_formals(old.sexp),
new_body,
harp::fn_env(old.sexp),
));

harp::attrib_poke_from(out.sexp, old.sexp);
harp::attrib_poke(
out.sexp,
r_symbol!("srcref"),
harp::attrib_get(new, r_symbol!("srcref")),
);

harp::env_bind_force(ns_env, binding.name.sexp, out.sexp);
}

// Finally push the srcref attribute for the whole function
Rf_setAttrib(
old.sexp,
r_symbol!("srcref"),
Rf_getAttrib(new, r_symbol!("srcref")),
);
}

let text: Vec<String> = RObject::view(text).try_into()?;
Expand Down
20 changes: 16 additions & 4 deletions crates/harp/src/attrib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
use libr::SEXP;

use crate::object::r_length;
use crate::r::attrib_poke;
use crate::r::attrib_poke_from;
use crate::r::fn_body;
use crate::r::fn_env;
use crate::r::fn_formals;
use crate::r::new_function;
use crate::r_null;
use crate::r_symbol;
use crate::RObject;
Expand All @@ -18,12 +24,18 @@ pub fn zap_srcref(x: SEXP) -> RObject {

fn zap_srcref_fn(x: SEXP) -> RObject {
unsafe {
let x = RObject::view(x).shallow_duplicate();
let formals = fn_formals(x);
let body = fn_body(x);
let env = fn_env(x);

x.set_attribute("srcref", r_null());
libr::SET_BODY(x.sexp, zap_srcref(libr::R_ClosureExpr(x.sexp)).sexp);
let new_body = zap_srcref(body);
let out = RObject::new(new_function(formals, new_body.sexp, env));

x
// Copy attributes from the original, but zap `srcref`
attrib_poke_from(out.sexp, x);
attrib_poke(out.sexp, r_symbol!("srcref"), r_null());

out
}
}

Expand Down
Loading