Repository: rust-analyzer/rust-analyzer
Pull request #2681 (2019-12-29 18:15:54)

cargo-watcher: Resolve macro call site in more cases
This resolves the actual macro call site in a few more cases, f.x. when a macro invokes `compile_error!` (I'm looking at you `ra_hir_def::path::__path`).


Patch:
diff --git a/crates/ra_cargo_watch/src/conv.rs b/crates/ra_cargo_watch/src/conv.rs
index dedb0751ea..172f6b07c5 100644
--- a/crates/ra_cargo_watch/src/conv.rs
+++ b/crates/ra_cargo_watch/src/conv.rs
@@ -8,11 +8,7 @@ use lsp_types::{
     Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location,
     NumberOrString, Position, Range, Url,
 };
-use std::{
-    fmt::Write,
-    path::{Component, Path, PathBuf, Prefix},
-    str::FromStr,
-};
+use std::{fmt::Write, path::PathBuf};
 
 #[cfg(test)]
 mod test;
@@ -50,18 +46,23 @@ fn map_macro_span_to_location(
     None
 }
 
-/// Converts a Rust span to a LSP location
+/// Converts a Rust span to a LSP location, resolving macro expansion site if neccesary
 fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location {
-    if is_from_macro(&span.file_name) && span.expansion.is_some() {
+    if 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;
         }
     }
 
+    map_span_to_location_naive(span, workspace_root)
+}
+
+/// Converts a Rust span to a LSP location
+fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location {
     let mut file_name = workspace_root.clone();
     file_name.push(&span.file_name);
-    let uri = url_from_path_with_drive_lowercasing(file_name).unwrap();
+    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),
@@ -228,6 +229,16 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
     let mut related_information = vec![];
     let mut tags = vec![];
 
+    // If error occurs from macro expansion, add related info pointing to
+    // where the error originated
+    if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() {
+        let def_loc = map_span_to_location_naive(&primary_span, workspace_root);
+        related_information.push(DiagnosticRelatedInformation {
+            location: def_loc,
+            message: "Error originated from macro here".to_string(),
+        });
+    }
+
     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 {
@@ -282,63 +293,3 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
 
     Some(MappedRustDiagnostic { location, diagnostic, suggested_fixes })
 }
-
-/// Returns a `Url` object from a given path, will lowercase drive letters if present.
-/// This will only happen when processing windows paths.
-///
-/// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
-pub fn url_from_path_with_drive_lowercasing(
-    path: impl AsRef<Path>,
-) -> Result<Url, Box<dyn std::error::Error + Send + Sync>> {
-    let component_has_windows_drive = path.as_ref().components().any(|comp| {
-        if let Component::Prefix(c) = comp {
-            match c.kind() {
-                Prefix::Disk(_) | Prefix::VerbatimDisk(_) => return true,
-                _ => return false,
-            }
-        }
-        false
-    });
-
-    // VSCode expects drive letters to be lowercased, where rust will uppercase the drive letters.
-    if component_has_windows_drive {
-        let url_original = Url::from_file_path(&path)
-            .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?;
-
-        let drive_partition: Vec<&str> = url_original.as_str().rsplitn(2, ':').collect();
-
-        // There is a drive partition, but we never found a colon.
-        // This should not happen, but in this case we just pass it through.
-        if drive_partition.len() == 1 {
-            return Ok(url_original);
-        }
-
-        let joined = drive_partition[1].to_ascii_lowercase() + ":" + drive_partition[0];
-        let url = Url::from_str(&joined).expect("This came from a valid `Url`");
-
-        Ok(url)
-    } else {
-        Ok(Url::from_file_path(&path)
-            .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?)
-    }
-}
-
-// `Url` is not able to parse windows paths on unix machines.
-#[cfg(target_os = "windows")]
-#[cfg(test)]
-mod path_conversion_windows_tests {
-    use super::url_from_path_with_drive_lowercasing;
-    #[test]
-    fn test_lowercase_drive_letter_with_drive() {
-        let url = url_from_path_with_drive_lowercasing("C:\\Test").unwrap();
-
-        assert_eq!(url.to_string(), "file:///c:/Test");
-    }
-
-    #[test]
-    fn test_drive_without_colon_passthrough() {
-        let url = url_from_path_with_drive_lowercasing(r#"\\localhost\C$\my_dir"#).unwrap();
-
-        assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
-    }
-}
diff --git a/crates/ra_cargo_watch/src/lib.rs b/crates/ra_cargo_watch/src/lib.rs
index 9bc0fd405d..e5c22e599e 100644
--- a/crates/ra_cargo_watch/src/lib.rs
+++ b/crates/ra_cargo_watch/src/lib.rs
@@ -21,8 +21,6 @@ mod conv;
 
 use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic, SuggestedFix};
 
-pub use crate::conv::url_from_path_with_drive_lowercasing;
-
 #[derive(Clone, Debug)]
 pub struct CheckOptions {
     pub enable: bool,
diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs
index 121ddfd1f7..4b3959e38a 100644
--- a/crates/ra_lsp_server/src/world.rs
+++ b/crates/ra_lsp_server/src/world.rs
@@ -12,9 +12,7 @@ use crossbeam_channel::{unbounded, Receiver};
 use lsp_server::ErrorCode;
 use lsp_types::Url;
 use parking_lot::RwLock;
-use ra_cargo_watch::{
-    url_from_path_with_drive_lowercasing, CheckOptions, CheckWatcher, CheckWatcherSharedState,
-};
+use ra_cargo_watch::{CheckOptions, CheckWatcher, CheckWatcherSharedState};
 use ra_ide::{
     Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData,
     SourceRootId,
@@ -23,11 +21,13 @@ use ra_project_model::{get_rustc_cfg_options, ProjectWorkspace};
 use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch};
 use ra_vfs_glob::{Glob, RustPackageFilterBuilder};
 use relative_path::RelativePathBuf;
+use std::path::{Component, Prefix};
 
 use crate::{
     main_loop::pending_requests::{CompletedRequest, LatestRequests},
     LspError, Result,
 };
+use std::str::FromStr;
 
 #[derive(Debug, Clone)]
 pub struct Options {
@@ -294,3 +294,61 @@ impl WorldSnapshot {
         self.analysis.feature_flags()
     }
 }
+
+/// Returns a `Url` object from a given path, will lowercase drive letters if present.
+/// This will only happen when processing windows paths.
+///
+/// When processing non-windows path, this is essentially the same as `Url::from_file_path`.
+fn url_from_path_with_drive_lowercasing(path: impl AsRef<Path>) -> Result<Url> {
+    let component_has_windows_drive = path.as_ref().components().any(|comp| {
+        if let Component::Prefix(c) = comp {
+            match c.kind() {
+                Prefix::Disk(_) | Prefix::VerbatimDisk(_) => return true,
+                _ => return false,
+            }
+        }
+        false
+    });
+
+    // VSCode expects drive letters to be lowercased, where rust will uppercase the drive letters.
+    if component_has_windows_drive {
+        let url_original = Url::from_file_path(&path)
+            .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?;
+
+        let drive_partition: Vec<&str> = url_original.as_str().rsplitn(2, ':').collect();
+
+        // There is a drive partition, but we never found a colon.
+        // This should not happen, but in this case we just pass it through.
+        if drive_partition.len() == 1 {
+            return Ok(url_original);
+        }
+
+        let joined = drive_partition[1].to_ascii_lowercase() + ":" + drive_partition[0];
+        let url = Url::from_str(&joined).expect("This came from a valid `Url`");
+
+        Ok(url)
+    } else {
+        Ok(Url::from_file_path(&path)
+            .map_err(|_| format!("can't convert path to url: {}", path.as_ref().display()))?)
+    }
+}
+
+// `Url` is not able to parse windows paths on unix machines.
+#[cfg(target_os = "windows")]
+#[cfg(test)]
+mod path_conversion_windows_tests {
+    use super::url_from_path_with_drive_lowercasing;
+    #[test]
+    fn test_lowercase_drive_letter_with_drive() {
+        let url = url_from_path_with_drive_lowercasing("C:\\Test").unwrap();
+
+        assert_eq!(url.to_string(), "file:///c:/Test");
+    }
+
+    #[test]
+    fn test_drive_without_colon_passthrough() {
+        let url = url_from_path_with_drive_lowercasing(r#"\\localhost\C$\my_dir"#).unwrap();
+
+        assert_eq!(url.to_string(), "file://localhost/C$/my_dir");
+    }
+}


Test patch:
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_macro_compiler_error.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_macro_compiler_error.snap
new file mode 100644
index 0000000000..92f7eec057
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_macro_compiler_error.snap
@@ -0,0 +1,61 @@
+---
+source: crates/ra_cargo_watch/src/conv/test.rs
+expression: diag
+---
+MappedRustDiagnostic {
+    location: Location {
+        uri: "file:///test/crates/ra_hir_def/src/data.rs",
+        range: Range {
+            start: Position {
+                line: 79,
+                character: 15,
+            },
+            end: Position {
+                line: 79,
+                character: 41,
+            },
+        },
+    },
+    diagnostic: Diagnostic {
+        range: Range {
+            start: Position {
+                line: 79,
+                character: 15,
+            },
+            end: Position {
+                line: 79,
+                character: 41,
+            },
+        },
+        severity: Some(
+            Error,
+        ),
+        code: None,
+        source: Some(
+            "rustc",
+        ),
+        message: "Please register your known path in the path module",
+        related_information: Some(
+            [
+                DiagnosticRelatedInformation {
+                    location: Location {
+                        uri: "file:///test/crates/ra_hir_def/src/path.rs",
+                        range: Range {
+                            start: Position {
+                                line: 264,
+                                character: 8,
+                            },
+                            end: Position {
+                                line: 264,
+                                character: 76,
+                            },
+                        },
+                    },
+                    message: "Error originated from macro here",
+                },
+            ],
+        ),
+        tags: None,
+    },
+    suggested_fixes: [],
+}
diff --git a/crates/ra_cargo_watch/src/conv/test.rs b/crates/ra_cargo_watch/src/conv/test.rs
index 6817245c2b..3819923888 100644
--- a/crates/ra_cargo_watch/src/conv/test.rs
+++ b/crates/ra_cargo_watch/src/conv/test.rs
@@ -698,3 +698,232 @@ fn snap_handles_macro_location() {
     let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
     insta::assert_debug_snapshot!(diag);
 }
+
+#[test]
+fn snap_macro_compiler_error() {
+    let diag = parse_diagnostic(
+        r##"{
+    "rendered": "error: Please register your known path in the path module\n   --> crates/ra_hir_def/src/path.rs:265:9\n    |\n265 |         compile_error!(\"Please register your known path in the path module\")\n    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n    | \n   ::: crates/ra_hir_def/src/data.rs:80:16\n    |\n80  |     let path = path![std::future::Future];\n    |                -------------------------- in this macro invocation\n\n",
+    "children": [],
+    "code": null,
+    "level": "error",
+    "message": "Please register your known path in the path module",
+    "spans": [
+        {
+            "byte_end": 8285,
+            "byte_start": 8217,
+            "column_end": 77,
+            "column_start": 9,
+            "expansion": {
+                "def_site_span": {
+                    "byte_end": 8294,
+                    "byte_start": 7858,
+                    "column_end": 2,
+                    "column_start": 1,
+                    "expansion": null,
+                    "file_name": "crates/ra_hir_def/src/path.rs",
+                    "is_primary": false,
+                    "label": null,
+                    "line_end": 267,
+                    "line_start": 254,
+                    "suggested_replacement": null,
+                    "suggestion_applicability": null,
+                    "text": [
+                        {
+                            "highlight_end": 28,
+                            "highlight_start": 1,
+                            "text": "macro_rules! __known_path {"
+                        },
+                        {
+                            "highlight_end": 37,
+                            "highlight_start": 1,
+                            "text": "    (std::iter::IntoIterator) => {};"
+                        },
+                        {
+                            "highlight_end": 33,
+                            "highlight_start": 1,
+                            "text": "    (std::result::Result) => {};"
+                        },
+                        {
+                            "highlight_end": 29,
+                            "highlight_start": 1,
+                            "text": "    (std::ops::Range) => {};"
+                        },
+                        {
+                            "highlight_end": 33,
+                            "highlight_start": 1,
+                            "text": "    (std::ops::RangeFrom) => {};"
+                        },
+                        {
+                            "highlight_end": 33,
+                            "highlight_start": 1,
+                            "text": "    (std::ops::RangeFull) => {};"
+                        },
+                        {
+                            "highlight_end": 31,
+                            "highlight_start": 1,
+                            "text": "    (std::ops::RangeTo) => {};"
+                        },
+                        {
+                            "highlight_end": 40,
+                            "highlight_start": 1,
+                            "text": "    (std::ops::RangeToInclusive) => {};"
+                        },
+                        {
+                            "highlight_end": 38,
+                            "highlight_start": 1,
+                            "text": "    (std::ops::RangeInclusive) => {};"
+                        },
+                        {
+                            "highlight_end": 27,
+                            "highlight_start": 1,
+                            "text": "    (std::ops::Try) => {};"
+                        },
+                        {
+                            "highlight_end": 22,
+                            "highlight_start": 1,
+                            "text": "    ($path:path) => {"
+                        },
+                        {
+                            "highlight_end": 77,
+                            "highlight_start": 1,
+                            "text": "        compile_error!(\"Please register your known path in the path module\")"
+                        },
+                        {
+                            "highlight_end": 7,
+                            "highlight_start": 1,
+                            "text": "    };"
+                        },
+                        {
+                            "highlight_end": 2,
+                            "highlight_start": 1,
+                            "text": "}"
+                        }
+                    ]
+                },
+                "macro_decl_name": "$crate::__known_path!",
+                "span": {
+                    "byte_end": 8427,
+                    "byte_start": 8385,
+                    "column_end": 51,
+                    "column_start": 9,
+                    "expansion": {
+                        "def_site_span": {
+                            "byte_end": 8611,
+                            "byte_start": 8312,
+                            "column_end": 2,
+                            "column_start": 1,
+                            "expansion": null,
+                            "file_name": "crates/ra_hir_def/src/path.rs",
+                            "is_primary": false,
+                            "label": null,
+                            "line_end": 277,
+                            "line_start": 270,
+                            "suggested_replacement": null,
+                            "suggestion_applicability": null,
+                            "text": [
+                                {
+                                    "highlight_end": 22,
+                                    "highlight_start": 1,
+                                    "text": "macro_rules! __path {"
+                                },
+                                {
+                                    "highlight_end": 43,
+                                    "highlight_start": 1,
+                                    "text": "    ($start:ident $(:: $seg:ident)*) => ({"
+                                },
+                                {
+                                    "highlight_end": 51,
+                                    "highlight_start": 1,
+                                    "text": "        $crate::__known_path!($start $(:: $seg)*);"
+                                },
+                                {
+                                    "highlight_end": 87,
+                                    "highlight_start": 1,
+                                    "text": "        $crate::path::ModPath::from_simple_segments($crate::path::PathKind::Abs, vec!["
+                                },
+                                {
+                                    "highlight_end": 76,
+                                    "highlight_start": 1,
+                                    "text": "            $crate::path::__name![$start], $($crate::path::__name![$seg],)*"
+                                },
+                                {
+                                    "highlight_end": 11,
+                                    "highlight_start": 1,
+                                    "text": "        ])"
+                                },
+                                {
+                                    "highlight_end": 8,
+                                    "highlight_start": 1,
+                                    "text": "    });"
+                                },
+                                {
+                                    "highlight_end": 2,
+                                    "highlight_start": 1,
+                                    "text": "}"
+                                }
+                            ]
+                        },
+                        "macro_decl_name": "path!",
+                        "span": {
+                            "byte_end": 2966,
+                            "byte_start": 2940,
+                            "column_end": 42,
+                            "column_start": 16,
+                            "expansion": null,
+                            "file_name": "crates/ra_hir_def/src/data.rs",
+                            "is_primary": false,
+                            "label": null,
+                            "line_end": 80,
+                            "line_start": 80,
+                            "suggested_replacement": null,
+                            "suggestion_applicability": null,
+                            "text": [
+                                {
+                                    "highlight_end": 42,
+                                    "highlight_start": 16,
+                                    "text": "    let path = path![std::future::Future];"
+                                }
+                            ]
+                        }
+                    },
+                    "file_name": "crates/ra_hir_def/src/path.rs",
+                    "is_primary": false,
+                    "label": null,
+                    "line_end": 272,
+                    "line_start": 272,
+                    "suggested_replacement": null,
+                    "suggestion_applicability": null,
+                    "text": [
+                        {
+                            "highlight_end": 51,
+                            "highlight_start": 9,
+                            "text": "        $crate::__known_path!($start $(:: $seg)*);"
+                        }
+                    ]
+                }
+            },
+            "file_name": "crates/ra_hir_def/src/path.rs",
+            "is_primary": true,
+            "label": null,
+            "line_end": 265,
+            "line_start": 265,
+            "suggested_replacement": null,
+            "suggestion_applicability": null,
+            "text": [
+                {
+                    "highlight_end": 77,
+                    "highlight_start": 9,
+                    "text": "        compile_error!(\"Please register your known path in the path module\")"
+                }
+            ]
+        }
+    ]
+}
+        "##,
+    );
+
+    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);
+}