]> git.lizzy.rs Git - rust.git/commitdiff
Auto merge of #12808 - Veykril:check-workspace, r=Veykril
authorbors <bors@rust-lang.org>
Thu, 4 Aug 2022 12:57:04 +0000 (12:57 +0000)
committerbors <bors@rust-lang.org>
Thu, 4 Aug 2022 12:57:04 +0000 (12:57 +0000)
feat: Only flycheck workspace that belongs to saved file

Supercedes https://github.com/rust-lang/rust-analyzer/pull/11038

There is still the problem that all the diagnostics are cleared, only clearing diagnostics of the relevant workspace isn't easily doable though I think, will have to dig into that

1  2 
crates/flycheck/src/lib.rs
crates/paths/src/lib.rs
crates/rust-analyzer/src/global_state.rs
crates/rust-analyzer/src/handlers.rs
crates/rust-analyzer/src/main_loop.rs
crates/rust-analyzer/src/reload.rs

index 4e8bc881ae7383f8caf04e0627ff244979becd92,7f14fe5798a02249ef3e3396bcac71008b3d8865..3347940ec6d639015304407bdd708dcff184c2bb
@@@ -2,8 -2,6 +2,8 @@@
  //! another compatible command (f.x. clippy) in a background thread and provide
  //! LSP diagnostics based on the output of the command.
  
 +#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
 +
  use std::{
      fmt, io,
      process::{ChildStderr, ChildStdout, Command, Stdio},
@@@ -57,6 -55,7 +57,7 @@@ pub struct FlycheckHandle 
      // XXX: drop order is significant
      sender: Sender<Restart>,
      _thread: jod_thread::JoinHandle,
+     id: usize,
  }
  
  impl FlycheckHandle {
              .name("Flycheck".to_owned())
              .spawn(move || actor.run(receiver))
              .expect("failed to spawn thread");
-         FlycheckHandle { sender, _thread: thread }
+         FlycheckHandle { id, sender, _thread: thread }
      }
  
      /// Schedule a re-start of the cargo check worker.
      pub fn update(&self) {
          self.sender.send(Restart).unwrap();
      }
+     pub fn id(&self) -> usize {
+         self.id
+     }
  }
  
  pub enum Message {
      /// Request adding a diagnostic with fixes included to a file
-     AddDiagnostic { workspace_root: AbsPathBuf, diagnostic: Diagnostic },
+     AddDiagnostic { id: usize, workspace_root: AbsPathBuf, diagnostic: Diagnostic },
  
      /// Request check progress notification to client
      Progress {
  impl fmt::Debug for Message {
      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
          match self {
-             Message::AddDiagnostic { workspace_root, diagnostic } => f
+             Message::AddDiagnostic { id, workspace_root, diagnostic } => f
                  .debug_struct("AddDiagnostic")
+                 .field("id", id)
                  .field("workspace_root", workspace_root)
                  .field("diagnostic_code", &diagnostic.code.as_ref().map(|it| &it.code))
                  .finish(),
@@@ -183,7 -187,7 +189,7 @@@ impl FlycheckActor 
                      }
                  }
                  Event::CheckEvent(None) => {
-                     tracing::debug!("flycheck finished");
+                     tracing::debug!(flycheck_id = self.id, "flycheck finished");
  
                      // Watcher finished
                      let cargo_handle = self.cargo_handle.take().unwrap();
  
                      CargoMessage::Diagnostic(msg) => {
                          self.send(Message::AddDiagnostic {
+                             id: self.id,
                              workspace_root: self.workspace_root.clone(),
                              diagnostic: msg,
                          });
diff --combined crates/paths/src/lib.rs
index 025093f4a94ab6ea81d4297bb64d6bdb0e26a15f,cf06bf0c27b848a99f7739f51ead7fe21659704e..6ae23ac841adaa6a7f97d8841c57aec96f2d2439
@@@ -1,8 -1,5 +1,8 @@@
  //! Thin wrappers around `std::path`, distinguishing between absolute and
  //! relative paths.
 +
 +#![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
 +
  use std::{
      borrow::Borrow,
      ffi::OsStr,
@@@ -106,6 -103,14 +106,14 @@@ impl AsRef<Path> for AbsPath 
      }
  }
  
+ impl ToOwned for AbsPath {
+     type Owned = AbsPathBuf;
+     fn to_owned(&self) -> Self::Owned {
+         AbsPathBuf(self.0.to_owned())
+     }
+ }
  impl<'a> TryFrom<&'a Path> for &'a AbsPath {
      type Error = &'a Path;
      fn try_from(path: &'a Path) -> Result<&'a AbsPath, &'a Path> {
index 8f881cba4dbd7b113534e3cfdf1d6aa85bf95d27,55c4cfcf8608f20150d54656b7636d1a17ee8f4a..932a31e08f6fa4ac67badb51e8bc2c88093c23ec
@@@ -61,7 -61,7 +61,7 @@@ pub(crate) struct GlobalState 
      pub(crate) proc_macro_changed: bool,
      pub(crate) last_reported_status: Option<lsp_ext::ServerStatusParams>,
      pub(crate) source_root_config: SourceRootConfig,
 -    pub(crate) proc_macro_client: Option<ProcMacroServer>,
 +    pub(crate) proc_macro_clients: Vec<Result<ProcMacroServer, String>>,
  
      pub(crate) flycheck: Vec<FlycheckHandle>,
      pub(crate) flycheck_sender: Sender<flycheck::Message>,
@@@ -151,7 -151,7 +151,7 @@@ impl GlobalState 
              proc_macro_changed: false,
              last_reported_status: None,
              source_root_config: SourceRootConfig::default(),
 -            proc_macro_client: None,
 +            proc_macro_clients: vec![],
  
              flycheck: Vec::new(),
              flycheck_sender,
                  if let Some(path) = vfs.file_path(file.file_id).as_path() {
                      let path = path.to_path_buf();
                      if reload::should_refresh_for_change(&path, file.change_kind) {
+                         tracing::warn!("fetch-fiel_change");
                          self.fetch_workspaces_queue
                              .request_op(format!("vfs file change: {}", path.display()));
                      }
                      }
                  }
  
+                 // Clear native diagnostics when their file gets deleted
                  if !file.exists() {
                      self.diagnostics.clear_native_for(file.file_id);
                  }
index deb777c952fdf7d7658f92b77b8d50f68244499c,a64dc57ec644892eff5b5d03e185c242278e2a9b..47daa732d5d3254ab779a9af4ae2091697c0bd13
@@@ -44,7 -44,7 +44,7 @@@ use crate::
  };
  
  pub(crate) fn handle_workspace_reload(state: &mut GlobalState, _: ()) -> Result<()> {
 -    state.proc_macro_client = None;
 +    state.proc_macro_clients.clear();
      state.proc_macro_changed = false;
      state.fetch_workspaces_queue.request_op("reload workspace request".to_string());
      state.fetch_build_data_queue.request_op("reload workspace request".to_string());
@@@ -1094,7 -1094,9 +1094,9 @@@ pub(crate) fn handle_code_action
      }
  
      // Fixes from `cargo check`.
-     for fix in snap.check_fixes.get(&frange.file_id).into_iter().flatten() {
+     for fix in
+         snap.check_fixes.values().filter_map(|it| it.get(&frange.file_id)).into_iter().flatten()
+     {
          // FIXME: this mapping is awkward and shouldn't exist. Refactor
          // `snap.check_fixes` to not convert to LSP prematurely.
          let intersect_fix_range = fix
@@@ -1318,8 -1320,7 +1320,8 @@@ pub(crate) fn publish_diagnostics
                  .unwrap(),
              }),
              source: Some("rust-analyzer".to_string()),
 -            message: d.message,
 +            // https://github.com/rust-lang/rust-analyzer/issues/11404
 +            message: if !d.message.is_empty() { d.message } else { " ".to_string() },
              related_information: None,
              tags: if d.unused { Some(vec![DiagnosticTag::UNNECESSARY]) } else { None },
              data: None,
index 5845cf712c899d6dd578956bcea77d4a37a69968,4ed34df01ca930a34d61cc474c640aa9da4c9351..b504c248782667752c8adfb5f10d8cbe1ecef11a
@@@ -2,13 -2,15 +2,15 @@@
  //! requests/replies and notifications back to the client.
  use std::{
      fmt,
+     ops::Deref,
      sync::Arc,
      time::{Duration, Instant},
  };
  
  use always_assert::always;
  use crossbeam_channel::{select, Receiver};
- use ide_db::base_db::{SourceDatabaseExt, VfsPath};
+ use ide_db::base_db::{SourceDatabase, SourceDatabaseExt, VfsPath};
+ use itertools::Itertools;
  use lsp_server::{Connection, Notification, Request};
  use lsp_types::notification::Notification as _;
  use vfs::{ChangeKind, FileId};
@@@ -75,8 -77,8 +77,8 @@@ pub(crate) enum PrimeCachesProgress 
  }
  
  impl fmt::Debug for Event {
 -    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 -        let debug_verbose_not = |not: &Notification, f: &mut fmt::Formatter| {
 +    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 +        let debug_verbose_not = |not: &Notification, f: &mut fmt::Formatter<'_>| {
              f.debug_struct("Notification").field("method", &not.method).finish()
          };
  
@@@ -371,7 -373,7 +373,7 @@@ impl GlobalState 
                  let _p = profile::span("GlobalState::handle_event/flycheck");
                  loop {
                      match task {
-                         flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => {
+                         flycheck::Message::AddDiagnostic { id, workspace_root, diagnostic } => {
                              let snap = self.snapshot();
                              let diagnostics =
                                  crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
                              for diag in diagnostics {
                                  match url_to_file_id(&self.vfs.read().0, &diag.url) {
                                      Ok(file_id) => self.diagnostics.add_check_diagnostic(
+                                         id,
                                          file_id,
                                          diag.diagnostic,
                                          diag.fix,
                          flycheck::Message::Progress { id, progress } => {
                              let (state, message) = match progress {
                                  flycheck::Progress::DidStart => {
-                                     self.diagnostics.clear_check();
+                                     self.diagnostics.clear_check(id);
                                      (Progress::Begin, None)
                                  }
                                  flycheck::Progress::DidCheckCrate(target) => {
          let memdocs_added_or_removed = self.mem_docs.take_changes();
  
          if self.is_quiescent() {
-             if !was_quiescent {
+             if !was_quiescent
+                 && !self.fetch_workspaces_queue.op_requested()
+                 && !self.fetch_build_data_queue.op_requested()
+             {
                  for flycheck in &self.flycheck {
                      flycheck.update();
                  }
                  }
  
                  let url = file_id_to_url(&self.vfs.read().0, file_id);
 -                let diagnostics = self.diagnostics.diagnostics_for(file_id).cloned().collect();
 +                let mut diagnostics =
 +                    self.diagnostics.diagnostics_for(file_id).cloned().collect::<Vec<_>>();
 +                // https://github.com/rust-lang/rust-analyzer/issues/11404
 +                for d in &mut diagnostics {
 +                    if d.message.is_empty() {
 +                        d.message = " ".to_string();
 +                    }
 +                    if let Some(rds) = d.related_information.as_mut() {
 +                        for rd in rds {
 +                            if rd.message.is_empty() {
 +                                rd.message = " ".to_string();
 +                            }
 +                        }
 +                    }
 +                }
                  let version = from_proto::vfs_path(&url)
                      .map(|path| self.mem_docs.get(&path).map(|it| it.version))
                      .unwrap_or_default();
                  Ok(())
              })?
              .on::<lsp_types::notification::DidSaveTextDocument>(|this, params| {
-                 for flycheck in &this.flycheck {
-                     flycheck.update();
+                 let mut updated = false;
+                 if let Ok(vfs_path) = from_proto::vfs_path(&params.text_document.uri) {
+                     let (vfs, _) = &*this.vfs.read();
+                     if let Some(file_id) = vfs.file_id(&vfs_path) {
+                         let analysis = this.analysis_host.analysis();
+                         // Crates containing or depending on the saved file
+                         let crate_ids: Vec<_> = analysis
+                             .crate_for(file_id)?
+                             .into_iter()
+                             .flat_map(|id| {
+                                 this.analysis_host
+                                     .raw_database()
+                                     .crate_graph()
+                                     .transitive_rev_deps(id)
+                             })
+                             .sorted()
+                             .unique()
+                             .collect();
+                         let crate_root_paths: Vec<_> = crate_ids
+                             .iter()
+                             .filter_map(|&crate_id| {
+                                 analysis
+                                     .crate_root(crate_id)
+                                     .map(|file_id| {
+                                         vfs.file_path(file_id).as_path().map(ToOwned::to_owned)
+                                     })
+                                     .transpose()
+                             })
+                             .collect::<ide::Cancellable<_>>()?;
+                         let crate_root_paths: Vec<_> =
+                             crate_root_paths.iter().map(Deref::deref).collect();
+                         // Find all workspaces that have at least one target containing the saved file
+                         let workspace_ids =
+                             this.workspaces.iter().enumerate().filter(|(_, ws)| match ws {
+                                 project_model::ProjectWorkspace::Cargo { cargo, .. } => {
+                                     cargo.packages().any(|pkg| {
+                                         cargo[pkg].targets.iter().any(|&it| {
+                                             crate_root_paths.contains(&cargo[it].root.as_path())
+                                         })
+                                     })
+                                 }
+                                 project_model::ProjectWorkspace::Json { project, .. } => project
+                                     .crates()
+                                     .any(|(c, _)| crate_ids.iter().any(|&crate_id| crate_id == c)),
+                                 project_model::ProjectWorkspace::DetachedFiles { .. } => false,
+                             });
+                         // Find and trigger corresponding flychecks
+                         for flycheck in &this.flycheck {
+                             for (id, _) in workspace_ids.clone() {
+                                 if id == flycheck.id() {
+                                     updated = true;
+                                     flycheck.update();
+                                     continue;
+                                 }
+                             }
+                         }
+                     }
+                     if let Some(abs_path) = vfs_path.as_path() {
+                         if reload::should_refresh_for_change(&abs_path, ChangeKind::Modify) {
+                             this.fetch_workspaces_queue
+                                 .request_op(format!("DidSaveTextDocument {}", abs_path.display()));
+                         }
+                     }
                  }
-                 if let Ok(abs_path) = from_proto::abs_path(&params.text_document.uri) {
-                     if reload::should_refresh_for_change(&abs_path, ChangeKind::Modify) {
-                         this.fetch_workspaces_queue
-                             .request_op(format!("DidSaveTextDocument {}", abs_path.display()));
+                 if !updated {
+                     for flycheck in &this.flycheck {
+                         flycheck.update();
                      }
                  }
                  Ok(())
index 9a9a9dd4d84c9028273994434d3ac4553f039b6f,579ba380273ab608be0ae957747d13407e3c6a1a..c90291eb5e29ad337dba58b8117eeed2bffbf76d
@@@ -303,62 -303,24 +303,62 @@@ impl GlobalState 
          let files_config = self.config.files();
          let project_folders = ProjectFolders::new(&self.workspaces, &files_config.exclude);
  
 -        if self.proc_macro_client.is_none() {
 +        let standalone_server_name =
 +            format!("rust-analyzer-proc-macro-srv{}", std::env::consts::EXE_SUFFIX);
 +
 +        if self.proc_macro_clients.is_empty() {
              if let Some((path, args)) = self.config.proc_macro_srv() {
 -                match ProcMacroServer::spawn(path.clone(), args) {
 -                    Ok(it) => self.proc_macro_client = Some(it),
 -                    Err(err) => {
 -                        tracing::error!(
 -                            "Failed to run proc_macro_srv from path {}, error: {:?}",
 +                self.proc_macro_clients = self
 +                    .workspaces
 +                    .iter()
 +                    .map(|ws| {
 +                        let mut args = args.clone();
 +                        let mut path = path.clone();
 +
 +                        if let ProjectWorkspace::Cargo { sysroot, .. } = ws {
 +                            tracing::info!("Found a cargo workspace...");
 +                            if let Some(sysroot) = sysroot.as_ref() {
 +                                tracing::info!("Found a cargo workspace with a sysroot...");
 +                                let server_path =
 +                                    sysroot.root().join("libexec").join(&standalone_server_name);
 +                                if std::fs::metadata(&server_path).is_ok() {
 +                                    tracing::info!(
 +                                        "And the server exists at {}",
 +                                        server_path.display()
 +                                    );
 +                                    path = server_path;
 +                                    args = vec![];
 +                                } else {
 +                                    tracing::info!(
 +                                        "And the server does not exist at {}",
 +                                        server_path.display()
 +                                    );
 +                                }
 +                            }
 +                        }
 +
 +                        tracing::info!(
 +                            "Using proc-macro server at {} with args {:?}",
                              path.display(),
 -                            err
 +                            args
                          );
 -                    }
 -                }
 +                        ProcMacroServer::spawn(path.clone(), args.clone()).map_err(|err| {
 +                            let error = format!(
 +                                "Failed to run proc_macro_srv from path {}, error: {:?}",
 +                                path.display(),
 +                                err
 +                            );
 +                            tracing::error!(error);
 +                            error
 +                        })
 +                    })
 +                    .collect();
              }
          }
  
          let watch = match files_config.watcher {
              FilesWatcher::Client => vec![],
 -            FilesWatcher::Notify => project_folders.watch,
 +            FilesWatcher::Server => project_folders.watch,
          };
          self.vfs_config_version += 1;
          self.loader.handle.set_config(vfs::loader::Config {
  
          // Create crate graph from all the workspaces
          let crate_graph = {
 -            let proc_macro_client = self.proc_macro_client.as_ref();
              let dummy_replacements = self.config.dummy_replacements();
 -            let mut load_proc_macro = move |crate_name: &str, path: &AbsPath| {
 -                load_proc_macro(
 -                    proc_macro_client,
 -                    path,
 -                    dummy_replacements.get(crate_name).map(|v| &**v).unwrap_or_default(),
 -                )
 -            };
  
              let vfs = &mut self.vfs.write().0;
              let loader = &mut self.loader;
              };
  
              let mut crate_graph = CrateGraph::default();
 -            for ws in self.workspaces.iter() {
 +            for (idx, ws) in self.workspaces.iter().enumerate() {
 +                let proc_macro_client = match self.proc_macro_clients.get(idx) {
 +                    Some(res) => res.as_ref().map_err(|e| &**e),
 +                    None => Err("Proc macros are disabled"),
 +                };
 +                let mut load_proc_macro = move |crate_name: &str, path: &AbsPath| {
 +                    load_proc_macro(
 +                        proc_macro_client,
 +                        path,
 +                        dummy_replacements.get(crate_name).map(|v| &**v).unwrap_or_default(),
 +                    )
 +                };
                  crate_graph.extend(ws.to_crate_graph(&mut load_proc_macro, &mut load));
              }
              crate_graph
              Some(it) => it,
              None => {
                  self.flycheck = Vec::new();
-                 self.diagnostics.clear_check();
+                 self.diagnostics.clear_check_all();
                  return;
              }
          };
@@@ -577,14 -536,14 +577,14 @@@ impl SourceRootConfig 
  /// Load the proc-macros for the given lib path, replacing all expanders whose names are in `dummy_replace`
  /// with an identity dummy expander.
  pub(crate) fn load_proc_macro(
 -    server: Option<&ProcMacroServer>,
 +    server: Result<&ProcMacroServer, &str>,
      path: &AbsPath,
      dummy_replace: &[Box<str>],
  ) -> ProcMacroLoadResult {
      let res: Result<Vec<_>, String> = (|| {
          let dylib = MacroDylib::new(path.to_path_buf())
              .map_err(|io| format!("Proc-macro dylib loading failed: {io}"))?;
 -        let server = server.ok_or_else(|| format!("Proc-macro server not started"))?;
 +        let server = server.map_err(ToOwned::to_owned)?;
          let vec = server.load_dylib(dylib).map_err(|e| format!("{e}"))?;
          if vec.is_empty() {
              return Err("proc macro library returned no proc macros".to_string());
          };
          let expander: Arc<dyn ProcMacroExpander> =
              if dummy_replace.iter().any(|replace| &**replace == name) {
 -                Arc::new(DummyExpander)
 +                match kind {
 +                    ProcMacroKind::Attr => Arc::new(IdentityExpander),
 +                    _ => Arc::new(EmptyExpander),
 +                }
              } else {
                  Arc::new(Expander(expander))
              };
          }
      }
  
 -    /// Dummy identity expander, used for proc-macros that are deliberately ignored by the user.
 +    /// Dummy identity expander, used for attribute proc-macros that are deliberately ignored by the user.
      #[derive(Debug)]
 -    struct DummyExpander;
 +    struct IdentityExpander;
  
 -    impl ProcMacroExpander for DummyExpander {
 +    impl ProcMacroExpander for IdentityExpander {
          fn expand(
              &self,
              subtree: &tt::Subtree,
              Ok(subtree.clone())
          }
      }
 +
 +    /// Empty expander, used for proc-macros that are deliberately ignored by the user.
 +    #[derive(Debug)]
 +    struct EmptyExpander;
 +
 +    impl ProcMacroExpander for EmptyExpander {
 +        fn expand(
 +            &self,
 +            _: &tt::Subtree,
 +            _: Option<&tt::Subtree>,
 +            _: &Env,
 +        ) -> Result<tt::Subtree, ProcMacroExpansionError> {
 +            Ok(tt::Subtree::default())
 +        }
 +    }
  }
  
  pub(crate) fn should_refresh_for_change(path: &AbsPath, change_kind: ChangeKind) -> bool {