From 66e8ef53a0ed018d03340577a0443030a193f773 Mon Sep 17 00:00:00 2001 From: Emil Lauridsen Date: Wed, 25 Dec 2019 12:21:38 +0100 Subject: Initial implementation of cargo check watching --- crates/ra_lsp_server/Cargo.toml | 1 + crates/ra_lsp_server/src/caps.rs | 4 +- crates/ra_lsp_server/src/cargo_check.rs | 533 +++++++++++++++++++++++++ crates/ra_lsp_server/src/lib.rs | 1 + crates/ra_lsp_server/src/main_loop.rs | 27 +- crates/ra_lsp_server/src/main_loop/handlers.rs | 28 +- crates/ra_lsp_server/src/world.rs | 8 + 7 files changed, 598 insertions(+), 4 deletions(-) create mode 100644 crates/ra_lsp_server/src/cargo_check.rs (limited to 'crates/ra_lsp_server') diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index 030e9033c..aa1acdc33 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml @@ -27,6 +27,7 @@ ra_project_model = { path = "../ra_project_model" } ra_prof = { path = "../ra_prof" } ra_vfs_glob = { path = "../ra_vfs_glob" } env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } +cargo_metadata = "0.9.1" [dev-dependencies] tempfile = "3" diff --git a/crates/ra_lsp_server/src/caps.rs b/crates/ra_lsp_server/src/caps.rs index ceb4c4259..0f84e7a34 100644 --- a/crates/ra_lsp_server/src/caps.rs +++ b/crates/ra_lsp_server/src/caps.rs @@ -6,7 +6,7 @@ use lsp_types::{ ImplementationProviderCapability, RenameOptions, RenameProviderCapability, SelectionRangeProviderCapability, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, - TypeDefinitionProviderCapability, WorkDoneProgressOptions, + TypeDefinitionProviderCapability, WorkDoneProgressOptions, SaveOptions }; pub fn server_capabilities() -> ServerCapabilities { @@ -16,7 +16,7 @@ pub fn server_capabilities() -> ServerCapabilities { change: Some(TextDocumentSyncKind::Full), will_save: None, will_save_wait_until: None, - save: None, + save: Some(SaveOptions::default()), })), hover_provider: Some(true), completion_provider: Some(CompletionOptions { diff --git a/crates/ra_lsp_server/src/cargo_check.rs b/crates/ra_lsp_server/src/cargo_check.rs new file mode 100644 index 000000000..d5ff02154 --- /dev/null +++ b/crates/ra_lsp_server/src/cargo_check.rs @@ -0,0 +1,533 @@ +use cargo_metadata::{ + diagnostic::{ + Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan, + DiagnosticSpanMacroExpansion, + }, + Message, +}; +use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender, TryRecvError}; +use lsp_types::{ + Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location, + NumberOrString, Position, Range, Url, +}; +use parking_lot::RwLock; +use std::{ + collections::HashMap, + fmt::Write, + path::PathBuf, + process::{Command, Stdio}, + sync::Arc, + thread::JoinHandle, + time::Instant, +}; + +#[derive(Debug)] +pub struct CheckWatcher { + pub task_recv: Receiver, + pub cmd_send: Sender, + pub shared: Arc>, + handle: JoinHandle<()>, +} + +impl CheckWatcher { + pub fn new(workspace_root: PathBuf) -> CheckWatcher { + let shared = Arc::new(RwLock::new(CheckWatcherSharedState::new())); + + let (task_send, task_recv) = unbounded::(); + let (cmd_send, cmd_recv) = unbounded::(); + let shared_ = shared.clone(); + let handle = std::thread::spawn(move || { + let mut check = CheckWatcherState::new(shared_, workspace_root); + check.run(&task_send, &cmd_recv); + }); + + CheckWatcher { task_recv, cmd_send, handle, shared } + } + + pub fn update(&self) { + self.cmd_send.send(CheckCommand::Update).unwrap(); + } +} + +pub struct CheckWatcherState { + workspace_root: PathBuf, + running: bool, + watcher: WatchThread, + last_update_req: Option, + shared: Arc>, +} + +#[derive(Debug)] +pub struct CheckWatcherSharedState { + diagnostic_collection: HashMap>, + suggested_fix_collection: HashMap>, +} + +impl CheckWatcherSharedState { + fn new() -> CheckWatcherSharedState { + CheckWatcherSharedState { + diagnostic_collection: HashMap::new(), + suggested_fix_collection: HashMap::new(), + } + } + + pub fn clear(&mut self, task_send: &Sender) { + let cleared_files: Vec = self.diagnostic_collection.keys().cloned().collect(); + + self.diagnostic_collection.clear(); + self.suggested_fix_collection.clear(); + + for uri in cleared_files { + task_send.send(CheckTask::Update(uri.clone())).unwrap(); + } + } + + pub fn diagnostics_for(&self, uri: &Url) -> Option<&[Diagnostic]> { + self.diagnostic_collection.get(uri).map(|d| d.as_slice()) + } + + pub fn fixes_for(&self, uri: &Url) -> Option<&[SuggestedFix]> { + self.suggested_fix_collection.get(uri).map(|d| d.as_slice()) + } + + fn add_diagnostic(&mut self, file_uri: Url, diagnostic: Diagnostic) { + let diagnostics = self.diagnostic_collection.entry(file_uri).or_default(); + + // If we're building multiple targets it's possible we've already seen this diagnostic + let is_duplicate = diagnostics.iter().any(|d| are_diagnostics_equal(d, &diagnostic)); + if is_duplicate { + return; + } + + diagnostics.push(diagnostic); + } + + fn add_suggested_fix_for_diagnostic( + &mut self, + mut suggested_fix: SuggestedFix, + diagnostic: &Diagnostic, + ) { + let file_uri = suggested_fix.location.uri.clone(); + let file_suggestions = self.suggested_fix_collection.entry(file_uri).or_default(); + + let existing_suggestion: Option<&mut SuggestedFix> = + file_suggestions.iter_mut().find(|s| s == &&suggested_fix); + if let Some(existing_suggestion) = existing_suggestion { + // The existing suggestion also applies to this new diagnostic + existing_suggestion.diagnostics.push(diagnostic.clone()); + } else { + // We haven't seen this suggestion before + suggested_fix.diagnostics.push(diagnostic.clone()); + file_suggestions.push(suggested_fix); + } + } +} + +#[derive(Debug)] +pub enum CheckTask { + Update(Url), +} + +pub enum CheckCommand { + Update, +} + +impl CheckWatcherState { + pub fn new( + shared: Arc>, + workspace_root: PathBuf, + ) -> CheckWatcherState { + let watcher = WatchThread::new(&workspace_root); + CheckWatcherState { workspace_root, running: false, watcher, last_update_req: None, shared } + } + + pub fn run(&mut self, task_send: &Sender, cmd_recv: &Receiver) { + self.running = true; + while self.running { + select! { + recv(&cmd_recv) -> cmd => match cmd { + Ok(cmd) => self.handle_command(cmd), + Err(RecvError) => { + // Command channel has closed, so shut down + self.running = false; + }, + }, + recv(self.watcher.message_recv) -> msg => match msg { + Ok(msg) => self.handle_message(msg, task_send), + Err(RecvError) => {}, + } + }; + + if self.should_recheck() { + self.last_update_req.take(); + self.shared.write().clear(task_send); + + self.watcher.cancel(); + self.watcher = WatchThread::new(&self.workspace_root); + } + } + } + + fn should_recheck(&mut self) -> bool { + if let Some(_last_update_req) = &self.last_update_req { + // We currently only request an update on save, as we need up to + // date source on disk for cargo check to do it's magic, so we + // don't really need to debounce the requests at this point. + return true; + } + false + } + + fn handle_command(&mut self, cmd: CheckCommand) { + match cmd { + CheckCommand::Update => self.last_update_req = Some(Instant::now()), + } + } + + fn handle_message(&mut self, msg: cargo_metadata::Message, task_send: &Sender) { + match msg { + Message::CompilerArtifact(_msg) => { + // TODO: Status display + } + + Message::CompilerMessage(msg) => { + let map_result = + match map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root) { + Some(map_result) => map_result, + None => return, + }; + + let MappedRustDiagnostic { location, diagnostic, suggested_fixes } = map_result; + let file_uri = location.uri.clone(); + + if !suggested_fixes.is_empty() { + for suggested_fix in suggested_fixes { + self.shared + .write() + .add_suggested_fix_for_diagnostic(suggested_fix, &diagnostic); + } + } + self.shared.write().add_diagnostic(file_uri, diagnostic); + + task_send.send(CheckTask::Update(location.uri)).unwrap(); + } + + Message::BuildScriptExecuted(_msg) => {} + Message::Unknown => {} + } + } +} + +/// WatchThread exists to wrap around the communication needed to be able to +/// run `cargo check` without blocking. Currently the Rust standard library +/// doesn't provide a way to read sub-process output without blocking, so we +/// have to wrap sub-processes output handling in a thread and pass messages +/// back over a channel. +struct WatchThread { + message_recv: Receiver, + cancel_send: Sender<()>, +} + +impl WatchThread { + fn new(workspace_root: &PathBuf) -> WatchThread { + let manifest_path = format!("{}/Cargo.toml", workspace_root.to_string_lossy()); + let (message_send, message_recv) = unbounded(); + let (cancel_send, cancel_recv) = unbounded(); + std::thread::spawn(move || { + let mut command = Command::new("cargo") + .args(&["check", "--message-format=json", "--manifest-path", &manifest_path]) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn() + .expect("couldn't launch cargo"); + + for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) { + match cancel_recv.try_recv() { + Ok(()) | Err(TryRecvError::Disconnected) => { + command.kill().expect("couldn't kill command"); + } + Err(TryRecvError::Empty) => (), + } + + message_send.send(message.unwrap()).unwrap(); + } + }); + WatchThread { message_recv, cancel_send } + } + + fn cancel(&self) { + let _ = self.cancel_send.send(()); + } +} + +/// Converts a Rust level string to a LSP severity +fn map_level_to_severity(val: DiagnosticLevel) -> Option { + match val { + DiagnosticLevel::Ice => Some(DiagnosticSeverity::Error), + DiagnosticLevel::Error => Some(DiagnosticSeverity::Error), + DiagnosticLevel::Warning => Some(DiagnosticSeverity::Warning), + DiagnosticLevel::Note => Some(DiagnosticSeverity::Information), + DiagnosticLevel::Help => Some(DiagnosticSeverity::Hint), + DiagnosticLevel::Unknown => None, + } +} + +/// Check whether a file name is from macro invocation +fn is_from_macro(file_name: &str) -> bool { + file_name.starts_with('<') && file_name.ends_with('>') +} + +/// Converts a Rust macro span to a LSP location recursively +fn map_macro_span_to_location( + span_macro: &DiagnosticSpanMacroExpansion, + workspace_root: &PathBuf, +) -> Option { + if !is_from_macro(&span_macro.span.file_name) { + return Some(map_span_to_location(&span_macro.span, workspace_root)); + } + + if let Some(expansion) = &span_macro.span.expansion { + return map_macro_span_to_location(&expansion, workspace_root); + } + + None +} + +/// Converts a Rust span to a LSP location +fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location { + if is_from_macro(&span.file_name) && span.expansion.is_some() { + let expansion = span.expansion.as_ref().unwrap(); + if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) { + return macro_range; + } + } + + let mut file_name = workspace_root.clone(); + file_name.push(&span.file_name); + let uri = Url::from_file_path(file_name).unwrap(); + + let range = Range::new( + Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1), + Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1), + ); + + Location { uri, range } +} + +/// Converts a secondary Rust span to a LSP related information +/// +/// If the span is unlabelled this will return `None`. +fn map_secondary_span_to_related( + span: &DiagnosticSpan, + workspace_root: &PathBuf, +) -> Option { + if let Some(label) = &span.label { + let location = map_span_to_location(span, workspace_root); + Some(DiagnosticRelatedInformation { location, message: label.clone() }) + } else { + // Nothing to label this with + None + } +} + +/// Determines if diagnostic is related to unused code +fn is_unused_or_unnecessary(rd: &RustDiagnostic) -> bool { + if let Some(code) = &rd.code { + match code.code.as_str() { + "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes" + | "unused_imports" | "unused_macros" | "unused_variables" => true, + _ => false, + } + } else { + false + } +} + +/// Determines if diagnostic is related to deprecated code +fn is_deprecated(rd: &RustDiagnostic) -> bool { + if let Some(code) = &rd.code { + match code.code.as_str() { + "deprecated" => true, + _ => false, + } + } else { + false + } +} + +#[derive(Debug)] +pub struct SuggestedFix { + pub title: String, + pub location: Location, + pub replacement: String, + pub applicability: Applicability, + pub diagnostics: Vec, +} + +impl std::cmp::PartialEq for SuggestedFix { + fn eq(&self, other: &SuggestedFix) -> bool { + if self.title == other.title + && self.location == other.location + && self.replacement == other.replacement + { + // Applicability doesn't impl PartialEq... + match (&self.applicability, &other.applicability) { + (Applicability::MachineApplicable, Applicability::MachineApplicable) => true, + (Applicability::HasPlaceholders, Applicability::HasPlaceholders) => true, + (Applicability::MaybeIncorrect, Applicability::MaybeIncorrect) => true, + (Applicability::Unspecified, Applicability::Unspecified) => true, + _ => false, + } + } else { + false + } + } +} + +enum MappedRustChildDiagnostic { + Related(DiagnosticRelatedInformation), + SuggestedFix(SuggestedFix), + MessageLine(String), +} + +fn map_rust_child_diagnostic( + rd: &RustDiagnostic, + workspace_root: &PathBuf, +) -> MappedRustChildDiagnostic { + let span: &DiagnosticSpan = match rd.spans.iter().find(|s| s.is_primary) { + Some(span) => span, + None => { + // `rustc` uses these spanless children as a way to print multi-line + // messages + return MappedRustChildDiagnostic::MessageLine(rd.message.clone()); + } + }; + + // If we have a primary span use its location, otherwise use the parent + let location = map_span_to_location(&span, workspace_root); + + if let Some(suggested_replacement) = &span.suggested_replacement { + // Include our replacement in the title unless it's empty + let title = if !suggested_replacement.is_empty() { + format!("{}: '{}'", rd.message, suggested_replacement) + } else { + rd.message.clone() + }; + + MappedRustChildDiagnostic::SuggestedFix(SuggestedFix { + title, + location, + replacement: suggested_replacement.clone(), + applicability: span.suggestion_applicability.clone().unwrap_or(Applicability::Unknown), + diagnostics: vec![], + }) + } else { + MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation { + location, + message: rd.message.clone(), + }) + } +} + +struct MappedRustDiagnostic { + location: Location, + diagnostic: Diagnostic, + suggested_fixes: Vec, +} + +/// Converts a Rust root diagnostic to LSP form +/// +/// This flattens the Rust diagnostic by: +/// +/// 1. Creating a LSP diagnostic with the root message and primary span. +/// 2. Adding any labelled secondary spans to `relatedInformation` +/// 3. Categorising child diagnostics as either `SuggestedFix`es, +/// `relatedInformation` or additional message lines. +/// +/// If the diagnostic has no primary span this will return `None` +fn map_rust_diagnostic_to_lsp( + rd: &RustDiagnostic, + workspace_root: &PathBuf, +) -> Option { + let primary_span = rd.spans.iter().find(|s| s.is_primary)?; + + let location = map_span_to_location(&primary_span, workspace_root); + + let severity = map_level_to_severity(rd.level); + let mut primary_span_label = primary_span.label.as_ref(); + + let mut source = String::from("rustc"); + let mut code = rd.code.as_ref().map(|c| c.code.clone()); + if let Some(code_val) = &code { + // See if this is an RFC #2103 scoped lint (e.g. from Clippy) + let scoped_code: Vec<&str> = code_val.split("::").collect(); + if scoped_code.len() == 2 { + source = String::from(scoped_code[0]); + code = Some(String::from(scoped_code[1])); + } + } + + let mut related_information = vec![]; + let mut tags = vec![]; + + for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) { + let related = map_secondary_span_to_related(secondary_span, workspace_root); + if let Some(related) = related { + related_information.push(related); + } + } + + let mut suggested_fixes = vec![]; + let mut message = rd.message.clone(); + for child in &rd.children { + let child = map_rust_child_diagnostic(&child, workspace_root); + match child { + MappedRustChildDiagnostic::Related(related) => related_information.push(related), + MappedRustChildDiagnostic::SuggestedFix(suggested_fix) => { + suggested_fixes.push(suggested_fix) + } + MappedRustChildDiagnostic::MessageLine(message_line) => { + write!(&mut message, "\n{}", message_line).unwrap(); + + // These secondary messages usually duplicate the content of the + // primary span label. + primary_span_label = None; + } + } + } + + if let Some(primary_span_label) = primary_span_label { + write!(&mut message, "\n{}", primary_span_label).unwrap(); + } + + if is_unused_or_unnecessary(rd) { + tags.push(DiagnosticTag::Unnecessary); + } + + if is_deprecated(rd) { + tags.push(DiagnosticTag::Deprecated); + } + + let diagnostic = Diagnostic { + range: location.range, + severity, + code: code.map(NumberOrString::String), + source: Some(source), + message: rd.message.clone(), + related_information: if !related_information.is_empty() { + Some(related_information) + } else { + None + }, + tags: if !tags.is_empty() { Some(tags) } else { None }, + }; + + Some(MappedRustDiagnostic { location, diagnostic, suggested_fixes }) +} + +fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool { + left.source == right.source + && left.severity == right.severity + && left.range == right.range + && left.message == right.message +} diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs index 2ca149fd5..2811231fa 100644 --- a/crates/ra_lsp_server/src/lib.rs +++ b/crates/ra_lsp_server/src/lib.rs @@ -22,6 +22,7 @@ macro_rules! print { } mod caps; +mod cargo_check; mod cargo_target_spec; mod conv; mod main_loop; diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index dda318e43..943d38943 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -19,6 +19,7 @@ use serde::{de::DeserializeOwned, Serialize}; use threadpool::ThreadPool; use crate::{ + cargo_check::CheckTask, main_loop::{ pending_requests::{PendingRequest, PendingRequests}, subscriptions::Subscriptions, @@ -176,7 +177,8 @@ pub fn main_loop( Ok(task) => Event::Vfs(task), Err(RecvError) => Err("vfs died")?, }, - recv(libdata_receiver) -> data => Event::Lib(data.unwrap()) + recv(libdata_receiver) -> data => Event::Lib(data.unwrap()), + recv(world_state.check_watcher.task_recv) -> task => Event::CheckWatcher(task.unwrap()) }; if let Event::Msg(Message::Request(req)) = &event { if connection.handle_shutdown(&req)? { @@ -222,6 +224,7 @@ enum Event { Task(Task), Vfs(VfsTask), Lib(LibraryData), + CheckWatcher(CheckTask), } impl fmt::Debug for Event { @@ -259,6 +262,7 @@ impl fmt::Debug for Event { Event::Task(it) => fmt::Debug::fmt(it, f), Event::Vfs(it) => fmt::Debug::fmt(it, f), Event::Lib(it) => fmt::Debug::fmt(it, f), + Event::CheckWatcher(it) => fmt::Debug::fmt(it, f), } } } @@ -318,6 +322,20 @@ fn loop_turn( world_state.maybe_collect_garbage(); loop_state.in_flight_libraries -= 1; } + Event::CheckWatcher(task) => match task { + CheckTask::Update(uri) => { + // We manually send a diagnostic update when the watcher asks + // us to, to avoid the issue of having to change the file to + // receive updated diagnostics. + let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?; + if let Some(file_id) = world_state.vfs.read().path2file(&path) { + let params = + handlers::publish_diagnostics(&world_state.snapshot(), FileId(file_id.0))?; + let not = notification_new::(params); + task_sender.send(Task::Notify(not)).unwrap(); + } + } + }, Event::Msg(msg) => match msg { Message::Request(req) => on_request( world_state, @@ -517,6 +535,13 @@ fn on_notification( } Err(not) => not, }; + let not = match notification_cast::(not) { + Ok(_params) => { + state.check_watcher.update(); + return Ok(()); + } + Err(not) => not, + }; let not = match notification_cast::(not) { Ok(params) => { let uri = params.text_document.uri; diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 39eb3df3e..331beab13 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs @@ -654,6 +654,29 @@ pub fn handle_code_action( res.push(action.into()); } + for fix in world.check_watcher.read().fixes_for(¶ms.text_document.uri).into_iter().flatten() + { + let fix_range = fix.location.range.conv_with(&line_index); + if fix_range.intersection(&range).is_none() { + continue; + } + + let edits = vec![TextEdit::new(fix.location.range, fix.replacement.clone())]; + let mut edit_map = std::collections::HashMap::new(); + edit_map.insert(fix.location.uri.clone(), edits); + let edit = WorkspaceEdit::new(edit_map); + + let action = CodeAction { + title: fix.title.clone(), + kind: Some("quickfix".to_string()), + diagnostics: Some(fix.diagnostics.clone()), + edit: Some(edit), + command: None, + is_preferred: None, + }; + res.push(action.into()); + } + for assist in assists { let title = assist.change.label.clone(); let edit = assist.change.try_conv_with(&world)?; @@ -820,7 +843,7 @@ pub fn publish_diagnostics( let _p = profile("publish_diagnostics"); let uri = world.file_id_to_uri(file_id)?; let line_index = world.analysis().file_line_index(file_id)?; - let diagnostics = world + let mut diagnostics: Vec = world .analysis() .diagnostics(file_id)? .into_iter() @@ -834,6 +857,9 @@ pub fn publish_diagnostics( tags: None, }) .collect(); + if let Some(check_diags) = world.check_watcher.read().diagnostics_for(&uri) { + diagnostics.extend(check_diags.iter().cloned()); + } Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None }) } diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs index 79431e7e6..8e9380ca0 100644 --- a/crates/ra_lsp_server/src/world.rs +++ b/crates/ra_lsp_server/src/world.rs @@ -24,6 +24,7 @@ use std::path::{Component, Prefix}; use crate::{ main_loop::pending_requests::{CompletedRequest, LatestRequests}, + cargo_check::{CheckWatcher, CheckWatcherSharedState}, LspError, Result, }; use std::str::FromStr; @@ -52,6 +53,7 @@ pub struct WorldState { pub vfs: Arc>, pub task_receiver: Receiver, pub latest_requests: Arc>, + pub check_watcher: CheckWatcher, } /// An immutable snapshot of the world's state at a point in time. @@ -61,6 +63,7 @@ pub struct WorldSnapshot { pub analysis: Analysis, pub vfs: Arc>, pub latest_requests: Arc>, + pub check_watcher: Arc>, } impl WorldState { @@ -127,6 +130,9 @@ impl WorldState { } change.set_crate_graph(crate_graph); + // FIXME: Figure out the multi-workspace situation + let check_watcher = CheckWatcher::new(folder_roots.first().cloned().unwrap()); + let mut analysis_host = AnalysisHost::new(lru_capacity, feature_flags); analysis_host.apply_change(change); WorldState { @@ -138,6 +144,7 @@ impl WorldState { vfs: Arc::new(RwLock::new(vfs)), task_receiver, latest_requests: Default::default(), + check_watcher, } } @@ -199,6 +206,7 @@ impl WorldState { analysis: self.analysis_host.analysis(), vfs: Arc::clone(&self.vfs), latest_requests: Arc::clone(&self.latest_requests), + check_watcher: self.check_watcher.shared.clone(), } } -- cgit v1.2.3 From 6af4bf7a8d27e653d2e6316172fe140871054d27 Mon Sep 17 00:00:00 2001 From: Emil Lauridsen Date: Wed, 25 Dec 2019 16:50:38 +0100 Subject: Configuration plumbing for cargo watcher --- crates/ra_lsp_server/src/cargo_check.rs | 50 +++++++++++++++++++++++++++------ crates/ra_lsp_server/src/config.rs | 7 +++++ crates/ra_lsp_server/src/main_loop.rs | 3 ++ crates/ra_lsp_server/src/world.rs | 5 +++- 4 files changed, 55 insertions(+), 10 deletions(-) (limited to 'crates/ra_lsp_server') diff --git a/crates/ra_lsp_server/src/cargo_check.rs b/crates/ra_lsp_server/src/cargo_check.rs index d5ff02154..f98b4f69c 100644 --- a/crates/ra_lsp_server/src/cargo_check.rs +++ b/crates/ra_lsp_server/src/cargo_check.rs @@ -1,3 +1,4 @@ +use crate::world::Options; use cargo_metadata::{ diagnostic::{ Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan, @@ -30,14 +31,17 @@ pub struct CheckWatcher { } impl CheckWatcher { - pub fn new(workspace_root: PathBuf) -> CheckWatcher { + pub fn new(options: &Options, workspace_root: PathBuf) -> CheckWatcher { + let check_command = options.cargo_check_command.clone(); + let check_args = options.cargo_check_args.clone(); let shared = Arc::new(RwLock::new(CheckWatcherSharedState::new())); let (task_send, task_recv) = unbounded::(); let (cmd_send, cmd_recv) = unbounded::(); let shared_ = shared.clone(); let handle = std::thread::spawn(move || { - let mut check = CheckWatcherState::new(shared_, workspace_root); + let mut check = + CheckWatcherState::new(check_command, check_args, workspace_root, shared_); check.run(&task_send, &cmd_recv); }); @@ -50,6 +54,8 @@ impl CheckWatcher { } pub struct CheckWatcherState { + check_command: Option, + check_args: Vec, workspace_root: PathBuf, running: bool, watcher: WatchThread, @@ -134,11 +140,21 @@ pub enum CheckCommand { impl CheckWatcherState { pub fn new( - shared: Arc>, + check_command: Option, + check_args: Vec, workspace_root: PathBuf, + shared: Arc>, ) -> CheckWatcherState { - let watcher = WatchThread::new(&workspace_root); - CheckWatcherState { workspace_root, running: false, watcher, last_update_req: None, shared } + let watcher = WatchThread::new(check_command.as_ref(), &check_args, &workspace_root); + CheckWatcherState { + check_command, + check_args, + workspace_root, + running: false, + watcher, + last_update_req: None, + shared, + } } pub fn run(&mut self, task_send: &Sender, cmd_recv: &Receiver) { @@ -163,7 +179,11 @@ impl CheckWatcherState { self.shared.write().clear(task_send); self.watcher.cancel(); - self.watcher = WatchThread::new(&self.workspace_root); + self.watcher = WatchThread::new( + self.check_command.as_ref(), + &self.check_args, + &self.workspace_root, + ); } } } @@ -229,13 +249,25 @@ struct WatchThread { } impl WatchThread { - fn new(workspace_root: &PathBuf) -> WatchThread { - let manifest_path = format!("{}/Cargo.toml", workspace_root.to_string_lossy()); + fn new( + check_command: Option<&String>, + check_args: &[String], + workspace_root: &PathBuf, + ) -> WatchThread { + let check_command = check_command.cloned().unwrap_or("check".to_string()); + let mut args: Vec = vec![ + check_command, + "--message-format=json".to_string(), + "--manifest-path".to_string(), + format!("{}/Cargo.toml", workspace_root.to_string_lossy()), + ]; + args.extend(check_args.iter().cloned()); + let (message_send, message_recv) = unbounded(); let (cancel_send, cancel_recv) = unbounded(); std::thread::spawn(move || { let mut command = Command::new("cargo") - .args(&["check", "--message-format=json", "--manifest-path", &manifest_path]) + .args(&args) .stdout(Stdio::piped()) .stderr(Stdio::null()) .spawn() diff --git a/crates/ra_lsp_server/src/config.rs b/crates/ra_lsp_server/src/config.rs index 67942aa41..621f2238c 100644 --- a/crates/ra_lsp_server/src/config.rs +++ b/crates/ra_lsp_server/src/config.rs @@ -32,6 +32,10 @@ pub struct ServerConfig { pub max_inlay_hint_length: Option, + pub cargo_check_enable: bool, + pub cargo_check_command: Option, + pub cargo_check_args: Vec, + /// For internal usage to make integrated tests faster. #[serde(deserialize_with = "nullable_bool_true")] pub with_sysroot: bool, @@ -51,6 +55,9 @@ impl Default for ServerConfig { use_client_watching: false, lru_capacity: None, max_inlay_hint_length: None, + cargo_check_enable: true, + cargo_check_command: None, + cargo_check_args: vec![], with_sysroot: true, feature_flags: FxHashMap::default(), cargo_features: Default::default(), diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index 943d38943..1f6175699 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -127,6 +127,9 @@ pub fn main_loop( .and_then(|it| it.line_folding_only) .unwrap_or(false), max_inlay_hint_length: config.max_inlay_hint_length, + cargo_check_enable: config.cargo_check_enable, + cargo_check_command: config.cargo_check_command, + cargo_check_args: config.cargo_check_args, } }; diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs index 8e9380ca0..235eb199d 100644 --- a/crates/ra_lsp_server/src/world.rs +++ b/crates/ra_lsp_server/src/world.rs @@ -35,6 +35,9 @@ pub struct Options { pub supports_location_link: bool, pub line_folding_only: bool, pub max_inlay_hint_length: Option, + pub cargo_check_enable: bool, + pub cargo_check_command: Option, + pub cargo_check_args: Vec, } /// `WorldState` is the primary mutable state of the language server @@ -131,7 +134,7 @@ impl WorldState { change.set_crate_graph(crate_graph); // FIXME: Figure out the multi-workspace situation - let check_watcher = CheckWatcher::new(folder_roots.first().cloned().unwrap()); + let check_watcher = CheckWatcher::new(&options, folder_roots.first().cloned().unwrap()); let mut analysis_host = AnalysisHost::new(lru_capacity, feature_flags); analysis_host.apply_change(change); -- cgit v1.2.3 From 17360b5d14ec98085169bfb344b220dcfc75b9f0 Mon Sep 17 00:00:00 2001 From: Emil Lauridsen Date: Wed, 25 Dec 2019 17:14:20 +0100 Subject: Fix use of wrong message in diagnostic --- crates/ra_lsp_server/src/cargo_check.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates/ra_lsp_server') diff --git a/crates/ra_lsp_server/src/cargo_check.rs b/crates/ra_lsp_server/src/cargo_check.rs index f98b4f69c..30716fc43 100644 --- a/crates/ra_lsp_server/src/cargo_check.rs +++ b/crates/ra_lsp_server/src/cargo_check.rs @@ -545,7 +545,7 @@ fn map_rust_diagnostic_to_lsp( severity, code: code.map(NumberOrString::String), source: Some(source), - message: rd.message.clone(), + message, related_information: if !related_information.is_empty() { Some(related_information) } else { -- cgit v1.2.3 From c21fbc3e87d1e701f29fafcdad0a73e8d69a2f29 Mon Sep 17 00:00:00 2001 From: Emil Lauridsen Date: Wed, 25 Dec 2019 17:31:49 +0100 Subject: Migrate tests from extension to rust --- crates/ra_lsp_server/Cargo.toml | 1 + crates/ra_lsp_server/src/cargo_check.rs | 709 +++++++++++++++++++++ .../snapshots/test__snap_clippy_pass_by_ref.snap | 85 +++ .../test__snap_handles_macro_location.snap | 46 ++ ...st__snap_rustc_incompatible_type_for_trait.snap | 46 ++ .../test__snap_rustc_mismatched_type.snap | 46 ++ .../test__snap_rustc_unused_variable.snap | 70 ++ ...est__snap_rustc_wrong_number_of_parameters.snap | 65 ++ 8 files changed, 1068 insertions(+) create mode 100644 crates/ra_lsp_server/src/snapshots/test__snap_clippy_pass_by_ref.snap create mode 100644 crates/ra_lsp_server/src/snapshots/test__snap_handles_macro_location.snap create mode 100644 crates/ra_lsp_server/src/snapshots/test__snap_rustc_incompatible_type_for_trait.snap create mode 100644 crates/ra_lsp_server/src/snapshots/test__snap_rustc_mismatched_type.snap create mode 100644 crates/ra_lsp_server/src/snapshots/test__snap_rustc_unused_variable.snap create mode 100644 crates/ra_lsp_server/src/snapshots/test__snap_rustc_wrong_number_of_parameters.snap (limited to 'crates/ra_lsp_server') diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index aa1acdc33..54a01d7a2 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml @@ -30,6 +30,7 @@ env_logger = { version = "0.7.1", default-features = false, features = ["humanti cargo_metadata = "0.9.1" [dev-dependencies] +insta = "0.12.0" tempfile = "3" test_utils = { path = "../test_utils" } diff --git a/crates/ra_lsp_server/src/cargo_check.rs b/crates/ra_lsp_server/src/cargo_check.rs index 30716fc43..5a6a209eb 100644 --- a/crates/ra_lsp_server/src/cargo_check.rs +++ b/crates/ra_lsp_server/src/cargo_check.rs @@ -461,6 +461,7 @@ fn map_rust_child_diagnostic( } } +#[derive(Debug)] struct MappedRustDiagnostic { location: Location, diagnostic: Diagnostic, @@ -563,3 +564,711 @@ fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool { && left.range == right.range && left.message == right.message } + +#[cfg(test)] +mod test { + use super::*; + + fn parse_diagnostic(val: &str) -> cargo_metadata::diagnostic::Diagnostic { + serde_json::from_str::(val).unwrap() + } + + #[test] + fn snap_rustc_incompatible_type_for_trait() { + let diag = parse_diagnostic( + r##"{ + "message": "method `next` has an incompatible type for trait", + "code": { + "code": "E0053", + "explanation": "\nThe parameters of any trait method must match between a trait implementation\nand the trait definition.\n\nHere are a couple examples of this error:\n\n```compile_fail,E0053\ntrait Foo {\n fn foo(x: u16);\n fn bar(&self);\n}\n\nstruct Bar;\n\nimpl Foo for Bar {\n // error, expected u16, found i16\n fn foo(x: i16) { }\n\n // error, types differ in mutability\n fn bar(&mut self) { }\n}\n```\n" + }, + "level": "error", + "spans": [ + { + "file_name": "compiler/ty/list_iter.rs", + "byte_start": 1307, + "byte_end": 1350, + "line_start": 52, + "line_end": 52, + "column_start": 5, + "column_end": 48, + "is_primary": true, + "text": [ + { + "text": " fn next(&self) -> Option<&'list ty::Ref> {", + "highlight_start": 5, + "highlight_end": 48 + } + ], + "label": "types differ in mutability", + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + } + ], + "children": [ + { + "message": "expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref>`", + "code": null, + "level": "note", + "spans": [], + "children": [], + "rendered": null + } + ], + "rendered": "error[E0053]: method `next` has an incompatible type for trait\n --> compiler/ty/list_iter.rs:52:5\n |\n52 | fn next(&self) -> Option<&'list ty::Ref> {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability\n |\n = note: expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref>`\n\n" + } + "##, + ); + + let workspace_root = PathBuf::from("/test/"); + let diag = + map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic"); + insta::assert_debug_snapshot!(diag); + } + + #[test] + fn snap_rustc_unused_variable() { + let diag = parse_diagnostic( + r##"{ + "message": "unused variable: `foo`", + "code": { + "code": "unused_variables", + "explanation": null + }, + "level": "warning", + "spans": [ + { + "file_name": "driver/subcommand/repl.rs", + "byte_start": 9228, + "byte_end": 9231, + "line_start": 291, + "line_end": 291, + "column_start": 9, + "column_end": 12, + "is_primary": true, + "text": [ + { + "text": " let foo = 42;", + "highlight_start": 9, + "highlight_end": 12 + } + ], + "label": null, + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + } + ], + "children": [ + { + "message": "#[warn(unused_variables)] on by default", + "code": null, + "level": "note", + "spans": [], + "children": [], + "rendered": null + }, + { + "message": "consider prefixing with an underscore", + "code": null, + "level": "help", + "spans": [ + { + "file_name": "driver/subcommand/repl.rs", + "byte_start": 9228, + "byte_end": 9231, + "line_start": 291, + "line_end": 291, + "column_start": 9, + "column_end": 12, + "is_primary": true, + "text": [ + { + "text": " let foo = 42;", + "highlight_start": 9, + "highlight_end": 12 + } + ], + "label": null, + "suggested_replacement": "_foo", + "suggestion_applicability": "MachineApplicable", + "expansion": null + } + ], + "children": [], + "rendered": null + } + ], + "rendered": "warning: unused variable: `foo`\n --> driver/subcommand/repl.rs:291:9\n |\n291 | let foo = 42;\n | ^^^ help: consider prefixing with an underscore: `_foo`\n |\n = note: #[warn(unused_variables)] on by default\n\n" +}"##, + ); + + let workspace_root = PathBuf::from("/test/"); + let diag = + map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic"); + insta::assert_debug_snapshot!(diag); + } + + #[test] + fn snap_rustc_wrong_number_of_parameters() { + let diag = parse_diagnostic( + r##"{ + "message": "this function takes 2 parameters but 3 parameters were supplied", + "code": { + "code": "E0061", + "explanation": "\nThe number of arguments passed to a function must match the number of arguments\nspecified in the function signature.\n\nFor example, a function like:\n\n```\nfn f(a: u16, b: &str) {}\n```\n\nMust always be called with exactly two arguments, e.g., `f(2, \"test\")`.\n\nNote that Rust does not have a notion of optional function arguments or\nvariadic functions (except for its C-FFI).\n" + }, + "level": "error", + "spans": [ + { + "file_name": "compiler/ty/select.rs", + "byte_start": 8787, + "byte_end": 9241, + "line_start": 219, + "line_end": 231, + "column_start": 5, + "column_end": 6, + "is_primary": false, + "text": [ + { + "text": " pub fn add_evidence(", + "highlight_start": 5, + "highlight_end": 25 + }, + { + "text": " &mut self,", + "highlight_start": 1, + "highlight_end": 19 + }, + { + "text": " target_poly: &ty::Ref,", + "highlight_start": 1, + "highlight_end": 41 + }, + { + "text": " evidence_poly: &ty::Ref,", + "highlight_start": 1, + "highlight_end": 43 + }, + { + "text": " ) {", + "highlight_start": 1, + "highlight_end": 8 + }, + { + "text": " match target_poly {", + "highlight_start": 1, + "highlight_end": 28 + }, + { + "text": " ty::Ref::Var(tvar, _) => self.add_var_evidence(tvar, evidence_poly),", + "highlight_start": 1, + "highlight_end": 81 + }, + { + "text": " ty::Ref::Fixed(target_ty) => {", + "highlight_start": 1, + "highlight_end": 43 + }, + { + "text": " let evidence_ty = evidence_poly.resolve_to_ty();", + "highlight_start": 1, + "highlight_end": 65 + }, + { + "text": " self.add_evidence_ty(target_ty, evidence_poly, evidence_ty)", + "highlight_start": 1, + "highlight_end": 76 + }, + { + "text": " }", + "highlight_start": 1, + "highlight_end": 14 + }, + { + "text": " }", + "highlight_start": 1, + "highlight_end": 10 + }, + { + "text": " }", + "highlight_start": 1, + "highlight_end": 6 + } + ], + "label": "defined here", + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + }, + { + "file_name": "compiler/ty/select.rs", + "byte_start": 4045, + "byte_end": 4057, + "line_start": 104, + "line_end": 104, + "column_start": 18, + "column_end": 30, + "is_primary": true, + "text": [ + { + "text": " self.add_evidence(target_fixed, evidence_fixed, false);", + "highlight_start": 18, + "highlight_end": 30 + } + ], + "label": "expected 2 parameters", + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + } + ], + "children": [], + "rendered": "error[E0061]: this function takes 2 parameters but 3 parameters were supplied\n --> compiler/ty/select.rs:104:18\n |\n104 | self.add_evidence(target_fixed, evidence_fixed, false);\n | ^^^^^^^^^^^^ expected 2 parameters\n...\n219 | / pub fn add_evidence(\n220 | | &mut self,\n221 | | target_poly: &ty::Ref,\n222 | | evidence_poly: &ty::Ref,\n... |\n230 | | }\n231 | | }\n | |_____- defined here\n\n" +}"##, + ); + + let workspace_root = PathBuf::from("/test/"); + let diag = + map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic"); + insta::assert_debug_snapshot!(diag); + } + + #[test] + fn snap_clippy_pass_by_ref() { + let diag = parse_diagnostic( + r##"{ + "message": "this argument is passed by reference, but would be more efficient if passed by value", + "code": { + "code": "clippy::trivially_copy_pass_by_ref", + "explanation": null + }, + "level": "warning", + "spans": [ + { + "file_name": "compiler/mir/tagset.rs", + "byte_start": 941, + "byte_end": 946, + "line_start": 42, + "line_end": 42, + "column_start": 24, + "column_end": 29, + "is_primary": true, + "text": [ + { + "text": " pub fn is_disjoint(&self, other: Self) -> bool {", + "highlight_start": 24, + "highlight_end": 29 + } + ], + "label": null, + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + } + ], + "children": [ + { + "message": "lint level defined here", + "code": null, + "level": "note", + "spans": [ + { + "file_name": "compiler/lib.rs", + "byte_start": 8, + "byte_end": 19, + "line_start": 1, + "line_end": 1, + "column_start": 9, + "column_end": 20, + "is_primary": true, + "text": [ + { + "text": "#![warn(clippy::all)]", + "highlight_start": 9, + "highlight_end": 20 + } + ], + "label": null, + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + } + ], + "children": [], + "rendered": null + }, + { + "message": "#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]", + "code": null, + "level": "note", + "spans": [], + "children": [], + "rendered": null + }, + { + "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref", + "code": null, + "level": "help", + "spans": [], + "children": [], + "rendered": null + }, + { + "message": "consider passing by value instead", + "code": null, + "level": "help", + "spans": [ + { + "file_name": "compiler/mir/tagset.rs", + "byte_start": 941, + "byte_end": 946, + "line_start": 42, + "line_end": 42, + "column_start": 24, + "column_end": 29, + "is_primary": true, + "text": [ + { + "text": " pub fn is_disjoint(&self, other: Self) -> bool {", + "highlight_start": 24, + "highlight_end": 29 + } + ], + "label": null, + "suggested_replacement": "self", + "suggestion_applicability": "Unspecified", + "expansion": null + } + ], + "children": [], + "rendered": null + } + ], + "rendered": "warning: this argument is passed by reference, but would be more efficient if passed by value\n --> compiler/mir/tagset.rs:42:24\n |\n42 | pub fn is_disjoint(&self, other: Self) -> bool {\n | ^^^^^ help: consider passing by value instead: `self`\n |\nnote: lint level defined here\n --> compiler/lib.rs:1:9\n |\n1 | #![warn(clippy::all)]\n | ^^^^^^^^^^^\n = note: #[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref\n\n" +}"##, + ); + + let workspace_root = PathBuf::from("/test/"); + let diag = + map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic"); + insta::assert_debug_snapshot!(diag); + } + + #[test] + fn snap_rustc_mismatched_type() { + let diag = parse_diagnostic( + r##"{ + "message": "mismatched types", + "code": { + "code": "E0308", + "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n" + }, + "level": "error", + "spans": [ + { + "file_name": "runtime/compiler_support.rs", + "byte_start": 1589, + "byte_end": 1594, + "line_start": 48, + "line_end": 48, + "column_start": 65, + "column_end": 70, + "is_primary": true, + "text": [ + { + "text": " let layout = alloc::Layout::from_size_align_unchecked(size, align);", + "highlight_start": 65, + "highlight_end": 70 + } + ], + "label": "expected usize, found u32", + "suggested_replacement": null, + "suggestion_applicability": null, + "expansion": null + } + ], + "children": [], + "rendered": "error[E0308]: mismatched types\n --> runtime/compiler_support.rs:48:65\n |\n48 | let layout = alloc::Layout::from_size_align_unchecked(size, align);\n | ^^^^^ expected usize, found u32\n\n" +}"##, + ); + + let workspace_root = PathBuf::from("/test/"); + let diag = + map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic"); + insta::assert_debug_snapshot!(diag); + } + + #[test] + fn snap_handles_macro_location() { + let diag = parse_diagnostic( + r##"{ + "rendered": "error[E0277]: can't compare `{integer}` with `&str`\n --> src/main.rs:2:5\n |\n2 | assert_eq!(1, \"love\");\n | ^^^^^^^^^^^^^^^^^^^^^^ no implementation for `{integer} == &str`\n |\n = help: the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`\n = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)\n\n", + "children": [ + { + "children": [], + "code": null, + "level": "help", + "message": "the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`", + "rendered": null, + "spans": [] + } + ], + "code": { + "code": "E0277", + "explanation": "\nYou tried to use a type which doesn't implement some trait in a place which\nexpected that trait. Erroneous code example:\n\n```compile_fail,E0277\n// here we declare the Foo trait with a bar method\ntrait Foo {\n fn bar(&self);\n}\n\n// we now declare a function which takes an object implementing the Foo trait\nfn some_func(foo: T) {\n foo.bar();\n}\n\nfn main() {\n // we now call the method with the i32 type, which doesn't implement\n // the Foo trait\n some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n fn bar(&self);\n}\n\nfn some_func(foo: T) {\n foo.bar(); // we can now use this method since i32 implements the\n // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n fn bar(&self) {}\n}\n\nfn main() {\n some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func(foo: T) {\n println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n // implemented for the type `T`\n}\n\nfn main() {\n // We now call the method with the i32 type,\n // which *does* implement the Debug trait.\n some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func(foo: T) {\n println!(\"{:?}\", foo);\n}\n\nfn main() {\n // Calling the method is still fine, as i32 implements Debug.\n some_func(5i32);\n\n // This would fail to compile now:\n // struct WithoutDebug;\n // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n" + }, + "level": "error", + "message": "can't compare `{integer}` with `&str`", + "spans": [ + { + "byte_end": 155, + "byte_start": 153, + "column_end": 33, + "column_start": 31, + "expansion": { + "def_site_span": { + "byte_end": 940, + "byte_start": 0, + "column_end": 6, + "column_start": 1, + "expansion": null, + "file_name": "<::core::macros::assert_eq macros>", + "is_primary": false, + "label": null, + "line_end": 36, + "line_start": 1, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 35, + "highlight_start": 1, + "text": "($ left : expr, $ right : expr) =>" + }, + { + "highlight_end": 3, + "highlight_start": 1, + "text": "({" + }, + { + "highlight_end": 33, + "highlight_start": 1, + "text": " match (& $ left, & $ right)" + }, + { + "highlight_end": 7, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 34, + "highlight_start": 1, + "text": " (left_val, right_val) =>" + }, + { + "highlight_end": 11, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 46, + "highlight_start": 1, + "text": " if ! (* left_val == * right_val)" + }, + { + "highlight_end": 15, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 25, + "highlight_start": 1, + "text": " panic !" + }, + { + "highlight_end": 57, + "highlight_start": 1, + "text": " (r#\"assertion failed: `(left == right)`" + }, + { + "highlight_end": 16, + "highlight_start": 1, + "text": " left: `{:?}`," + }, + { + "highlight_end": 18, + "highlight_start": 1, + "text": " right: `{:?}`\"#," + }, + { + "highlight_end": 47, + "highlight_start": 1, + "text": " & * left_val, & * right_val)" + }, + { + "highlight_end": 15, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 11, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 7, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 42, + "highlight_start": 1, + "text": " }) ; ($ left : expr, $ right : expr,) =>" + }, + { + "highlight_end": 49, + "highlight_start": 1, + "text": "({ $ crate :: assert_eq ! ($ left, $ right) }) ;" + }, + { + "highlight_end": 53, + "highlight_start": 1, + "text": "($ left : expr, $ right : expr, $ ($ arg : tt) +) =>" + }, + { + "highlight_end": 3, + "highlight_start": 1, + "text": "({" + }, + { + "highlight_end": 37, + "highlight_start": 1, + "text": " match (& ($ left), & ($ right))" + }, + { + "highlight_end": 7, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 34, + "highlight_start": 1, + "text": " (left_val, right_val) =>" + }, + { + "highlight_end": 11, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 46, + "highlight_start": 1, + "text": " if ! (* left_val == * right_val)" + }, + { + "highlight_end": 15, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 25, + "highlight_start": 1, + "text": " panic !" + }, + { + "highlight_end": 57, + "highlight_start": 1, + "text": " (r#\"assertion failed: `(left == right)`" + }, + { + "highlight_end": 16, + "highlight_start": 1, + "text": " left: `{:?}`," + }, + { + "highlight_end": 22, + "highlight_start": 1, + "text": " right: `{:?}`: {}\"#," + }, + { + "highlight_end": 72, + "highlight_start": 1, + "text": " & * left_val, & * right_val, $ crate :: format_args !" + }, + { + "highlight_end": 33, + "highlight_start": 1, + "text": " ($ ($ arg) +))" + }, + { + "highlight_end": 15, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 11, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 7, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 6, + "highlight_start": 1, + "text": " }) ;" + } + ] + }, + "macro_decl_name": "assert_eq!", + "span": { + "byte_end": 38, + "byte_start": 16, + "column_end": 27, + "column_start": 5, + "expansion": null, + "file_name": "src/main.rs", + "is_primary": false, + "label": null, + "line_end": 2, + "line_start": 2, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 27, + "highlight_start": 5, + "text": " assert_eq!(1, \"love\");" + } + ] + } + }, + "file_name": "<::core::macros::assert_eq macros>", + "is_primary": true, + "label": "no implementation for `{integer} == &str`", + "line_end": 7, + "line_start": 7, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 33, + "highlight_start": 31, + "text": " if ! (* left_val == * right_val)" + } + ] + } + ] +}"##, + ); + + let workspace_root = PathBuf::from("/test/"); + let diag = + map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic"); + insta::assert_debug_snapshot!(diag); + } +} diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_clippy_pass_by_ref.snap b/crates/ra_lsp_server/src/snapshots/test__snap_clippy_pass_by_ref.snap new file mode 100644 index 000000000..a5ce29157 --- /dev/null +++ b/crates/ra_lsp_server/src/snapshots/test__snap_clippy_pass_by_ref.snap @@ -0,0 +1,85 @@ +--- +source: crates/ra_lsp_server/src/cargo_check.rs +expression: diag +--- +MappedRustDiagnostic { + location: Location { + uri: "file:///test/compiler/mir/tagset.rs", + range: Range { + start: Position { + line: 41, + character: 23, + }, + end: Position { + line: 41, + character: 28, + }, + }, + }, + diagnostic: Diagnostic { + range: Range { + start: Position { + line: 41, + character: 23, + }, + end: Position { + line: 41, + character: 28, + }, + }, + severity: Some( + Warning, + ), + code: Some( + String( + "trivially_copy_pass_by_ref", + ), + ), + source: Some( + "clippy", + ), + message: "this argument is passed by reference, but would be more efficient if passed by value\n#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\nfor further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref", + related_information: Some( + [ + DiagnosticRelatedInformation { + location: Location { + uri: "file:///test/compiler/lib.rs", + range: Range { + start: Position { + line: 0, + character: 8, + }, + end: Position { + line: 0, + character: 19, + }, + }, + }, + message: "lint level defined here", + }, + ], + ), + tags: None, + }, + suggested_fixes: [ + SuggestedFix { + title: "consider passing by value instead: \'self\'", + location: Location { + uri: "file:///test/compiler/mir/tagset.rs", + range: Range { + start: Position { + line: 41, + character: 23, + }, + end: Position { + line: 41, + character: 28, + }, + }, + }, + replacement: "self", + applicability: Unspecified, + diagnostics: [], + }, + ], +} diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_handles_macro_location.snap b/crates/ra_lsp_server/src/snapshots/test__snap_handles_macro_location.snap new file mode 100644 index 000000000..07e363ebf --- /dev/null +++ b/crates/ra_lsp_server/src/snapshots/test__snap_handles_macro_location.snap @@ -0,0 +1,46 @@ +--- +source: crates/ra_lsp_server/src/cargo_check.rs +expression: diag +--- +MappedRustDiagnostic { + location: Location { + uri: "file:///test/src/main.rs", + range: Range { + start: Position { + line: 1, + character: 4, + }, + end: Position { + line: 1, + character: 26, + }, + }, + }, + diagnostic: Diagnostic { + range: Range { + start: Position { + line: 1, + character: 4, + }, + end: Position { + line: 1, + character: 26, + }, + }, + severity: Some( + Error, + ), + code: Some( + String( + "E0277", + ), + ), + source: Some( + "rustc", + ), + message: "can\'t compare `{integer}` with `&str`\nthe trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`", + related_information: None, + tags: None, + }, + suggested_fixes: [], +} diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_incompatible_type_for_trait.snap b/crates/ra_lsp_server/src/snapshots/test__snap_rustc_incompatible_type_for_trait.snap new file mode 100644 index 000000000..85a87db0b --- /dev/null +++ b/crates/ra_lsp_server/src/snapshots/test__snap_rustc_incompatible_type_for_trait.snap @@ -0,0 +1,46 @@ +--- +source: crates/ra_lsp_server/src/cargo_check.rs +expression: diag +--- +MappedRustDiagnostic { + location: Location { + uri: "file:///test/compiler/ty/list_iter.rs", + range: Range { + start: Position { + line: 51, + character: 4, + }, + end: Position { + line: 51, + character: 47, + }, + }, + }, + diagnostic: Diagnostic { + range: Range { + start: Position { + line: 51, + character: 4, + }, + end: Position { + line: 51, + character: 47, + }, + }, + severity: Some( + Error, + ), + code: Some( + String( + "E0053", + ), + ), + source: Some( + "rustc", + ), + message: "method `next` has an incompatible type for trait\nexpected type `fn(&mut ty::list_iter::ListIterator<\'list, M>) -> std::option::Option<&ty::Ref>`\n found type `fn(&ty::list_iter::ListIterator<\'list, M>) -> std::option::Option<&\'list ty::Ref>`", + related_information: None, + tags: None, + }, + suggested_fixes: [], +} diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_mismatched_type.snap b/crates/ra_lsp_server/src/snapshots/test__snap_rustc_mismatched_type.snap new file mode 100644 index 000000000..69cb8badf --- /dev/null +++ b/crates/ra_lsp_server/src/snapshots/test__snap_rustc_mismatched_type.snap @@ -0,0 +1,46 @@ +--- +source: crates/ra_lsp_server/src/cargo_check.rs +expression: diag +--- +MappedRustDiagnostic { + location: Location { + uri: "file:///test/runtime/compiler_support.rs", + range: Range { + start: Position { + line: 47, + character: 64, + }, + end: Position { + line: 47, + character: 69, + }, + }, + }, + diagnostic: Diagnostic { + range: Range { + start: Position { + line: 47, + character: 64, + }, + end: Position { + line: 47, + character: 69, + }, + }, + severity: Some( + Error, + ), + code: Some( + String( + "E0308", + ), + ), + source: Some( + "rustc", + ), + message: "mismatched types\nexpected usize, found u32", + related_information: None, + tags: None, + }, + suggested_fixes: [], +} diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_unused_variable.snap b/crates/ra_lsp_server/src/snapshots/test__snap_rustc_unused_variable.snap new file mode 100644 index 000000000..33a3e3034 --- /dev/null +++ b/crates/ra_lsp_server/src/snapshots/test__snap_rustc_unused_variable.snap @@ -0,0 +1,70 @@ +--- +source: crates/ra_lsp_server/src/cargo_check.rs +expression: diag +--- +MappedRustDiagnostic { + location: Location { + uri: "file:///test/driver/subcommand/repl.rs", + range: Range { + start: Position { + line: 290, + character: 8, + }, + end: Position { + line: 290, + character: 11, + }, + }, + }, + diagnostic: Diagnostic { + range: Range { + start: Position { + line: 290, + character: 8, + }, + end: Position { + line: 290, + character: 11, + }, + }, + severity: Some( + Warning, + ), + code: Some( + String( + "unused_variables", + ), + ), + source: Some( + "rustc", + ), + message: "unused variable: `foo`\n#[warn(unused_variables)] on by default", + related_information: None, + tags: Some( + [ + Unnecessary, + ], + ), + }, + suggested_fixes: [ + SuggestedFix { + title: "consider prefixing with an underscore: \'_foo\'", + location: Location { + uri: "file:///test/driver/subcommand/repl.rs", + range: Range { + start: Position { + line: 290, + character: 8, + }, + end: Position { + line: 290, + character: 11, + }, + }, + }, + replacement: "_foo", + applicability: MachineApplicable, + diagnostics: [], + }, + ], +} diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_wrong_number_of_parameters.snap b/crates/ra_lsp_server/src/snapshots/test__snap_rustc_wrong_number_of_parameters.snap new file mode 100644 index 000000000..ced6fa4df --- /dev/null +++ b/crates/ra_lsp_server/src/snapshots/test__snap_rustc_wrong_number_of_parameters.snap @@ -0,0 +1,65 @@ +--- +source: crates/ra_lsp_server/src/cargo_check.rs +expression: diag +--- +MappedRustDiagnostic { + location: Location { + uri: "file:///test/compiler/ty/select.rs", + range: Range { + start: Position { + line: 103, + character: 17, + }, + end: Position { + line: 103, + character: 29, + }, + }, + }, + diagnostic: Diagnostic { + range: Range { + start: Position { + line: 103, + character: 17, + }, + end: Position { + line: 103, + character: 29, + }, + }, + severity: Some( + Error, + ), + code: Some( + String( + "E0061", + ), + ), + source: Some( + "rustc", + ), + message: "this function takes 2 parameters but 3 parameters were supplied\nexpected 2 parameters", + related_information: Some( + [ + DiagnosticRelatedInformation { + location: Location { + uri: "file:///test/compiler/ty/select.rs", + range: Range { + start: Position { + line: 218, + character: 4, + }, + end: Position { + line: 230, + character: 5, + }, + }, + }, + message: "defined here", + }, + ], + ), + tags: None, + }, + suggested_fixes: [], +} -- cgit v1.2.3 From 178c23f50549298aad0dc0f098f8ed510a57f9d6 Mon Sep 17 00:00:00 2001 From: Emil Lauridsen Date: Wed, 25 Dec 2019 19:08:44 +0100 Subject: Re-implement status display using LSP 3.15 progress event --- crates/ra_lsp_server/src/cargo_check.rs | 53 +++++++++++++++++++++++++++------ crates/ra_lsp_server/src/main_loop.rs | 8 +++++ 2 files changed, 52 insertions(+), 9 deletions(-) (limited to 'crates/ra_lsp_server') diff --git a/crates/ra_lsp_server/src/cargo_check.rs b/crates/ra_lsp_server/src/cargo_check.rs index 5a6a209eb..dd8c5d407 100644 --- a/crates/ra_lsp_server/src/cargo_check.rs +++ b/crates/ra_lsp_server/src/cargo_check.rs @@ -9,7 +9,8 @@ use cargo_metadata::{ use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender, TryRecvError}; use lsp_types::{ Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location, - NumberOrString, Position, Range, Url, + NumberOrString, Position, Range, Url, WorkDoneProgress, WorkDoneProgressBegin, + WorkDoneProgressEnd, WorkDoneProgressReport, }; use parking_lot::RwLock; use std::{ @@ -132,6 +133,7 @@ impl CheckWatcherSharedState { #[derive(Debug)] pub enum CheckTask { Update(Url), + Status(WorkDoneProgress), } pub enum CheckCommand { @@ -204,13 +206,38 @@ impl CheckWatcherState { } } - fn handle_message(&mut self, msg: cargo_metadata::Message, task_send: &Sender) { + fn handle_message(&mut self, msg: CheckEvent, task_send: &Sender) { match msg { - Message::CompilerArtifact(_msg) => { - // TODO: Status display + CheckEvent::Begin => { + task_send + .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin { + title: "Running 'cargo check'".to_string(), + cancellable: Some(false), + message: None, + percentage: None, + }))) + .unwrap(); } - Message::CompilerMessage(msg) => { + CheckEvent::End => { + task_send + .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd { + message: None, + }))) + .unwrap(); + } + + CheckEvent::Msg(Message::CompilerArtifact(msg)) => { + task_send + .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport { + cancellable: Some(false), + message: Some(msg.target.name), + percentage: None, + }))) + .unwrap(); + } + + CheckEvent::Msg(Message::CompilerMessage(msg)) => { let map_result = match map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root) { Some(map_result) => map_result, @@ -232,8 +259,8 @@ impl CheckWatcherState { task_send.send(CheckTask::Update(location.uri)).unwrap(); } - Message::BuildScriptExecuted(_msg) => {} - Message::Unknown => {} + CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {} + CheckEvent::Msg(Message::Unknown) => {} } } } @@ -244,10 +271,16 @@ impl CheckWatcherState { /// have to wrap sub-processes output handling in a thread and pass messages /// back over a channel. struct WatchThread { - message_recv: Receiver, + message_recv: Receiver, cancel_send: Sender<()>, } +enum CheckEvent { + Begin, + Msg(cargo_metadata::Message), + End, +} + impl WatchThread { fn new( check_command: Option<&String>, @@ -273,6 +306,7 @@ impl WatchThread { .spawn() .expect("couldn't launch cargo"); + message_send.send(CheckEvent::Begin).unwrap(); for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) { match cancel_recv.try_recv() { Ok(()) | Err(TryRecvError::Disconnected) => { @@ -281,8 +315,9 @@ impl WatchThread { Err(TryRecvError::Empty) => (), } - message_send.send(message.unwrap()).unwrap(); + message_send.send(CheckEvent::Msg(message.unwrap())).unwrap(); } + message_send.send(CheckEvent::End).unwrap(); }); WatchThread { message_recv, cancel_send } } diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index 1f6175699..045e4660d 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -338,6 +338,14 @@ fn loop_turn( task_sender.send(Task::Notify(not)).unwrap(); } } + CheckTask::Status(progress) => { + let params = req::ProgressParams { + token: req::ProgressToken::String("rustAnalyzer/cargoWatcher".to_string()), + value: req::ProgressParamsValue::WorkDone(progress), + }; + let not = notification_new::(params); + task_sender.send(Task::Notify(not)).unwrap(); + } }, Event::Msg(msg) => match msg { Message::Request(req) => on_request( -- cgit v1.2.3 From f7d04d05756e7074de2b73b35718b5b4ae670d1b Mon Sep 17 00:00:00 2001 From: Emil Lauridsen Date: Wed, 25 Dec 2019 19:10:45 +0100 Subject: Re-format ra_lsp_server changes --- crates/ra_lsp_server/src/caps.rs | 4 ++-- crates/ra_lsp_server/src/req.rs | 8 ++++---- crates/ra_lsp_server/src/world.rs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'crates/ra_lsp_server') diff --git a/crates/ra_lsp_server/src/caps.rs b/crates/ra_lsp_server/src/caps.rs index 0f84e7a34..0dee1f6fe 100644 --- a/crates/ra_lsp_server/src/caps.rs +++ b/crates/ra_lsp_server/src/caps.rs @@ -3,10 +3,10 @@ use lsp_types::{ CodeActionProviderCapability, CodeLensOptions, CompletionOptions, DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability, - ImplementationProviderCapability, RenameOptions, RenameProviderCapability, + ImplementationProviderCapability, RenameOptions, RenameProviderCapability, SaveOptions, SelectionRangeProviderCapability, ServerCapabilities, SignatureHelpOptions, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, - TypeDefinitionProviderCapability, WorkDoneProgressOptions, SaveOptions + TypeDefinitionProviderCapability, WorkDoneProgressOptions, }; pub fn server_capabilities() -> ServerCapabilities { diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs index b34e6f9b8..40edaf677 100644 --- a/crates/ra_lsp_server/src/req.rs +++ b/crates/ra_lsp_server/src/req.rs @@ -9,10 +9,10 @@ pub use lsp_types::{ CodeLensParams, CompletionParams, CompletionResponse, DidChangeConfigurationParams, DidChangeWatchedFilesParams, DidChangeWatchedFilesRegistrationOptions, DocumentOnTypeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse, - FileSystemWatcher, Hover, InitializeResult, MessageType, PublishDiagnosticsParams, - ReferenceParams, Registration, RegistrationParams, SelectionRange, SelectionRangeParams, - ShowMessageParams, SignatureHelp, TextDocumentEdit, TextDocumentPositionParams, TextEdit, - WorkspaceEdit, WorkspaceSymbolParams, + FileSystemWatcher, Hover, InitializeResult, MessageType, ProgressParams, ProgressParamsValue, + ProgressToken, PublishDiagnosticsParams, ReferenceParams, Registration, RegistrationParams, + SelectionRange, SelectionRangeParams, ShowMessageParams, SignatureHelp, TextDocumentEdit, + TextDocumentPositionParams, TextEdit, WorkspaceEdit, WorkspaceSymbolParams, }; pub enum AnalyzerStatus {} diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs index 235eb199d..47c3823fb 100644 --- a/crates/ra_lsp_server/src/world.rs +++ b/crates/ra_lsp_server/src/world.rs @@ -23,8 +23,8 @@ use relative_path::RelativePathBuf; use std::path::{Component, Prefix}; use crate::{ - main_loop::pending_requests::{CompletedRequest, LatestRequests}, cargo_check::{CheckWatcher, CheckWatcherSharedState}, + main_loop::pending_requests::{CompletedRequest, LatestRequests}, LspError, Result, }; use std::str::FromStr; -- cgit v1.2.3 From 069c1655369aa33223788b4f1b8407b6d6b63193 Mon Sep 17 00:00:00 2001 From: Emil Lauridsen Date: Wed, 25 Dec 2019 19:17:54 +0100 Subject: Actually respect disabling cargo check functionality --- crates/ra_lsp_server/src/cargo_check.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) (limited to 'crates/ra_lsp_server') diff --git a/crates/ra_lsp_server/src/cargo_check.rs b/crates/ra_lsp_server/src/cargo_check.rs index dd8c5d407..42e55565f 100644 --- a/crates/ra_lsp_server/src/cargo_check.rs +++ b/crates/ra_lsp_server/src/cargo_check.rs @@ -33,6 +33,7 @@ pub struct CheckWatcher { impl CheckWatcher { pub fn new(options: &Options, workspace_root: PathBuf) -> CheckWatcher { + let check_enabled = options.cargo_check_enable; let check_command = options.cargo_check_command.clone(); let check_args = options.cargo_check_args.clone(); let shared = Arc::new(RwLock::new(CheckWatcherSharedState::new())); @@ -41,8 +42,13 @@ impl CheckWatcher { let (cmd_send, cmd_recv) = unbounded::(); let shared_ = shared.clone(); let handle = std::thread::spawn(move || { - let mut check = - CheckWatcherState::new(check_command, check_args, workspace_root, shared_); + let mut check = CheckWatcherState::new( + check_enabled, + check_command, + check_args, + workspace_root, + shared_, + ); check.run(&task_send, &cmd_recv); }); @@ -55,6 +61,7 @@ impl CheckWatcher { } pub struct CheckWatcherState { + check_enabled: bool, check_command: Option, check_args: Vec, workspace_root: PathBuf, @@ -142,13 +149,16 @@ pub enum CheckCommand { impl CheckWatcherState { pub fn new( + check_enabled: bool, check_command: Option, check_args: Vec, workspace_root: PathBuf, shared: Arc>, ) -> CheckWatcherState { - let watcher = WatchThread::new(check_command.as_ref(), &check_args, &workspace_root); + let watcher = + WatchThread::new(check_enabled, check_command.as_ref(), &check_args, &workspace_root); CheckWatcherState { + check_enabled, check_command, check_args, workspace_root, @@ -182,6 +192,7 @@ impl CheckWatcherState { self.watcher.cancel(); self.watcher = WatchThread::new( + self.check_enabled, self.check_command.as_ref(), &self.check_args, &self.workspace_root, @@ -283,6 +294,7 @@ enum CheckEvent { impl WatchThread { fn new( + check_enabled: bool, check_command: Option<&String>, check_args: &[String], workspace_root: &PathBuf, @@ -299,6 +311,10 @@ impl WatchThread { let (message_send, message_recv) = unbounded(); let (cancel_send, cancel_recv) = unbounded(); std::thread::spawn(move || { + if !check_enabled { + return; + } + let mut command = Command::new("cargo") .args(&args) .stdout(Stdio::piped()) -- cgit v1.2.3 From 71d2d81dcc879bbb7898df11ac00578e93b27ab5 Mon Sep 17 00:00:00 2001 From: Emil Lauridsen Date: Wed, 25 Dec 2019 19:56:07 +0100 Subject: Some documentatioN --- crates/ra_lsp_server/src/cargo_check.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'crates/ra_lsp_server') diff --git a/crates/ra_lsp_server/src/cargo_check.rs b/crates/ra_lsp_server/src/cargo_check.rs index 42e55565f..fa0409ee0 100644 --- a/crates/ra_lsp_server/src/cargo_check.rs +++ b/crates/ra_lsp_server/src/cargo_check.rs @@ -1,3 +1,6 @@ +//! cargo_check provides the functionality needed to run `cargo check` or +//! another compatible command (f.x. clippy) in a background thread and provide +//! LSP diagnostics based on the output of the command. use crate::world::Options; use cargo_metadata::{ diagnostic::{ @@ -23,6 +26,9 @@ use std::{ time::Instant, }; +/// CheckWatcher wraps the shared state and communication machinery used for +/// running `cargo check` (or other compatible command) and providing +/// diagnostics based on the output. #[derive(Debug)] pub struct CheckWatcher { pub task_recv: Receiver, @@ -55,6 +61,7 @@ impl CheckWatcher { CheckWatcher { task_recv, cmd_send, handle, shared } } + /// Schedule a re-start of the cargo check worker. pub fn update(&self) { self.cmd_send.send(CheckCommand::Update).unwrap(); } @@ -85,6 +92,8 @@ impl CheckWatcherSharedState { } } + /// Clear the cached diagnostics, and schedule updating diagnostics by the + /// server, to clear stale results. pub fn clear(&mut self, task_send: &Sender) { let cleared_files: Vec = self.diagnostic_collection.keys().cloned().collect(); @@ -139,11 +148,15 @@ impl CheckWatcherSharedState { #[derive(Debug)] pub enum CheckTask { + /// Request a update of the given files diagnostics Update(Url), + + /// Request check progress notification to client Status(WorkDoneProgress), } pub enum CheckCommand { + /// Request re-start of check thread Update, } -- cgit v1.2.3 From 0cdbd0814958e174c5481d6bf16bd2a7e53ec981 Mon Sep 17 00:00:00 2001 From: Emil Lauridsen Date: Wed, 25 Dec 2019 20:23:44 +0100 Subject: Keep VSCode config mostly backwards compatible --- crates/ra_lsp_server/src/cargo_check.rs | 52 ++++++++++----------------------- crates/ra_lsp_server/src/config.rs | 14 +++++---- crates/ra_lsp_server/src/main_loop.rs | 7 +++-- crates/ra_lsp_server/src/world.rs | 7 +++-- 4 files changed, 31 insertions(+), 49 deletions(-) (limited to 'crates/ra_lsp_server') diff --git a/crates/ra_lsp_server/src/cargo_check.rs b/crates/ra_lsp_server/src/cargo_check.rs index fa0409ee0..70c723b19 100644 --- a/crates/ra_lsp_server/src/cargo_check.rs +++ b/crates/ra_lsp_server/src/cargo_check.rs @@ -39,22 +39,14 @@ pub struct CheckWatcher { impl CheckWatcher { pub fn new(options: &Options, workspace_root: PathBuf) -> CheckWatcher { - let check_enabled = options.cargo_check_enable; - let check_command = options.cargo_check_command.clone(); - let check_args = options.cargo_check_args.clone(); + let options = options.clone(); let shared = Arc::new(RwLock::new(CheckWatcherSharedState::new())); let (task_send, task_recv) = unbounded::(); let (cmd_send, cmd_recv) = unbounded::(); let shared_ = shared.clone(); let handle = std::thread::spawn(move || { - let mut check = CheckWatcherState::new( - check_enabled, - check_command, - check_args, - workspace_root, - shared_, - ); + let mut check = CheckWatcherState::new(options, workspace_root, shared_); check.run(&task_send, &cmd_recv); }); @@ -68,9 +60,7 @@ impl CheckWatcher { } pub struct CheckWatcherState { - check_enabled: bool, - check_command: Option, - check_args: Vec, + options: Options, workspace_root: PathBuf, running: bool, watcher: WatchThread, @@ -162,18 +152,13 @@ pub enum CheckCommand { impl CheckWatcherState { pub fn new( - check_enabled: bool, - check_command: Option, - check_args: Vec, + options: Options, workspace_root: PathBuf, shared: Arc>, ) -> CheckWatcherState { - let watcher = - WatchThread::new(check_enabled, check_command.as_ref(), &check_args, &workspace_root); + let watcher = WatchThread::new(&options, &workspace_root); CheckWatcherState { - check_enabled, - check_command, - check_args, + options, workspace_root, running: false, watcher, @@ -204,12 +189,7 @@ impl CheckWatcherState { self.shared.write().clear(task_send); self.watcher.cancel(); - self.watcher = WatchThread::new( - self.check_enabled, - self.check_command.as_ref(), - &self.check_args, - &self.workspace_root, - ); + self.watcher = WatchThread::new(&self.options, &self.workspace_root); } } } @@ -306,25 +286,23 @@ enum CheckEvent { } impl WatchThread { - fn new( - check_enabled: bool, - check_command: Option<&String>, - check_args: &[String], - workspace_root: &PathBuf, - ) -> WatchThread { - let check_command = check_command.cloned().unwrap_or("check".to_string()); + fn new(options: &Options, workspace_root: &PathBuf) -> WatchThread { let mut args: Vec = vec![ - check_command, + options.cargo_watch_command.clone(), "--message-format=json".to_string(), "--manifest-path".to_string(), format!("{}/Cargo.toml", workspace_root.to_string_lossy()), ]; - args.extend(check_args.iter().cloned()); + if options.cargo_watch_all_targets { + args.push("--all-targets".to_string()); + } + args.extend(options.cargo_watch_args.iter().cloned()); let (message_send, message_recv) = unbounded(); let (cancel_send, cancel_recv) = unbounded(); + let enabled = options.cargo_watch_enable; std::thread::spawn(move || { - if !check_enabled { + if !enabled { return; } diff --git a/crates/ra_lsp_server/src/config.rs b/crates/ra_lsp_server/src/config.rs index 621f2238c..2d7948d74 100644 --- a/crates/ra_lsp_server/src/config.rs +++ b/crates/ra_lsp_server/src/config.rs @@ -32,9 +32,10 @@ pub struct ServerConfig { pub max_inlay_hint_length: Option, - pub cargo_check_enable: bool, - pub cargo_check_command: Option, - pub cargo_check_args: Vec, + pub cargo_watch_enable: bool, + pub cargo_watch_args: Vec, + pub cargo_watch_command: String, + pub cargo_watch_all_targets: bool, /// For internal usage to make integrated tests faster. #[serde(deserialize_with = "nullable_bool_true")] @@ -55,9 +56,10 @@ impl Default for ServerConfig { use_client_watching: false, lru_capacity: None, max_inlay_hint_length: None, - cargo_check_enable: true, - cargo_check_command: None, - cargo_check_args: vec![], + cargo_watch_enable: true, + cargo_watch_args: Vec::new(), + cargo_watch_command: "check".to_string(), + cargo_watch_all_targets: true, with_sysroot: true, feature_flags: FxHashMap::default(), cargo_features: Default::default(), diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index 045e4660d..c58af7e47 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -127,9 +127,10 @@ pub fn main_loop( .and_then(|it| it.line_folding_only) .unwrap_or(false), max_inlay_hint_length: config.max_inlay_hint_length, - cargo_check_enable: config.cargo_check_enable, - cargo_check_command: config.cargo_check_command, - cargo_check_args: config.cargo_check_args, + cargo_watch_enable: config.cargo_watch_enable, + cargo_watch_args: config.cargo_watch_args, + cargo_watch_command: config.cargo_watch_command, + cargo_watch_all_targets: config.cargo_watch_all_targets, } }; diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs index 47c3823fb..39a07c01a 100644 --- a/crates/ra_lsp_server/src/world.rs +++ b/crates/ra_lsp_server/src/world.rs @@ -35,9 +35,10 @@ pub struct Options { pub supports_location_link: bool, pub line_folding_only: bool, pub max_inlay_hint_length: Option, - pub cargo_check_enable: bool, - pub cargo_check_command: Option, - pub cargo_check_args: Vec, + pub cargo_watch_enable: bool, + pub cargo_watch_args: Vec, + pub cargo_watch_command: String, + pub cargo_watch_all_targets: bool, } /// `WorldState` is the primary mutable state of the language server -- cgit v1.2.3 From 428a6ff5b8bad2c80a3522599195bf2a393f744e Mon Sep 17 00:00:00 2001 From: Emil Lauridsen Date: Fri, 27 Dec 2019 11:10:07 +0100 Subject: Move cargo watch functionality to separate crate --- crates/ra_lsp_server/Cargo.toml | 3 +- crates/ra_lsp_server/src/cargo_check.rs | 1316 -------------------- crates/ra_lsp_server/src/lib.rs | 1 - crates/ra_lsp_server/src/main_loop.rs | 12 +- .../snapshots/test__snap_clippy_pass_by_ref.snap | 85 -- .../test__snap_handles_macro_location.snap | 46 - ...st__snap_rustc_incompatible_type_for_trait.snap | 46 - .../test__snap_rustc_mismatched_type.snap | 46 - .../test__snap_rustc_unused_variable.snap | 70 -- ...est__snap_rustc_wrong_number_of_parameters.snap | 65 - crates/ra_lsp_server/src/world.rs | 10 +- 11 files changed, 12 insertions(+), 1688 deletions(-) delete mode 100644 crates/ra_lsp_server/src/cargo_check.rs delete mode 100644 crates/ra_lsp_server/src/snapshots/test__snap_clippy_pass_by_ref.snap delete mode 100644 crates/ra_lsp_server/src/snapshots/test__snap_handles_macro_location.snap delete mode 100644 crates/ra_lsp_server/src/snapshots/test__snap_rustc_incompatible_type_for_trait.snap delete mode 100644 crates/ra_lsp_server/src/snapshots/test__snap_rustc_mismatched_type.snap delete mode 100644 crates/ra_lsp_server/src/snapshots/test__snap_rustc_unused_variable.snap delete mode 100644 crates/ra_lsp_server/src/snapshots/test__snap_rustc_wrong_number_of_parameters.snap (limited to 'crates/ra_lsp_server') diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index 54a01d7a2..9b7dcb6e9 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml @@ -27,10 +27,9 @@ ra_project_model = { path = "../ra_project_model" } ra_prof = { path = "../ra_prof" } ra_vfs_glob = { path = "../ra_vfs_glob" } env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } -cargo_metadata = "0.9.1" +ra_cargo_watch = { path = "../ra_cargo_watch" } [dev-dependencies] -insta = "0.12.0" tempfile = "3" test_utils = { path = "../test_utils" } diff --git a/crates/ra_lsp_server/src/cargo_check.rs b/crates/ra_lsp_server/src/cargo_check.rs deleted file mode 100644 index 70c723b19..000000000 --- a/crates/ra_lsp_server/src/cargo_check.rs +++ /dev/null @@ -1,1316 +0,0 @@ -//! cargo_check provides the functionality needed to run `cargo check` or -//! another compatible command (f.x. clippy) in a background thread and provide -//! LSP diagnostics based on the output of the command. -use crate::world::Options; -use cargo_metadata::{ - diagnostic::{ - Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan, - DiagnosticSpanMacroExpansion, - }, - Message, -}; -use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender, TryRecvError}; -use lsp_types::{ - Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location, - NumberOrString, Position, Range, Url, WorkDoneProgress, WorkDoneProgressBegin, - WorkDoneProgressEnd, WorkDoneProgressReport, -}; -use parking_lot::RwLock; -use std::{ - collections::HashMap, - fmt::Write, - path::PathBuf, - process::{Command, Stdio}, - sync::Arc, - thread::JoinHandle, - time::Instant, -}; - -/// CheckWatcher wraps the shared state and communication machinery used for -/// running `cargo check` (or other compatible command) and providing -/// diagnostics based on the output. -#[derive(Debug)] -pub struct CheckWatcher { - pub task_recv: Receiver, - pub cmd_send: Sender, - pub shared: Arc>, - handle: JoinHandle<()>, -} - -impl CheckWatcher { - pub fn new(options: &Options, workspace_root: PathBuf) -> CheckWatcher { - let options = options.clone(); - let shared = Arc::new(RwLock::new(CheckWatcherSharedState::new())); - - let (task_send, task_recv) = unbounded::(); - let (cmd_send, cmd_recv) = unbounded::(); - let shared_ = shared.clone(); - let handle = std::thread::spawn(move || { - let mut check = CheckWatcherState::new(options, workspace_root, shared_); - check.run(&task_send, &cmd_recv); - }); - - CheckWatcher { task_recv, cmd_send, handle, shared } - } - - /// Schedule a re-start of the cargo check worker. - pub fn update(&self) { - self.cmd_send.send(CheckCommand::Update).unwrap(); - } -} - -pub struct CheckWatcherState { - options: Options, - workspace_root: PathBuf, - running: bool, - watcher: WatchThread, - last_update_req: Option, - shared: Arc>, -} - -#[derive(Debug)] -pub struct CheckWatcherSharedState { - diagnostic_collection: HashMap>, - suggested_fix_collection: HashMap>, -} - -impl CheckWatcherSharedState { - fn new() -> CheckWatcherSharedState { - CheckWatcherSharedState { - diagnostic_collection: HashMap::new(), - suggested_fix_collection: HashMap::new(), - } - } - - /// Clear the cached diagnostics, and schedule updating diagnostics by the - /// server, to clear stale results. - pub fn clear(&mut self, task_send: &Sender) { - let cleared_files: Vec = self.diagnostic_collection.keys().cloned().collect(); - - self.diagnostic_collection.clear(); - self.suggested_fix_collection.clear(); - - for uri in cleared_files { - task_send.send(CheckTask::Update(uri.clone())).unwrap(); - } - } - - pub fn diagnostics_for(&self, uri: &Url) -> Option<&[Diagnostic]> { - self.diagnostic_collection.get(uri).map(|d| d.as_slice()) - } - - pub fn fixes_for(&self, uri: &Url) -> Option<&[SuggestedFix]> { - self.suggested_fix_collection.get(uri).map(|d| d.as_slice()) - } - - fn add_diagnostic(&mut self, file_uri: Url, diagnostic: Diagnostic) { - let diagnostics = self.diagnostic_collection.entry(file_uri).or_default(); - - // If we're building multiple targets it's possible we've already seen this diagnostic - let is_duplicate = diagnostics.iter().any(|d| are_diagnostics_equal(d, &diagnostic)); - if is_duplicate { - return; - } - - diagnostics.push(diagnostic); - } - - fn add_suggested_fix_for_diagnostic( - &mut self, - mut suggested_fix: SuggestedFix, - diagnostic: &Diagnostic, - ) { - let file_uri = suggested_fix.location.uri.clone(); - let file_suggestions = self.suggested_fix_collection.entry(file_uri).or_default(); - - let existing_suggestion: Option<&mut SuggestedFix> = - file_suggestions.iter_mut().find(|s| s == &&suggested_fix); - if let Some(existing_suggestion) = existing_suggestion { - // The existing suggestion also applies to this new diagnostic - existing_suggestion.diagnostics.push(diagnostic.clone()); - } else { - // We haven't seen this suggestion before - suggested_fix.diagnostics.push(diagnostic.clone()); - file_suggestions.push(suggested_fix); - } - } -} - -#[derive(Debug)] -pub enum CheckTask { - /// Request a update of the given files diagnostics - Update(Url), - - /// Request check progress notification to client - Status(WorkDoneProgress), -} - -pub enum CheckCommand { - /// Request re-start of check thread - Update, -} - -impl CheckWatcherState { - pub fn new( - options: Options, - workspace_root: PathBuf, - shared: Arc>, - ) -> CheckWatcherState { - let watcher = WatchThread::new(&options, &workspace_root); - CheckWatcherState { - options, - workspace_root, - running: false, - watcher, - last_update_req: None, - shared, - } - } - - pub fn run(&mut self, task_send: &Sender, cmd_recv: &Receiver) { - self.running = true; - while self.running { - select! { - recv(&cmd_recv) -> cmd => match cmd { - Ok(cmd) => self.handle_command(cmd), - Err(RecvError) => { - // Command channel has closed, so shut down - self.running = false; - }, - }, - recv(self.watcher.message_recv) -> msg => match msg { - Ok(msg) => self.handle_message(msg, task_send), - Err(RecvError) => {}, - } - }; - - if self.should_recheck() { - self.last_update_req.take(); - self.shared.write().clear(task_send); - - self.watcher.cancel(); - self.watcher = WatchThread::new(&self.options, &self.workspace_root); - } - } - } - - fn should_recheck(&mut self) -> bool { - if let Some(_last_update_req) = &self.last_update_req { - // We currently only request an update on save, as we need up to - // date source on disk for cargo check to do it's magic, so we - // don't really need to debounce the requests at this point. - return true; - } - false - } - - fn handle_command(&mut self, cmd: CheckCommand) { - match cmd { - CheckCommand::Update => self.last_update_req = Some(Instant::now()), - } - } - - fn handle_message(&mut self, msg: CheckEvent, task_send: &Sender) { - match msg { - CheckEvent::Begin => { - task_send - .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin { - title: "Running 'cargo check'".to_string(), - cancellable: Some(false), - message: None, - percentage: None, - }))) - .unwrap(); - } - - CheckEvent::End => { - task_send - .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd { - message: None, - }))) - .unwrap(); - } - - CheckEvent::Msg(Message::CompilerArtifact(msg)) => { - task_send - .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport { - cancellable: Some(false), - message: Some(msg.target.name), - percentage: None, - }))) - .unwrap(); - } - - CheckEvent::Msg(Message::CompilerMessage(msg)) => { - let map_result = - match map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root) { - Some(map_result) => map_result, - None => return, - }; - - let MappedRustDiagnostic { location, diagnostic, suggested_fixes } = map_result; - let file_uri = location.uri.clone(); - - if !suggested_fixes.is_empty() { - for suggested_fix in suggested_fixes { - self.shared - .write() - .add_suggested_fix_for_diagnostic(suggested_fix, &diagnostic); - } - } - self.shared.write().add_diagnostic(file_uri, diagnostic); - - task_send.send(CheckTask::Update(location.uri)).unwrap(); - } - - CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {} - CheckEvent::Msg(Message::Unknown) => {} - } - } -} - -/// WatchThread exists to wrap around the communication needed to be able to -/// run `cargo check` without blocking. Currently the Rust standard library -/// doesn't provide a way to read sub-process output without blocking, so we -/// have to wrap sub-processes output handling in a thread and pass messages -/// back over a channel. -struct WatchThread { - message_recv: Receiver, - cancel_send: Sender<()>, -} - -enum CheckEvent { - Begin, - Msg(cargo_metadata::Message), - End, -} - -impl WatchThread { - fn new(options: &Options, workspace_root: &PathBuf) -> WatchThread { - let mut args: Vec = vec![ - options.cargo_watch_command.clone(), - "--message-format=json".to_string(), - "--manifest-path".to_string(), - format!("{}/Cargo.toml", workspace_root.to_string_lossy()), - ]; - if options.cargo_watch_all_targets { - args.push("--all-targets".to_string()); - } - args.extend(options.cargo_watch_args.iter().cloned()); - - let (message_send, message_recv) = unbounded(); - let (cancel_send, cancel_recv) = unbounded(); - let enabled = options.cargo_watch_enable; - std::thread::spawn(move || { - if !enabled { - return; - } - - let mut command = Command::new("cargo") - .args(&args) - .stdout(Stdio::piped()) - .stderr(Stdio::null()) - .spawn() - .expect("couldn't launch cargo"); - - message_send.send(CheckEvent::Begin).unwrap(); - for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) { - match cancel_recv.try_recv() { - Ok(()) | Err(TryRecvError::Disconnected) => { - command.kill().expect("couldn't kill command"); - } - Err(TryRecvError::Empty) => (), - } - - message_send.send(CheckEvent::Msg(message.unwrap())).unwrap(); - } - message_send.send(CheckEvent::End).unwrap(); - }); - WatchThread { message_recv, cancel_send } - } - - fn cancel(&self) { - let _ = self.cancel_send.send(()); - } -} - -/// Converts a Rust level string to a LSP severity -fn map_level_to_severity(val: DiagnosticLevel) -> Option { - match val { - DiagnosticLevel::Ice => Some(DiagnosticSeverity::Error), - DiagnosticLevel::Error => Some(DiagnosticSeverity::Error), - DiagnosticLevel::Warning => Some(DiagnosticSeverity::Warning), - DiagnosticLevel::Note => Some(DiagnosticSeverity::Information), - DiagnosticLevel::Help => Some(DiagnosticSeverity::Hint), - DiagnosticLevel::Unknown => None, - } -} - -/// Check whether a file name is from macro invocation -fn is_from_macro(file_name: &str) -> bool { - file_name.starts_with('<') && file_name.ends_with('>') -} - -/// Converts a Rust macro span to a LSP location recursively -fn map_macro_span_to_location( - span_macro: &DiagnosticSpanMacroExpansion, - workspace_root: &PathBuf, -) -> Option { - if !is_from_macro(&span_macro.span.file_name) { - return Some(map_span_to_location(&span_macro.span, workspace_root)); - } - - if let Some(expansion) = &span_macro.span.expansion { - return map_macro_span_to_location(&expansion, workspace_root); - } - - None -} - -/// Converts a Rust span to a LSP location -fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location { - if is_from_macro(&span.file_name) && span.expansion.is_some() { - let expansion = span.expansion.as_ref().unwrap(); - if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) { - return macro_range; - } - } - - let mut file_name = workspace_root.clone(); - file_name.push(&span.file_name); - let uri = Url::from_file_path(file_name).unwrap(); - - let range = Range::new( - Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1), - Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1), - ); - - Location { uri, range } -} - -/// Converts a secondary Rust span to a LSP related information -/// -/// If the span is unlabelled this will return `None`. -fn map_secondary_span_to_related( - span: &DiagnosticSpan, - workspace_root: &PathBuf, -) -> Option { - if let Some(label) = &span.label { - let location = map_span_to_location(span, workspace_root); - Some(DiagnosticRelatedInformation { location, message: label.clone() }) - } else { - // Nothing to label this with - None - } -} - -/// Determines if diagnostic is related to unused code -fn is_unused_or_unnecessary(rd: &RustDiagnostic) -> bool { - if let Some(code) = &rd.code { - match code.code.as_str() { - "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes" - | "unused_imports" | "unused_macros" | "unused_variables" => true, - _ => false, - } - } else { - false - } -} - -/// Determines if diagnostic is related to deprecated code -fn is_deprecated(rd: &RustDiagnostic) -> bool { - if let Some(code) = &rd.code { - match code.code.as_str() { - "deprecated" => true, - _ => false, - } - } else { - false - } -} - -#[derive(Debug)] -pub struct SuggestedFix { - pub title: String, - pub location: Location, - pub replacement: String, - pub applicability: Applicability, - pub diagnostics: Vec, -} - -impl std::cmp::PartialEq for SuggestedFix { - fn eq(&self, other: &SuggestedFix) -> bool { - if self.title == other.title - && self.location == other.location - && self.replacement == other.replacement - { - // Applicability doesn't impl PartialEq... - match (&self.applicability, &other.applicability) { - (Applicability::MachineApplicable, Applicability::MachineApplicable) => true, - (Applicability::HasPlaceholders, Applicability::HasPlaceholders) => true, - (Applicability::MaybeIncorrect, Applicability::MaybeIncorrect) => true, - (Applicability::Unspecified, Applicability::Unspecified) => true, - _ => false, - } - } else { - false - } - } -} - -enum MappedRustChildDiagnostic { - Related(DiagnosticRelatedInformation), - SuggestedFix(SuggestedFix), - MessageLine(String), -} - -fn map_rust_child_diagnostic( - rd: &RustDiagnostic, - workspace_root: &PathBuf, -) -> MappedRustChildDiagnostic { - let span: &DiagnosticSpan = match rd.spans.iter().find(|s| s.is_primary) { - Some(span) => span, - None => { - // `rustc` uses these spanless children as a way to print multi-line - // messages - return MappedRustChildDiagnostic::MessageLine(rd.message.clone()); - } - }; - - // If we have a primary span use its location, otherwise use the parent - let location = map_span_to_location(&span, workspace_root); - - if let Some(suggested_replacement) = &span.suggested_replacement { - // Include our replacement in the title unless it's empty - let title = if !suggested_replacement.is_empty() { - format!("{}: '{}'", rd.message, suggested_replacement) - } else { - rd.message.clone() - }; - - MappedRustChildDiagnostic::SuggestedFix(SuggestedFix { - title, - location, - replacement: suggested_replacement.clone(), - applicability: span.suggestion_applicability.clone().unwrap_or(Applicability::Unknown), - diagnostics: vec![], - }) - } else { - MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation { - location, - message: rd.message.clone(), - }) - } -} - -#[derive(Debug)] -struct MappedRustDiagnostic { - location: Location, - diagnostic: Diagnostic, - suggested_fixes: Vec, -} - -/// Converts a Rust root diagnostic to LSP form -/// -/// This flattens the Rust diagnostic by: -/// -/// 1. Creating a LSP diagnostic with the root message and primary span. -/// 2. Adding any labelled secondary spans to `relatedInformation` -/// 3. Categorising child diagnostics as either `SuggestedFix`es, -/// `relatedInformation` or additional message lines. -/// -/// If the diagnostic has no primary span this will return `None` -fn map_rust_diagnostic_to_lsp( - rd: &RustDiagnostic, - workspace_root: &PathBuf, -) -> Option { - let primary_span = rd.spans.iter().find(|s| s.is_primary)?; - - let location = map_span_to_location(&primary_span, workspace_root); - - let severity = map_level_to_severity(rd.level); - let mut primary_span_label = primary_span.label.as_ref(); - - let mut source = String::from("rustc"); - let mut code = rd.code.as_ref().map(|c| c.code.clone()); - if let Some(code_val) = &code { - // See if this is an RFC #2103 scoped lint (e.g. from Clippy) - let scoped_code: Vec<&str> = code_val.split("::").collect(); - if scoped_code.len() == 2 { - source = String::from(scoped_code[0]); - code = Some(String::from(scoped_code[1])); - } - } - - let mut related_information = vec![]; - let mut tags = vec![]; - - for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) { - let related = map_secondary_span_to_related(secondary_span, workspace_root); - if let Some(related) = related { - related_information.push(related); - } - } - - let mut suggested_fixes = vec![]; - let mut message = rd.message.clone(); - for child in &rd.children { - let child = map_rust_child_diagnostic(&child, workspace_root); - match child { - MappedRustChildDiagnostic::Related(related) => related_information.push(related), - MappedRustChildDiagnostic::SuggestedFix(suggested_fix) => { - suggested_fixes.push(suggested_fix) - } - MappedRustChildDiagnostic::MessageLine(message_line) => { - write!(&mut message, "\n{}", message_line).unwrap(); - - // These secondary messages usually duplicate the content of the - // primary span label. - primary_span_label = None; - } - } - } - - if let Some(primary_span_label) = primary_span_label { - write!(&mut message, "\n{}", primary_span_label).unwrap(); - } - - if is_unused_or_unnecessary(rd) { - tags.push(DiagnosticTag::Unnecessary); - } - - if is_deprecated(rd) { - tags.push(DiagnosticTag::Deprecated); - } - - let diagnostic = Diagnostic { - range: location.range, - severity, - code: code.map(NumberOrString::String), - source: Some(source), - message, - related_information: if !related_information.is_empty() { - Some(related_information) - } else { - None - }, - tags: if !tags.is_empty() { Some(tags) } else { None }, - }; - - Some(MappedRustDiagnostic { location, diagnostic, suggested_fixes }) -} - -fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool { - left.source == right.source - && left.severity == right.severity - && left.range == right.range - && left.message == right.message -} - -#[cfg(test)] -mod test { - use super::*; - - fn parse_diagnostic(val: &str) -> cargo_metadata::diagnostic::Diagnostic { - serde_json::from_str::(val).unwrap() - } - - #[test] - fn snap_rustc_incompatible_type_for_trait() { - let diag = parse_diagnostic( - r##"{ - "message": "method `next` has an incompatible type for trait", - "code": { - "code": "E0053", - "explanation": "\nThe parameters of any trait method must match between a trait implementation\nand the trait definition.\n\nHere are a couple examples of this error:\n\n```compile_fail,E0053\ntrait Foo {\n fn foo(x: u16);\n fn bar(&self);\n}\n\nstruct Bar;\n\nimpl Foo for Bar {\n // error, expected u16, found i16\n fn foo(x: i16) { }\n\n // error, types differ in mutability\n fn bar(&mut self) { }\n}\n```\n" - }, - "level": "error", - "spans": [ - { - "file_name": "compiler/ty/list_iter.rs", - "byte_start": 1307, - "byte_end": 1350, - "line_start": 52, - "line_end": 52, - "column_start": 5, - "column_end": 48, - "is_primary": true, - "text": [ - { - "text": " fn next(&self) -> Option<&'list ty::Ref> {", - "highlight_start": 5, - "highlight_end": 48 - } - ], - "label": "types differ in mutability", - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } - ], - "children": [ - { - "message": "expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref>`", - "code": null, - "level": "note", - "spans": [], - "children": [], - "rendered": null - } - ], - "rendered": "error[E0053]: method `next` has an incompatible type for trait\n --> compiler/ty/list_iter.rs:52:5\n |\n52 | fn next(&self) -> Option<&'list ty::Ref> {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability\n |\n = note: expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref>`\n\n" - } - "##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = - map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic"); - insta::assert_debug_snapshot!(diag); - } - - #[test] - fn snap_rustc_unused_variable() { - let diag = parse_diagnostic( - r##"{ - "message": "unused variable: `foo`", - "code": { - "code": "unused_variables", - "explanation": null - }, - "level": "warning", - "spans": [ - { - "file_name": "driver/subcommand/repl.rs", - "byte_start": 9228, - "byte_end": 9231, - "line_start": 291, - "line_end": 291, - "column_start": 9, - "column_end": 12, - "is_primary": true, - "text": [ - { - "text": " let foo = 42;", - "highlight_start": 9, - "highlight_end": 12 - } - ], - "label": null, - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } - ], - "children": [ - { - "message": "#[warn(unused_variables)] on by default", - "code": null, - "level": "note", - "spans": [], - "children": [], - "rendered": null - }, - { - "message": "consider prefixing with an underscore", - "code": null, - "level": "help", - "spans": [ - { - "file_name": "driver/subcommand/repl.rs", - "byte_start": 9228, - "byte_end": 9231, - "line_start": 291, - "line_end": 291, - "column_start": 9, - "column_end": 12, - "is_primary": true, - "text": [ - { - "text": " let foo = 42;", - "highlight_start": 9, - "highlight_end": 12 - } - ], - "label": null, - "suggested_replacement": "_foo", - "suggestion_applicability": "MachineApplicable", - "expansion": null - } - ], - "children": [], - "rendered": null - } - ], - "rendered": "warning: unused variable: `foo`\n --> driver/subcommand/repl.rs:291:9\n |\n291 | let foo = 42;\n | ^^^ help: consider prefixing with an underscore: `_foo`\n |\n = note: #[warn(unused_variables)] on by default\n\n" -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = - map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic"); - insta::assert_debug_snapshot!(diag); - } - - #[test] - fn snap_rustc_wrong_number_of_parameters() { - let diag = parse_diagnostic( - r##"{ - "message": "this function takes 2 parameters but 3 parameters were supplied", - "code": { - "code": "E0061", - "explanation": "\nThe number of arguments passed to a function must match the number of arguments\nspecified in the function signature.\n\nFor example, a function like:\n\n```\nfn f(a: u16, b: &str) {}\n```\n\nMust always be called with exactly two arguments, e.g., `f(2, \"test\")`.\n\nNote that Rust does not have a notion of optional function arguments or\nvariadic functions (except for its C-FFI).\n" - }, - "level": "error", - "spans": [ - { - "file_name": "compiler/ty/select.rs", - "byte_start": 8787, - "byte_end": 9241, - "line_start": 219, - "line_end": 231, - "column_start": 5, - "column_end": 6, - "is_primary": false, - "text": [ - { - "text": " pub fn add_evidence(", - "highlight_start": 5, - "highlight_end": 25 - }, - { - "text": " &mut self,", - "highlight_start": 1, - "highlight_end": 19 - }, - { - "text": " target_poly: &ty::Ref,", - "highlight_start": 1, - "highlight_end": 41 - }, - { - "text": " evidence_poly: &ty::Ref,", - "highlight_start": 1, - "highlight_end": 43 - }, - { - "text": " ) {", - "highlight_start": 1, - "highlight_end": 8 - }, - { - "text": " match target_poly {", - "highlight_start": 1, - "highlight_end": 28 - }, - { - "text": " ty::Ref::Var(tvar, _) => self.add_var_evidence(tvar, evidence_poly),", - "highlight_start": 1, - "highlight_end": 81 - }, - { - "text": " ty::Ref::Fixed(target_ty) => {", - "highlight_start": 1, - "highlight_end": 43 - }, - { - "text": " let evidence_ty = evidence_poly.resolve_to_ty();", - "highlight_start": 1, - "highlight_end": 65 - }, - { - "text": " self.add_evidence_ty(target_ty, evidence_poly, evidence_ty)", - "highlight_start": 1, - "highlight_end": 76 - }, - { - "text": " }", - "highlight_start": 1, - "highlight_end": 14 - }, - { - "text": " }", - "highlight_start": 1, - "highlight_end": 10 - }, - { - "text": " }", - "highlight_start": 1, - "highlight_end": 6 - } - ], - "label": "defined here", - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - }, - { - "file_name": "compiler/ty/select.rs", - "byte_start": 4045, - "byte_end": 4057, - "line_start": 104, - "line_end": 104, - "column_start": 18, - "column_end": 30, - "is_primary": true, - "text": [ - { - "text": " self.add_evidence(target_fixed, evidence_fixed, false);", - "highlight_start": 18, - "highlight_end": 30 - } - ], - "label": "expected 2 parameters", - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } - ], - "children": [], - "rendered": "error[E0061]: this function takes 2 parameters but 3 parameters were supplied\n --> compiler/ty/select.rs:104:18\n |\n104 | self.add_evidence(target_fixed, evidence_fixed, false);\n | ^^^^^^^^^^^^ expected 2 parameters\n...\n219 | / pub fn add_evidence(\n220 | | &mut self,\n221 | | target_poly: &ty::Ref,\n222 | | evidence_poly: &ty::Ref,\n... |\n230 | | }\n231 | | }\n | |_____- defined here\n\n" -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = - map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic"); - insta::assert_debug_snapshot!(diag); - } - - #[test] - fn snap_clippy_pass_by_ref() { - let diag = parse_diagnostic( - r##"{ - "message": "this argument is passed by reference, but would be more efficient if passed by value", - "code": { - "code": "clippy::trivially_copy_pass_by_ref", - "explanation": null - }, - "level": "warning", - "spans": [ - { - "file_name": "compiler/mir/tagset.rs", - "byte_start": 941, - "byte_end": 946, - "line_start": 42, - "line_end": 42, - "column_start": 24, - "column_end": 29, - "is_primary": true, - "text": [ - { - "text": " pub fn is_disjoint(&self, other: Self) -> bool {", - "highlight_start": 24, - "highlight_end": 29 - } - ], - "label": null, - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } - ], - "children": [ - { - "message": "lint level defined here", - "code": null, - "level": "note", - "spans": [ - { - "file_name": "compiler/lib.rs", - "byte_start": 8, - "byte_end": 19, - "line_start": 1, - "line_end": 1, - "column_start": 9, - "column_end": 20, - "is_primary": true, - "text": [ - { - "text": "#![warn(clippy::all)]", - "highlight_start": 9, - "highlight_end": 20 - } - ], - "label": null, - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } - ], - "children": [], - "rendered": null - }, - { - "message": "#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]", - "code": null, - "level": "note", - "spans": [], - "children": [], - "rendered": null - }, - { - "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref", - "code": null, - "level": "help", - "spans": [], - "children": [], - "rendered": null - }, - { - "message": "consider passing by value instead", - "code": null, - "level": "help", - "spans": [ - { - "file_name": "compiler/mir/tagset.rs", - "byte_start": 941, - "byte_end": 946, - "line_start": 42, - "line_end": 42, - "column_start": 24, - "column_end": 29, - "is_primary": true, - "text": [ - { - "text": " pub fn is_disjoint(&self, other: Self) -> bool {", - "highlight_start": 24, - "highlight_end": 29 - } - ], - "label": null, - "suggested_replacement": "self", - "suggestion_applicability": "Unspecified", - "expansion": null - } - ], - "children": [], - "rendered": null - } - ], - "rendered": "warning: this argument is passed by reference, but would be more efficient if passed by value\n --> compiler/mir/tagset.rs:42:24\n |\n42 | pub fn is_disjoint(&self, other: Self) -> bool {\n | ^^^^^ help: consider passing by value instead: `self`\n |\nnote: lint level defined here\n --> compiler/lib.rs:1:9\n |\n1 | #![warn(clippy::all)]\n | ^^^^^^^^^^^\n = note: #[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref\n\n" -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = - map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic"); - insta::assert_debug_snapshot!(diag); - } - - #[test] - fn snap_rustc_mismatched_type() { - let diag = parse_diagnostic( - r##"{ - "message": "mismatched types", - "code": { - "code": "E0308", - "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n" - }, - "level": "error", - "spans": [ - { - "file_name": "runtime/compiler_support.rs", - "byte_start": 1589, - "byte_end": 1594, - "line_start": 48, - "line_end": 48, - "column_start": 65, - "column_end": 70, - "is_primary": true, - "text": [ - { - "text": " let layout = alloc::Layout::from_size_align_unchecked(size, align);", - "highlight_start": 65, - "highlight_end": 70 - } - ], - "label": "expected usize, found u32", - "suggested_replacement": null, - "suggestion_applicability": null, - "expansion": null - } - ], - "children": [], - "rendered": "error[E0308]: mismatched types\n --> runtime/compiler_support.rs:48:65\n |\n48 | let layout = alloc::Layout::from_size_align_unchecked(size, align);\n | ^^^^^ expected usize, found u32\n\n" -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = - map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic"); - insta::assert_debug_snapshot!(diag); - } - - #[test] - fn snap_handles_macro_location() { - let diag = parse_diagnostic( - r##"{ - "rendered": "error[E0277]: can't compare `{integer}` with `&str`\n --> src/main.rs:2:5\n |\n2 | assert_eq!(1, \"love\");\n | ^^^^^^^^^^^^^^^^^^^^^^ no implementation for `{integer} == &str`\n |\n = help: the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`\n = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)\n\n", - "children": [ - { - "children": [], - "code": null, - "level": "help", - "message": "the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`", - "rendered": null, - "spans": [] - } - ], - "code": { - "code": "E0277", - "explanation": "\nYou tried to use a type which doesn't implement some trait in a place which\nexpected that trait. Erroneous code example:\n\n```compile_fail,E0277\n// here we declare the Foo trait with a bar method\ntrait Foo {\n fn bar(&self);\n}\n\n// we now declare a function which takes an object implementing the Foo trait\nfn some_func(foo: T) {\n foo.bar();\n}\n\nfn main() {\n // we now call the method with the i32 type, which doesn't implement\n // the Foo trait\n some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n fn bar(&self);\n}\n\nfn some_func(foo: T) {\n foo.bar(); // we can now use this method since i32 implements the\n // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n fn bar(&self) {}\n}\n\nfn main() {\n some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func(foo: T) {\n println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n // implemented for the type `T`\n}\n\nfn main() {\n // We now call the method with the i32 type,\n // which *does* implement the Debug trait.\n some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func(foo: T) {\n println!(\"{:?}\", foo);\n}\n\nfn main() {\n // Calling the method is still fine, as i32 implements Debug.\n some_func(5i32);\n\n // This would fail to compile now:\n // struct WithoutDebug;\n // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n" - }, - "level": "error", - "message": "can't compare `{integer}` with `&str`", - "spans": [ - { - "byte_end": 155, - "byte_start": 153, - "column_end": 33, - "column_start": 31, - "expansion": { - "def_site_span": { - "byte_end": 940, - "byte_start": 0, - "column_end": 6, - "column_start": 1, - "expansion": null, - "file_name": "<::core::macros::assert_eq macros>", - "is_primary": false, - "label": null, - "line_end": 36, - "line_start": 1, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 35, - "highlight_start": 1, - "text": "($ left : expr, $ right : expr) =>" - }, - { - "highlight_end": 3, - "highlight_start": 1, - "text": "({" - }, - { - "highlight_end": 33, - "highlight_start": 1, - "text": " match (& $ left, & $ right)" - }, - { - "highlight_end": 7, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 34, - "highlight_start": 1, - "text": " (left_val, right_val) =>" - }, - { - "highlight_end": 11, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 46, - "highlight_start": 1, - "text": " if ! (* left_val == * right_val)" - }, - { - "highlight_end": 15, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 25, - "highlight_start": 1, - "text": " panic !" - }, - { - "highlight_end": 57, - "highlight_start": 1, - "text": " (r#\"assertion failed: `(left == right)`" - }, - { - "highlight_end": 16, - "highlight_start": 1, - "text": " left: `{:?}`," - }, - { - "highlight_end": 18, - "highlight_start": 1, - "text": " right: `{:?}`\"#," - }, - { - "highlight_end": 47, - "highlight_start": 1, - "text": " & * left_val, & * right_val)" - }, - { - "highlight_end": 15, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 11, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 7, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 42, - "highlight_start": 1, - "text": " }) ; ($ left : expr, $ right : expr,) =>" - }, - { - "highlight_end": 49, - "highlight_start": 1, - "text": "({ $ crate :: assert_eq ! ($ left, $ right) }) ;" - }, - { - "highlight_end": 53, - "highlight_start": 1, - "text": "($ left : expr, $ right : expr, $ ($ arg : tt) +) =>" - }, - { - "highlight_end": 3, - "highlight_start": 1, - "text": "({" - }, - { - "highlight_end": 37, - "highlight_start": 1, - "text": " match (& ($ left), & ($ right))" - }, - { - "highlight_end": 7, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 34, - "highlight_start": 1, - "text": " (left_val, right_val) =>" - }, - { - "highlight_end": 11, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 46, - "highlight_start": 1, - "text": " if ! (* left_val == * right_val)" - }, - { - "highlight_end": 15, - "highlight_start": 1, - "text": " {" - }, - { - "highlight_end": 25, - "highlight_start": 1, - "text": " panic !" - }, - { - "highlight_end": 57, - "highlight_start": 1, - "text": " (r#\"assertion failed: `(left == right)`" - }, - { - "highlight_end": 16, - "highlight_start": 1, - "text": " left: `{:?}`," - }, - { - "highlight_end": 22, - "highlight_start": 1, - "text": " right: `{:?}`: {}\"#," - }, - { - "highlight_end": 72, - "highlight_start": 1, - "text": " & * left_val, & * right_val, $ crate :: format_args !" - }, - { - "highlight_end": 33, - "highlight_start": 1, - "text": " ($ ($ arg) +))" - }, - { - "highlight_end": 15, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 11, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 7, - "highlight_start": 1, - "text": " }" - }, - { - "highlight_end": 6, - "highlight_start": 1, - "text": " }) ;" - } - ] - }, - "macro_decl_name": "assert_eq!", - "span": { - "byte_end": 38, - "byte_start": 16, - "column_end": 27, - "column_start": 5, - "expansion": null, - "file_name": "src/main.rs", - "is_primary": false, - "label": null, - "line_end": 2, - "line_start": 2, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 27, - "highlight_start": 5, - "text": " assert_eq!(1, \"love\");" - } - ] - } - }, - "file_name": "<::core::macros::assert_eq macros>", - "is_primary": true, - "label": "no implementation for `{integer} == &str`", - "line_end": 7, - "line_start": 7, - "suggested_replacement": null, - "suggestion_applicability": null, - "text": [ - { - "highlight_end": 33, - "highlight_start": 31, - "text": " if ! (* left_val == * right_val)" - } - ] - } - ] -}"##, - ); - - let workspace_root = PathBuf::from("/test/"); - let diag = - map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic"); - insta::assert_debug_snapshot!(diag); - } -} diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs index 2811231fa..2ca149fd5 100644 --- a/crates/ra_lsp_server/src/lib.rs +++ b/crates/ra_lsp_server/src/lib.rs @@ -22,7 +22,6 @@ macro_rules! print { } mod caps; -mod cargo_check; mod cargo_target_spec; mod conv; mod main_loop; diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index c58af7e47..e66b8f9eb 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -10,6 +10,7 @@ use std::{error::Error, fmt, panic, path::PathBuf, sync::Arc, time::Instant}; use crossbeam_channel::{select, unbounded, RecvError, Sender}; use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; use lsp_types::{ClientCapabilities, NumberOrString}; +use ra_cargo_watch::{CheckOptions, CheckTask}; use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId}; use ra_prof::profile; use ra_vfs::{VfsTask, Watch}; @@ -19,7 +20,6 @@ use serde::{de::DeserializeOwned, Serialize}; use threadpool::ThreadPool; use crate::{ - cargo_check::CheckTask, main_loop::{ pending_requests::{PendingRequest, PendingRequests}, subscriptions::Subscriptions, @@ -127,10 +127,12 @@ pub fn main_loop( .and_then(|it| it.line_folding_only) .unwrap_or(false), max_inlay_hint_length: config.max_inlay_hint_length, - cargo_watch_enable: config.cargo_watch_enable, - cargo_watch_args: config.cargo_watch_args, - cargo_watch_command: config.cargo_watch_command, - cargo_watch_all_targets: config.cargo_watch_all_targets, + cargo_watch: CheckOptions { + enable: config.cargo_watch_enable, + args: config.cargo_watch_args, + command: config.cargo_watch_command, + all_targets: config.cargo_watch_all_targets, + }, } }; diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_clippy_pass_by_ref.snap b/crates/ra_lsp_server/src/snapshots/test__snap_clippy_pass_by_ref.snap deleted file mode 100644 index a5ce29157..000000000 --- a/crates/ra_lsp_server/src/snapshots/test__snap_clippy_pass_by_ref.snap +++ /dev/null @@ -1,85 +0,0 @@ ---- -source: crates/ra_lsp_server/src/cargo_check.rs -expression: diag ---- -MappedRustDiagnostic { - location: Location { - uri: "file:///test/compiler/mir/tagset.rs", - range: Range { - start: Position { - line: 41, - character: 23, - }, - end: Position { - line: 41, - character: 28, - }, - }, - }, - diagnostic: Diagnostic { - range: Range { - start: Position { - line: 41, - character: 23, - }, - end: Position { - line: 41, - character: 28, - }, - }, - severity: Some( - Warning, - ), - code: Some( - String( - "trivially_copy_pass_by_ref", - ), - ), - source: Some( - "clippy", - ), - message: "this argument is passed by reference, but would be more efficient if passed by value\n#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\nfor further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref", - related_information: Some( - [ - DiagnosticRelatedInformation { - location: Location { - uri: "file:///test/compiler/lib.rs", - range: Range { - start: Position { - line: 0, - character: 8, - }, - end: Position { - line: 0, - character: 19, - }, - }, - }, - message: "lint level defined here", - }, - ], - ), - tags: None, - }, - suggested_fixes: [ - SuggestedFix { - title: "consider passing by value instead: \'self\'", - location: Location { - uri: "file:///test/compiler/mir/tagset.rs", - range: Range { - start: Position { - line: 41, - character: 23, - }, - end: Position { - line: 41, - character: 28, - }, - }, - }, - replacement: "self", - applicability: Unspecified, - diagnostics: [], - }, - ], -} diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_handles_macro_location.snap b/crates/ra_lsp_server/src/snapshots/test__snap_handles_macro_location.snap deleted file mode 100644 index 07e363ebf..000000000 --- a/crates/ra_lsp_server/src/snapshots/test__snap_handles_macro_location.snap +++ /dev/null @@ -1,46 +0,0 @@ ---- -source: crates/ra_lsp_server/src/cargo_check.rs -expression: diag ---- -MappedRustDiagnostic { - location: Location { - uri: "file:///test/src/main.rs", - range: Range { - start: Position { - line: 1, - character: 4, - }, - end: Position { - line: 1, - character: 26, - }, - }, - }, - diagnostic: Diagnostic { - range: Range { - start: Position { - line: 1, - character: 4, - }, - end: Position { - line: 1, - character: 26, - }, - }, - severity: Some( - Error, - ), - code: Some( - String( - "E0277", - ), - ), - source: Some( - "rustc", - ), - message: "can\'t compare `{integer}` with `&str`\nthe trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`", - related_information: None, - tags: None, - }, - suggested_fixes: [], -} diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_incompatible_type_for_trait.snap b/crates/ra_lsp_server/src/snapshots/test__snap_rustc_incompatible_type_for_trait.snap deleted file mode 100644 index 85a87db0b..000000000 --- a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_incompatible_type_for_trait.snap +++ /dev/null @@ -1,46 +0,0 @@ ---- -source: crates/ra_lsp_server/src/cargo_check.rs -expression: diag ---- -MappedRustDiagnostic { - location: Location { - uri: "file:///test/compiler/ty/list_iter.rs", - range: Range { - start: Position { - line: 51, - character: 4, - }, - end: Position { - line: 51, - character: 47, - }, - }, - }, - diagnostic: Diagnostic { - range: Range { - start: Position { - line: 51, - character: 4, - }, - end: Position { - line: 51, - character: 47, - }, - }, - severity: Some( - Error, - ), - code: Some( - String( - "E0053", - ), - ), - source: Some( - "rustc", - ), - message: "method `next` has an incompatible type for trait\nexpected type `fn(&mut ty::list_iter::ListIterator<\'list, M>) -> std::option::Option<&ty::Ref>`\n found type `fn(&ty::list_iter::ListIterator<\'list, M>) -> std::option::Option<&\'list ty::Ref>`", - related_information: None, - tags: None, - }, - suggested_fixes: [], -} diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_mismatched_type.snap b/crates/ra_lsp_server/src/snapshots/test__snap_rustc_mismatched_type.snap deleted file mode 100644 index 69cb8badf..000000000 --- a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_mismatched_type.snap +++ /dev/null @@ -1,46 +0,0 @@ ---- -source: crates/ra_lsp_server/src/cargo_check.rs -expression: diag ---- -MappedRustDiagnostic { - location: Location { - uri: "file:///test/runtime/compiler_support.rs", - range: Range { - start: Position { - line: 47, - character: 64, - }, - end: Position { - line: 47, - character: 69, - }, - }, - }, - diagnostic: Diagnostic { - range: Range { - start: Position { - line: 47, - character: 64, - }, - end: Position { - line: 47, - character: 69, - }, - }, - severity: Some( - Error, - ), - code: Some( - String( - "E0308", - ), - ), - source: Some( - "rustc", - ), - message: "mismatched types\nexpected usize, found u32", - related_information: None, - tags: None, - }, - suggested_fixes: [], -} diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_unused_variable.snap b/crates/ra_lsp_server/src/snapshots/test__snap_rustc_unused_variable.snap deleted file mode 100644 index 33a3e3034..000000000 --- a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_unused_variable.snap +++ /dev/null @@ -1,70 +0,0 @@ ---- -source: crates/ra_lsp_server/src/cargo_check.rs -expression: diag ---- -MappedRustDiagnostic { - location: Location { - uri: "file:///test/driver/subcommand/repl.rs", - range: Range { - start: Position { - line: 290, - character: 8, - }, - end: Position { - line: 290, - character: 11, - }, - }, - }, - diagnostic: Diagnostic { - range: Range { - start: Position { - line: 290, - character: 8, - }, - end: Position { - line: 290, - character: 11, - }, - }, - severity: Some( - Warning, - ), - code: Some( - String( - "unused_variables", - ), - ), - source: Some( - "rustc", - ), - message: "unused variable: `foo`\n#[warn(unused_variables)] on by default", - related_information: None, - tags: Some( - [ - Unnecessary, - ], - ), - }, - suggested_fixes: [ - SuggestedFix { - title: "consider prefixing with an underscore: \'_foo\'", - location: Location { - uri: "file:///test/driver/subcommand/repl.rs", - range: Range { - start: Position { - line: 290, - character: 8, - }, - end: Position { - line: 290, - character: 11, - }, - }, - }, - replacement: "_foo", - applicability: MachineApplicable, - diagnostics: [], - }, - ], -} diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_wrong_number_of_parameters.snap b/crates/ra_lsp_server/src/snapshots/test__snap_rustc_wrong_number_of_parameters.snap deleted file mode 100644 index ced6fa4df..000000000 --- a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_wrong_number_of_parameters.snap +++ /dev/null @@ -1,65 +0,0 @@ ---- -source: crates/ra_lsp_server/src/cargo_check.rs -expression: diag ---- -MappedRustDiagnostic { - location: Location { - uri: "file:///test/compiler/ty/select.rs", - range: Range { - start: Position { - line: 103, - character: 17, - }, - end: Position { - line: 103, - character: 29, - }, - }, - }, - diagnostic: Diagnostic { - range: Range { - start: Position { - line: 103, - character: 17, - }, - end: Position { - line: 103, - character: 29, - }, - }, - severity: Some( - Error, - ), - code: Some( - String( - "E0061", - ), - ), - source: Some( - "rustc", - ), - message: "this function takes 2 parameters but 3 parameters were supplied\nexpected 2 parameters", - related_information: Some( - [ - DiagnosticRelatedInformation { - location: Location { - uri: "file:///test/compiler/ty/select.rs", - range: Range { - start: Position { - line: 218, - character: 4, - }, - end: Position { - line: 230, - character: 5, - }, - }, - }, - message: "defined here", - }, - ], - ), - tags: None, - }, - suggested_fixes: [], -} diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs index 39a07c01a..4b3959e38 100644 --- a/crates/ra_lsp_server/src/world.rs +++ b/crates/ra_lsp_server/src/world.rs @@ -12,6 +12,7 @@ use crossbeam_channel::{unbounded, Receiver}; use lsp_server::ErrorCode; use lsp_types::Url; use parking_lot::RwLock; +use ra_cargo_watch::{CheckOptions, CheckWatcher, CheckWatcherSharedState}; use ra_ide::{ Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData, SourceRootId, @@ -23,7 +24,6 @@ use relative_path::RelativePathBuf; use std::path::{Component, Prefix}; use crate::{ - cargo_check::{CheckWatcher, CheckWatcherSharedState}, main_loop::pending_requests::{CompletedRequest, LatestRequests}, LspError, Result, }; @@ -35,10 +35,7 @@ pub struct Options { pub supports_location_link: bool, pub line_folding_only: bool, pub max_inlay_hint_length: Option, - pub cargo_watch_enable: bool, - pub cargo_watch_args: Vec, - pub cargo_watch_command: String, - pub cargo_watch_all_targets: bool, + pub cargo_watch: CheckOptions, } /// `WorldState` is the primary mutable state of the language server @@ -135,7 +132,8 @@ impl WorldState { change.set_crate_graph(crate_graph); // FIXME: Figure out the multi-workspace situation - let check_watcher = CheckWatcher::new(&options, folder_roots.first().cloned().unwrap()); + let check_watcher = + CheckWatcher::new(&options.cargo_watch, folder_roots.first().cloned().unwrap()); let mut analysis_host = AnalysisHost::new(lru_capacity, feature_flags); analysis_host.apply_change(change); -- cgit v1.2.3 From c732f215cb31e9f022090b8d0212f6ea9c134c11 Mon Sep 17 00:00:00 2001 From: Emil Lauridsen Date: Fri, 27 Dec 2019 12:43:14 +0100 Subject: Don't finish main cargo watch thread when subprocess finishes. --- crates/ra_lsp_server/src/main_loop.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'crates/ra_lsp_server') diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index e66b8f9eb..af1a487de 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -184,7 +184,10 @@ pub fn main_loop( Err(RecvError) => Err("vfs died")?, }, recv(libdata_receiver) -> data => Event::Lib(data.unwrap()), - recv(world_state.check_watcher.task_recv) -> task => Event::CheckWatcher(task.unwrap()) + recv(world_state.check_watcher.task_recv) -> task => match task { + Ok(task) => Event::CheckWatcher(task), + Err(RecvError) => Err("check watcher died")?, + } }; if let Event::Msg(Message::Request(req)) = &event { if connection.handle_shutdown(&req)? { -- cgit v1.2.3