]> git.lizzy.rs Git - rust.git/blob - crates/ra_lsp_server/src/cargo_check.rs
5a6a209eba390918b511c06db8ace57b6a5cb387
[rust.git] / crates / ra_lsp_server / src / cargo_check.rs
1 use crate::world::Options;
2 use cargo_metadata::{
3     diagnostic::{
4         Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan,
5         DiagnosticSpanMacroExpansion,
6     },
7     Message,
8 };
9 use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender, TryRecvError};
10 use lsp_types::{
11     Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location,
12     NumberOrString, Position, Range, Url,
13 };
14 use parking_lot::RwLock;
15 use std::{
16     collections::HashMap,
17     fmt::Write,
18     path::PathBuf,
19     process::{Command, Stdio},
20     sync::Arc,
21     thread::JoinHandle,
22     time::Instant,
23 };
24
25 #[derive(Debug)]
26 pub struct CheckWatcher {
27     pub task_recv: Receiver<CheckTask>,
28     pub cmd_send: Sender<CheckCommand>,
29     pub shared: Arc<RwLock<CheckWatcherSharedState>>,
30     handle: JoinHandle<()>,
31 }
32
33 impl CheckWatcher {
34     pub fn new(options: &Options, workspace_root: PathBuf) -> CheckWatcher {
35         let check_command = options.cargo_check_command.clone();
36         let check_args = options.cargo_check_args.clone();
37         let shared = Arc::new(RwLock::new(CheckWatcherSharedState::new()));
38
39         let (task_send, task_recv) = unbounded::<CheckTask>();
40         let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
41         let shared_ = shared.clone();
42         let handle = std::thread::spawn(move || {
43             let mut check =
44                 CheckWatcherState::new(check_command, check_args, workspace_root, shared_);
45             check.run(&task_send, &cmd_recv);
46         });
47
48         CheckWatcher { task_recv, cmd_send, handle, shared }
49     }
50
51     pub fn update(&self) {
52         self.cmd_send.send(CheckCommand::Update).unwrap();
53     }
54 }
55
56 pub struct CheckWatcherState {
57     check_command: Option<String>,
58     check_args: Vec<String>,
59     workspace_root: PathBuf,
60     running: bool,
61     watcher: WatchThread,
62     last_update_req: Option<Instant>,
63     shared: Arc<RwLock<CheckWatcherSharedState>>,
64 }
65
66 #[derive(Debug)]
67 pub struct CheckWatcherSharedState {
68     diagnostic_collection: HashMap<Url, Vec<Diagnostic>>,
69     suggested_fix_collection: HashMap<Url, Vec<SuggestedFix>>,
70 }
71
72 impl CheckWatcherSharedState {
73     fn new() -> CheckWatcherSharedState {
74         CheckWatcherSharedState {
75             diagnostic_collection: HashMap::new(),
76             suggested_fix_collection: HashMap::new(),
77         }
78     }
79
80     pub fn clear(&mut self, task_send: &Sender<CheckTask>) {
81         let cleared_files: Vec<Url> = self.diagnostic_collection.keys().cloned().collect();
82
83         self.diagnostic_collection.clear();
84         self.suggested_fix_collection.clear();
85
86         for uri in cleared_files {
87             task_send.send(CheckTask::Update(uri.clone())).unwrap();
88         }
89     }
90
91     pub fn diagnostics_for(&self, uri: &Url) -> Option<&[Diagnostic]> {
92         self.diagnostic_collection.get(uri).map(|d| d.as_slice())
93     }
94
95     pub fn fixes_for(&self, uri: &Url) -> Option<&[SuggestedFix]> {
96         self.suggested_fix_collection.get(uri).map(|d| d.as_slice())
97     }
98
99     fn add_diagnostic(&mut self, file_uri: Url, diagnostic: Diagnostic) {
100         let diagnostics = self.diagnostic_collection.entry(file_uri).or_default();
101
102         // If we're building multiple targets it's possible we've already seen this diagnostic
103         let is_duplicate = diagnostics.iter().any(|d| are_diagnostics_equal(d, &diagnostic));
104         if is_duplicate {
105             return;
106         }
107
108         diagnostics.push(diagnostic);
109     }
110
111     fn add_suggested_fix_for_diagnostic(
112         &mut self,
113         mut suggested_fix: SuggestedFix,
114         diagnostic: &Diagnostic,
115     ) {
116         let file_uri = suggested_fix.location.uri.clone();
117         let file_suggestions = self.suggested_fix_collection.entry(file_uri).or_default();
118
119         let existing_suggestion: Option<&mut SuggestedFix> =
120             file_suggestions.iter_mut().find(|s| s == &&suggested_fix);
121         if let Some(existing_suggestion) = existing_suggestion {
122             // The existing suggestion also applies to this new diagnostic
123             existing_suggestion.diagnostics.push(diagnostic.clone());
124         } else {
125             // We haven't seen this suggestion before
126             suggested_fix.diagnostics.push(diagnostic.clone());
127             file_suggestions.push(suggested_fix);
128         }
129     }
130 }
131
132 #[derive(Debug)]
133 pub enum CheckTask {
134     Update(Url),
135 }
136
137 pub enum CheckCommand {
138     Update,
139 }
140
141 impl CheckWatcherState {
142     pub fn new(
143         check_command: Option<String>,
144         check_args: Vec<String>,
145         workspace_root: PathBuf,
146         shared: Arc<RwLock<CheckWatcherSharedState>>,
147     ) -> CheckWatcherState {
148         let watcher = WatchThread::new(check_command.as_ref(), &check_args, &workspace_root);
149         CheckWatcherState {
150             check_command,
151             check_args,
152             workspace_root,
153             running: false,
154             watcher,
155             last_update_req: None,
156             shared,
157         }
158     }
159
160     pub fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) {
161         self.running = true;
162         while self.running {
163             select! {
164                 recv(&cmd_recv) -> cmd => match cmd {
165                     Ok(cmd) => self.handle_command(cmd),
166                     Err(RecvError) => {
167                         // Command channel has closed, so shut down
168                         self.running = false;
169                     },
170                 },
171                 recv(self.watcher.message_recv) -> msg => match msg {
172                     Ok(msg) => self.handle_message(msg, task_send),
173                     Err(RecvError) => {},
174                 }
175             };
176
177             if self.should_recheck() {
178                 self.last_update_req.take();
179                 self.shared.write().clear(task_send);
180
181                 self.watcher.cancel();
182                 self.watcher = WatchThread::new(
183                     self.check_command.as_ref(),
184                     &self.check_args,
185                     &self.workspace_root,
186                 );
187             }
188         }
189     }
190
191     fn should_recheck(&mut self) -> bool {
192         if let Some(_last_update_req) = &self.last_update_req {
193             // We currently only request an update on save, as we need up to
194             // date source on disk for cargo check to do it's magic, so we
195             // don't really need to debounce the requests at this point.
196             return true;
197         }
198         false
199     }
200
201     fn handle_command(&mut self, cmd: CheckCommand) {
202         match cmd {
203             CheckCommand::Update => self.last_update_req = Some(Instant::now()),
204         }
205     }
206
207     fn handle_message(&mut self, msg: cargo_metadata::Message, task_send: &Sender<CheckTask>) {
208         match msg {
209             Message::CompilerArtifact(_msg) => {
210                 // TODO: Status display
211             }
212
213             Message::CompilerMessage(msg) => {
214                 let map_result =
215                     match map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root) {
216                         Some(map_result) => map_result,
217                         None => return,
218                     };
219
220                 let MappedRustDiagnostic { location, diagnostic, suggested_fixes } = map_result;
221                 let file_uri = location.uri.clone();
222
223                 if !suggested_fixes.is_empty() {
224                     for suggested_fix in suggested_fixes {
225                         self.shared
226                             .write()
227                             .add_suggested_fix_for_diagnostic(suggested_fix, &diagnostic);
228                     }
229                 }
230                 self.shared.write().add_diagnostic(file_uri, diagnostic);
231
232                 task_send.send(CheckTask::Update(location.uri)).unwrap();
233             }
234
235             Message::BuildScriptExecuted(_msg) => {}
236             Message::Unknown => {}
237         }
238     }
239 }
240
241 /// WatchThread exists to wrap around the communication needed to be able to
242 /// run `cargo check` without blocking. Currently the Rust standard library
243 /// doesn't provide a way to read sub-process output without blocking, so we
244 /// have to wrap sub-processes output handling in a thread and pass messages
245 /// back over a channel.
246 struct WatchThread {
247     message_recv: Receiver<cargo_metadata::Message>,
248     cancel_send: Sender<()>,
249 }
250
251 impl WatchThread {
252     fn new(
253         check_command: Option<&String>,
254         check_args: &[String],
255         workspace_root: &PathBuf,
256     ) -> WatchThread {
257         let check_command = check_command.cloned().unwrap_or("check".to_string());
258         let mut args: Vec<String> = vec![
259             check_command,
260             "--message-format=json".to_string(),
261             "--manifest-path".to_string(),
262             format!("{}/Cargo.toml", workspace_root.to_string_lossy()),
263         ];
264         args.extend(check_args.iter().cloned());
265
266         let (message_send, message_recv) = unbounded();
267         let (cancel_send, cancel_recv) = unbounded();
268         std::thread::spawn(move || {
269             let mut command = Command::new("cargo")
270                 .args(&args)
271                 .stdout(Stdio::piped())
272                 .stderr(Stdio::null())
273                 .spawn()
274                 .expect("couldn't launch cargo");
275
276             for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) {
277                 match cancel_recv.try_recv() {
278                     Ok(()) | Err(TryRecvError::Disconnected) => {
279                         command.kill().expect("couldn't kill command");
280                     }
281                     Err(TryRecvError::Empty) => (),
282                 }
283
284                 message_send.send(message.unwrap()).unwrap();
285             }
286         });
287         WatchThread { message_recv, cancel_send }
288     }
289
290     fn cancel(&self) {
291         let _ = self.cancel_send.send(());
292     }
293 }
294
295 /// Converts a Rust level string to a LSP severity
296 fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> {
297     match val {
298         DiagnosticLevel::Ice => Some(DiagnosticSeverity::Error),
299         DiagnosticLevel::Error => Some(DiagnosticSeverity::Error),
300         DiagnosticLevel::Warning => Some(DiagnosticSeverity::Warning),
301         DiagnosticLevel::Note => Some(DiagnosticSeverity::Information),
302         DiagnosticLevel::Help => Some(DiagnosticSeverity::Hint),
303         DiagnosticLevel::Unknown => None,
304     }
305 }
306
307 /// Check whether a file name is from macro invocation
308 fn is_from_macro(file_name: &str) -> bool {
309     file_name.starts_with('<') && file_name.ends_with('>')
310 }
311
312 /// Converts a Rust macro span to a LSP location recursively
313 fn map_macro_span_to_location(
314     span_macro: &DiagnosticSpanMacroExpansion,
315     workspace_root: &PathBuf,
316 ) -> Option<Location> {
317     if !is_from_macro(&span_macro.span.file_name) {
318         return Some(map_span_to_location(&span_macro.span, workspace_root));
319     }
320
321     if let Some(expansion) = &span_macro.span.expansion {
322         return map_macro_span_to_location(&expansion, workspace_root);
323     }
324
325     None
326 }
327
328 /// Converts a Rust span to a LSP location
329 fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location {
330     if is_from_macro(&span.file_name) && span.expansion.is_some() {
331         let expansion = span.expansion.as_ref().unwrap();
332         if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) {
333             return macro_range;
334         }
335     }
336
337     let mut file_name = workspace_root.clone();
338     file_name.push(&span.file_name);
339     let uri = Url::from_file_path(file_name).unwrap();
340
341     let range = Range::new(
342         Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1),
343         Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1),
344     );
345
346     Location { uri, range }
347 }
348
349 /// Converts a secondary Rust span to a LSP related information
350 ///
351 /// If the span is unlabelled this will return `None`.
352 fn map_secondary_span_to_related(
353     span: &DiagnosticSpan,
354     workspace_root: &PathBuf,
355 ) -> Option<DiagnosticRelatedInformation> {
356     if let Some(label) = &span.label {
357         let location = map_span_to_location(span, workspace_root);
358         Some(DiagnosticRelatedInformation { location, message: label.clone() })
359     } else {
360         // Nothing to label this with
361         None
362     }
363 }
364
365 /// Determines if diagnostic is related to unused code
366 fn is_unused_or_unnecessary(rd: &RustDiagnostic) -> bool {
367     if let Some(code) = &rd.code {
368         match code.code.as_str() {
369             "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes"
370             | "unused_imports" | "unused_macros" | "unused_variables" => true,
371             _ => false,
372         }
373     } else {
374         false
375     }
376 }
377
378 /// Determines if diagnostic is related to deprecated code
379 fn is_deprecated(rd: &RustDiagnostic) -> bool {
380     if let Some(code) = &rd.code {
381         match code.code.as_str() {
382             "deprecated" => true,
383             _ => false,
384         }
385     } else {
386         false
387     }
388 }
389
390 #[derive(Debug)]
391 pub struct SuggestedFix {
392     pub title: String,
393     pub location: Location,
394     pub replacement: String,
395     pub applicability: Applicability,
396     pub diagnostics: Vec<Diagnostic>,
397 }
398
399 impl std::cmp::PartialEq<SuggestedFix> for SuggestedFix {
400     fn eq(&self, other: &SuggestedFix) -> bool {
401         if self.title == other.title
402             && self.location == other.location
403             && self.replacement == other.replacement
404         {
405             // Applicability doesn't impl PartialEq...
406             match (&self.applicability, &other.applicability) {
407                 (Applicability::MachineApplicable, Applicability::MachineApplicable) => true,
408                 (Applicability::HasPlaceholders, Applicability::HasPlaceholders) => true,
409                 (Applicability::MaybeIncorrect, Applicability::MaybeIncorrect) => true,
410                 (Applicability::Unspecified, Applicability::Unspecified) => true,
411                 _ => false,
412             }
413         } else {
414             false
415         }
416     }
417 }
418
419 enum MappedRustChildDiagnostic {
420     Related(DiagnosticRelatedInformation),
421     SuggestedFix(SuggestedFix),
422     MessageLine(String),
423 }
424
425 fn map_rust_child_diagnostic(
426     rd: &RustDiagnostic,
427     workspace_root: &PathBuf,
428 ) -> MappedRustChildDiagnostic {
429     let span: &DiagnosticSpan = match rd.spans.iter().find(|s| s.is_primary) {
430         Some(span) => span,
431         None => {
432             // `rustc` uses these spanless children as a way to print multi-line
433             // messages
434             return MappedRustChildDiagnostic::MessageLine(rd.message.clone());
435         }
436     };
437
438     // If we have a primary span use its location, otherwise use the parent
439     let location = map_span_to_location(&span, workspace_root);
440
441     if let Some(suggested_replacement) = &span.suggested_replacement {
442         // Include our replacement in the title unless it's empty
443         let title = if !suggested_replacement.is_empty() {
444             format!("{}: '{}'", rd.message, suggested_replacement)
445         } else {
446             rd.message.clone()
447         };
448
449         MappedRustChildDiagnostic::SuggestedFix(SuggestedFix {
450             title,
451             location,
452             replacement: suggested_replacement.clone(),
453             applicability: span.suggestion_applicability.clone().unwrap_or(Applicability::Unknown),
454             diagnostics: vec![],
455         })
456     } else {
457         MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation {
458             location,
459             message: rd.message.clone(),
460         })
461     }
462 }
463
464 #[derive(Debug)]
465 struct MappedRustDiagnostic {
466     location: Location,
467     diagnostic: Diagnostic,
468     suggested_fixes: Vec<SuggestedFix>,
469 }
470
471 /// Converts a Rust root diagnostic to LSP form
472 ///
473 /// This flattens the Rust diagnostic by:
474 ///
475 /// 1. Creating a LSP diagnostic with the root message and primary span.
476 /// 2. Adding any labelled secondary spans to `relatedInformation`
477 /// 3. Categorising child diagnostics as either `SuggestedFix`es,
478 ///    `relatedInformation` or additional message lines.
479 ///
480 /// If the diagnostic has no primary span this will return `None`
481 fn map_rust_diagnostic_to_lsp(
482     rd: &RustDiagnostic,
483     workspace_root: &PathBuf,
484 ) -> Option<MappedRustDiagnostic> {
485     let primary_span = rd.spans.iter().find(|s| s.is_primary)?;
486
487     let location = map_span_to_location(&primary_span, workspace_root);
488
489     let severity = map_level_to_severity(rd.level);
490     let mut primary_span_label = primary_span.label.as_ref();
491
492     let mut source = String::from("rustc");
493     let mut code = rd.code.as_ref().map(|c| c.code.clone());
494     if let Some(code_val) = &code {
495         // See if this is an RFC #2103 scoped lint (e.g. from Clippy)
496         let scoped_code: Vec<&str> = code_val.split("::").collect();
497         if scoped_code.len() == 2 {
498             source = String::from(scoped_code[0]);
499             code = Some(String::from(scoped_code[1]));
500         }
501     }
502
503     let mut related_information = vec![];
504     let mut tags = vec![];
505
506     for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) {
507         let related = map_secondary_span_to_related(secondary_span, workspace_root);
508         if let Some(related) = related {
509             related_information.push(related);
510         }
511     }
512
513     let mut suggested_fixes = vec![];
514     let mut message = rd.message.clone();
515     for child in &rd.children {
516         let child = map_rust_child_diagnostic(&child, workspace_root);
517         match child {
518             MappedRustChildDiagnostic::Related(related) => related_information.push(related),
519             MappedRustChildDiagnostic::SuggestedFix(suggested_fix) => {
520                 suggested_fixes.push(suggested_fix)
521             }
522             MappedRustChildDiagnostic::MessageLine(message_line) => {
523                 write!(&mut message, "\n{}", message_line).unwrap();
524
525                 // These secondary messages usually duplicate the content of the
526                 // primary span label.
527                 primary_span_label = None;
528             }
529         }
530     }
531
532     if let Some(primary_span_label) = primary_span_label {
533         write!(&mut message, "\n{}", primary_span_label).unwrap();
534     }
535
536     if is_unused_or_unnecessary(rd) {
537         tags.push(DiagnosticTag::Unnecessary);
538     }
539
540     if is_deprecated(rd) {
541         tags.push(DiagnosticTag::Deprecated);
542     }
543
544     let diagnostic = Diagnostic {
545         range: location.range,
546         severity,
547         code: code.map(NumberOrString::String),
548         source: Some(source),
549         message,
550         related_information: if !related_information.is_empty() {
551             Some(related_information)
552         } else {
553             None
554         },
555         tags: if !tags.is_empty() { Some(tags) } else { None },
556     };
557
558     Some(MappedRustDiagnostic { location, diagnostic, suggested_fixes })
559 }
560
561 fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool {
562     left.source == right.source
563         && left.severity == right.severity
564         && left.range == right.range
565         && left.message == right.message
566 }
567
568 #[cfg(test)]
569 mod test {
570     use super::*;
571
572     fn parse_diagnostic(val: &str) -> cargo_metadata::diagnostic::Diagnostic {
573         serde_json::from_str::<cargo_metadata::diagnostic::Diagnostic>(val).unwrap()
574     }
575
576     #[test]
577     fn snap_rustc_incompatible_type_for_trait() {
578         let diag = parse_diagnostic(
579             r##"{
580                 "message": "method `next` has an incompatible type for trait",
581                 "code": {
582                     "code": "E0053",
583                     "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"
584                 },
585                 "level": "error",
586                 "spans": [
587                     {
588                         "file_name": "compiler/ty/list_iter.rs",
589                         "byte_start": 1307,
590                         "byte_end": 1350,
591                         "line_start": 52,
592                         "line_end": 52,
593                         "column_start": 5,
594                         "column_end": 48,
595                         "is_primary": true,
596                         "text": [
597                             {
598                                 "text": "    fn next(&self) -> Option<&'list ty::Ref<M>> {",
599                                 "highlight_start": 5,
600                                 "highlight_end": 48
601                             }
602                         ],
603                         "label": "types differ in mutability",
604                         "suggested_replacement": null,
605                         "suggestion_applicability": null,
606                         "expansion": null
607                     }
608                 ],
609                 "children": [
610                     {
611                         "message": "expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>`\n   found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>`",
612                         "code": null,
613                         "level": "note",
614                         "spans": [],
615                         "children": [],
616                         "rendered": null
617                     }
618                 ],
619                 "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<M>> {\n   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability\n   |\n   = note: expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>`\n              found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>`\n\n"
620             }
621             "##,
622         );
623
624         let workspace_root = PathBuf::from("/test/");
625         let diag =
626             map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
627         insta::assert_debug_snapshot!(diag);
628     }
629
630     #[test]
631     fn snap_rustc_unused_variable() {
632         let diag = parse_diagnostic(
633             r##"{
634     "message": "unused variable: `foo`",
635     "code": {
636         "code": "unused_variables",
637         "explanation": null
638     },
639     "level": "warning",
640     "spans": [
641         {
642             "file_name": "driver/subcommand/repl.rs",
643             "byte_start": 9228,
644             "byte_end": 9231,
645             "line_start": 291,
646             "line_end": 291,
647             "column_start": 9,
648             "column_end": 12,
649             "is_primary": true,
650             "text": [
651                 {
652                     "text": "    let foo = 42;",
653                     "highlight_start": 9,
654                     "highlight_end": 12
655                 }
656             ],
657             "label": null,
658             "suggested_replacement": null,
659             "suggestion_applicability": null,
660             "expansion": null
661         }
662     ],
663     "children": [
664         {
665             "message": "#[warn(unused_variables)] on by default",
666             "code": null,
667             "level": "note",
668             "spans": [],
669             "children": [],
670             "rendered": null
671         },
672         {
673             "message": "consider prefixing with an underscore",
674             "code": null,
675             "level": "help",
676             "spans": [
677                 {
678                     "file_name": "driver/subcommand/repl.rs",
679                     "byte_start": 9228,
680                     "byte_end": 9231,
681                     "line_start": 291,
682                     "line_end": 291,
683                     "column_start": 9,
684                     "column_end": 12,
685                     "is_primary": true,
686                     "text": [
687                         {
688                             "text": "    let foo = 42;",
689                             "highlight_start": 9,
690                             "highlight_end": 12
691                         }
692                     ],
693                     "label": null,
694                     "suggested_replacement": "_foo",
695                     "suggestion_applicability": "MachineApplicable",
696                     "expansion": null
697                 }
698             ],
699             "children": [],
700             "rendered": null
701         }
702     ],
703     "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"
704 }"##,
705         );
706
707         let workspace_root = PathBuf::from("/test/");
708         let diag =
709             map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
710         insta::assert_debug_snapshot!(diag);
711     }
712
713     #[test]
714     fn snap_rustc_wrong_number_of_parameters() {
715         let diag = parse_diagnostic(
716             r##"{
717     "message": "this function takes 2 parameters but 3 parameters were supplied",
718     "code": {
719         "code": "E0061",
720         "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"
721     },
722     "level": "error",
723     "spans": [
724         {
725             "file_name": "compiler/ty/select.rs",
726             "byte_start": 8787,
727             "byte_end": 9241,
728             "line_start": 219,
729             "line_end": 231,
730             "column_start": 5,
731             "column_end": 6,
732             "is_primary": false,
733             "text": [
734                 {
735                     "text": "    pub fn add_evidence(",
736                     "highlight_start": 5,
737                     "highlight_end": 25
738                 },
739                 {
740                     "text": "        &mut self,",
741                     "highlight_start": 1,
742                     "highlight_end": 19
743                 },
744                 {
745                     "text": "        target_poly: &ty::Ref<ty::Poly>,",
746                     "highlight_start": 1,
747                     "highlight_end": 41
748                 },
749                 {
750                     "text": "        evidence_poly: &ty::Ref<ty::Poly>,",
751                     "highlight_start": 1,
752                     "highlight_end": 43
753                 },
754                 {
755                     "text": "    ) {",
756                     "highlight_start": 1,
757                     "highlight_end": 8
758                 },
759                 {
760                     "text": "        match target_poly {",
761                     "highlight_start": 1,
762                     "highlight_end": 28
763                 },
764                 {
765                     "text": "            ty::Ref::Var(tvar, _) => self.add_var_evidence(tvar, evidence_poly),",
766                     "highlight_start": 1,
767                     "highlight_end": 81
768                 },
769                 {
770                     "text": "            ty::Ref::Fixed(target_ty) => {",
771                     "highlight_start": 1,
772                     "highlight_end": 43
773                 },
774                 {
775                     "text": "                let evidence_ty = evidence_poly.resolve_to_ty();",
776                     "highlight_start": 1,
777                     "highlight_end": 65
778                 },
779                 {
780                     "text": "                self.add_evidence_ty(target_ty, evidence_poly, evidence_ty)",
781                     "highlight_start": 1,
782                     "highlight_end": 76
783                 },
784                 {
785                     "text": "            }",
786                     "highlight_start": 1,
787                     "highlight_end": 14
788                 },
789                 {
790                     "text": "        }",
791                     "highlight_start": 1,
792                     "highlight_end": 10
793                 },
794                 {
795                     "text": "    }",
796                     "highlight_start": 1,
797                     "highlight_end": 6
798                 }
799             ],
800             "label": "defined here",
801             "suggested_replacement": null,
802             "suggestion_applicability": null,
803             "expansion": null
804         },
805         {
806             "file_name": "compiler/ty/select.rs",
807             "byte_start": 4045,
808             "byte_end": 4057,
809             "line_start": 104,
810             "line_end": 104,
811             "column_start": 18,
812             "column_end": 30,
813             "is_primary": true,
814             "text": [
815                 {
816                     "text": "            self.add_evidence(target_fixed, evidence_fixed, false);",
817                     "highlight_start": 18,
818                     "highlight_end": 30
819                 }
820             ],
821             "label": "expected 2 parameters",
822             "suggested_replacement": null,
823             "suggestion_applicability": null,
824             "expansion": null
825         }
826     ],
827     "children": [],
828     "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<ty::Poly>,\n222 | |         evidence_poly: &ty::Ref<ty::Poly>,\n...   |\n230 | |         }\n231 | |     }\n    | |_____- defined here\n\n"
829 }"##,
830         );
831
832         let workspace_root = PathBuf::from("/test/");
833         let diag =
834             map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
835         insta::assert_debug_snapshot!(diag);
836     }
837
838     #[test]
839     fn snap_clippy_pass_by_ref() {
840         let diag = parse_diagnostic(
841             r##"{
842     "message": "this argument is passed by reference, but would be more efficient if passed by value",
843     "code": {
844         "code": "clippy::trivially_copy_pass_by_ref",
845         "explanation": null
846     },
847     "level": "warning",
848     "spans": [
849         {
850             "file_name": "compiler/mir/tagset.rs",
851             "byte_start": 941,
852             "byte_end": 946,
853             "line_start": 42,
854             "line_end": 42,
855             "column_start": 24,
856             "column_end": 29,
857             "is_primary": true,
858             "text": [
859                 {
860                     "text": "    pub fn is_disjoint(&self, other: Self) -> bool {",
861                     "highlight_start": 24,
862                     "highlight_end": 29
863                 }
864             ],
865             "label": null,
866             "suggested_replacement": null,
867             "suggestion_applicability": null,
868             "expansion": null
869         }
870     ],
871     "children": [
872         {
873             "message": "lint level defined here",
874             "code": null,
875             "level": "note",
876             "spans": [
877                 {
878                     "file_name": "compiler/lib.rs",
879                     "byte_start": 8,
880                     "byte_end": 19,
881                     "line_start": 1,
882                     "line_end": 1,
883                     "column_start": 9,
884                     "column_end": 20,
885                     "is_primary": true,
886                     "text": [
887                         {
888                             "text": "#![warn(clippy::all)]",
889                             "highlight_start": 9,
890                             "highlight_end": 20
891                         }
892                     ],
893                     "label": null,
894                     "suggested_replacement": null,
895                     "suggestion_applicability": null,
896                     "expansion": null
897                 }
898             ],
899             "children": [],
900             "rendered": null
901         },
902         {
903             "message": "#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]",
904             "code": null,
905             "level": "note",
906             "spans": [],
907             "children": [],
908             "rendered": null
909         },
910         {
911             "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref",
912             "code": null,
913             "level": "help",
914             "spans": [],
915             "children": [],
916             "rendered": null
917         },
918         {
919             "message": "consider passing by value instead",
920             "code": null,
921             "level": "help",
922             "spans": [
923                 {
924                     "file_name": "compiler/mir/tagset.rs",
925                     "byte_start": 941,
926                     "byte_end": 946,
927                     "line_start": 42,
928                     "line_end": 42,
929                     "column_start": 24,
930                     "column_end": 29,
931                     "is_primary": true,
932                     "text": [
933                         {
934                             "text": "    pub fn is_disjoint(&self, other: Self) -> bool {",
935                             "highlight_start": 24,
936                             "highlight_end": 29
937                         }
938                     ],
939                     "label": null,
940                     "suggested_replacement": "self",
941                     "suggestion_applicability": "Unspecified",
942                     "expansion": null
943                 }
944             ],
945             "children": [],
946             "rendered": null
947         }
948     ],
949     "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"
950 }"##,
951         );
952
953         let workspace_root = PathBuf::from("/test/");
954         let diag =
955             map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
956         insta::assert_debug_snapshot!(diag);
957     }
958
959     #[test]
960     fn snap_rustc_mismatched_type() {
961         let diag = parse_diagnostic(
962             r##"{
963     "message": "mismatched types",
964     "code": {
965         "code": "E0308",
966         "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"
967     },
968     "level": "error",
969     "spans": [
970         {
971             "file_name": "runtime/compiler_support.rs",
972             "byte_start": 1589,
973             "byte_end": 1594,
974             "line_start": 48,
975             "line_end": 48,
976             "column_start": 65,
977             "column_end": 70,
978             "is_primary": true,
979             "text": [
980                 {
981                     "text": "    let layout = alloc::Layout::from_size_align_unchecked(size, align);",
982                     "highlight_start": 65,
983                     "highlight_end": 70
984                 }
985             ],
986             "label": "expected usize, found u32",
987             "suggested_replacement": null,
988             "suggestion_applicability": null,
989             "expansion": null
990         }
991     ],
992     "children": [],
993     "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"
994 }"##,
995         );
996
997         let workspace_root = PathBuf::from("/test/");
998         let diag =
999             map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
1000         insta::assert_debug_snapshot!(diag);
1001     }
1002
1003     #[test]
1004     fn snap_handles_macro_location() {
1005         let diag = parse_diagnostic(
1006             r##"{
1007     "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",
1008     "children": [
1009         {
1010             "children": [],
1011             "code": null,
1012             "level": "help",
1013             "message": "the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`",
1014             "rendered": null,
1015             "spans": []
1016         }
1017     ],
1018     "code": {
1019         "code": "E0277",
1020         "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<T: Foo>(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<T: Foo>(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<T>(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<T: fmt::Debug>(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"
1021     },
1022     "level": "error",
1023     "message": "can't compare `{integer}` with `&str`",
1024     "spans": [
1025         {
1026             "byte_end": 155,
1027             "byte_start": 153,
1028             "column_end": 33,
1029             "column_start": 31,
1030             "expansion": {
1031                 "def_site_span": {
1032                     "byte_end": 940,
1033                     "byte_start": 0,
1034                     "column_end": 6,
1035                     "column_start": 1,
1036                     "expansion": null,
1037                     "file_name": "<::core::macros::assert_eq macros>",
1038                     "is_primary": false,
1039                     "label": null,
1040                     "line_end": 36,
1041                     "line_start": 1,
1042                     "suggested_replacement": null,
1043                     "suggestion_applicability": null,
1044                     "text": [
1045                         {
1046                             "highlight_end": 35,
1047                             "highlight_start": 1,
1048                             "text": "($ left : expr, $ right : expr) =>"
1049                         },
1050                         {
1051                             "highlight_end": 3,
1052                             "highlight_start": 1,
1053                             "text": "({"
1054                         },
1055                         {
1056                             "highlight_end": 33,
1057                             "highlight_start": 1,
1058                             "text": "     match (& $ left, & $ right)"
1059                         },
1060                         {
1061                             "highlight_end": 7,
1062                             "highlight_start": 1,
1063                             "text": "     {"
1064                         },
1065                         {
1066                             "highlight_end": 34,
1067                             "highlight_start": 1,
1068                             "text": "         (left_val, right_val) =>"
1069                         },
1070                         {
1071                             "highlight_end": 11,
1072                             "highlight_start": 1,
1073                             "text": "         {"
1074                         },
1075                         {
1076                             "highlight_end": 46,
1077                             "highlight_start": 1,
1078                             "text": "             if ! (* left_val == * right_val)"
1079                         },
1080                         {
1081                             "highlight_end": 15,
1082                             "highlight_start": 1,
1083                             "text": "             {"
1084                         },
1085                         {
1086                             "highlight_end": 25,
1087                             "highlight_start": 1,
1088                             "text": "                 panic !"
1089                         },
1090                         {
1091                             "highlight_end": 57,
1092                             "highlight_start": 1,
1093                             "text": "                 (r#\"assertion failed: `(left == right)`"
1094                         },
1095                         {
1096                             "highlight_end": 16,
1097                             "highlight_start": 1,
1098                             "text": "  left: `{:?}`,"
1099                         },
1100                         {
1101                             "highlight_end": 18,
1102                             "highlight_start": 1,
1103                             "text": " right: `{:?}`\"#,"
1104                         },
1105                         {
1106                             "highlight_end": 47,
1107                             "highlight_start": 1,
1108                             "text": "                  & * left_val, & * right_val)"
1109                         },
1110                         {
1111                             "highlight_end": 15,
1112                             "highlight_start": 1,
1113                             "text": "             }"
1114                         },
1115                         {
1116                             "highlight_end": 11,
1117                             "highlight_start": 1,
1118                             "text": "         }"
1119                         },
1120                         {
1121                             "highlight_end": 7,
1122                             "highlight_start": 1,
1123                             "text": "     }"
1124                         },
1125                         {
1126                             "highlight_end": 42,
1127                             "highlight_start": 1,
1128                             "text": " }) ; ($ left : expr, $ right : expr,) =>"
1129                         },
1130                         {
1131                             "highlight_end": 49,
1132                             "highlight_start": 1,
1133                             "text": "({ $ crate :: assert_eq ! ($ left, $ right) }) ;"
1134                         },
1135                         {
1136                             "highlight_end": 53,
1137                             "highlight_start": 1,
1138                             "text": "($ left : expr, $ right : expr, $ ($ arg : tt) +) =>"
1139                         },
1140                         {
1141                             "highlight_end": 3,
1142                             "highlight_start": 1,
1143                             "text": "({"
1144                         },
1145                         {
1146                             "highlight_end": 37,
1147                             "highlight_start": 1,
1148                             "text": "     match (& ($ left), & ($ right))"
1149                         },
1150                         {
1151                             "highlight_end": 7,
1152                             "highlight_start": 1,
1153                             "text": "     {"
1154                         },
1155                         {
1156                             "highlight_end": 34,
1157                             "highlight_start": 1,
1158                             "text": "         (left_val, right_val) =>"
1159                         },
1160                         {
1161                             "highlight_end": 11,
1162                             "highlight_start": 1,
1163                             "text": "         {"
1164                         },
1165                         {
1166                             "highlight_end": 46,
1167                             "highlight_start": 1,
1168                             "text": "             if ! (* left_val == * right_val)"
1169                         },
1170                         {
1171                             "highlight_end": 15,
1172                             "highlight_start": 1,
1173                             "text": "             {"
1174                         },
1175                         {
1176                             "highlight_end": 25,
1177                             "highlight_start": 1,
1178                             "text": "                 panic !"
1179                         },
1180                         {
1181                             "highlight_end": 57,
1182                             "highlight_start": 1,
1183                             "text": "                 (r#\"assertion failed: `(left == right)`"
1184                         },
1185                         {
1186                             "highlight_end": 16,
1187                             "highlight_start": 1,
1188                             "text": "  left: `{:?}`,"
1189                         },
1190                         {
1191                             "highlight_end": 22,
1192                             "highlight_start": 1,
1193                             "text": " right: `{:?}`: {}\"#,"
1194                         },
1195                         {
1196                             "highlight_end": 72,
1197                             "highlight_start": 1,
1198                             "text": "                  & * left_val, & * right_val, $ crate :: format_args !"
1199                         },
1200                         {
1201                             "highlight_end": 33,
1202                             "highlight_start": 1,
1203                             "text": "                  ($ ($ arg) +))"
1204                         },
1205                         {
1206                             "highlight_end": 15,
1207                             "highlight_start": 1,
1208                             "text": "             }"
1209                         },
1210                         {
1211                             "highlight_end": 11,
1212                             "highlight_start": 1,
1213                             "text": "         }"
1214                         },
1215                         {
1216                             "highlight_end": 7,
1217                             "highlight_start": 1,
1218                             "text": "     }"
1219                         },
1220                         {
1221                             "highlight_end": 6,
1222                             "highlight_start": 1,
1223                             "text": " }) ;"
1224                         }
1225                     ]
1226                 },
1227                 "macro_decl_name": "assert_eq!",
1228                 "span": {
1229                     "byte_end": 38,
1230                     "byte_start": 16,
1231                     "column_end": 27,
1232                     "column_start": 5,
1233                     "expansion": null,
1234                     "file_name": "src/main.rs",
1235                     "is_primary": false,
1236                     "label": null,
1237                     "line_end": 2,
1238                     "line_start": 2,
1239                     "suggested_replacement": null,
1240                     "suggestion_applicability": null,
1241                     "text": [
1242                         {
1243                             "highlight_end": 27,
1244                             "highlight_start": 5,
1245                             "text": "    assert_eq!(1, \"love\");"
1246                         }
1247                     ]
1248                 }
1249             },
1250             "file_name": "<::core::macros::assert_eq macros>",
1251             "is_primary": true,
1252             "label": "no implementation for `{integer} == &str`",
1253             "line_end": 7,
1254             "line_start": 7,
1255             "suggested_replacement": null,
1256             "suggestion_applicability": null,
1257             "text": [
1258                 {
1259                     "highlight_end": 33,
1260                     "highlight_start": 31,
1261                     "text": "             if ! (* left_val == * right_val)"
1262                 }
1263             ]
1264         }
1265     ]
1266 }"##,
1267         );
1268
1269         let workspace_root = PathBuf::from("/test/");
1270         let diag =
1271             map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
1272         insta::assert_debug_snapshot!(diag);
1273     }
1274 }