Skip to content

[VS Code Extension] Remap error location if it extracted inside macros #2575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 261 additions & 0 deletions editors/code/src/test/fixtures/rust-diagnostics/error/E0277.json
Original file line number Diff line number Diff line change
@@ -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)"
}
]
}
]
}
34 changes: 34 additions & 0 deletions editors/code/src/test/utils/diagnotics/rust.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
);
});
});
38 changes: 38 additions & 0 deletions editors/code/src/utils/diagnostics/rust.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -20,6 +26,7 @@ export interface RustDiagnosticSpan {
is_primary: boolean;
file_name: string;
label?: string;
expansion?: RustDiagnosticSpanMacroExpansion;
suggested_replacement?: string;
suggestion_applicability?: SuggestionApplicability;
}
Expand Down Expand Up @@ -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);

Expand Down