Skip to content

Commit b1854ce

Browse files
bors[bot]Veykril
andauthored
Merge #11958
11958: Show config deseralization failures on start up r=Veykril a=Veykril We now also show deserialization errors to the user when starting the server. This PR also adds a small validation "pass" on the config that we will probably populate over time with more checks. Fixes #11950 bors r+ Co-authored-by: Lukas Wirth <[email protected]>
2 parents 7a564af + b90df79 commit b1854ce

File tree

5 files changed

+97
-61
lines changed

5 files changed

+97
-61
lines changed

crates/rust-analyzer/src/bin/main.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,17 @@ fn run_server() -> Result<()> {
150150

151151
let mut config = Config::new(root_path, initialize_params.capabilities);
152152
if let Some(json) = initialize_params.initialization_options {
153-
let _ = config.update(json);
153+
if let Err(e) = config.update(json) {
154+
use lsp_types::{
155+
notification::{Notification, ShowMessage},
156+
MessageType, ShowMessageParams,
157+
};
158+
let not = lsp_server::Notification::new(
159+
ShowMessage::METHOD.to_string(),
160+
ShowMessageParams { typ: MessageType::WARNING, message: e.to_string() },
161+
);
162+
connection.sender.send(lsp_server::Message::Notification(not)).unwrap();
163+
}
154164
}
155165

156166
let server_capabilities = rust_analyzer::server_capabilities(&config);
@@ -161,7 +171,11 @@ fn run_server() -> Result<()> {
161171
name: String::from("rust-analyzer"),
162172
version: Some(String::from(env!("REV"))),
163173
}),
164-
offset_encoding: if supports_utf8(&config.caps) { Some("utf-8".to_string()) } else { None },
174+
offset_encoding: if supports_utf8(config.caps()) {
175+
Some("utf-8".to_string())
176+
} else {
177+
None
178+
},
165179
};
166180

167181
let initialize_result = serde_json::to_value(initialize_result).unwrap();
@@ -183,7 +197,7 @@ fn run_server() -> Result<()> {
183197
.collect::<Vec<_>>()
184198
})
185199
.filter(|workspaces| !workspaces.is_empty())
186-
.unwrap_or_else(|| vec![config.root_path.clone()]);
200+
.unwrap_or_else(|| vec![config.root_path().clone()]);
187201

188202
let discovered = ProjectManifest::discover_all(&workspace_roots);
189203
tracing::info!("discovered projects: {:?}", discovered);

crates/rust-analyzer/src/caps.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
2727
})),
2828
hover_provider: Some(HoverProviderCapability::Simple(true)),
2929
completion_provider: Some(CompletionOptions {
30-
resolve_provider: completions_resolve_provider(&config.caps),
30+
resolve_provider: completions_resolve_provider(config.caps()),
3131
trigger_characters: Some(vec![":".to_string(), ".".to_string(), "'".to_string()]),
3232
all_commit_characters: None,
3333
completion_item: None,
@@ -46,7 +46,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
4646
document_highlight_provider: Some(OneOf::Left(true)),
4747
document_symbol_provider: Some(OneOf::Left(true)),
4848
workspace_symbol_provider: Some(OneOf::Left(true)),
49-
code_action_provider: Some(code_action_capabilities(&config.caps)),
49+
code_action_provider: Some(code_action_capabilities(config.caps())),
5050
code_lens_provider: Some(CodeLensOptions { resolve_provider: Some(true) }),
5151
document_formatting_provider: Some(OneOf::Left(true)),
5252
document_range_formatting_provider: match config.rustfmt() {

crates/rust-analyzer/src/cargo_target_spec.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ impl CargoTargetSpec {
123123
file_id: FileId,
124124
) -> Result<Option<CargoTargetSpec>> {
125125
let crate_id = match global_state_snapshot.analysis.crate_for(file_id)?.first() {
126-
Some(crate_id) => *crate_id,
126+
Some(&crate_id) => crate_id,
127127
None => return Ok(None),
128128
};
129129
let (cargo_ws, target) = match global_state_snapshot.cargo_target_for_crate_root(crate_id) {

crates/rust-analyzer/src/config.rs

Lines changed: 75 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
//! configure the server itself, feature flags are passed into analysis, and
88
//! tweak things like automatic insertion of `()` in completions.
99
10-
use std::{ffi::OsString, iter, path::PathBuf};
10+
use std::{ffi::OsString, fmt, iter, path::PathBuf};
1111

1212
use flycheck::FlycheckConfig;
1313
use ide::{
@@ -19,6 +19,7 @@ use ide_db::{
1919
imports::insert_use::{ImportGranularity, InsertUseConfig, PrefixKind},
2020
SnippetCap,
2121
};
22+
use itertools::Itertools;
2223
use lsp_types::{ClientCapabilities, MarkupKind};
2324
use project_model::{
2425
CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest, RustcSource, UnsetTestCrates,
@@ -31,9 +32,7 @@ use crate::{
3132
caps::completion_item_edit_resolve,
3233
diagnostics::DiagnosticsMapConfig,
3334
line_index::OffsetEncoding,
34-
lsp_ext::supports_utf8,
35-
lsp_ext::WorkspaceSymbolSearchScope,
36-
lsp_ext::{self, WorkspaceSymbolSearchKind},
35+
lsp_ext::{self, supports_utf8, WorkspaceSymbolSearchKind, WorkspaceSymbolSearchScope},
3736
};
3837

3938
// Defines the server-side configuration of the rust-analyzer. We generate
@@ -369,11 +368,11 @@ impl Default for ConfigData {
369368

370369
#[derive(Debug, Clone)]
371370
pub struct Config {
372-
pub caps: lsp_types::ClientCapabilities,
371+
pub discovered_projects: Option<Vec<ProjectManifest>>,
372+
caps: lsp_types::ClientCapabilities,
373+
root_path: AbsPathBuf,
373374
data: ConfigData,
374375
detached_files: Vec<AbsPathBuf>,
375-
pub discovered_projects: Option<Vec<ProjectManifest>>,
376-
pub root_path: AbsPathBuf,
377376
snippets: Vec<Snippet>,
378377
}
379378

@@ -505,6 +504,27 @@ pub struct ClientCommandsConfig {
505504
pub trigger_parameter_hints: bool,
506505
}
507506

507+
pub struct ConfigUpdateError {
508+
errors: Vec<(String, serde_json::Error)>,
509+
}
510+
511+
impl fmt::Display for ConfigUpdateError {
512+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
513+
let errors = self.errors.iter().format_with("\n", |(key, e), f| {
514+
f(key)?;
515+
f(&": ")?;
516+
f(e)
517+
});
518+
write!(
519+
f,
520+
"rust-analyzer found {} invalid config value{}:\n{}",
521+
self.errors.len(),
522+
if self.errors.len() == 1 { "" } else { "s" },
523+
errors
524+
)
525+
}
526+
}
527+
508528
impl Config {
509529
pub fn new(root_path: AbsPathBuf, caps: ClientCapabilities) -> Self {
510530
Config {
@@ -516,10 +536,8 @@ impl Config {
516536
snippets: Default::default(),
517537
}
518538
}
519-
pub fn update(
520-
&mut self,
521-
mut json: serde_json::Value,
522-
) -> Result<(), Vec<(String, serde_json::Error)>> {
539+
540+
pub fn update(&mut self, mut json: serde_json::Value) -> Result<(), ConfigUpdateError> {
523541
tracing::info!("updating config from JSON: {:#}", json);
524542
if json.is_null() || json.as_object().map_or(false, |it| it.is_empty()) {
525543
return Ok(());
@@ -553,16 +571,41 @@ impl Config {
553571
None => tracing::info!("Invalid snippet {}", name),
554572
}
555573
}
574+
575+
self.validate(&mut errors);
576+
556577
if errors.is_empty() {
557578
Ok(())
558579
} else {
559-
Err(errors)
580+
Err(ConfigUpdateError { errors })
581+
}
582+
}
583+
584+
fn validate(&self, error_sink: &mut Vec<(String, serde_json::Error)>) {
585+
use serde::de::Error;
586+
if self.data.checkOnSave_command.is_empty() {
587+
error_sink.push((
588+
"/checkOnSave/command".to_string(),
589+
serde_json::Error::custom("expected a non-empty string"),
590+
));
560591
}
561592
}
562593

563594
pub fn json_schema() -> serde_json::Value {
564595
ConfigData::json_schema()
565596
}
597+
598+
pub fn root_path(&self) -> &AbsPathBuf {
599+
&self.root_path
600+
}
601+
602+
pub fn caps(&self) -> &lsp_types::ClientCapabilities {
603+
&self.caps
604+
}
605+
606+
pub fn detached_files(&self) -> &[AbsPathBuf] {
607+
&self.detached_files
608+
}
566609
}
567610

568611
macro_rules! try_ {
@@ -578,43 +621,31 @@ macro_rules! try_or {
578621

579622
impl Config {
580623
pub fn linked_projects(&self) -> Vec<LinkedProject> {
581-
if self.data.linkedProjects.is_empty() {
582-
self.discovered_projects
583-
.as_ref()
584-
.into_iter()
585-
.flatten()
586-
.cloned()
587-
.map(LinkedProject::from)
588-
.collect()
589-
} else {
590-
self.data
591-
.linkedProjects
624+
match self.data.linkedProjects.as_slice() {
625+
[] => match self.discovered_projects.as_ref() {
626+
Some(discovered_projects) => {
627+
discovered_projects.iter().cloned().map(LinkedProject::from).collect()
628+
}
629+
None => Vec::new(),
630+
},
631+
linked_projects => linked_projects
592632
.iter()
593-
.filter_map(|linked_project| {
594-
let res = match linked_project {
595-
ManifestOrProjectJson::Manifest(it) => {
596-
let path = self.root_path.join(it);
597-
ProjectManifest::from_manifest_file(path)
598-
.map_err(|e| {
599-
tracing::error!("failed to load linked project: {}", e)
600-
})
601-
.ok()?
602-
.into()
603-
}
604-
ManifestOrProjectJson::ProjectJson(it) => {
605-
ProjectJson::new(&self.root_path, it.clone()).into()
606-
}
607-
};
608-
Some(res)
633+
.filter_map(|linked_project| match linked_project {
634+
ManifestOrProjectJson::Manifest(it) => {
635+
let path = self.root_path.join(it);
636+
ProjectManifest::from_manifest_file(path)
637+
.map_err(|e| tracing::error!("failed to load linked project: {}", e))
638+
.ok()
639+
.map(Into::into)
640+
}
641+
ManifestOrProjectJson::ProjectJson(it) => {
642+
Some(ProjectJson::new(&self.root_path, it.clone()).into())
643+
}
609644
})
610-
.collect()
645+
.collect(),
611646
}
612647
}
613648

614-
pub fn detached_files(&self) -> &[AbsPathBuf] {
615-
&self.detached_files
616-
}
617-
618649
pub fn did_save_text_document_dynamic_registration(&self) -> bool {
619650
let caps =
620651
try_or!(self.caps.text_document.as_ref()?.synchronization.clone()?, Default::default());

crates/rust-analyzer/src/main_loop.rs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use std::{
99
use always_assert::always;
1010
use crossbeam_channel::{select, Receiver};
1111
use ide_db::base_db::{SourceDatabaseExt, VfsPath};
12-
use itertools::Itertools;
1312
use lsp_server::{Connection, Notification, Request};
1413
use lsp_types::notification::Notification as _;
1514
use vfs::{ChangeKind, FileId};
@@ -747,16 +746,8 @@ impl GlobalState {
747746
// Note that json can be null according to the spec if the client can't
748747
// provide a configuration. This is handled in Config::update below.
749748
let mut config = Config::clone(&*this.config);
750-
if let Err(errors) = config.update(json.take()) {
751-
let errors = errors
752-
.iter()
753-
.format_with("\n", |(key, e),f| {
754-
f(key)?;
755-
f(&": ")?;
756-
f(e)
757-
});
758-
let msg= format!("Failed to deserialize config key(s):\n{}", errors);
759-
this.show_message(lsp_types::MessageType::WARNING, msg);
749+
if let Err(error) = config.update(json.take()) {
750+
this.show_message(lsp_types::MessageType::WARNING, error.to_string());
760751
}
761752
this.update_configuration(config);
762753
}

0 commit comments

Comments
 (0)