Skip to content

Commit e71a14c

Browse files
committed
Define new QueuedTask variant Flycheck
1 parent dab022f commit e71a14c

File tree

3 files changed

+245
-6
lines changed

3 files changed

+245
-6
lines changed

crates/rust-analyzer/src/handlers/notification.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,12 @@ pub(crate) fn handle_did_save_text_document(
177177
}
178178
}
179179

180-
if !state.config.check_on_save() || run_flycheck(state, vfs_path) {
180+
if state.config.check_on_save() {
181+
let _ = state
182+
.deferred_task_queue
183+
.sender
184+
.send(crate::main_loop::QueuedTask::Flycheck(vfs_path));
185+
181186
return Ok(());
182187
}
183188
} else if state.config.check_on_save() {

crates/rust-analyzer/src/main_loop.rs

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
44
use std::{
55
fmt,
6-
ops::Div as _,
6+
ops::{Deref, Div as _},
77
time::{Duration, Instant},
88
};
99

1010
use always_assert::always;
1111
use crossbeam_channel::{select, Receiver};
1212
use ide_db::base_db::{SourceDatabase, SourceRootDatabase, VfsPath};
13+
use itertools::Itertools;
1314
use lsp_server::{Connection, Notification, Request};
1415
use lsp_types::{notification::Notification as _, TextDocumentIdentifier};
1516
use stdx::thread::ThreadIntent;
@@ -30,6 +31,7 @@ use crate::{
3031
},
3132
lsp_ext,
3233
reload::{BuildDataProgress, ProcMacroProgress, ProjectWorkspaceProgress},
34+
target_spec::TargetSpec,
3335
test_runner::{CargoTestMessage, TestState},
3436
};
3537

@@ -86,6 +88,7 @@ impl fmt::Display for Event {
8688
pub(crate) enum QueuedTask {
8789
CheckIfIndexed(lsp_types::Url),
8890
CheckProcMacroSources(Vec<FileId>),
91+
Flycheck(VfsPath),
8992
}
9093

9194
#[derive(Debug)]
@@ -846,6 +849,123 @@ impl GlobalState {
846849
}
847850
});
848851
}
852+
QueuedTask::Flycheck(vfs_path) => {
853+
let _p = tracing::info_span!("run_flycheck").entered();
854+
let file_id = self.vfs.read().0.file_id(&vfs_path);
855+
if let Some(file_id) = file_id {
856+
let world = self.snapshot();
857+
let mut updated = false;
858+
let task = move || -> std::result::Result<(), ide::Cancelled> {
859+
// Is the target binary? If so we let flycheck trigger only for the workspace that contains it.
860+
let target_is_bin = TargetSpec::for_file(&world, file_id)?
861+
.is_some_and(|x| x.target_kind() == project_model::TargetKind::Bin);
862+
863+
let crate_ids = if target_is_bin {
864+
// Trigger flychecks for the only workspace which the binary crate belongs to
865+
world
866+
.analysis
867+
.crates_for(file_id)?
868+
.into_iter()
869+
.unique()
870+
.collect::<Vec<_>>()
871+
} else {
872+
// Trigger flychecks for all workspaces that depend on the saved file
873+
// Crates containing or depending on the saved file
874+
world
875+
.analysis
876+
.crates_for(file_id)?
877+
.into_iter()
878+
.flat_map(|id| world.analysis.transitive_rev_deps(id))
879+
.flatten()
880+
.unique()
881+
.collect::<Vec<_>>()
882+
};
883+
884+
let crate_root_paths: Vec<_> = crate_ids
885+
.iter()
886+
.filter_map(|&crate_id| {
887+
world
888+
.analysis
889+
.crate_root(crate_id)
890+
.map(|file_id| {
891+
world
892+
.file_id_to_file_path(file_id)
893+
.as_path()
894+
.map(ToOwned::to_owned)
895+
})
896+
.transpose()
897+
})
898+
.collect::<ide::Cancellable<_>>()?;
899+
let crate_root_paths: Vec<_> =
900+
crate_root_paths.iter().map(Deref::deref).collect();
901+
902+
// Find all workspaces that have at least one target containing the saved file
903+
let workspace_ids =
904+
world.workspaces.iter().enumerate().filter_map(|(idx, ws)| {
905+
let package = match &ws.kind {
906+
project_model::ProjectWorkspaceKind::Cargo {
907+
cargo, ..
908+
}
909+
| project_model::ProjectWorkspaceKind::DetachedFile {
910+
cargo: Some((cargo, _)),
911+
..
912+
} => cargo.packages().find_map(|pkg| {
913+
let has_target_with_root =
914+
cargo[pkg].targets.iter().any(|&it| {
915+
crate_root_paths.contains(&cargo[it].root.as_path())
916+
});
917+
has_target_with_root.then(|| cargo[pkg].name.clone())
918+
}),
919+
project_model::ProjectWorkspaceKind::Json(project) => {
920+
if !project.crates().any(|(_, krate)| {
921+
crate_root_paths.contains(&krate.root_module.as_path())
922+
}) {
923+
return None;
924+
}
925+
None
926+
}
927+
project_model::ProjectWorkspaceKind::DetachedFile {
928+
..
929+
} => return None,
930+
};
931+
Some((idx, package))
932+
});
933+
934+
let saved_file = vfs_path.as_path().map(|p| p.to_owned());
935+
936+
// Find and trigger corresponding flychecks
937+
for flycheck in world.flycheck.iter() {
938+
for (id, package) in workspace_ids.clone() {
939+
if id == flycheck.id() {
940+
updated = true;
941+
match package.filter(|_| !world.config.flycheck_workspace()) {
942+
Some(package) => flycheck.restart_for_package(package),
943+
None => flycheck.restart_workspace(saved_file.clone()),
944+
}
945+
continue;
946+
}
947+
}
948+
}
949+
// No specific flycheck was triggered, so let's trigger all of them.
950+
if !updated {
951+
for flycheck in world.flycheck.iter() {
952+
flycheck.restart_workspace(saved_file.clone());
953+
}
954+
}
955+
Ok(())
956+
};
957+
self.task_pool.handle.spawn_with_sender(
958+
stdx::thread::ThreadIntent::Worker,
959+
move |_| {
960+
if let Err(e) = std::panic::catch_unwind(task) {
961+
tracing::error!("flycheck task panicked: {e:?}")
962+
}
963+
},
964+
);
965+
} else {
966+
error!("FileId for VfsPath could not be found. This caused a flycheck request to be ignored. Related path {}" , vfs_path);
967+
}
968+
}
849969
}
850970
}
851971

crates/rust-analyzer/tests/slow-tests/main.rs

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ mod testdir;
1717
use std::{collections::HashMap, path::PathBuf, time::Instant};
1818

1919
use lsp_types::{
20-
notification::DidOpenTextDocument,
20+
notification::{DidOpenTextDocument, DidSaveTextDocument},
2121
request::{
2222
CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest,
2323
InlayHintRequest, InlayHintResolveRequest, WillRenameFiles, WorkspaceSymbolRequest,
2424
},
2525
CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams,
26-
DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams,
27-
InlayHint, InlayHintLabel, InlayHintParams, PartialResultParams, Position, Range,
28-
RenameFilesParams, TextDocumentItem, TextDocumentPositionParams, WorkDoneProgressParams,
26+
DidSaveTextDocumentParams, DocumentFormattingParams, FileRename, FormattingOptions,
27+
GotoDefinitionParams, HoverParams, InlayHint, InlayHintLabel, InlayHintParams,
28+
PartialResultParams, Position, Range, RenameFilesParams, TextDocumentIdentifier,
29+
TextDocumentItem, TextDocumentPositionParams, Url, WorkDoneProgressParams,
2930
};
3031
use rust_analyzer::lsp::ext::{OnEnter, Runnables, RunnablesParams};
3132
use serde_json::json;
@@ -1394,3 +1395,116 @@ version = "0.0.0"
13941395

13951396
server.request::<WorkspaceSymbolRequest>(Default::default(), json!([]));
13961397
}
1398+
1399+
#[test]
1400+
/// When flycheck is run on a binary, only the binary crate
1401+
/// should be taken in consideration.
1402+
fn flycheck_only_for_single_crate() {
1403+
if skip_slow_tests() {
1404+
return;
1405+
}
1406+
1407+
let tmp_dir = TestDir::new();
1408+
let path_str = tmp_dir.path().to_owned();
1409+
1410+
let server = Project::with_fixture(
1411+
r#"
1412+
//- /foo/Cargo.toml
1413+
[workspace]
1414+
members = ["crates/*"]
1415+
resolver = "2"
1416+
1417+
1418+
[workspace.dependencies]
1419+
luib = { version = "0.1.0" , path = "./crates/luib" }
1420+
luib2 = { version = "0.1.0" , path = "./crates/luib2" }
1421+
1422+
1423+
1424+
//- /foo/crates/luib/Cargo.toml
1425+
[package]
1426+
name = "luib"
1427+
version = "0.1.0"
1428+
edition = "2021"
1429+
1430+
[dependencies]
1431+
luib2.workspace = true
1432+
1433+
//- /foo/crates/luib/src/lib.rs
1434+
pub fn add(left: u64, right: u64) -> u64 {
1435+
left + right
1436+
}
1437+
1438+
#[cfg(test)]
1439+
mod tests {
1440+
use super::*;
1441+
1442+
#[test]
1443+
fn it_works() {
1444+
let result = add(2, 2);
1445+
assert_eq!(result, 4);
1446+
}
1447+
}
1448+
1449+
//- /foo/crates/buin/Cargo.toml
1450+
[package]
1451+
name = "buin"
1452+
version = "0.1.0"
1453+
edition = "2021"
1454+
1455+
[dependencies]
1456+
luib.workspace = true
1457+
luib2.workspace = true
1458+
1459+
//- /foo/crates/buin/src/main.rs
1460+
use luib;
1461+
fn main() {
1462+
luib::add(3, 4);
1463+
println!("Hello, world!");
1464+
}
1465+
1466+
//- /foo/crates/luib2/Cargo.toml
1467+
[package]
1468+
name = "luib2"
1469+
version = "0.1.0"
1470+
edition = "2021"
1471+
1472+
[dependencies]
1473+
1474+
//- /foo/crates/luib2/src/lib.rs
1475+
pub fn add(left: u64, right: u64) -> u64 {
1476+
left + right
1477+
}
1478+
1479+
#[cfg(test)]
1480+
mod tests {
1481+
use super::*;
1482+
1483+
#[test]
1484+
fn it_works() {
1485+
let result = add(2, 2);
1486+
assert_eq!(result, 4);
1487+
}
1488+
}
1489+
"#,
1490+
)
1491+
.root("foo")
1492+
.server()
1493+
.wait_until_workspace_is_loaded();
1494+
1495+
let mut path_to_file = path_str;
1496+
path_to_file.push("foo");
1497+
path_to_file.push("crates");
1498+
path_to_file.push("buin");
1499+
path_to_file.push("src");
1500+
path_to_file.push("main.rs");
1501+
1502+
dbg!(&path_to_file);
1503+
1504+
server.notification::<DidSaveTextDocument>(DidSaveTextDocumentParams {
1505+
text_document: TextDocumentIdentifier {
1506+
uri: Url::parse(&format!("file://{}", path_to_file)).unwrap(),
1507+
},
1508+
text: None,
1509+
});
1510+
}

0 commit comments

Comments
 (0)