From 7ab7633e915e99093c35353078a24347ffb49bfc Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 27 Feb 2025 17:40:10 +0100 Subject: [PATCH] Warn when the used toolchain looks too old for rust-analyzer --- crates/rust-analyzer/src/handlers/request.rs | 2 +- crates/rust-analyzer/src/lib.rs | 121 ++----------------- crates/rust-analyzer/src/lsp.rs | 113 +++++++++++++++++ crates/rust-analyzer/src/lsp/to_proto.rs | 2 +- crates/rust-analyzer/src/reload.rs | 16 +++ 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( .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))), ..