aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-04-17 12:06:02 +0100
committerAleksey Kladov <[email protected]>2020-04-17 12:56:38 +0100
commita8196ffe8466aa60dec56e77c2da717793c0debe (patch)
tree03869b8175d5325de1baa95e0a385d2823b5946e
parent302bf97bbf1855e3c7def9ab4f9f3d338be5e3b7 (diff)
Correctly highlight ranges of diagnostics from macros
closes #2799
-rw-r--r--crates/ra_hir/src/semantics.rs8
-rw-r--r--crates/ra_hir_def/src/diagnostics.rs6
-rw-r--r--crates/ra_hir_expand/src/diagnostics.rs2
-rw-r--r--crates/ra_hir_ty/src/diagnostics.rs22
-rw-r--r--crates/ra_ide/src/diagnostics.rs72
5 files changed, 89 insertions, 21 deletions
diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs
index 2707e422d..0b477f0e9 100644
--- a/crates/ra_hir/src/semantics.rs
+++ b/crates/ra_hir/src/semantics.rs
@@ -20,6 +20,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
20 20
21use crate::{ 21use crate::{
22 db::HirDatabase, 22 db::HirDatabase,
23 diagnostics::Diagnostic,
23 semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, 24 semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
24 source_analyzer::{resolve_hir_path, SourceAnalyzer}, 25 source_analyzer::{resolve_hir_path, SourceAnalyzer},
25 AssocItem, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef, Name, 26 AssocItem, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef, Name,
@@ -126,6 +127,13 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
126 original_range(self.db, node.as_ref()) 127 original_range(self.db, node.as_ref())
127 } 128 }
128 129
130 pub fn diagnostics_range(&self, diagnostics: &dyn Diagnostic) -> FileRange {
131 let src = diagnostics.source();
132 let root = self.db.parse_or_expand(src.file_id).unwrap();
133 let node = src.value.to_node(&root);
134 original_range(self.db, src.with_value(&node))
135 }
136
129 pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ { 137 pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ {
130 let node = self.find_file(node); 138 let node = self.find_file(node);
131 node.ancestors_with_macros(self.db).map(|it| it.value) 139 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 dbaf4deef..2ee28fbaa 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 {
20 fn message(&self) -> String { 20 fn message(&self) -> String {
21 "unresolved module".to_string() 21 "unresolved module".to_string()
22 } 22 }
23 fn highlight_range(&self) -> TextRange { 23 fn highlight_range(&self) -> InFile<TextRange> {
24 self.highlight_range 24 InFile::new(self.file, self.highlight_range)
25 } 25 }
26 fn source(&self) -> InFile<SyntaxNodePtr> { 26 fn source(&self) -> InFile<SyntaxNodePtr> {
27 InFile { file_id: self.file, value: self.decl.clone().into() } 27 InFile::new(self.file, self.decl.clone().into())
28 } 28 }
29 fn as_any(&self) -> &(dyn Any + Send + 'static) { 29 fn as_any(&self) -> &(dyn Any + Send + 'static) {
30 self 30 self
diff --git a/crates/ra_hir_expand/src/diagnostics.rs b/crates/ra_hir_expand/src/diagnostics.rs
index 714e700f7..813fbf0e2 100644
--- a/crates/ra_hir_expand/src/diagnostics.rs
+++ b/crates/ra_hir_expand/src/diagnostics.rs
@@ -22,7 +22,7 @@ use crate::{db::AstDatabase, InFile};
22 22
23pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { 23pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static {
24 fn message(&self) -> String; 24 fn message(&self) -> String;
25 fn highlight_range(&self) -> TextRange; 25 fn highlight_range(&self) -> InFile<TextRange>;
26 fn source(&self) -> InFile<SyntaxNodePtr>; 26 fn source(&self) -> InFile<SyntaxNodePtr>;
27 fn as_any(&self) -> &(dyn Any + Send + 'static); 27 fn as_any(&self) -> &(dyn Any + Send + 'static);
28} 28}
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs
index da85bd082..018c2ad3f 100644
--- a/crates/ra_hir_ty/src/diagnostics.rs
+++ b/crates/ra_hir_ty/src/diagnostics.rs
@@ -21,12 +21,12 @@ impl Diagnostic for NoSuchField {
21 "no such field".to_string() 21 "no such field".to_string()
22 } 22 }
23 23
24 fn highlight_range(&self) -> TextRange { 24 fn highlight_range(&self) -> InFile<TextRange> {
25 self.highlight_range 25 InFile::new(self.file, self.highlight_range)
26 } 26 }
27 27
28 fn source(&self) -> InFile<SyntaxNodePtr> { 28 fn source(&self) -> InFile<SyntaxNodePtr> {
29 InFile { file_id: self.file, value: self.field.clone().into() } 29 InFile::new(self.file, self.field.clone().into())
30 } 30 }
31 31
32 fn as_any(&self) -> &(dyn Any + Send + 'static) { 32 fn as_any(&self) -> &(dyn Any + Send + 'static) {
@@ -50,8 +50,8 @@ impl Diagnostic for MissingFields {
50 } 50 }
51 buf 51 buf
52 } 52 }
53 fn highlight_range(&self) -> TextRange { 53 fn highlight_range(&self) -> InFile<TextRange> {
54 self.highlight_range 54 InFile::new(self.file, self.highlight_range)
55 } 55 }
56 56
57 fn source(&self) -> InFile<SyntaxNodePtr> { 57 fn source(&self) -> InFile<SyntaxNodePtr> {
@@ -88,8 +88,8 @@ impl Diagnostic for MissingPatFields {
88 } 88 }
89 buf 89 buf
90 } 90 }
91 fn highlight_range(&self) -> TextRange { 91 fn highlight_range(&self) -> InFile<TextRange> {
92 self.highlight_range 92 InFile::new(self.file, self.highlight_range)
93 } 93 }
94 fn source(&self) -> InFile<SyntaxNodePtr> { 94 fn source(&self) -> InFile<SyntaxNodePtr> {
95 InFile { file_id: self.file, value: self.field_list.clone().into() } 95 InFile { file_id: self.file, value: self.field_list.clone().into() }
@@ -111,8 +111,8 @@ impl Diagnostic for MissingMatchArms {
111 fn message(&self) -> String { 111 fn message(&self) -> String {
112 String::from("Missing match arm") 112 String::from("Missing match arm")
113 } 113 }
114 fn highlight_range(&self) -> TextRange { 114 fn highlight_range(&self) -> InFile<TextRange> {
115 self.highlight_range 115 InFile::new(self.file, self.highlight_range)
116 } 116 }
117 fn source(&self) -> InFile<SyntaxNodePtr> { 117 fn source(&self) -> InFile<SyntaxNodePtr> {
118 InFile { file_id: self.file, value: self.match_expr.clone().into() } 118 InFile { file_id: self.file, value: self.match_expr.clone().into() }
@@ -133,8 +133,8 @@ impl Diagnostic for MissingOkInTailExpr {
133 fn message(&self) -> String { 133 fn message(&self) -> String {
134 "wrap return expression in Ok".to_string() 134 "wrap return expression in Ok".to_string()
135 } 135 }
136 fn highlight_range(&self) -> TextRange { 136 fn highlight_range(&self) -> InFile<TextRange> {
137 self.highlight_range 137 InFile::new(self.file, self.highlight_range)
138 } 138 }
139 fn source(&self) -> InFile<SyntaxNodePtr> { 139 fn source(&self) -> InFile<SyntaxNodePtr> {
140 InFile { file_id: self.file, value: self.expr.clone().into() } 140 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 901ad104c..e7e201709 100644
--- a/crates/ra_ide/src/diagnostics.rs
+++ b/crates/ra_ide/src/diagnostics.rs
@@ -1,4 +1,8 @@
1//! FIXME: write short doc here 1//! Collects diagnostics & fixits for a single file.
2//!
3//! The tricky bit here is that diagnostics are produced by hir in terms of
4//! macro-expanded files, but we need to present them to the users in terms of
5//! original files. So we need to map the ranges.
2 6
3use std::cell::RefCell; 7use std::cell::RefCell;
4 8
@@ -46,7 +50,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
46 let mut sink = DiagnosticSink::new(|d| { 50 let mut sink = DiagnosticSink::new(|d| {
47 res.borrow_mut().push(Diagnostic { 51 res.borrow_mut().push(Diagnostic {
48 message: d.message(), 52 message: d.message(),
49 range: d.highlight_range(), 53 range: sema.diagnostics_range(d).range,
50 severity: Severity::Error, 54 severity: Severity::Error,
51 fix: None, 55 fix: None,
52 }) 56 })
@@ -62,7 +66,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
62 let create_file = FileSystemEdit::CreateFile { source_root, path }; 66 let create_file = FileSystemEdit::CreateFile { source_root, path };
63 let fix = SourceChange::file_system_edit("create module", create_file); 67 let fix = SourceChange::file_system_edit("create module", create_file);
64 res.borrow_mut().push(Diagnostic { 68 res.borrow_mut().push(Diagnostic {
65 range: d.highlight_range(), 69 range: sema.diagnostics_range(d).range,
66 message: d.message(), 70 message: d.message(),
67 severity: Severity::Error, 71 severity: Severity::Error,
68 fix: Some(fix), 72 fix: Some(fix),
@@ -95,7 +99,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
95 }; 99 };
96 100
97 res.borrow_mut().push(Diagnostic { 101 res.borrow_mut().push(Diagnostic {
98 range: d.highlight_range(), 102 range: sema.diagnostics_range(d).range,
99 message: d.message(), 103 message: d.message(),
100 severity: Severity::Error, 104 severity: Severity::Error,
101 fix, 105 fix,
@@ -103,7 +107,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
103 }) 107 })
104 .on::<hir::diagnostics::MissingMatchArms, _>(|d| { 108 .on::<hir::diagnostics::MissingMatchArms, _>(|d| {
105 res.borrow_mut().push(Diagnostic { 109 res.borrow_mut().push(Diagnostic {
106 range: d.highlight_range(), 110 range: sema.diagnostics_range(d).range,
107 message: d.message(), 111 message: d.message(),
108 severity: Severity::Error, 112 severity: Severity::Error,
109 fix: None, 113 fix: None,
@@ -115,7 +119,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
115 let edit = TextEdit::replace(node.syntax().text_range(), replacement); 119 let edit = TextEdit::replace(node.syntax().text_range(), replacement);
116 let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); 120 let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit);
117 res.borrow_mut().push(Diagnostic { 121 res.borrow_mut().push(Diagnostic {
118 range: d.highlight_range(), 122 range: sema.diagnostics_range(d).range,
119 message: d.message(), 123 message: d.message(),
120 severity: Severity::Error, 124 severity: Severity::Error,
121 fix: Some(fix), 125 fix: Some(fix),
@@ -622,6 +626,62 @@ mod tests {
622 } 626 }
623 627
624 #[test] 628 #[test]
629 fn range_mapping_out_of_macros() {
630 let (analysis, file_id) = single_file(
631 r"
632 fn some() {}
633 fn items() {}
634 fn here() {}
635
636 macro_rules! id {
637 ($($tt:tt)*) => { $($tt)*};
638 }
639
640 fn main() {
641 let _x = id![Foo { a: 42 }];
642 }
643
644 pub struct Foo {
645 pub a: i32,
646 pub b: i32,
647 }
648 ",
649 );
650 let diagnostics = analysis.diagnostics(file_id).unwrap();
651 assert_debug_snapshot!(diagnostics, @r###"
652 [
653 Diagnostic {
654 message: "Missing structure fields:\n- b",
655 range: [224; 233),
656 fix: Some(
657 SourceChange {
658 label: "fill struct fields",
659 source_file_edits: [
660 SourceFileEdit {
661 file_id: FileId(
662 1,
663 ),
664 edit: TextEdit {
665 atoms: [
666 AtomTextEdit {
667 delete: [3; 9),
668 insert: "{a:42, b: ()}",
669 },
670 ],
671 },
672 },
673 ],
674 file_system_edits: [],
675 cursor_position: None,
676 },
677 ),
678 severity: Error,
679 },
680 ]
681 "###);
682 }
683
684 #[test]
625 fn test_check_unnecessary_braces_in_use_statement() { 685 fn test_check_unnecessary_braces_in_use_statement() {
626 check_not_applicable( 686 check_not_applicable(
627 " 687 "