Skip to content

Commit 82a992b

Browse files
committed
Relax Send requirement on r_task() closure
The control flow of a task is strictly delineated in such a way that the task is necessarily completed before control is returned to the caller. So it is safe to erase the `Send` requirement on the objects captured by the closure.
1 parent 9818b16 commit 82a992b

File tree

3 files changed

+92
-123
lines changed

3 files changed

+92
-123
lines changed

crates/ark/src/lsp/backend.rs

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use std::sync::Arc;
1313

1414
use crossbeam::channel::Sender;
1515
use dashmap::DashMap;
16-
use harp::r_lock;
1716
use log::*;
1817
use parking_lot::Mutex;
1918
use regex::Regex;
@@ -373,17 +372,8 @@ impl LanguageServer for Backend {
373372

374373
log::info!("Completion context: {:#?}", context);
375374

376-
// FIXME: Use `r_task()`
377-
// rustc [E0277]: `*const c_void` cannot be shared between threads safely
378-
// within `completions::CompletionContext<'_>`, the trait `Sync` is not implemented for `*const c_void`
379-
// required for `&completions::CompletionContext<'_>` to implement `Send`
380-
// rustc [E0277]: `*const tree_sitter::ffi::TSTree` cannot be shared between threads safely
381-
// within `completions::CompletionContext<'_>`, the trait `Sync` is not implemented for `*const tree_sitter::ffi::TSTree`
382-
// required for `&completions::CompletionContext<'_>` to implement `Send`
383-
// rustc [E0277]: required because it's used within this closure
384-
385375
// Add session completions
386-
let result = r_lock! { append_session_completions(&context, &mut completions) };
376+
let result = r_task(|| unsafe { append_session_completions(&context, &mut completions) });
387377
if let Err(error) = result {
388378
error!("{:?}", error);
389379
}
@@ -482,20 +472,8 @@ impl LanguageServer for Backend {
482472
return Ok(None);
483473
});
484474

485-
// FIXME: Use `r_task()`
486-
// rustc [E0277]: required by a bound introduced by this call
487-
// rustc [E0277]: `*const tree_sitter::ffi::TSTree` cannot be shared between threads safely
488-
// within `completions::CompletionContext<'_>`, the trait `Sync` is not implemented for `*const tree_sitter::ffi::TSTree`
489-
// required for `&completions::CompletionContext<'_>` to implement `Send`
490-
// rustc [E0277]: `*const c_void` cannot be shared between threads safely
491-
// within `completions::CompletionContext<'_>`, the trait `Sync` is not implemented for `*const c_void`
492-
// required for `&completions::CompletionContext<'_>` to implement `Send`
493-
// rustc [E0277]: required because it's used within this closure
494-
495475
// request hover information
496-
let result = r_lock! {
497-
hover(&document, &context)
498-
};
476+
let result = r_task(|| unsafe { hover(&document, &context) });
499477

500478
// unwrap errors
501479
let result = unwrap!(result, Err(error) => {

crates/ark/src/lsp/diagnostics.rs

Lines changed: 87 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ use harp::external_ptr::ExternalPointer;
1717
use harp::object::RObject;
1818
use harp::protect::RProtect;
1919
use harp::r_symbol;
20+
use harp::utils::r_is_null;
2021
use harp::utils::r_symbol_quote_invalid;
2122
use harp::utils::r_symbol_valid;
2223
use harp::vector::CharacterVector;
2324
use harp::vector::Vector;
2425
use libR_sys::*;
26+
use libc::c_void;
2527
use stdext::*;
2628
use tower_lsp::lsp_types::Diagnostic;
2729
use tower_lsp::lsp_types::DiagnosticSeverity;
@@ -635,78 +637,72 @@ impl<'a> TreeSitterCall<'a> {
635637
}
636638
}
637639

638-
// FIXME: Can't directly switch to `r_task()`
639-
// rustc [E0277]: `*const tree_sitter::ffi::TSTree` cannot be shared between threads safely
640-
// within `tree_sitter::Node<'_>`, the trait `Sync` is not implemented for `*const tree_sitter::ffi::TSTree`
641-
// required for `&tree_sitter::Node<'_>` to implement `Send`
642-
// rustc [E0277]: `*const c_void` cannot be shared between threads safely
643-
644-
// fn recurse_call_arguments_custom(
645-
// node: Node,
646-
// context: &mut DiagnosticContext,
647-
// diagnostics: &mut Vec<Diagnostic>,
648-
// function: &str,
649-
// diagnostic_function: &str,
650-
// ) -> Result<()> {
651-
// r_lock! {
652-
// // Build a call that mixes treesitter nodes (as external pointers)
653-
// // library(foo, pos = 2 + 2)
654-
// // ->
655-
// // library([0, <node0>], pos = [1, <node1>])
656-
// // where:
657-
// // - node0 is an external pointer to a treesitter Node for the identifier `foo`
658-
// // - node1 is an external pointer to a treesitter Node for the call `2 + 2`
659-
// //
660-
// // The TreeSitterCall object holds on to the nodes, so that they can be
661-
// // safely passed down to the R side as external pointers
662-
// let call = TreeSitterCall::new(node, function, context)?;
663-
664-
// let custom_diagnostics = RFunction::from(diagnostic_function)
665-
// .add(&call)
666-
// .add(ExternalPointer::new(&context.source))
667-
// .call()?;
668-
669-
// if !r_is_null(*custom_diagnostics) {
670-
// let n = XLENGTH(*custom_diagnostics);
671-
// for i in 0..n {
672-
// // diag is a list with:
673-
// // - The kind of diagnostic: skip, default, simple
674-
// // - The node external pointer, i.e. the ones made in TreeSitterCall::new
675-
// // - The message, when kind is "simple"
676-
// let diag = VECTOR_ELT(*custom_diagnostics, i);
677-
678-
// let kind: String = RObject::view(VECTOR_ELT(diag, 0)).try_into()?;
679-
680-
// if kind == "skip" {
681-
// // skip the diagnostic entirely, e.g.
682-
// // library(foo)
683-
// // ^^^
684-
// continue;
685-
// }
686-
687-
// let ptr = VECTOR_ELT(diag, 1);
688-
// let value = *(R_ExternalPtrAddr(ptr) as *const c_void as *const Node);
689-
690-
// if kind == "default" {
691-
// // the R side gives up, so proceed as normal, e.g.
692-
// // library(foo, pos = ...)
693-
// // ^^^
694-
// recurse(value, context, diagnostics)?;
695-
// } else if kind == "simple" {
696-
// // Simple diagnostic from R, e.g.
697-
// // library("ggplot3")
698-
// // ^^^^^^^ Package 'ggplot3' is not installed
699-
// let message: String = RObject::view(VECTOR_ELT(diag, 2)).try_into()?;
700-
// let range: Range = value.range().into();
701-
// let diagnostic = Diagnostic::new_simple(range.into(), message.into());
702-
// diagnostics.push(diagnostic);
703-
// }
704-
// }
705-
// }
706-
707-
// ().ok()
708-
// }
709-
// }
640+
fn recurse_call_arguments_custom(
641+
node: Node,
642+
context: &mut DiagnosticContext,
643+
diagnostics: &mut Vec<Diagnostic>,
644+
function: &str,
645+
diagnostic_function: &str,
646+
) -> Result<()> {
647+
r_task(|| unsafe {
648+
// Build a call that mixes treesitter nodes (as external pointers)
649+
// library(foo, pos = 2 + 2)
650+
// ->
651+
// library([0, <node0>], pos = [1, <node1>])
652+
// where:
653+
// - node0 is an external pointer to a treesitter Node for the identifier `foo`
654+
// - node1 is an external pointer to a treesitter Node for the call `2 + 2`
655+
//
656+
// The TreeSitterCall object holds on to the nodes, so that they can be
657+
// safely passed down to the R side as external pointers
658+
let call = TreeSitterCall::new(node, function, context)?;
659+
660+
let custom_diagnostics = RFunction::from(diagnostic_function)
661+
.add(&call)
662+
.add(ExternalPointer::new(&context.source))
663+
.call()?;
664+
665+
if !r_is_null(*custom_diagnostics) {
666+
let n = XLENGTH(*custom_diagnostics);
667+
for i in 0..n {
668+
// diag is a list with:
669+
// - The kind of diagnostic: skip, default, simple
670+
// - The node external pointer, i.e. the ones made in TreeSitterCall::new
671+
// - The message, when kind is "simple"
672+
let diag = VECTOR_ELT(*custom_diagnostics, i);
673+
674+
let kind: String = RObject::view(VECTOR_ELT(diag, 0)).try_into()?;
675+
676+
if kind == "skip" {
677+
// skip the diagnostic entirely, e.g.
678+
// library(foo)
679+
// ^^^
680+
continue;
681+
}
682+
683+
let ptr = VECTOR_ELT(diag, 1);
684+
let value = *(R_ExternalPtrAddr(ptr) as *const c_void as *const Node);
685+
686+
if kind == "default" {
687+
// the R side gives up, so proceed as normal, e.g.
688+
// library(foo, pos = ...)
689+
// ^^^
690+
recurse(value, context, diagnostics)?;
691+
} else if kind == "simple" {
692+
// Simple diagnostic from R, e.g.
693+
// library("ggplot3")
694+
// ^^^^^^^ Package 'ggplot3' is not installed
695+
let message: String = RObject::view(VECTOR_ELT(diag, 2)).try_into()?;
696+
let range: Range = value.range().into();
697+
let diagnostic = Diagnostic::new_simple(range.into(), message.into());
698+
diagnostics.push(diagnostic);
699+
}
700+
}
701+
}
702+
703+
().ok()
704+
})
705+
}
710706

711707
fn recurse_call(
712708
node: Node,
@@ -726,30 +722,25 @@ fn recurse_call(
726722
// things like 'local({ ... })'.
727723
let fun = callee.utf8_text(context.source.as_bytes())?;
728724
match fun {
729-
// FIXME: These are currently disabled because they use the R
730-
// runtime to match arguments with an approach that meshes R and
731-
// treesitter objects. This is not compatible with `r_task()`
732-
// because treesitter objects are not Send/Sync.
733-
734-
// // TODO: there should be some sort of registration mechanism so
735-
// // that functions can declare that they know how to generate
736-
// // diagnostics, i.e. ggplot2::aes() would skip diagnostics
737-
738-
// // special case to deal with library() and require() nse
739-
// "library" => recurse_call_arguments_custom(
740-
// node,
741-
// context,
742-
// diagnostics,
743-
// "library",
744-
// ".ps.diagnostics.custom.library",
745-
// )?,
746-
// "require" => recurse_call_arguments_custom(
747-
// node,
748-
// context,
749-
// diagnostics,
750-
// "require",
751-
// ".ps.diagnostics.custom.require",
752-
// )?,
725+
// TODO: there should be some sort of registration mechanism so
726+
// that functions can declare that they know how to generate
727+
// diagnostics, i.e. ggplot2::aes() would skip diagnostics
728+
729+
// special case to deal with library() and require() nse
730+
"library" => recurse_call_arguments_custom(
731+
node,
732+
context,
733+
diagnostics,
734+
"library",
735+
".ps.diagnostics.custom.library",
736+
)?,
737+
"require" => recurse_call_arguments_custom(
738+
node,
739+
context,
740+
diagnostics,
741+
"require",
742+
".ps.diagnostics.custom.require",
743+
)?,
753744

754745
// default case: recurse into each argument
755746
_ => recurse_call_arguments_default(node, context, diagnostics)?,

crates/ark/src/r_task.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ type SharedOption<T> = Arc<Mutex<Option<T>>>;
2525
pub fn r_task<'env, F, T>(f: F) -> T
2626
where
2727
F: FnOnce() -> T,
28-
F: 'env + Send,
29-
T: 'env + Send,
28+
F: 'env,
29+
T: 'env,
3030
{
3131
// Escape hatch for unit tests
3232
if unsafe { R_TASK_BYPASS } {
@@ -62,7 +62,7 @@ where
6262
};
6363

6464
// Move `f` to heap and erase its lifetime
65-
let closure: Box<dyn FnOnce() + Send + 'env> = Box::new(closure);
65+
let closure: Box<dyn FnOnce() + 'env> = Box::new(closure);
6666
let closure: Box<dyn FnOnce() + Send + 'static> = unsafe { std::mem::transmute(closure) };
6767

6868
// Channel to communicate completion status of the task/closure

0 commit comments

Comments
 (0)