about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Wirth <lukastw97@gmail.com>2025-03-03 11:41:12 +0000
committerGitHub <noreply@github.com>2025-03-03 11:41:12 +0000
commit6610e60175f772dc3c193530957ad72ac8b27529 (patch)
tree16b615d4340e4f00ed4e43a292054bbf00d6a1e4
parent015e81d08f4320fd9bbaed390405cbea4e3b21a1 (diff)
parentf19b205df4cf90067c9590a752b3de46df4baf22 (diff)
downloadrust-6610e60175f772dc3c193530957ad72ac8b27529.tar.gz
rust-6610e60175f772dc3c193530957ad72ac8b27529.zip
Merge pull request #19244 from Veykril/push-nmnrnlysvyvk
Warn when the used toolchain looks too old for rust-analyzer
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs121
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lsp.rs113
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs2
-rw-r--r--src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs16
5 files changed, 140 insertions, 114 deletions
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
index b91a5dbd416..4ab96e9e2d5 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers/request.rs
@@ -32,13 +32,13 @@ use triomphe::Arc;
 use vfs::{AbsPath, AbsPathBuf, FileId, VfsPath};
 
 use crate::{
-    completion_item_hash,
     config::{Config, RustfmtConfig, WorkspaceSymbolConfig},
     diagnostics::convert_diagnostic,
     global_state::{FetchWorkspaceRequest, GlobalState, GlobalStateSnapshot},
     hack_recover_crate_name,
     line_index::LineEndings,
     lsp::{
+        completion_item_hash,
         ext::{
             InternalTestingFetchConfigOption, InternalTestingFetchConfigParams,
             InternalTestingFetchConfigResponse,
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs
index 27d6225cdb7..a0d6a0d6da6 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lib.rs
@@ -9,6 +9,15 @@
 //! The `cli` submodule implements some batch-processing analysis, primarily as
 //! a debugging aid.
 
+/// Any toolchain less than this version will likely not work with rust-analyzer built from this revision.
+pub const MINIMUM_SUPPORTED_TOOLCHAIN_VERSION: semver::Version = semver::Version {
+    major: 1,
+    minor: 78,
+    patch: 0,
+    pre: semver::Prerelease::EMPTY,
+    build: semver::BuildMetadata::EMPTY,
+};
+
 pub mod cli;
 
 mod command;
@@ -47,10 +56,7 @@ use self::lsp::ext as lsp_ext;
 #[cfg(test)]
 mod integrated_benchmarks;
 
-use hir::Mutability;
-use ide::{CompletionItem, CompletionItemRefMode, CompletionRelevance};
 use serde::de::DeserializeOwned;
-use tenthash::TentHash;
 
 pub use crate::{
     lsp::capabilities::server_capabilities, main_loop::main_loop, reload::ws_to_crate_graph,
@@ -65,115 +71,6 @@ pub fn from_json<T: DeserializeOwned>(
         .map_err(|e| anyhow::format_err!("Failed to deserialize {what}: {e}; {json}"))
 }
 
-fn completion_item_hash(item: &CompletionItem, is_ref_completion: bool) -> [u8; 20] {
-    fn hash_completion_relevance(hasher: &mut TentHash, relevance: &CompletionRelevance) {
-        use ide_completion::{
-            CompletionRelevancePostfixMatch, CompletionRelevanceReturnType,
-            CompletionRelevanceTypeMatch,
-        };
-
-        hasher.update([
-            u8::from(relevance.exact_name_match),
-            u8::from(relevance.is_local),
-            u8::from(relevance.is_name_already_imported),
-            u8::from(relevance.requires_import),
-            u8::from(relevance.is_private_editable),
-        ]);
-
-        match relevance.type_match {
-            None => hasher.update([0u8]),
-            Some(CompletionRelevanceTypeMatch::CouldUnify) => hasher.update([1u8]),
-            Some(CompletionRelevanceTypeMatch::Exact) => hasher.update([2u8]),
-        }
-
-        hasher.update([u8::from(relevance.trait_.is_some())]);
-        if let Some(trait_) = &relevance.trait_ {
-            hasher.update([u8::from(trait_.is_op_method), u8::from(trait_.notable_trait)]);
-        }
-
-        match relevance.postfix_match {
-            None => hasher.update([0u8]),
-            Some(CompletionRelevancePostfixMatch::NonExact) => hasher.update([1u8]),
-            Some(CompletionRelevancePostfixMatch::Exact) => hasher.update([2u8]),
-        }
-
-        hasher.update([u8::from(relevance.function.is_some())]);
-        if let Some(function) = &relevance.function {
-            hasher.update([u8::from(function.has_params), u8::from(function.has_self_param)]);
-            let discriminant: u8 = match function.return_type {
-                CompletionRelevanceReturnType::Other => 0,
-                CompletionRelevanceReturnType::DirectConstructor => 1,
-                CompletionRelevanceReturnType::Constructor => 2,
-                CompletionRelevanceReturnType::Builder => 3,
-            };
-            hasher.update([discriminant]);
-        }
-    }
-
-    let mut hasher = TentHash::new();
-    hasher.update([
-        u8::from(is_ref_completion),
-        u8::from(item.is_snippet),
-        u8::from(item.deprecated),
-        u8::from(item.trigger_call_info),
-    ]);
-
-    hasher.update(item.label.primary.len().to_ne_bytes());
-    hasher.update(&item.label.primary);
-
-    hasher.update([u8::from(item.label.detail_left.is_some())]);
-    if let Some(label_detail) = &item.label.detail_left {
-        hasher.update(label_detail.len().to_ne_bytes());
-        hasher.update(label_detail);
-    }
-
-    hasher.update([u8::from(item.label.detail_right.is_some())]);
-    if let Some(label_detail) = &item.label.detail_right {
-        hasher.update(label_detail.len().to_ne_bytes());
-        hasher.update(label_detail);
-    }
-
-    // NB: do not hash edits or source range, as those may change between the time the client sends the resolve request
-    // and the time it receives it: some editors do allow changing the buffer between that, leading to ranges being different.
-    //
-    // Documentation hashing is skipped too, as it's a large blob to process,
-    // while not really making completion properties more unique as they are already.
-
-    let kind_tag = item.kind.tag();
-    hasher.update(kind_tag.len().to_ne_bytes());
-    hasher.update(kind_tag);
-
-    hasher.update(item.lookup.len().to_ne_bytes());
-    hasher.update(&item.lookup);
-
-    hasher.update([u8::from(item.detail.is_some())]);
-    if let Some(detail) = &item.detail {
-        hasher.update(detail.len().to_ne_bytes());
-        hasher.update(detail);
-    }
-
-    hash_completion_relevance(&mut hasher, &item.relevance);
-
-    hasher.update([u8::from(item.ref_match.is_some())]);
-    if let Some((ref_mode, text_size)) = &item.ref_match {
-        let discriminant = match ref_mode {
-            CompletionItemRefMode::Reference(Mutability::Shared) => 0u8,
-            CompletionItemRefMode::Reference(Mutability::Mut) => 1u8,
-            CompletionItemRefMode::Dereference => 2u8,
-        };
-        hasher.update([discriminant]);
-        hasher.update(u32::from(*text_size).to_ne_bytes());
-    }
-
-    hasher.update(item.import_to_add.len().to_ne_bytes());
-    for import_path in &item.import_to_add {
-        hasher.update(import_path.len().to_ne_bytes());
-        hasher.update(import_path);
-    }
-
-    hasher.finalize()
-}
-
 #[doc(hidden)]
 macro_rules! try_default_ {
     ($it:expr $(,)?) => {
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp.rs
index 122ad20d65e..c7a5a95e66b 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp.rs
@@ -2,6 +2,10 @@
 
 use core::fmt;
 
+use hir::Mutability;
+use ide::{CompletionItem, CompletionItemRefMode, CompletionRelevance};
+use tenthash::TentHash;
+
 pub mod ext;
 
 pub(crate) mod capabilities;
@@ -29,3 +33,112 @@ impl fmt::Display for LspError {
 }
 
 impl std::error::Error for LspError {}
+
+pub(crate) fn completion_item_hash(item: &CompletionItem, is_ref_completion: bool) -> [u8; 20] {
+    fn hash_completion_relevance(hasher: &mut TentHash, relevance: &CompletionRelevance) {
+        use ide_completion::{
+            CompletionRelevancePostfixMatch, CompletionRelevanceReturnType,
+            CompletionRelevanceTypeMatch,
+        };
+
+        hasher.update([
+            u8::from(relevance.exact_name_match),
+            u8::from(relevance.is_local),
+            u8::from(relevance.is_name_already_imported),
+            u8::from(relevance.requires_import),
+            u8::from(relevance.is_private_editable),
+        ]);
+
+        match relevance.type_match {
+            None => hasher.update([0u8]),
+            Some(CompletionRelevanceTypeMatch::CouldUnify) => hasher.update([1u8]),
+            Some(CompletionRelevanceTypeMatch::Exact) => hasher.update([2u8]),
+        }
+
+        hasher.update([u8::from(relevance.trait_.is_some())]);
+        if let Some(trait_) = &relevance.trait_ {
+            hasher.update([u8::from(trait_.is_op_method), u8::from(trait_.notable_trait)]);
+        }
+
+        match relevance.postfix_match {
+            None => hasher.update([0u8]),
+            Some(CompletionRelevancePostfixMatch::NonExact) => hasher.update([1u8]),
+            Some(CompletionRelevancePostfixMatch::Exact) => hasher.update([2u8]),
+        }
+
+        hasher.update([u8::from(relevance.function.is_some())]);
+        if let Some(function) = &relevance.function {
+            hasher.update([u8::from(function.has_params), u8::from(function.has_self_param)]);
+            let discriminant: u8 = match function.return_type {
+                CompletionRelevanceReturnType::Other => 0,
+                CompletionRelevanceReturnType::DirectConstructor => 1,
+                CompletionRelevanceReturnType::Constructor => 2,
+                CompletionRelevanceReturnType::Builder => 3,
+            };
+            hasher.update([discriminant]);
+        }
+    }
+
+    let mut hasher = TentHash::new();
+    hasher.update([
+        u8::from(is_ref_completion),
+        u8::from(item.is_snippet),
+        u8::from(item.deprecated),
+        u8::from(item.trigger_call_info),
+    ]);
+
+    hasher.update(item.label.primary.len().to_ne_bytes());
+    hasher.update(&item.label.primary);
+
+    hasher.update([u8::from(item.label.detail_left.is_some())]);
+    if let Some(label_detail) = &item.label.detail_left {
+        hasher.update(label_detail.len().to_ne_bytes());
+        hasher.update(label_detail);
+    }
+
+    hasher.update([u8::from(item.label.detail_right.is_some())]);
+    if let Some(label_detail) = &item.label.detail_right {
+        hasher.update(label_detail.len().to_ne_bytes());
+        hasher.update(label_detail);
+    }
+
+    // NB: do not hash edits or source range, as those may change between the time the client sends the resolve request
+    // and the time it receives it: some editors do allow changing the buffer between that, leading to ranges being different.
+    //
+    // Documentation hashing is skipped too, as it's a large blob to process,
+    // while not really making completion properties more unique as they are already.
+
+    let kind_tag = item.kind.tag();
+    hasher.update(kind_tag.len().to_ne_bytes());
+    hasher.update(kind_tag);
+
+    hasher.update(item.lookup.len().to_ne_bytes());
+    hasher.update(&item.lookup);
+
+    hasher.update([u8::from(item.detail.is_some())]);
+    if let Some(detail) = &item.detail {
+        hasher.update(detail.len().to_ne_bytes());
+        hasher.update(detail);
+    }
+
+    hash_completion_relevance(&mut hasher, &item.relevance);
+
+    hasher.update([u8::from(item.ref_match.is_some())]);
+    if let Some((ref_mode, text_size)) = &item.ref_match {
+        let discriminant = match ref_mode {
+            CompletionItemRefMode::Reference(Mutability::Shared) => 0u8,
+            CompletionItemRefMode::Reference(Mutability::Mut) => 1u8,
+            CompletionItemRefMode::Dereference => 2u8,
+        };
+        hasher.update([discriminant]);
+        hasher.update(u32::from(*text_size).to_ne_bytes());
+    }
+
+    hasher.update(item.import_to_add.len().to_ne_bytes());
+    for import_path in &item.import_to_add {
+        hasher.update(import_path.len().to_ne_bytes());
+        hasher.update(import_path);
+    }
+
+    hasher.finalize()
+}
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs
index 446549c9070..3c206f47db5 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/lsp/to_proto.rs
@@ -24,11 +24,11 @@ use serde_json::to_value;
 use vfs::AbsPath;
 
 use crate::{
-    completion_item_hash,
     config::{CallInfoConfig, Config},
     global_state::GlobalStateSnapshot,
     line_index::{LineEndings, LineIndex, PositionEncoding},
     lsp::{
+        completion_item_hash,
         ext::ShellRunnableArgs,
         semantic_tokens::{self, standard_fallback_type},
         utils::invalid_params_error,
diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
index 56dcad0eb18..733a7c359b8 100644
--- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
+++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs
@@ -182,6 +182,22 @@ impl GlobalState {
                 self.proc_macro_clients.iter().map(Some).chain(iter::repeat_with(|| None));
 
             for (ws, proc_macro_client) in self.workspaces.iter().zip(proc_macro_clients) {
+                if let Some(toolchain) = &ws.toolchain {
+                    if *toolchain < crate::MINIMUM_SUPPORTED_TOOLCHAIN_VERSION {
+                        status.health |= lsp_ext::Health::Warning;
+                        format_to!(
+                            message,
+                            "Workspace `{}` is using an outdated toolchain version `{}` but \
+                            rust-analyzer only supports `{}` and higher.\n\
+                            Consider using the rust-analyzer rustup component for your toolchain or
+                            upgrade your toolchain to a supported version.\n\n",
+                            ws.manifest_or_root(),
+                            toolchain,
+                            crate::MINIMUM_SUPPORTED_TOOLCHAIN_VERSION,
+                        );
+                    }
+                }
+
                 if let ProjectWorkspaceKind::Cargo { error: Some(error), .. }
                 | ProjectWorkspaceKind::DetachedFile {
                     cargo: Some((_, _, Some(error))), ..