diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_assists/src/handlers/fix_visibility.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir/src/diagnostics.rs | 4 | ||||
-rw-r--r-- | crates/ra_hir/src/semantics.rs | 18 | ||||
-rw-r--r-- | crates/ra_hir_def/src/diagnostics.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir_expand/src/diagnostics.rs | 25 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/diagnostics.rs | 128 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/diagnostics/expr.rs | 14 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/diagnostics/match_check.rs | 8 | ||||
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 184 | ||||
-rw-r--r-- | crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs | 171 | ||||
-rw-r--r-- | crates/ra_ide/src/lib.rs | 10 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 9 |
12 files changed, 288 insertions, 287 deletions
diff --git a/crates/ra_assists/src/handlers/fix_visibility.rs b/crates/ra_assists/src/handlers/fix_visibility.rs index 1aefa79cc..a19dbf33f 100644 --- a/crates/ra_assists/src/handlers/fix_visibility.rs +++ b/crates/ra_assists/src/handlers/fix_visibility.rs | |||
@@ -121,7 +121,7 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> | |||
121 | Some(cap) => match current_visibility { | 121 | Some(cap) => match current_visibility { |
122 | Some(current_visibility) => builder.replace_snippet( | 122 | Some(current_visibility) => builder.replace_snippet( |
123 | cap, | 123 | cap, |
124 | dbg!(current_visibility.syntax()).text_range(), | 124 | current_visibility.syntax().text_range(), |
125 | format!("$0{}", missing_visibility), | 125 | format!("$0{}", missing_visibility), |
126 | ), | 126 | ), |
127 | None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), | 127 | None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), |
diff --git a/crates/ra_hir/src/diagnostics.rs b/crates/ra_hir/src/diagnostics.rs index 266b513dc..363164b9b 100644 --- a/crates/ra_hir/src/diagnostics.rs +++ b/crates/ra_hir/src/diagnostics.rs | |||
@@ -1,8 +1,6 @@ | |||
1 | //! FIXME: write short doc here | 1 | //! FIXME: write short doc here |
2 | pub use hir_def::diagnostics::UnresolvedModule; | 2 | pub use hir_def::diagnostics::UnresolvedModule; |
3 | pub use hir_expand::diagnostics::{ | 3 | pub use hir_expand::diagnostics::{Diagnostic, DiagnosticSink, DiagnosticSinkBuilder}; |
4 | AstDiagnostic, Diagnostic, DiagnosticSink, DiagnosticSinkBuilder, | ||
5 | }; | ||
6 | pub use hir_ty::diagnostics::{ | 4 | pub use hir_ty::diagnostics::{ |
7 | MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField, | 5 | MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkInTailExpr, NoSuchField, |
8 | }; | 6 | }; |
diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs index 872f5fa4c..36b688ccb 100644 --- a/crates/ra_hir/src/semantics.rs +++ b/crates/ra_hir/src/semantics.rs | |||
@@ -8,7 +8,7 @@ use hir_def::{ | |||
8 | resolver::{self, HasResolver, Resolver}, | 8 | resolver::{self, HasResolver, Resolver}, |
9 | AsMacroCall, FunctionId, TraitId, VariantId, | 9 | AsMacroCall, FunctionId, TraitId, VariantId, |
10 | }; | 10 | }; |
11 | use hir_expand::{diagnostics::AstDiagnostic, hygiene::Hygiene, name::AsName, ExpansionInfo}; | 11 | use hir_expand::{hygiene::Hygiene, name::AsName, ExpansionInfo}; |
12 | use hir_ty::associated_type_shorthand_candidates; | 12 | use hir_ty::associated_type_shorthand_candidates; |
13 | use itertools::Itertools; | 13 | use itertools::Itertools; |
14 | use ra_db::{FileId, FileRange}; | 14 | use ra_db::{FileId, FileRange}; |
@@ -110,13 +110,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { | |||
110 | self.imp.parse(file_id) | 110 | self.imp.parse(file_id) |
111 | } | 111 | } |
112 | 112 | ||
113 | pub fn ast<T: AstDiagnostic + Diagnostic>(&self, d: &T) -> <T as AstDiagnostic>::AST { | ||
114 | let file_id = d.source().file_id; | ||
115 | let root = self.db.parse_or_expand(file_id).unwrap(); | ||
116 | self.imp.cache(root, file_id); | ||
117 | d.ast(self.db.upcast()) | ||
118 | } | ||
119 | |||
120 | pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> { | 113 | pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> { |
121 | self.imp.expand(macro_call) | 114 | self.imp.expand(macro_call) |
122 | } | 115 | } |
@@ -146,8 +139,8 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { | |||
146 | self.imp.original_range(node) | 139 | self.imp.original_range(node) |
147 | } | 140 | } |
148 | 141 | ||
149 | pub fn diagnostics_range(&self, diagnostics: &dyn Diagnostic) -> FileRange { | 142 | pub fn diagnostics_display_range(&self, diagnostics: &dyn Diagnostic) -> FileRange { |
150 | self.imp.diagnostics_range(diagnostics) | 143 | self.imp.diagnostics_display_range(diagnostics) |
151 | } | 144 | } |
152 | 145 | ||
153 | pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ { | 146 | pub fn ancestors_with_macros(&self, node: SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ { |
@@ -389,10 +382,11 @@ impl<'db> SemanticsImpl<'db> { | |||
389 | original_range(self.db, node.as_ref()) | 382 | original_range(self.db, node.as_ref()) |
390 | } | 383 | } |
391 | 384 | ||
392 | fn diagnostics_range(&self, diagnostics: &dyn Diagnostic) -> FileRange { | 385 | fn diagnostics_display_range(&self, diagnostics: &dyn Diagnostic) -> FileRange { |
393 | let src = diagnostics.source(); | 386 | let src = diagnostics.display_source(); |
394 | let root = self.db.parse_or_expand(src.file_id).unwrap(); | 387 | let root = self.db.parse_or_expand(src.file_id).unwrap(); |
395 | let node = src.value.to_node(&root); | 388 | let node = src.value.to_node(&root); |
389 | self.cache(root, src.file_id); | ||
396 | original_range(self.db, src.with_value(&node)) | 390 | original_range(self.db, src.with_value(&node)) |
397 | } | 391 | } |
398 | 392 | ||
diff --git a/crates/ra_hir_def/src/diagnostics.rs b/crates/ra_hir_def/src/diagnostics.rs index 30db48f86..71d177070 100644 --- a/crates/ra_hir_def/src/diagnostics.rs +++ b/crates/ra_hir_def/src/diagnostics.rs | |||
@@ -18,7 +18,7 @@ impl Diagnostic for UnresolvedModule { | |||
18 | fn message(&self) -> String { | 18 | fn message(&self) -> String { |
19 | "unresolved module".to_string() | 19 | "unresolved module".to_string() |
20 | } | 20 | } |
21 | fn source(&self) -> InFile<SyntaxNodePtr> { | 21 | fn display_source(&self) -> InFile<SyntaxNodePtr> { |
22 | InFile::new(self.file, self.decl.clone().into()) | 22 | InFile::new(self.file, self.decl.clone().into()) |
23 | } | 23 | } |
24 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | 24 | fn as_any(&self) -> &(dyn Any + Send + 'static) { |
diff --git a/crates/ra_hir_expand/src/diagnostics.rs b/crates/ra_hir_expand/src/diagnostics.rs index 84ba97b14..b138500e7 100644 --- a/crates/ra_hir_expand/src/diagnostics.rs +++ b/crates/ra_hir_expand/src/diagnostics.rs | |||
@@ -16,35 +16,20 @@ | |||
16 | 16 | ||
17 | use std::{any::Any, fmt}; | 17 | use std::{any::Any, fmt}; |
18 | 18 | ||
19 | use ra_syntax::{SyntaxNode, SyntaxNodePtr}; | 19 | use ra_syntax::SyntaxNodePtr; |
20 | 20 | ||
21 | use crate::{db::AstDatabase, InFile}; | 21 | use crate::InFile; |
22 | 22 | ||
23 | pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { | 23 | pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { |
24 | fn message(&self) -> String; | 24 | fn message(&self) -> String; |
25 | fn source(&self) -> InFile<SyntaxNodePtr>; | 25 | /// Used in highlighting and related purposes |
26 | fn display_source(&self) -> InFile<SyntaxNodePtr>; | ||
26 | fn as_any(&self) -> &(dyn Any + Send + 'static); | 27 | fn as_any(&self) -> &(dyn Any + Send + 'static); |
27 | fn is_experimental(&self) -> bool { | 28 | fn is_experimental(&self) -> bool { |
28 | false | 29 | false |
29 | } | 30 | } |
30 | } | 31 | } |
31 | 32 | ||
32 | pub trait AstDiagnostic { | ||
33 | type AST; | ||
34 | fn ast(&self, db: &dyn AstDatabase) -> Self::AST; | ||
35 | } | ||
36 | |||
37 | impl dyn Diagnostic { | ||
38 | pub fn syntax_node(&self, db: &impl AstDatabase) -> SyntaxNode { | ||
39 | let node = db.parse_or_expand(self.source().file_id).unwrap(); | ||
40 | self.source().value.to_node(&node) | ||
41 | } | ||
42 | |||
43 | pub fn downcast_ref<D: Diagnostic>(&self) -> Option<&D> { | ||
44 | self.as_any().downcast_ref() | ||
45 | } | ||
46 | } | ||
47 | |||
48 | pub struct DiagnosticSink<'a> { | 33 | pub struct DiagnosticSink<'a> { |
49 | callbacks: Vec<Box<dyn FnMut(&dyn Diagnostic) -> Result<(), ()> + 'a>>, | 34 | callbacks: Vec<Box<dyn FnMut(&dyn Diagnostic) -> Result<(), ()> + 'a>>, |
50 | filters: Vec<Box<dyn FnMut(&dyn Diagnostic) -> bool + 'a>>, | 35 | filters: Vec<Box<dyn FnMut(&dyn Diagnostic) -> bool + 'a>>, |
@@ -89,7 +74,7 @@ impl<'a> DiagnosticSinkBuilder<'a> { | |||
89 | } | 74 | } |
90 | 75 | ||
91 | pub fn on<D: Diagnostic, F: FnMut(&D) + 'a>(mut self, mut cb: F) -> Self { | 76 | pub fn on<D: Diagnostic, F: FnMut(&D) + 'a>(mut self, mut cb: F) -> Self { |
92 | let cb = move |diag: &dyn Diagnostic| match diag.downcast_ref::<D>() { | 77 | let cb = move |diag: &dyn Diagnostic| match diag.as_any().downcast_ref::<D>() { |
93 | Some(d) => { | 78 | Some(d) => { |
94 | cb(d); | 79 | cb(d); |
95 | Ok(()) | 80 | Ok(()) |
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs index 977c0525b..7ab7f79db 100644 --- a/crates/ra_hir_ty/src/diagnostics.rs +++ b/crates/ra_hir_ty/src/diagnostics.rs | |||
@@ -6,10 +6,10 @@ mod unsafe_check; | |||
6 | use std::any::Any; | 6 | use std::any::Any; |
7 | 7 | ||
8 | use hir_def::DefWithBodyId; | 8 | use hir_def::DefWithBodyId; |
9 | use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; | 9 | use hir_expand::diagnostics::{Diagnostic, DiagnosticSink}; |
10 | use hir_expand::{db::AstDatabase, name::Name, HirFileId, InFile}; | 10 | use hir_expand::{name::Name, HirFileId, InFile}; |
11 | use ra_prof::profile; | 11 | use ra_prof::profile; |
12 | use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr}; | 12 | use ra_syntax::{ast, AstPtr, SyntaxNodePtr}; |
13 | use stdx::format_to; | 13 | use stdx::format_to; |
14 | 14 | ||
15 | use crate::db::HirDatabase; | 15 | use crate::db::HirDatabase; |
@@ -37,7 +37,7 @@ impl Diagnostic for NoSuchField { | |||
37 | "no such field".to_string() | 37 | "no such field".to_string() |
38 | } | 38 | } |
39 | 39 | ||
40 | fn source(&self) -> InFile<SyntaxNodePtr> { | 40 | fn display_source(&self) -> InFile<SyntaxNodePtr> { |
41 | InFile::new(self.file, self.field.clone().into()) | 41 | InFile::new(self.file, self.field.clone().into()) |
42 | } | 42 | } |
43 | 43 | ||
@@ -46,20 +46,11 @@ impl Diagnostic for NoSuchField { | |||
46 | } | 46 | } |
47 | } | 47 | } |
48 | 48 | ||
49 | impl AstDiagnostic for NoSuchField { | ||
50 | type AST = ast::RecordExprField; | ||
51 | |||
52 | fn ast(&self, db: &dyn AstDatabase) -> Self::AST { | ||
53 | let root = db.parse_or_expand(self.source().file_id).unwrap(); | ||
54 | let node = self.source().value.to_node(&root); | ||
55 | ast::RecordExprField::cast(node).unwrap() | ||
56 | } | ||
57 | } | ||
58 | |||
59 | #[derive(Debug)] | 49 | #[derive(Debug)] |
60 | pub struct MissingFields { | 50 | pub struct MissingFields { |
61 | pub file: HirFileId, | 51 | pub file: HirFileId, |
62 | pub field_list: AstPtr<ast::RecordExprFieldList>, | 52 | pub field_list_parent: AstPtr<ast::RecordExpr>, |
53 | pub field_list_parent_path: Option<AstPtr<ast::Path>>, | ||
63 | pub missed_fields: Vec<Name>, | 54 | pub missed_fields: Vec<Name>, |
64 | } | 55 | } |
65 | 56 | ||
@@ -71,28 +62,28 @@ impl Diagnostic for MissingFields { | |||
71 | } | 62 | } |
72 | buf | 63 | buf |
73 | } | 64 | } |
74 | fn source(&self) -> InFile<SyntaxNodePtr> { | 65 | |
75 | InFile { file_id: self.file, value: self.field_list.clone().into() } | 66 | fn display_source(&self) -> InFile<SyntaxNodePtr> { |
67 | InFile { | ||
68 | file_id: self.file, | ||
69 | value: self | ||
70 | .field_list_parent_path | ||
71 | .clone() | ||
72 | .map(SyntaxNodePtr::from) | ||
73 | .unwrap_or_else(|| self.field_list_parent.clone().into()), | ||
74 | } | ||
76 | } | 75 | } |
76 | |||
77 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | 77 | fn as_any(&self) -> &(dyn Any + Send + 'static) { |
78 | self | 78 | self |
79 | } | 79 | } |
80 | } | 80 | } |
81 | 81 | ||
82 | impl AstDiagnostic for MissingFields { | ||
83 | type AST = ast::RecordExprFieldList; | ||
84 | |||
85 | fn ast(&self, db: &dyn AstDatabase) -> Self::AST { | ||
86 | let root = db.parse_or_expand(self.source().file_id).unwrap(); | ||
87 | let node = self.source().value.to_node(&root); | ||
88 | ast::RecordExprFieldList::cast(node).unwrap() | ||
89 | } | ||
90 | } | ||
91 | |||
92 | #[derive(Debug)] | 82 | #[derive(Debug)] |
93 | pub struct MissingPatFields { | 83 | pub struct MissingPatFields { |
94 | pub file: HirFileId, | 84 | pub file: HirFileId, |
95 | pub field_list: AstPtr<ast::RecordPatFieldList>, | 85 | pub field_list_parent: AstPtr<ast::RecordPat>, |
86 | pub field_list_parent_path: Option<AstPtr<ast::Path>>, | ||
96 | pub missed_fields: Vec<Name>, | 87 | pub missed_fields: Vec<Name>, |
97 | } | 88 | } |
98 | 89 | ||
@@ -104,8 +95,15 @@ impl Diagnostic for MissingPatFields { | |||
104 | } | 95 | } |
105 | buf | 96 | buf |
106 | } | 97 | } |
107 | fn source(&self) -> InFile<SyntaxNodePtr> { | 98 | fn display_source(&self) -> InFile<SyntaxNodePtr> { |
108 | InFile { file_id: self.file, value: self.field_list.clone().into() } | 99 | InFile { |
100 | file_id: self.file, | ||
101 | value: self | ||
102 | .field_list_parent_path | ||
103 | .clone() | ||
104 | .map(SyntaxNodePtr::from) | ||
105 | .unwrap_or_else(|| self.field_list_parent.clone().into()), | ||
106 | } | ||
109 | } | 107 | } |
110 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | 108 | fn as_any(&self) -> &(dyn Any + Send + 'static) { |
111 | self | 109 | self |
@@ -123,7 +121,7 @@ impl Diagnostic for MissingMatchArms { | |||
123 | fn message(&self) -> String { | 121 | fn message(&self) -> String { |
124 | String::from("Missing match arm") | 122 | String::from("Missing match arm") |
125 | } | 123 | } |
126 | fn source(&self) -> InFile<SyntaxNodePtr> { | 124 | fn display_source(&self) -> InFile<SyntaxNodePtr> { |
127 | InFile { file_id: self.file, value: self.match_expr.clone().into() } | 125 | InFile { file_id: self.file, value: self.match_expr.clone().into() } |
128 | } | 126 | } |
129 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | 127 | fn as_any(&self) -> &(dyn Any + Send + 'static) { |
@@ -141,7 +139,7 @@ impl Diagnostic for MissingOkInTailExpr { | |||
141 | fn message(&self) -> String { | 139 | fn message(&self) -> String { |
142 | "wrap return expression in Ok".to_string() | 140 | "wrap return expression in Ok".to_string() |
143 | } | 141 | } |
144 | fn source(&self) -> InFile<SyntaxNodePtr> { | 142 | fn display_source(&self) -> InFile<SyntaxNodePtr> { |
145 | InFile { file_id: self.file, value: self.expr.clone().into() } | 143 | InFile { file_id: self.file, value: self.expr.clone().into() } |
146 | } | 144 | } |
147 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | 145 | fn as_any(&self) -> &(dyn Any + Send + 'static) { |
@@ -149,16 +147,6 @@ impl Diagnostic for MissingOkInTailExpr { | |||
149 | } | 147 | } |
150 | } | 148 | } |
151 | 149 | ||
152 | impl AstDiagnostic for MissingOkInTailExpr { | ||
153 | type AST = ast::Expr; | ||
154 | |||
155 | fn ast(&self, db: &dyn AstDatabase) -> Self::AST { | ||
156 | let root = db.parse_or_expand(self.file).unwrap(); | ||
157 | let node = self.source().value.to_node(&root); | ||
158 | ast::Expr::cast(node).unwrap() | ||
159 | } | ||
160 | } | ||
161 | |||
162 | #[derive(Debug)] | 150 | #[derive(Debug)] |
163 | pub struct BreakOutsideOfLoop { | 151 | pub struct BreakOutsideOfLoop { |
164 | pub file: HirFileId, | 152 | pub file: HirFileId, |
@@ -169,7 +157,7 @@ impl Diagnostic for BreakOutsideOfLoop { | |||
169 | fn message(&self) -> String { | 157 | fn message(&self) -> String { |
170 | "break outside of loop".to_string() | 158 | "break outside of loop".to_string() |
171 | } | 159 | } |
172 | fn source(&self) -> InFile<SyntaxNodePtr> { | 160 | fn display_source(&self) -> InFile<SyntaxNodePtr> { |
173 | InFile { file_id: self.file, value: self.expr.clone().into() } | 161 | InFile { file_id: self.file, value: self.expr.clone().into() } |
174 | } | 162 | } |
175 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | 163 | fn as_any(&self) -> &(dyn Any + Send + 'static) { |
@@ -177,16 +165,6 @@ impl Diagnostic for BreakOutsideOfLoop { | |||
177 | } | 165 | } |
178 | } | 166 | } |
179 | 167 | ||
180 | impl AstDiagnostic for BreakOutsideOfLoop { | ||
181 | type AST = ast::Expr; | ||
182 | |||
183 | fn ast(&self, db: &dyn AstDatabase) -> Self::AST { | ||
184 | let root = db.parse_or_expand(self.file).unwrap(); | ||
185 | let node = self.source().value.to_node(&root); | ||
186 | ast::Expr::cast(node).unwrap() | ||
187 | } | ||
188 | } | ||
189 | |||
190 | #[derive(Debug)] | 168 | #[derive(Debug)] |
191 | pub struct MissingUnsafe { | 169 | pub struct MissingUnsafe { |
192 | pub file: HirFileId, | 170 | pub file: HirFileId, |
@@ -197,7 +175,7 @@ impl Diagnostic for MissingUnsafe { | |||
197 | fn message(&self) -> String { | 175 | fn message(&self) -> String { |
198 | format!("This operation is unsafe and requires an unsafe function or block") | 176 | format!("This operation is unsafe and requires an unsafe function or block") |
199 | } | 177 | } |
200 | fn source(&self) -> InFile<SyntaxNodePtr> { | 178 | fn display_source(&self) -> InFile<SyntaxNodePtr> { |
201 | InFile { file_id: self.file, value: self.expr.clone().into() } | 179 | InFile { file_id: self.file, value: self.expr.clone().into() } |
202 | } | 180 | } |
203 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | 181 | fn as_any(&self) -> &(dyn Any + Send + 'static) { |
@@ -205,16 +183,6 @@ impl Diagnostic for MissingUnsafe { | |||
205 | } | 183 | } |
206 | } | 184 | } |
207 | 185 | ||
208 | impl AstDiagnostic for MissingUnsafe { | ||
209 | type AST = ast::Expr; | ||
210 | |||
211 | fn ast(&self, db: &dyn AstDatabase) -> Self::AST { | ||
212 | let root = db.parse_or_expand(self.source().file_id).unwrap(); | ||
213 | let node = self.source().value.to_node(&root); | ||
214 | ast::Expr::cast(node).unwrap() | ||
215 | } | ||
216 | } | ||
217 | |||
218 | #[derive(Debug)] | 186 | #[derive(Debug)] |
219 | pub struct MismatchedArgCount { | 187 | pub struct MismatchedArgCount { |
220 | pub file: HirFileId, | 188 | pub file: HirFileId, |
@@ -228,7 +196,7 @@ impl Diagnostic for MismatchedArgCount { | |||
228 | let s = if self.expected == 1 { "" } else { "s" }; | 196 | let s = if self.expected == 1 { "" } else { "s" }; |
229 | format!("Expected {} argument{}, found {}", self.expected, s, self.found) | 197 | format!("Expected {} argument{}, found {}", self.expected, s, self.found) |
230 | } | 198 | } |
231 | fn source(&self) -> InFile<SyntaxNodePtr> { | 199 | fn display_source(&self) -> InFile<SyntaxNodePtr> { |
232 | InFile { file_id: self.file, value: self.call_expr.clone().into() } | 200 | InFile { file_id: self.file, value: self.call_expr.clone().into() } |
233 | } | 201 | } |
234 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | 202 | fn as_any(&self) -> &(dyn Any + Send + 'static) { |
@@ -239,19 +207,13 @@ impl Diagnostic for MismatchedArgCount { | |||
239 | } | 207 | } |
240 | } | 208 | } |
241 | 209 | ||
242 | impl AstDiagnostic for MismatchedArgCount { | ||
243 | type AST = ast::CallExpr; | ||
244 | fn ast(&self, db: &dyn AstDatabase) -> Self::AST { | ||
245 | let root = db.parse_or_expand(self.source().file_id).unwrap(); | ||
246 | let node = self.source().value.to_node(&root); | ||
247 | ast::CallExpr::cast(node).unwrap() | ||
248 | } | ||
249 | } | ||
250 | |||
251 | #[cfg(test)] | 210 | #[cfg(test)] |
252 | mod tests { | 211 | mod tests { |
253 | use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId}; | 212 | use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId}; |
254 | use hir_expand::diagnostics::{Diagnostic, DiagnosticSinkBuilder}; | 213 | use hir_expand::{ |
214 | db::AstDatabase, | ||
215 | diagnostics::{Diagnostic, DiagnosticSinkBuilder}, | ||
216 | }; | ||
255 | use ra_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt}; | 217 | use ra_db::{fixture::WithFixture, FileId, SourceDatabase, SourceDatabaseExt}; |
256 | use ra_syntax::{TextRange, TextSize}; | 218 | use ra_syntax::{TextRange, TextSize}; |
257 | use rustc_hash::FxHashMap; | 219 | use rustc_hash::FxHashMap; |
@@ -296,9 +258,11 @@ mod tests { | |||
296 | 258 | ||
297 | let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default(); | 259 | let mut actual: FxHashMap<FileId, Vec<(TextRange, String)>> = FxHashMap::default(); |
298 | db.diagnostics(|d| { | 260 | db.diagnostics(|d| { |
299 | // FXIME: macros... | 261 | let src = d.display_source(); |
300 | let file_id = d.source().file_id.original_file(&db); | 262 | let root = db.parse_or_expand(src.file_id).unwrap(); |
301 | let range = d.syntax_node(&db).text_range(); | 263 | // FIXME: macros... |
264 | let file_id = src.file_id.original_file(&db); | ||
265 | let range = src.value.to_node(&root).text_range(); | ||
302 | let message = d.message().to_owned(); | 266 | let message = d.message().to_owned(); |
303 | actual.entry(file_id).or_default().push((range, message)); | 267 | actual.entry(file_id).or_default().push((range, message)); |
304 | }); | 268 | }); |
@@ -326,8 +290,8 @@ struct S { foo: i32, bar: () } | |||
326 | impl S { | 290 | impl S { |
327 | fn new() -> S { | 291 | fn new() -> S { |
328 | S { | 292 | S { |
329 | //^... Missing structure fields: | 293 | //^ Missing structure fields: |
330 | //| - bar | 294 | //| - bar |
331 | foo: 92, | 295 | foo: 92, |
332 | baz: 62, | 296 | baz: 62, |
333 | //^^^^^^^ no such field | 297 | //^^^^^^^ no such field |
@@ -448,8 +412,8 @@ impl Foo { | |||
448 | struct S { foo: i32, bar: () } | 412 | struct S { foo: i32, bar: () } |
449 | fn baz(s: S) { | 413 | fn baz(s: S) { |
450 | let S { foo: _ } = s; | 414 | let S { foo: _ } = s; |
451 | //^^^^^^^^^^ Missing structure fields: | 415 | //^ Missing structure fields: |
452 | // | - bar | 416 | //| - bar |
453 | } | 417 | } |
454 | "#, | 418 | "#, |
455 | ); | 419 | ); |
diff --git a/crates/ra_hir_ty/src/diagnostics/expr.rs b/crates/ra_hir_ty/src/diagnostics/expr.rs index 95bbf2d95..51adcecaf 100644 --- a/crates/ra_hir_ty/src/diagnostics/expr.rs +++ b/crates/ra_hir_ty/src/diagnostics/expr.rs | |||
@@ -100,8 +100,8 @@ impl<'a, 'b> ExprValidator<'a, 'b> { | |||
100 | 100 | ||
101 | if let Ok(source_ptr) = source_map.expr_syntax(id) { | 101 | if let Ok(source_ptr) = source_map.expr_syntax(id) { |
102 | let root = source_ptr.file_syntax(db.upcast()); | 102 | let root = source_ptr.file_syntax(db.upcast()); |
103 | if let ast::Expr::RecordExpr(record_lit) = &source_ptr.value.to_node(&root) { | 103 | if let ast::Expr::RecordExpr(record_expr) = &source_ptr.value.to_node(&root) { |
104 | if let Some(field_list) = record_lit.record_expr_field_list() { | 104 | if let Some(_) = record_expr.record_expr_field_list() { |
105 | let variant_data = variant_data(db.upcast(), variant_def); | 105 | let variant_data = variant_data(db.upcast(), variant_def); |
106 | let missed_fields = missed_fields | 106 | let missed_fields = missed_fields |
107 | .into_iter() | 107 | .into_iter() |
@@ -109,7 +109,8 @@ impl<'a, 'b> ExprValidator<'a, 'b> { | |||
109 | .collect(); | 109 | .collect(); |
110 | self.sink.push(MissingFields { | 110 | self.sink.push(MissingFields { |
111 | file: source_ptr.file_id, | 111 | file: source_ptr.file_id, |
112 | field_list: AstPtr::new(&field_list), | 112 | field_list_parent: AstPtr::new(&record_expr), |
113 | field_list_parent_path: record_expr.path().map(|path| AstPtr::new(&path)), | ||
113 | missed_fields, | 114 | missed_fields, |
114 | }) | 115 | }) |
115 | } | 116 | } |
@@ -131,7 +132,7 @@ impl<'a, 'b> ExprValidator<'a, 'b> { | |||
131 | if let Some(expr) = source_ptr.value.as_ref().left() { | 132 | if let Some(expr) = source_ptr.value.as_ref().left() { |
132 | let root = source_ptr.file_syntax(db.upcast()); | 133 | let root = source_ptr.file_syntax(db.upcast()); |
133 | if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) { | 134 | if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) { |
134 | if let Some(field_list) = record_pat.record_pat_field_list() { | 135 | if let Some(_) = record_pat.record_pat_field_list() { |
135 | let variant_data = variant_data(db.upcast(), variant_def); | 136 | let variant_data = variant_data(db.upcast(), variant_def); |
136 | let missed_fields = missed_fields | 137 | let missed_fields = missed_fields |
137 | .into_iter() | 138 | .into_iter() |
@@ -139,7 +140,10 @@ impl<'a, 'b> ExprValidator<'a, 'b> { | |||
139 | .collect(); | 140 | .collect(); |
140 | self.sink.push(MissingPatFields { | 141 | self.sink.push(MissingPatFields { |
141 | file: source_ptr.file_id, | 142 | file: source_ptr.file_id, |
142 | field_list: AstPtr::new(&field_list), | 143 | field_list_parent: AstPtr::new(&record_pat), |
144 | field_list_parent_path: record_pat | ||
145 | .path() | ||
146 | .map(|path| AstPtr::new(&path)), | ||
143 | missed_fields, | 147 | missed_fields, |
144 | }) | 148 | }) |
145 | } | 149 | } |
diff --git a/crates/ra_hir_ty/src/diagnostics/match_check.rs b/crates/ra_hir_ty/src/diagnostics/match_check.rs index 507edcb7d..deca244db 100644 --- a/crates/ra_hir_ty/src/diagnostics/match_check.rs +++ b/crates/ra_hir_ty/src/diagnostics/match_check.rs | |||
@@ -1161,15 +1161,15 @@ fn main() { | |||
1161 | //^ Missing match arm | 1161 | //^ Missing match arm |
1162 | match a { | 1162 | match a { |
1163 | Either::A { } => (), | 1163 | Either::A { } => (), |
1164 | //^^^ Missing structure fields: | 1164 | //^^^^^^^^^ Missing structure fields: |
1165 | // | - foo | 1165 | // | - foo |
1166 | Either::B => (), | 1166 | Either::B => (), |
1167 | } | 1167 | } |
1168 | match a { | 1168 | match a { |
1169 | //^ Missing match arm | 1169 | //^ Missing match arm |
1170 | Either::A { } => (), | 1170 | Either::A { } => (), |
1171 | } //^^^ Missing structure fields: | 1171 | } //^^^^^^^^^ Missing structure fields: |
1172 | // | - foo | 1172 | // | - foo |
1173 | 1173 | ||
1174 | match a { | 1174 | match a { |
1175 | Either::A { foo: true } => (), | 1175 | Either::A { foo: true } => (), |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 73c0b8275..1046d7ab3 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -6,22 +6,21 @@ | |||
6 | 6 | ||
7 | use std::cell::RefCell; | 7 | use std::cell::RefCell; |
8 | 8 | ||
9 | use hir::{ | 9 | use hir::{diagnostics::DiagnosticSinkBuilder, Semantics}; |
10 | diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSinkBuilder}, | ||
11 | HasSource, HirDisplay, Semantics, VariantDef, | ||
12 | }; | ||
13 | use itertools::Itertools; | 10 | use itertools::Itertools; |
14 | use ra_db::SourceDatabase; | 11 | use ra_db::SourceDatabase; |
15 | use ra_ide_db::RootDatabase; | 12 | use ra_ide_db::RootDatabase; |
16 | use ra_prof::profile; | 13 | use ra_prof::profile; |
17 | use ra_syntax::{ | 14 | use ra_syntax::{ |
18 | algo, | 15 | ast::{self, AstNode}, |
19 | ast::{self, edit::IndentLevel, make, AstNode}, | ||
20 | SyntaxNode, TextRange, T, | 16 | SyntaxNode, TextRange, T, |
21 | }; | 17 | }; |
22 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 18 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
23 | 19 | ||
24 | use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceFileEdit}; | 20 | use crate::{Diagnostic, FileId, Fix, SourceFileEdit}; |
21 | |||
22 | mod diagnostics_with_fix; | ||
23 | use diagnostics_with_fix::DiagnosticWithFix; | ||
25 | 24 | ||
26 | #[derive(Debug, Copy, Clone)] | 25 | #[derive(Debug, Copy, Clone)] |
27 | pub enum Severity { | 26 | pub enum Severity { |
@@ -54,73 +53,16 @@ pub(crate) fn diagnostics( | |||
54 | let res = RefCell::new(res); | 53 | let res = RefCell::new(res); |
55 | let mut sink = DiagnosticSinkBuilder::new() | 54 | let mut sink = DiagnosticSinkBuilder::new() |
56 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { | 55 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { |
57 | let original_file = d.source().file_id.original_file(db); | 56 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); |
58 | let fix = Fix::new( | ||
59 | "Create module", | ||
60 | FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() } | ||
61 | .into(), | ||
62 | ); | ||
63 | res.borrow_mut().push(Diagnostic { | ||
64 | range: sema.diagnostics_range(d).range, | ||
65 | message: d.message(), | ||
66 | severity: Severity::Error, | ||
67 | fix: Some(fix), | ||
68 | }) | ||
69 | }) | 57 | }) |
70 | .on::<hir::diagnostics::MissingFields, _>(|d| { | 58 | .on::<hir::diagnostics::MissingFields, _>(|d| { |
71 | // Note that although we could add a diagnostics to | 59 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); |
72 | // fill the missing tuple field, e.g : | ||
73 | // `struct A(usize);` | ||
74 | // `let a = A { 0: () }` | ||
75 | // but it is uncommon usage and it should not be encouraged. | ||
76 | let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { | ||
77 | None | ||
78 | } else { | ||
79 | let mut field_list = d.ast(db); | ||
80 | for f in d.missed_fields.iter() { | ||
81 | let field = make::record_expr_field( | ||
82 | make::name_ref(&f.to_string()), | ||
83 | Some(make::expr_unit()), | ||
84 | ); | ||
85 | field_list = field_list.append_field(&field); | ||
86 | } | ||
87 | |||
88 | let edit = { | ||
89 | let mut builder = TextEditBuilder::default(); | ||
90 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()) | ||
91 | .into_text_edit(&mut builder); | ||
92 | builder.finish() | ||
93 | }; | ||
94 | Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into())) | ||
95 | }; | ||
96 | |||
97 | res.borrow_mut().push(Diagnostic { | ||
98 | range: sema.diagnostics_range(d).range, | ||
99 | message: d.message(), | ||
100 | severity: Severity::Error, | ||
101 | fix, | ||
102 | }) | ||
103 | }) | 60 | }) |
104 | .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { | 61 | .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { |
105 | let node = d.ast(db); | 62 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); |
106 | let replacement = format!("Ok({})", node.syntax()); | ||
107 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); | ||
108 | let source_change = SourceFileEdit { file_id, edit }.into(); | ||
109 | let fix = Fix::new("Wrap with ok", source_change); | ||
110 | res.borrow_mut().push(Diagnostic { | ||
111 | range: sema.diagnostics_range(d).range, | ||
112 | message: d.message(), | ||
113 | severity: Severity::Error, | ||
114 | fix: Some(fix), | ||
115 | }) | ||
116 | }) | 63 | }) |
117 | .on::<hir::diagnostics::NoSuchField, _>(|d| { | 64 | .on::<hir::diagnostics::NoSuchField, _>(|d| { |
118 | res.borrow_mut().push(Diagnostic { | 65 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); |
119 | range: sema.diagnostics_range(d).range, | ||
120 | message: d.message(), | ||
121 | severity: Severity::Error, | ||
122 | fix: missing_struct_field_fix(&sema, file_id, d), | ||
123 | }) | ||
124 | }) | 66 | }) |
125 | // Only collect experimental diagnostics when they're enabled. | 67 | // Only collect experimental diagnostics when they're enabled. |
126 | .filter(|diag| !diag.is_experimental() || enable_experimental) | 68 | .filter(|diag| !diag.is_experimental() || enable_experimental) |
@@ -128,7 +70,7 @@ pub(crate) fn diagnostics( | |||
128 | .build(|d| { | 70 | .build(|d| { |
129 | res.borrow_mut().push(Diagnostic { | 71 | res.borrow_mut().push(Diagnostic { |
130 | message: d.message(), | 72 | message: d.message(), |
131 | range: sema.diagnostics_range(d).range, | 73 | range: sema.diagnostics_display_range(d).range, |
132 | severity: Severity::Error, | 74 | severity: Severity::Error, |
133 | fix: None, | 75 | fix: None, |
134 | }) | 76 | }) |
@@ -141,77 +83,12 @@ pub(crate) fn diagnostics( | |||
141 | res.into_inner() | 83 | res.into_inner() |
142 | } | 84 | } |
143 | 85 | ||
144 | fn missing_struct_field_fix( | 86 | fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { |
145 | sema: &Semantics<RootDatabase>, | 87 | Diagnostic { |
146 | usage_file_id: FileId, | 88 | range: sema.diagnostics_display_range(d).range, |
147 | d: &hir::diagnostics::NoSuchField, | 89 | message: d.message(), |
148 | ) -> Option<Fix> { | 90 | severity: Severity::Error, |
149 | let record_expr = sema.ast(d); | 91 | fix: d.fix(&sema), |
150 | |||
151 | let record_lit = ast::RecordExpr::cast(record_expr.syntax().parent()?.parent()?)?; | ||
152 | let def_id = sema.resolve_variant(record_lit)?; | ||
153 | let module; | ||
154 | let def_file_id; | ||
155 | let record_fields = match VariantDef::from(def_id) { | ||
156 | VariantDef::Struct(s) => { | ||
157 | module = s.module(sema.db); | ||
158 | let source = s.source(sema.db); | ||
159 | def_file_id = source.file_id; | ||
160 | let fields = source.value.field_list()?; | ||
161 | record_field_list(fields)? | ||
162 | } | ||
163 | VariantDef::Union(u) => { | ||
164 | module = u.module(sema.db); | ||
165 | let source = u.source(sema.db); | ||
166 | def_file_id = source.file_id; | ||
167 | source.value.record_field_list()? | ||
168 | } | ||
169 | VariantDef::EnumVariant(e) => { | ||
170 | module = e.module(sema.db); | ||
171 | let source = e.source(sema.db); | ||
172 | def_file_id = source.file_id; | ||
173 | let fields = source.value.field_list()?; | ||
174 | record_field_list(fields)? | ||
175 | } | ||
176 | }; | ||
177 | let def_file_id = def_file_id.original_file(sema.db); | ||
178 | |||
179 | let new_field_type = sema.type_of_expr(&record_expr.expr()?)?; | ||
180 | if new_field_type.is_unknown() { | ||
181 | return None; | ||
182 | } | ||
183 | let new_field = make::record_field( | ||
184 | record_expr.field_name()?, | ||
185 | make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?), | ||
186 | ); | ||
187 | |||
188 | let last_field = record_fields.fields().last()?; | ||
189 | let last_field_syntax = last_field.syntax(); | ||
190 | let indent = IndentLevel::from_node(last_field_syntax); | ||
191 | |||
192 | let mut new_field = new_field.to_string(); | ||
193 | if usage_file_id != def_file_id { | ||
194 | new_field = format!("pub(crate) {}", new_field); | ||
195 | } | ||
196 | new_field = format!("\n{}{}", indent, new_field); | ||
197 | |||
198 | let needs_comma = !last_field_syntax.to_string().ends_with(','); | ||
199 | if needs_comma { | ||
200 | new_field = format!(",{}", new_field); | ||
201 | } | ||
202 | |||
203 | let source_change = SourceFileEdit { | ||
204 | file_id: def_file_id, | ||
205 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), | ||
206 | }; | ||
207 | let fix = Fix::new("Create field", source_change.into()); | ||
208 | return Some(fix); | ||
209 | |||
210 | fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { | ||
211 | match field_def_list { | ||
212 | ast::FieldList::RecordFieldList(it) => Some(it), | ||
213 | ast::FieldList::TupleFieldList(_) => None, | ||
214 | } | ||
215 | } | 92 | } |
216 | } | 93 | } |
217 | 94 | ||
@@ -222,24 +99,25 @@ fn check_unnecessary_braces_in_use_statement( | |||
222 | ) -> Option<()> { | 99 | ) -> Option<()> { |
223 | let use_tree_list = ast::UseTreeList::cast(node.clone())?; | 100 | let use_tree_list = ast::UseTreeList::cast(node.clone())?; |
224 | if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { | 101 | if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { |
225 | let range = use_tree_list.syntax().text_range(); | 102 | let use_range = use_tree_list.syntax().text_range(); |
226 | let edit = | 103 | let edit = |
227 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree) | 104 | text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree) |
228 | .unwrap_or_else(|| { | 105 | .unwrap_or_else(|| { |
229 | let to_replace = single_use_tree.syntax().text().to_string(); | 106 | let to_replace = single_use_tree.syntax().text().to_string(); |
230 | let mut edit_builder = TextEditBuilder::default(); | 107 | let mut edit_builder = TextEditBuilder::default(); |
231 | edit_builder.delete(range); | 108 | edit_builder.delete(use_range); |
232 | edit_builder.insert(range.start(), to_replace); | 109 | edit_builder.insert(use_range.start(), to_replace); |
233 | edit_builder.finish() | 110 | edit_builder.finish() |
234 | }); | 111 | }); |
235 | 112 | ||
236 | acc.push(Diagnostic { | 113 | acc.push(Diagnostic { |
237 | range, | 114 | range: use_range, |
238 | message: "Unnecessary braces in use statement".to_string(), | 115 | message: "Unnecessary braces in use statement".to_string(), |
239 | severity: Severity::WeakWarning, | 116 | severity: Severity::WeakWarning, |
240 | fix: Some(Fix::new( | 117 | fix: Some(Fix::new( |
241 | "Remove unnecessary braces", | 118 | "Remove unnecessary braces", |
242 | SourceFileEdit { file_id, edit }.into(), | 119 | SourceFileEdit { file_id, edit }.into(), |
120 | use_range, | ||
243 | )), | 121 | )), |
244 | }); | 122 | }); |
245 | } | 123 | } |
@@ -254,8 +132,7 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | |||
254 | if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] { | 132 | if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] { |
255 | let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); | 133 | let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); |
256 | let end = use_tree_list_node.text_range().end(); | 134 | let end = use_tree_list_node.text_range().end(); |
257 | let range = TextRange::new(start, end); | 135 | return Some(TextEdit::delete(TextRange::new(start, end))); |
258 | return Some(TextEdit::delete(range)); | ||
259 | } | 136 | } |
260 | None | 137 | None |
261 | } | 138 | } |
@@ -278,13 +155,15 @@ fn check_struct_shorthand_initialization( | |||
278 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | 155 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); |
279 | let edit = edit_builder.finish(); | 156 | let edit = edit_builder.finish(); |
280 | 157 | ||
158 | let field_range = record_field.syntax().text_range(); | ||
281 | acc.push(Diagnostic { | 159 | acc.push(Diagnostic { |
282 | range: record_field.syntax().text_range(), | 160 | range: field_range, |
283 | message: "Shorthand struct initialization".to_string(), | 161 | message: "Shorthand struct initialization".to_string(), |
284 | severity: Severity::WeakWarning, | 162 | severity: Severity::WeakWarning, |
285 | fix: Some(Fix::new( | 163 | fix: Some(Fix::new( |
286 | "Use struct shorthand initialization", | 164 | "Use struct shorthand initialization", |
287 | SourceFileEdit { file_id, edit }.into(), | 165 | SourceFileEdit { file_id, edit }.into(), |
166 | field_range, | ||
288 | )), | 167 | )), |
289 | }); | 168 | }); |
290 | } | 169 | } |
@@ -304,7 +183,7 @@ mod tests { | |||
304 | /// Takes a multi-file input fixture with annotated cursor positions, | 183 | /// Takes a multi-file input fixture with annotated cursor positions, |
305 | /// and checks that: | 184 | /// and checks that: |
306 | /// * a diagnostic is produced | 185 | /// * a diagnostic is produced |
307 | /// * this diagnostic touches the input cursor position | 186 | /// * this diagnostic fix trigger range touches the input cursor position |
308 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied | 187 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied |
309 | fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { | 188 | fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { |
310 | let after = trim_indent(ra_fixture_after); | 189 | let after = trim_indent(ra_fixture_after); |
@@ -322,10 +201,10 @@ mod tests { | |||
322 | 201 | ||
323 | assert_eq_text!(&after, &actual); | 202 | assert_eq_text!(&after, &actual); |
324 | assert!( | 203 | assert!( |
325 | diagnostic.range.start() <= file_position.offset | 204 | fix.fix_trigger_range.start() <= file_position.offset |
326 | && diagnostic.range.end() >= file_position.offset, | 205 | && fix.fix_trigger_range.end() >= file_position.offset, |
327 | "diagnostic range {:?} does not touch cursor position {:?}", | 206 | "diagnostic fix range {:?} does not touch cursor position {:?}", |
328 | diagnostic.range, | 207 | fix.fix_trigger_range, |
329 | file_position.offset | 208 | file_position.offset |
330 | ); | 209 | ); |
331 | } | 210 | } |
@@ -642,6 +521,7 @@ fn test_fn() { | |||
642 | ], | 521 | ], |
643 | is_snippet: false, | 522 | is_snippet: false, |
644 | }, | 523 | }, |
524 | fix_trigger_range: 0..8, | ||
645 | }, | 525 | }, |
646 | ), | 526 | ), |
647 | }, | 527 | }, |
diff --git a/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs b/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs new file mode 100644 index 000000000..f7c73773f --- /dev/null +++ b/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs | |||
@@ -0,0 +1,171 @@ | |||
1 | //! Provides a way to attach fixes to the diagnostics. | ||
2 | //! The same module also has all curret custom fixes for the diagnostics implemented. | ||
3 | use crate::Fix; | ||
4 | use ast::{edit::IndentLevel, make}; | ||
5 | use hir::{ | ||
6 | db::AstDatabase, | ||
7 | diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule}, | ||
8 | HasSource, HirDisplay, Semantics, VariantDef, | ||
9 | }; | ||
10 | use ra_db::FileId; | ||
11 | use ra_ide_db::{ | ||
12 | source_change::{FileSystemEdit, SourceFileEdit}, | ||
13 | RootDatabase, | ||
14 | }; | ||
15 | use ra_syntax::{algo, ast, AstNode}; | ||
16 | use ra_text_edit::{TextEdit, TextEditBuilder}; | ||
17 | |||
18 | /// A [Diagnostic] that potentially has a fix available. | ||
19 | /// | ||
20 | /// [Diagnostic]: hir::diagnostics::Diagnostic | ||
21 | pub trait DiagnosticWithFix: Diagnostic { | ||
22 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix>; | ||
23 | } | ||
24 | |||
25 | impl DiagnosticWithFix for UnresolvedModule { | ||
26 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | ||
27 | let root = sema.db.parse_or_expand(self.file)?; | ||
28 | let unresolved_module = self.decl.to_node(&root); | ||
29 | Some(Fix::new( | ||
30 | "Create module", | ||
31 | FileSystemEdit::CreateFile { | ||
32 | anchor: self.file.original_file(sema.db), | ||
33 | dst: self.candidate.clone(), | ||
34 | } | ||
35 | .into(), | ||
36 | unresolved_module.syntax().text_range(), | ||
37 | )) | ||
38 | } | ||
39 | } | ||
40 | |||
41 | impl DiagnosticWithFix for NoSuchField { | ||
42 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | ||
43 | let root = sema.db.parse_or_expand(self.file)?; | ||
44 | missing_record_expr_field_fix( | ||
45 | &sema, | ||
46 | self.file.original_file(sema.db), | ||
47 | &self.field.to_node(&root), | ||
48 | ) | ||
49 | } | ||
50 | } | ||
51 | |||
52 | impl DiagnosticWithFix for MissingFields { | ||
53 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | ||
54 | // Note that although we could add a diagnostics to | ||
55 | // fill the missing tuple field, e.g : | ||
56 | // `struct A(usize);` | ||
57 | // `let a = A { 0: () }` | ||
58 | // but it is uncommon usage and it should not be encouraged. | ||
59 | if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { | ||
60 | return None; | ||
61 | } | ||
62 | |||
63 | let root = sema.db.parse_or_expand(self.file)?; | ||
64 | let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?; | ||
65 | let mut new_field_list = old_field_list.clone(); | ||
66 | for f in self.missed_fields.iter() { | ||
67 | let field = | ||
68 | make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); | ||
69 | new_field_list = new_field_list.append_field(&field); | ||
70 | } | ||
71 | |||
72 | let edit = { | ||
73 | let mut builder = TextEditBuilder::default(); | ||
74 | algo::diff(&old_field_list.syntax(), &new_field_list.syntax()) | ||
75 | .into_text_edit(&mut builder); | ||
76 | builder.finish() | ||
77 | }; | ||
78 | Some(Fix::new( | ||
79 | "Fill struct fields", | ||
80 | SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(), | ||
81 | sema.original_range(&old_field_list.syntax()).range, | ||
82 | )) | ||
83 | } | ||
84 | } | ||
85 | |||
86 | impl DiagnosticWithFix for MissingOkInTailExpr { | ||
87 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { | ||
88 | let root = sema.db.parse_or_expand(self.file)?; | ||
89 | let tail_expr = self.expr.to_node(&root); | ||
90 | let tail_expr_range = tail_expr.syntax().text_range(); | ||
91 | let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax())); | ||
92 | let source_change = | ||
93 | SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(); | ||
94 | Some(Fix::new("Wrap with ok", source_change, tail_expr_range)) | ||
95 | } | ||
96 | } | ||
97 | |||
98 | fn missing_record_expr_field_fix( | ||
99 | sema: &Semantics<RootDatabase>, | ||
100 | usage_file_id: FileId, | ||
101 | record_expr_field: &ast::RecordExprField, | ||
102 | ) -> Option<Fix> { | ||
103 | let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?; | ||
104 | let def_id = sema.resolve_variant(record_lit)?; | ||
105 | let module; | ||
106 | let def_file_id; | ||
107 | let record_fields = match VariantDef::from(def_id) { | ||
108 | VariantDef::Struct(s) => { | ||
109 | module = s.module(sema.db); | ||
110 | let source = s.source(sema.db); | ||
111 | def_file_id = source.file_id; | ||
112 | let fields = source.value.field_list()?; | ||
113 | record_field_list(fields)? | ||
114 | } | ||
115 | VariantDef::Union(u) => { | ||
116 | module = u.module(sema.db); | ||
117 | let source = u.source(sema.db); | ||
118 | def_file_id = source.file_id; | ||
119 | source.value.record_field_list()? | ||
120 | } | ||
121 | VariantDef::EnumVariant(e) => { | ||
122 | module = e.module(sema.db); | ||
123 | let source = e.source(sema.db); | ||
124 | def_file_id = source.file_id; | ||
125 | let fields = source.value.field_list()?; | ||
126 | record_field_list(fields)? | ||
127 | } | ||
128 | }; | ||
129 | let def_file_id = def_file_id.original_file(sema.db); | ||
130 | |||
131 | let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?; | ||
132 | if new_field_type.is_unknown() { | ||
133 | return None; | ||
134 | } | ||
135 | let new_field = make::record_field( | ||
136 | record_expr_field.field_name()?, | ||
137 | make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?), | ||
138 | ); | ||
139 | |||
140 | let last_field = record_fields.fields().last()?; | ||
141 | let last_field_syntax = last_field.syntax(); | ||
142 | let indent = IndentLevel::from_node(last_field_syntax); | ||
143 | |||
144 | let mut new_field = new_field.to_string(); | ||
145 | if usage_file_id != def_file_id { | ||
146 | new_field = format!("pub(crate) {}", new_field); | ||
147 | } | ||
148 | new_field = format!("\n{}{}", indent, new_field); | ||
149 | |||
150 | let needs_comma = !last_field_syntax.to_string().ends_with(','); | ||
151 | if needs_comma { | ||
152 | new_field = format!(",{}", new_field); | ||
153 | } | ||
154 | |||
155 | let source_change = SourceFileEdit { | ||
156 | file_id: def_file_id, | ||
157 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), | ||
158 | }; | ||
159 | return Some(Fix::new( | ||
160 | "Create field", | ||
161 | source_change.into(), | ||
162 | record_expr_field.syntax().text_range(), | ||
163 | )); | ||
164 | |||
165 | fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { | ||
166 | match field_def_list { | ||
167 | ast::FieldList::RecordFieldList(it) => Some(it), | ||
168 | ast::FieldList::TupleFieldList(_) => None, | ||
169 | } | ||
170 | } | ||
171 | } | ||
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 0fede0d87..89fcb6f17 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -112,13 +112,19 @@ pub struct Diagnostic { | |||
112 | pub struct Fix { | 112 | pub struct Fix { |
113 | pub label: String, | 113 | pub label: String, |
114 | pub source_change: SourceChange, | 114 | pub source_change: SourceChange, |
115 | /// Allows to trigger the fix only when the caret is in the range given | ||
116 | pub fix_trigger_range: TextRange, | ||
115 | } | 117 | } |
116 | 118 | ||
117 | impl Fix { | 119 | impl Fix { |
118 | pub fn new(label: impl Into<String>, source_change: SourceChange) -> Self { | 120 | pub fn new( |
121 | label: impl Into<String>, | ||
122 | source_change: SourceChange, | ||
123 | fix_trigger_range: TextRange, | ||
124 | ) -> Self { | ||
119 | let label = label.into(); | 125 | let label = label.into(); |
120 | assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.')); | 126 | assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.')); |
121 | Self { label, source_change } | 127 | Self { label, source_change, fix_trigger_range } |
122 | } | 128 | } |
123 | } | 129 | } |
124 | 130 | ||
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index c2afcf192..785dd2a26 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -773,12 +773,11 @@ fn handle_fixes( | |||
773 | 773 | ||
774 | let diagnostics = snap.analysis.diagnostics(file_id, snap.config.experimental_diagnostics)?; | 774 | let diagnostics = snap.analysis.diagnostics(file_id, snap.config.experimental_diagnostics)?; |
775 | 775 | ||
776 | let fixes_from_diagnostics = diagnostics | 776 | for fix in diagnostics |
777 | .into_iter() | 777 | .into_iter() |
778 | .filter_map(|d| Some((d.range, d.fix?))) | 778 | .filter_map(|d| d.fix) |
779 | .filter(|(diag_range, _fix)| diag_range.intersect(range).is_some()) | 779 | .filter(|fix| fix.fix_trigger_range.intersect(range).is_some()) |
780 | .map(|(_range, fix)| fix); | 780 | { |
781 | for fix in fixes_from_diagnostics { | ||
782 | let title = fix.label; | 781 | let title = fix.label; |
783 | let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?; | 782 | let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?; |
784 | let action = lsp_ext::CodeAction { | 783 | let action = lsp_ext::CodeAction { |