Skip to content

Commit 2481721

Browse files
committed
Auto merge of rust-lang#13447 - Veykril:didsavedoc-block, r=Veykril
fix: Fix DidSaveDocument requests blocking the server on startup Follow up to rust-lang/rust-analyzer#13428 Fixes rust-lang/rust-analyzer#13434
2 parents 7741e3d + de195ff commit 2481721

File tree

7 files changed

+132
-83
lines changed

7 files changed

+132
-83
lines changed

crates/ide/src/lib.rs

+10
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,16 @@ impl Analysis {
486486
self.with_db(|db| parent_module::crates_for(db, file_id))
487487
}
488488

489+
/// Returns crates this file belongs too.
490+
pub fn transitive_rev_deps(&self, crate_id: CrateId) -> Cancellable<Vec<CrateId>> {
491+
self.with_db(|db| db.crate_graph().transitive_rev_deps(crate_id).collect())
492+
}
493+
494+
/// Returns crates this file *might* belong too.
495+
pub fn relevant_crates_for(&self, file_id: FileId) -> Cancellable<Vec<CrateId>> {
496+
self.with_db(|db| db.relevant_crates(file_id).iter().copied().collect())
497+
}
498+
489499
/// Returns the edition of the given crate.
490500
pub fn crate_edition(&self, crate_id: CrateId) -> Cancellable<Edition> {
491501
self.with_db(|db| db.crate_graph()[crate_id].edition)

crates/ide/src/parent_module.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use hir::Semantics;
1+
use hir::{db::DefDatabase, Semantics};
22
use ide_db::{
33
base_db::{CrateId, FileId, FileLoader, FilePosition},
44
RootDatabase,
55
};
6+
use itertools::Itertools;
67
use syntax::{
78
algo::find_node_at_offset,
89
ast::{self, AstNode},
@@ -55,7 +56,12 @@ pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<Na
5556

5657
/// Returns `Vec` for the same reason as `parent_module`
5758
pub(crate) fn crates_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> {
58-
db.relevant_crates(file_id).iter().copied().collect()
59+
db.relevant_crates(file_id)
60+
.iter()
61+
.copied()
62+
.filter(|&crate_id| db.crate_def_map(crate_id).modules_for_file(file_id).next().is_some())
63+
.sorted()
64+
.collect()
5965
}
6066

6167
#[cfg(test)]

crates/rust-analyzer/src/cargo_target_spec.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,11 @@ impl CargoTargetSpec {
118118
global_state_snapshot: &GlobalStateSnapshot,
119119
file_id: FileId,
120120
) -> Result<Option<CargoTargetSpec>> {
121-
let (cargo_ws, target) = match global_state_snapshot.cargo_target_for_file_id(file_id) {
121+
let crate_id = match &*global_state_snapshot.analysis.crates_for(file_id)? {
122+
&[crate_id, ..] => crate_id,
123+
_ => return Ok(None),
124+
};
125+
let (cargo_ws, target) = match global_state_snapshot.cargo_target_for_crate_root(crate_id) {
122126
Some(it) => it,
123127
None => return Ok(None),
124128
};

crates/rust-analyzer/src/global_state.rs

+12-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{sync::Arc, time::Instant};
88
use crossbeam_channel::{unbounded, Receiver, Sender};
99
use flycheck::FlycheckHandle;
1010
use ide::{Analysis, AnalysisHost, Cancellable, Change, FileId};
11-
use ide_db::base_db::{FileLoader, SourceDatabase};
11+
use ide_db::base_db::{CrateId, FileLoader, SourceDatabase};
1212
use lsp_types::{SemanticTokens, Url};
1313
use parking_lot::{Mutex, RwLock};
1414
use proc_macro_api::ProcMacroServer;
@@ -64,7 +64,7 @@ pub(crate) struct GlobalState {
6464
pub(crate) source_root_config: SourceRootConfig,
6565
pub(crate) proc_macro_clients: Vec<Result<ProcMacroServer, String>>,
6666

67-
pub(crate) flycheck: Vec<FlycheckHandle>,
67+
pub(crate) flycheck: Arc<[FlycheckHandle]>,
6868
pub(crate) flycheck_sender: Sender<flycheck::Message>,
6969
pub(crate) flycheck_receiver: Receiver<flycheck::Message>,
7070

@@ -117,6 +117,7 @@ pub(crate) struct GlobalStateSnapshot {
117117
vfs: Arc<RwLock<(vfs::Vfs, NoHashHashMap<FileId, LineEndings>)>>,
118118
pub(crate) workspaces: Arc<Vec<ProjectWorkspace>>,
119119
pub(crate) proc_macros_loaded: bool,
120+
pub(crate) flycheck: Arc<[FlycheckHandle]>,
120121
}
121122

122123
impl std::panic::UnwindSafe for GlobalStateSnapshot {}
@@ -155,7 +156,7 @@ impl GlobalState {
155156
source_root_config: SourceRootConfig::default(),
156157
proc_macro_clients: vec![],
157158

158-
flycheck: Vec::new(),
159+
flycheck: Arc::new([]),
159160
flycheck_sender,
160161
flycheck_receiver,
161162

@@ -295,6 +296,7 @@ impl GlobalState {
295296
mem_docs: self.mem_docs.clone(),
296297
semantic_tokens_cache: Arc::clone(&self.semantic_tokens_cache),
297298
proc_macros_loaded: !self.fetch_build_data_queue.last_op_result().0.is_empty(),
299+
flycheck: self.flycheck.clone(),
298300
}
299301
}
300302

@@ -398,10 +400,15 @@ impl GlobalStateSnapshot {
398400
url_from_abs_path(path)
399401
}
400402

401-
pub(crate) fn cargo_target_for_file_id(
403+
pub(crate) fn file_id_to_file_path(&self, file_id: FileId) -> vfs::VfsPath {
404+
self.vfs.read().0.file_path(file_id)
405+
}
406+
407+
pub(crate) fn cargo_target_for_crate_root(
402408
&self,
403-
file_id: FileId,
409+
crate_id: CrateId,
404410
) -> Option<(&CargoWorkspace, Target)> {
411+
let file_id = self.analysis.crate_root(crate_id).ok()?;
405412
let path = self.vfs.read().0.file_path(file_id);
406413
let path = path.as_path()?;
407414
self.workspaces.iter().find_map(|ws| match ws {

crates/rust-analyzer/src/handlers.rs

+10-4
Original file line numberDiff line numberDiff line change
@@ -1782,7 +1782,15 @@ fn run_rustfmt(
17821782
) -> Result<Option<Vec<lsp_types::TextEdit>>> {
17831783
let file_id = from_proto::file_id(snap, &text_document.uri)?;
17841784
let file = snap.analysis.file_text(file_id)?;
1785-
let crate_ids = snap.analysis.crates_for(file_id)?;
1785+
1786+
// find the edition of the package the file belongs to
1787+
// (if it belongs to multiple we'll just pick the first one and pray)
1788+
let edition = snap
1789+
.analysis
1790+
.relevant_crates_for(file_id)?
1791+
.into_iter()
1792+
.find_map(|crate_id| snap.cargo_target_for_crate_root(crate_id))
1793+
.map(|(ws, target)| ws[ws[target].package].edition);
17861794

17871795
let line_index = snap.file_line_index(file_id)?;
17881796

@@ -1808,9 +1816,7 @@ fn run_rustfmt(
18081816
);
18091817
}
18101818
}
1811-
if let Some(&crate_id) = crate_ids.first() {
1812-
// Assume all crates are in the same edition
1813-
let edition = snap.analysis.crate_edition(crate_id)?;
1819+
if let Some(edition) = edition {
18141820
cmd.arg("--edition");
18151821
cmd.arg(edition.to_string());
18161822
}

crates/rust-analyzer/src/main_loop.rs

+84-69
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::{
1010
use always_assert::always;
1111
use crossbeam_channel::{select, Receiver};
1212
use flycheck::FlycheckHandle;
13-
use ide_db::base_db::{SourceDatabase, SourceDatabaseExt, VfsPath};
13+
use ide_db::base_db::{SourceDatabaseExt, VfsPath};
1414
use itertools::Itertools;
1515
use lsp_server::{Connection, Notification, Request};
1616
use lsp_types::notification::Notification as _;
@@ -191,7 +191,7 @@ impl GlobalState {
191191
// NOTE: don't count blocking select! call as a loop-turn time
192192
let _p = profile::span("GlobalState::handle_event");
193193

194-
tracing::debug!("handle_event({:?})", event);
194+
tracing::debug!("{:?} handle_event({:?})", loop_start, event);
195195
let task_queue_len = self.task_pool.handle.len();
196196
if task_queue_len > 0 {
197197
tracing::info!("task queue len: {}", task_queue_len);
@@ -727,7 +727,7 @@ impl GlobalState {
727727
.insert(path.clone(), DocumentData::new(params.text_document.version))
728728
.is_err();
729729
if already_exists {
730-
tracing::error!("duplicate DidOpenTextDocument: {}", path)
730+
tracing::error!("duplicate DidOpenTextDocument: {}", path);
731731
}
732732
this.vfs
733733
.write()
@@ -774,83 +774,98 @@ impl GlobalState {
774774
Ok(())
775775
})?
776776
.on::<lsp_types::notification::DidSaveTextDocument>(|this, params| {
777-
let mut updated = false;
778777
if let Ok(vfs_path) = from_proto::vfs_path(&params.text_document.uri) {
779-
let (vfs, _) = &*this.vfs.read();
780-
781-
// Trigger flychecks for all workspaces that depend on the saved file
782-
if let Some(file_id) = vfs.file_id(&vfs_path) {
783-
let analysis = this.analysis_host.analysis();
784-
// Crates containing or depending on the saved file
785-
let crate_ids: Vec<_> = analysis
786-
.crates_for(file_id)?
787-
.into_iter()
788-
.flat_map(|id| {
789-
this.analysis_host
790-
.raw_database()
791-
.crate_graph()
792-
.transitive_rev_deps(id)
793-
})
794-
.sorted()
795-
.unique()
796-
.collect();
797-
798-
let crate_root_paths: Vec<_> = crate_ids
799-
.iter()
800-
.filter_map(|&crate_id| {
801-
analysis
802-
.crate_root(crate_id)
803-
.map(|file_id| {
804-
vfs.file_path(file_id).as_path().map(ToOwned::to_owned)
805-
})
806-
.transpose()
807-
})
808-
.collect::<ide::Cancellable<_>>()?;
809-
let crate_root_paths: Vec<_> =
810-
crate_root_paths.iter().map(Deref::deref).collect();
811-
812-
// Find all workspaces that have at least one target containing the saved file
813-
let workspace_ids =
814-
this.workspaces.iter().enumerate().filter(|(_, ws)| match ws {
815-
project_model::ProjectWorkspace::Cargo { cargo, .. } => {
816-
cargo.packages().any(|pkg| {
817-
cargo[pkg].targets.iter().any(|&it| {
818-
crate_root_paths.contains(&cargo[it].root.as_path())
819-
})
820-
})
821-
}
822-
project_model::ProjectWorkspace::Json { project, .. } => project
823-
.crates()
824-
.any(|(c, _)| crate_ids.iter().any(|&crate_id| crate_id == c)),
825-
project_model::ProjectWorkspace::DetachedFiles { .. } => false,
826-
});
827-
828-
// Find and trigger corresponding flychecks
829-
for flycheck in &this.flycheck {
830-
for (id, _) in workspace_ids.clone() {
831-
if id == flycheck.id() {
832-
updated = true;
833-
flycheck.restart();
834-
continue;
835-
}
836-
}
837-
}
838-
}
839-
840778
// Re-fetch workspaces if a workspace related file has changed
841779
if let Some(abs_path) = vfs_path.as_path() {
842780
if reload::should_refresh_for_change(&abs_path, ChangeKind::Modify) {
843781
this.fetch_workspaces_queue
844782
.request_op(format!("DidSaveTextDocument {}", abs_path.display()));
845783
}
846784
}
785+
786+
let file_id = this.vfs.read().0.file_id(&vfs_path);
787+
if let Some(file_id) = file_id {
788+
let world = this.snapshot();
789+
let mut updated = false;
790+
let task = move || -> std::result::Result<(), ide::Cancelled> {
791+
// Trigger flychecks for all workspaces that depend on the saved file
792+
// Crates containing or depending on the saved file
793+
let crate_ids: Vec<_> = world
794+
.analysis
795+
.crates_for(file_id)?
796+
.into_iter()
797+
.flat_map(|id| world.analysis.transitive_rev_deps(id))
798+
.flatten()
799+
.sorted()
800+
.unique()
801+
.collect();
802+
803+
let crate_root_paths: Vec<_> = crate_ids
804+
.iter()
805+
.filter_map(|&crate_id| {
806+
world
807+
.analysis
808+
.crate_root(crate_id)
809+
.map(|file_id| {
810+
world
811+
.file_id_to_file_path(file_id)
812+
.as_path()
813+
.map(ToOwned::to_owned)
814+
})
815+
.transpose()
816+
})
817+
.collect::<ide::Cancellable<_>>()?;
818+
let crate_root_paths: Vec<_> =
819+
crate_root_paths.iter().map(Deref::deref).collect();
820+
821+
// Find all workspaces that have at least one target containing the saved file
822+
let workspace_ids =
823+
world.workspaces.iter().enumerate().filter(|(_, ws)| match ws {
824+
project_model::ProjectWorkspace::Cargo { cargo, .. } => {
825+
cargo.packages().any(|pkg| {
826+
cargo[pkg].targets.iter().any(|&it| {
827+
crate_root_paths.contains(&cargo[it].root.as_path())
828+
})
829+
})
830+
}
831+
project_model::ProjectWorkspace::Json { project, .. } => {
832+
project.crates().any(|(c, _)| {
833+
crate_ids.iter().any(|&crate_id| crate_id == c)
834+
})
835+
}
836+
project_model::ProjectWorkspace::DetachedFiles { .. } => false,
837+
});
838+
839+
// Find and trigger corresponding flychecks
840+
for flycheck in world.flycheck.iter() {
841+
for (id, _) in workspace_ids.clone() {
842+
if id == flycheck.id() {
843+
updated = true;
844+
flycheck.restart();
845+
continue;
846+
}
847+
}
848+
}
849+
// No specific flycheck was triggered, so let's trigger all of them.
850+
if !updated {
851+
for flycheck in world.flycheck.iter() {
852+
flycheck.restart();
853+
}
854+
}
855+
Ok(())
856+
};
857+
this.task_pool.handle.spawn_with_sender(move |_| {
858+
if let Err(e) = std::panic::catch_unwind(task) {
859+
tracing::error!("DidSaveTextDocument flycheck task panicked: {e:?}")
860+
}
861+
});
862+
return Ok(());
863+
}
847864
}
848865

849866
// No specific flycheck was triggered, so let's trigger all of them.
850-
if !updated {
851-
for flycheck in &this.flycheck {
852-
flycheck.restart();
853-
}
867+
for flycheck in this.flycheck.iter() {
868+
flycheck.restart();
854869
}
855870
Ok(())
856871
})?

crates/rust-analyzer/src/reload.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ impl GlobalState {
466466
let config = match self.config.flycheck() {
467467
Some(it) => it,
468468
None => {
469-
self.flycheck = Vec::new();
469+
self.flycheck = Arc::new([]);
470470
self.diagnostics.clear_check_all();
471471
return;
472472
}
@@ -510,7 +510,8 @@ impl GlobalState {
510510
})
511511
.collect()
512512
}
513-
};
513+
}
514+
.into();
514515
}
515516
}
516517

0 commit comments

Comments
 (0)