Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #19244 from Veykril/push-nmnrnlysvyvk
Warn when the used toolchain looks too old for rust-analyzer
Lukas Wirth 2025-03-03
parent 071eda7 · parent 7ab7633 · commit 8ff0b67
-rw-r--r--crates/rust-analyzer/src/handlers/request.rs2
-rw-r--r--crates/rust-analyzer/src/lib.rs121
-rw-r--r--crates/rust-analyzer/src/lsp.rs113
-rw-r--r--crates/rust-analyzer/src/lsp/to_proto.rs2
-rw-r--r--crates/rust-analyzer/src/reload.rs16
5 files changed, 140 insertions, 114 deletions
diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs
index b91a5dbd41..4ab96e9e2d 100644
--- a/crates/rust-analyzer/src/handlers/request.rs
+++ b/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/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs
index 27d6225cdb..a0d6a0d6da 100644
--- a/crates/rust-analyzer/src/lib.rs
+++ b/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/crates/rust-analyzer/src/lsp.rs b/crates/rust-analyzer/src/lsp.rs
index 122ad20d65..c7a5a95e66 100644
--- a/crates/rust-analyzer/src/lsp.rs
+++ b/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/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs
index 446549c907..3c206f47db 100644
--- a/crates/rust-analyzer/src/lsp/to_proto.rs
+++ b/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/crates/rust-analyzer/src/reload.rs b/crates/rust-analyzer/src/reload.rs
index 56dcad0eb1..733a7c359b 100644
--- a/crates/rust-analyzer/src/reload.rs
+++ b/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))), ..