Repository: rust-analyzer/rust-analyzer
Pull request #2575 (2019-12-17 05:53:19)

[VS Code Extension] Remap error location if it extracted inside macros
It should fix for #2569






kiljacken: Nice change, but CI is failing.
matklad: LGTM!
edwin0cheng: Block by #2583 :(


Patch:
diff --git a/editors/code/src/utils/diagnostics/rust.ts b/editors/code/src/utils/diagnostics/rust.ts
index b6efc0f564..1f0c0d3e40 100644
--- a/editors/code/src/utils/diagnostics/rust.ts
+++ b/editors/code/src/utils/diagnostics/rust.ts
@@ -10,6 +10,12 @@ export enum SuggestionApplicability {
     Unspecified = 'Unspecified',
 }
 
+export interface RustDiagnosticSpanMacroExpansion {
+    span: RustDiagnosticSpan;
+    macro_decl_name: string;
+    def_site_span?: RustDiagnosticSpan;
+}
+
 // Reference:
 // https://github.com/rust-lang/rust/blob/master/src/libsyntax/json.rs
 export interface RustDiagnosticSpan {
@@ -20,6 +26,7 @@ export interface RustDiagnosticSpan {
     is_primary: boolean;
     file_name: string;
     label?: string;
+    expansion?: RustDiagnosticSpanMacroExpansion;
     suggested_replacement?: string;
     suggestion_applicability?: SuggestionApplicability;
 }
@@ -60,10 +67,41 @@ function mapLevelToSeverity(s: string): vscode.DiagnosticSeverity {
     return vscode.DiagnosticSeverity.Information;
 }
 
+/**
+ * Check whether a file name is from macro invocation
+ */
+function isFromMacro(fileName: string): boolean {
+    return fileName.startsWith('<') && fileName.endsWith('>');
+}
+
+/**
+ * Converts a Rust macro span to a VsCode location recursively
+ */
+function mapMacroSpanToLocation(
+    spanMacro: RustDiagnosticSpanMacroExpansion,
+): vscode.Location | undefined {
+    if (!isFromMacro(spanMacro.span.file_name)) {
+        return mapSpanToLocation(spanMacro.span);
+    }
+
+    if (spanMacro.span.expansion) {
+        return mapMacroSpanToLocation(spanMacro.span.expansion);
+    }
+
+    return;
+}
+
 /**
  * Converts a Rust span to a VsCode location
  */
 function mapSpanToLocation(span: RustDiagnosticSpan): vscode.Location {
+    if (isFromMacro(span.file_name) && span.expansion) {
+        const macroLoc = mapMacroSpanToLocation(span.expansion);
+        if (macroLoc) {
+            return macroLoc;
+        }
+    }
+
     const fileName = path.join(vscode.workspace.rootPath || '', span.file_name);
     const fileUri = vscode.Uri.file(fileName);
 


Test patch:
diff --git a/editors/code/src/test/fixtures/rust-diagnostics/error/E0277.json b/editors/code/src/test/fixtures/rust-diagnostics/error/E0277.json
new file mode 100644
index 0000000000..bfef33c7de
--- /dev/null
+++ b/editors/code/src/test/fixtures/rust-diagnostics/error/E0277.json
@@ -0,0 +1,261 @@
+{
+    "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<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"
+    },
+    "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)"
+                }
+            ]
+        }
+    ]
+}
diff --git a/editors/code/src/test/utils/diagnotics/rust.test.ts b/editors/code/src/test/utils/diagnotics/rust.test.ts
index 0222dbbaa0..358325cc8d 100644
--- a/editors/code/src/test/utils/diagnotics/rust.test.ts
+++ b/editors/code/src/test/utils/diagnotics/rust.test.ts
@@ -199,4 +199,38 @@ describe('mapRustDiagnosticToVsCode', () => {
         // There are no suggested fixes
         assert.strictEqual(suggestedFixes.length, 0);
     });
+
+    it('should map a macro invocation location to normal file path', () => {
+        const { location, diagnostic, suggestedFixes } = mapFixtureToVsCode(
+            'error/E0277',
+        );
+
+        assert.strictEqual(
+            diagnostic.severity,
+            vscode.DiagnosticSeverity.Error,
+        );
+        assert.strictEqual(
+            diagnostic.message,
+            [
+                "can't compare `{integer}` with `&str`",
+                'the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`',
+            ].join('\n'),
+        );
+        assert.strictEqual(diagnostic.code, 'E0277');
+        assert.strictEqual(diagnostic.source, 'rustc');
+        assert.deepStrictEqual(diagnostic.tags, []);
+
+        // No related information
+        assert.deepStrictEqual(diagnostic.relatedInformation, []);
+
+        // There are no suggested fixes
+        assert.strictEqual(suggestedFixes.length, 0);
+
+        // The file url should be normal file
+        // Ignore the first part because it depends on vs workspace location
+        assert.strictEqual(
+            location.uri.path.substr(-'src/main.rs'.length),
+            'src/main.rs',
+        );
+    });
 });