From d2c1f8ee2606e10e196485d6bdbd87146d2545de Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Tue, 17 Dec 2019 13:50:00 +0800 Subject: Add macro span handling --- editors/code/src/utils/diagnostics/rust.ts | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'editors/code') diff --git a/editors/code/src/utils/diagnostics/rust.ts b/editors/code/src/utils/diagnostics/rust.ts index b6efc0f56..1f0c0d3e4 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); -- cgit v1.2.3 From 63c59308e6ece788084374c4fc393576684992a7 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Tue, 17 Dec 2019 13:50:08 +0800 Subject: Add tests --- .../fixtures/rust-diagnostics/error/E0277.json | 261 +++++++++++++++++++++ .../code/src/test/utils/diagnotics/rust.test.ts | 34 +++ 2 files changed, 295 insertions(+) create mode 100644 editors/code/src/test/fixtures/rust-diagnostics/error/E0277.json (limited to 'editors/code') 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 000000000..bfef33c7d --- /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(foo: T) {\n foo.bar();\n}\n\nfn main() {\n // we now call the method with the i32 type, which doesn't implement\n // the Foo trait\n some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n fn bar(&self);\n}\n\nfn some_func(foo: T) {\n foo.bar(); // we can now use this method since i32 implements the\n // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n fn bar(&self) {}\n}\n\nfn main() {\n some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func(foo: T) {\n println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n // implemented for the type `T`\n}\n\nfn main() {\n // We now call the method with the i32 type,\n // which *does* implement the Debug trait.\n some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func(foo: T) {\n println!(\"{:?}\", foo);\n}\n\nfn main() {\n // Calling the method is still fine, as i32 implements Debug.\n some_func(5i32);\n\n // This would fail to compile now:\n // struct WithoutDebug;\n // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n" + }, + "level": "error", + "message": "can't compare `{integer}` with `&str`", + "spans": [ + { + "byte_end": 155, + "byte_start": 153, + "column_end": 33, + "column_start": 31, + "expansion": { + "def_site_span": { + "byte_end": 940, + "byte_start": 0, + "column_end": 6, + "column_start": 1, + "expansion": null, + "file_name": "<::core::macros::assert_eq macros>", + "is_primary": false, + "label": null, + "line_end": 36, + "line_start": 1, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 35, + "highlight_start": 1, + "text": "($ left : expr, $ right : expr) =>" + }, + { + "highlight_end": 3, + "highlight_start": 1, + "text": "({" + }, + { + "highlight_end": 33, + "highlight_start": 1, + "text": " match (& $ left, & $ right)" + }, + { + "highlight_end": 7, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 34, + "highlight_start": 1, + "text": " (left_val, right_val) =>" + }, + { + "highlight_end": 11, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 46, + "highlight_start": 1, + "text": " if ! (* left_val == * right_val)" + }, + { + "highlight_end": 15, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 25, + "highlight_start": 1, + "text": " panic !" + }, + { + "highlight_end": 57, + "highlight_start": 1, + "text": " (r#\"assertion failed: `(left == right)`" + }, + { + "highlight_end": 16, + "highlight_start": 1, + "text": " left: `{:?}`," + }, + { + "highlight_end": 18, + "highlight_start": 1, + "text": " right: `{:?}`\"#," + }, + { + "highlight_end": 47, + "highlight_start": 1, + "text": " & * left_val, & * right_val)" + }, + { + "highlight_end": 15, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 11, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 7, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 42, + "highlight_start": 1, + "text": " }) ; ($ left : expr, $ right : expr,) =>" + }, + { + "highlight_end": 49, + "highlight_start": 1, + "text": "({ $ crate :: assert_eq ! ($ left, $ right) }) ;" + }, + { + "highlight_end": 53, + "highlight_start": 1, + "text": "($ left : expr, $ right : expr, $ ($ arg : tt) +) =>" + }, + { + "highlight_end": 3, + "highlight_start": 1, + "text": "({" + }, + { + "highlight_end": 37, + "highlight_start": 1, + "text": " match (& ($ left), & ($ right))" + }, + { + "highlight_end": 7, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 34, + "highlight_start": 1, + "text": " (left_val, right_val) =>" + }, + { + "highlight_end": 11, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 46, + "highlight_start": 1, + "text": " if ! (* left_val == * right_val)" + }, + { + "highlight_end": 15, + "highlight_start": 1, + "text": " {" + }, + { + "highlight_end": 25, + "highlight_start": 1, + "text": " panic !" + }, + { + "highlight_end": 57, + "highlight_start": 1, + "text": " (r#\"assertion failed: `(left == right)`" + }, + { + "highlight_end": 16, + "highlight_start": 1, + "text": " left: `{:?}`," + }, + { + "highlight_end": 22, + "highlight_start": 1, + "text": " right: `{:?}`: {}\"#," + }, + { + "highlight_end": 72, + "highlight_start": 1, + "text": " & * left_val, & * right_val, $ crate :: format_args !" + }, + { + "highlight_end": 33, + "highlight_start": 1, + "text": " ($ ($ arg) +))" + }, + { + "highlight_end": 15, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 11, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 7, + "highlight_start": 1, + "text": " }" + }, + { + "highlight_end": 6, + "highlight_start": 1, + "text": " }) ;" + } + ] + }, + "macro_decl_name": "assert_eq!", + "span": { + "byte_end": 38, + "byte_start": 16, + "column_end": 27, + "column_start": 5, + "expansion": null, + "file_name": "src/main.rs", + "is_primary": false, + "label": null, + "line_end": 2, + "line_start": 2, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 27, + "highlight_start": 5, + "text": " assert_eq!(1, \"love\");" + } + ] + } + }, + "file_name": "<::core::macros::assert_eq macros>", + "is_primary": true, + "label": "no implementation for `{integer} == &str`", + "line_end": 7, + "line_start": 7, + "suggested_replacement": null, + "suggestion_applicability": null, + "text": [ + { + "highlight_end": 33, + "highlight_start": 31, + "text": " if ! (* left_val == * right_val)" + } + ] + } + ] +} diff --git a/editors/code/src/test/utils/diagnotics/rust.test.ts b/editors/code/src/test/utils/diagnotics/rust.test.ts index 0222dbbaa..9acd319b3 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( + true, + location.uri.toString().endsWith('src/main.rs'), + ); + }); }); -- cgit v1.2.3 From bb9c60d90863b21a0e981f00e354d02b0e9fb584 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Tue, 17 Dec 2019 21:43:19 +0800 Subject: Use substr instead of endswith --- editors/code/src/test/utils/diagnotics/rust.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'editors/code') diff --git a/editors/code/src/test/utils/diagnotics/rust.test.ts b/editors/code/src/test/utils/diagnotics/rust.test.ts index 9acd319b3..358325cc8 100644 --- a/editors/code/src/test/utils/diagnotics/rust.test.ts +++ b/editors/code/src/test/utils/diagnotics/rust.test.ts @@ -212,7 +212,7 @@ describe('mapRustDiagnosticToVsCode', () => { assert.strictEqual( diagnostic.message, [ - 'can\'t compare `{integer}` with `&str`', + "can't compare `{integer}` with `&str`", 'the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`', ].join('\n'), ); @@ -229,8 +229,8 @@ describe('mapRustDiagnosticToVsCode', () => { // The file url should be normal file // Ignore the first part because it depends on vs workspace location assert.strictEqual( - true, - location.uri.toString().endsWith('src/main.rs'), + location.uri.path.substr(-'src/main.rs'.length), + 'src/main.rs', ); }); }); -- cgit v1.2.3