From a8196ffe8466aa60dec56e77c2da717793c0debe Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 17 Apr 2020 13:06:02 +0200 Subject: [PATCH] Correctly highlight ranges of diagnostics from macros closes #2799 --- crates/ra_hir/src/semantics.rs | 8 +++ crates/ra_hir_def/src/diagnostics.rs | 6 +-- crates/ra_hir_expand/src/diagnostics.rs | 2 +- crates/ra_hir_ty/src/diagnostics.rs | 22 ++++---- crates/ra_ide/src/diagnostics.rs | 72 ++++++++++++++++++++++--- 5 files changed, 89 insertions(+), 21 deletions(-) diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs index 2707e422d10..0b477f0e9ea 100644 --- a/crates/ra_hir/src/semantics.rs +++ b/crates/ra_hir/src/semantics.rs @@ -20,6 +20,7 @@ use crate::{ db::HirDatabase, + diagnostics::Diagnostic, semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, source_analyzer::{resolve_hir_path, SourceAnalyzer}, AssocItem, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef, Name, @@ -126,6 +127,13 @@ pub fn original_range(&self, node: &SyntaxNode) -> FileRange { original_range(self.db, node.as_ref()) } + pub fn diagnostics_range(&self, diagnostics: &dyn Diagnostic) -> FileRange { + let src = diagnostics.source(); + let root = self.db.parse_or_expand(src.file_id).unwrap(); + let node = src.value.to_node(&root); + original_range(self.db, src.with_value(&node)) + } + pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator + '_ { let node = self.find_file(node); node.ancestors_with_macros(self.db).map(|it| it.value) diff --git a/crates/ra_hir_def/src/diagnostics.rs b/crates/ra_hir_def/src/diagnostics.rs index dbaf4deef14..2ee28fbaaab 100644 --- a/crates/ra_hir_def/src/diagnostics.rs +++ b/crates/ra_hir_def/src/diagnostics.rs @@ -20,11 +20,11 @@ impl Diagnostic for UnresolvedModule { fn message(&self) -> String { "unresolved module".to_string() } - fn highlight_range(&self) -> TextRange { - self.highlight_range + fn highlight_range(&self) -> InFile { + InFile::new(self.file, self.highlight_range) } fn source(&self) -> InFile { - InFile { file_id: self.file, value: self.decl.clone().into() } + InFile::new(self.file, self.decl.clone().into()) } fn as_any(&self) -> &(dyn Any + Send + 'static) { self diff --git a/crates/ra_hir_expand/src/diagnostics.rs b/crates/ra_hir_expand/src/diagnostics.rs index 714e700f719..813fbf0e270 100644 --- a/crates/ra_hir_expand/src/diagnostics.rs +++ b/crates/ra_hir_expand/src/diagnostics.rs @@ -22,7 +22,7 @@ pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { fn message(&self) -> String; - fn highlight_range(&self) -> TextRange; + fn highlight_range(&self) -> InFile; fn source(&self) -> InFile; fn as_any(&self) -> &(dyn Any + Send + 'static); } diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs index da85bd08230..018c2ad3f00 100644 --- a/crates/ra_hir_ty/src/diagnostics.rs +++ b/crates/ra_hir_ty/src/diagnostics.rs @@ -21,12 +21,12 @@ fn message(&self) -> String { "no such field".to_string() } - fn highlight_range(&self) -> TextRange { - self.highlight_range + fn highlight_range(&self) -> InFile { + InFile::new(self.file, self.highlight_range) } fn source(&self) -> InFile { - InFile { file_id: self.file, value: self.field.clone().into() } + InFile::new(self.file, self.field.clone().into()) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -50,8 +50,8 @@ fn message(&self) -> String { } buf } - fn highlight_range(&self) -> TextRange { - self.highlight_range + fn highlight_range(&self) -> InFile { + InFile::new(self.file, self.highlight_range) } fn source(&self) -> InFile { @@ -88,8 +88,8 @@ fn message(&self) -> String { } buf } - fn highlight_range(&self) -> TextRange { - self.highlight_range + fn highlight_range(&self) -> InFile { + InFile::new(self.file, self.highlight_range) } fn source(&self) -> InFile { InFile { file_id: self.file, value: self.field_list.clone().into() } @@ -111,8 +111,8 @@ impl Diagnostic for MissingMatchArms { fn message(&self) -> String { String::from("Missing match arm") } - fn highlight_range(&self) -> TextRange { - self.highlight_range + fn highlight_range(&self) -> InFile { + InFile::new(self.file, self.highlight_range) } fn source(&self) -> InFile { InFile { file_id: self.file, value: self.match_expr.clone().into() } @@ -133,8 +133,8 @@ impl Diagnostic for MissingOkInTailExpr { fn message(&self) -> String { "wrap return expression in Ok".to_string() } - fn highlight_range(&self) -> TextRange { - self.highlight_range + fn highlight_range(&self) -> InFile { + InFile::new(self.file, self.highlight_range) } fn source(&self) -> InFile { InFile { file_id: self.file, value: self.expr.clone().into() } diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 901ad104c10..e7e20170988 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs @@ -1,4 +1,8 @@ -//! FIXME: write short doc here +//! Collects diagnostics & fixits for a single file. +//! +//! The tricky bit here is that diagnostics are produced by hir in terms of +//! macro-expanded files, but we need to present them to the users in terms of +//! original files. So we need to map the ranges. use std::cell::RefCell; @@ -46,7 +50,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec let mut sink = DiagnosticSink::new(|d| { res.borrow_mut().push(Diagnostic { message: d.message(), - range: d.highlight_range(), + range: sema.diagnostics_range(d).range, severity: Severity::Error, fix: None, }) @@ -62,7 +66,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec let create_file = FileSystemEdit::CreateFile { source_root, path }; let fix = SourceChange::file_system_edit("create module", create_file); res.borrow_mut().push(Diagnostic { - range: d.highlight_range(), + range: sema.diagnostics_range(d).range, message: d.message(), severity: Severity::Error, fix: Some(fix), @@ -95,7 +99,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec }; res.borrow_mut().push(Diagnostic { - range: d.highlight_range(), + range: sema.diagnostics_range(d).range, message: d.message(), severity: Severity::Error, fix, @@ -103,7 +107,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec }) .on::(|d| { res.borrow_mut().push(Diagnostic { - range: d.highlight_range(), + range: sema.diagnostics_range(d).range, message: d.message(), severity: Severity::Error, fix: None, @@ -115,7 +119,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec let edit = TextEdit::replace(node.syntax().text_range(), replacement); let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); res.borrow_mut().push(Diagnostic { - range: d.highlight_range(), + range: sema.diagnostics_range(d).range, message: d.message(), severity: Severity::Error, fix: Some(fix), @@ -621,6 +625,62 @@ fn test_unresolved_module_diagnostic() { "###); } + #[test] + fn range_mapping_out_of_macros() { + let (analysis, file_id) = single_file( + r" + fn some() {} + fn items() {} + fn here() {} + + macro_rules! id { + ($($tt:tt)*) => { $($tt)*}; + } + + fn main() { + let _x = id![Foo { a: 42 }]; + } + + pub struct Foo { + pub a: i32, + pub b: i32, + } + ", + ); + let diagnostics = analysis.diagnostics(file_id).unwrap(); + assert_debug_snapshot!(diagnostics, @r###" + [ + Diagnostic { + message: "Missing structure fields:\n- b", + range: [224; 233), + fix: Some( + SourceChange { + label: "fill struct fields", + source_file_edits: [ + SourceFileEdit { + file_id: FileId( + 1, + ), + edit: TextEdit { + atoms: [ + AtomTextEdit { + delete: [3; 9), + insert: "{a:42, b: ()}", + }, + ], + }, + }, + ], + file_system_edits: [], + cursor_position: None, + }, + ), + severity: Error, + }, + ] + "###); + } + #[test] fn test_check_unnecessary_braces_in_use_statement() { check_not_applicable(