aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/handlers/fix_visibility.rs2
-rw-r--r--crates/ra_hir/src/diagnostics.rs4
-rw-r--r--crates/ra_hir/src/semantics.rs18
-rw-r--r--crates/ra_hir_def/src/diagnostics.rs2
-rw-r--r--crates/ra_hir_expand/src/diagnostics.rs25
-rw-r--r--crates/ra_hir_ty/src/diagnostics.rs128
-rw-r--r--crates/ra_hir_ty/src/diagnostics/expr.rs14
-rw-r--r--crates/ra_hir_ty/src/diagnostics/match_check.rs8
-rw-r--r--crates/ra_ide/src/diagnostics.rs184
-rw-r--r--crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs171
-rw-r--r--crates/ra_ide/src/lib.rs10
-rw-r--r--crates/rust-analyzer/src/handlers.rs9
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
2pub use hir_def::diagnostics::UnresolvedModule; 2pub use hir_def::diagnostics::UnresolvedModule;
3pub use hir_expand::diagnostics::{ 3pub use hir_expand::diagnostics::{Diagnostic, DiagnosticSink, DiagnosticSinkBuilder};
4 AstDiagnostic, Diagnostic, DiagnosticSink, DiagnosticSinkBuilder,
5};
6pub use hir_ty::diagnostics::{ 4pub 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};
11use hir_expand::{diagnostics::AstDiagnostic, hygiene::Hygiene, name::AsName, ExpansionInfo}; 11use hir_expand::{hygiene::Hygiene, name::AsName, ExpansionInfo};
12use hir_ty::associated_type_shorthand_candidates; 12use hir_ty::associated_type_shorthand_candidates;
13use itertools::Itertools; 13use itertools::Itertools;
14use ra_db::{FileId, FileRange}; 14use 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
17use std::{any::Any, fmt}; 17use std::{any::Any, fmt};
18 18
19use ra_syntax::{SyntaxNode, SyntaxNodePtr}; 19use ra_syntax::SyntaxNodePtr;
20 20
21use crate::{db::AstDatabase, InFile}; 21use crate::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 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
32pub trait AstDiagnostic {
33 type AST;
34 fn ast(&self, db: &dyn AstDatabase) -> Self::AST;
35}
36
37impl 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
48pub struct DiagnosticSink<'a> { 33pub 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;
6use std::any::Any; 6use std::any::Any;
7 7
8use hir_def::DefWithBodyId; 8use hir_def::DefWithBodyId;
9use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; 9use hir_expand::diagnostics::{Diagnostic, DiagnosticSink};
10use hir_expand::{db::AstDatabase, name::Name, HirFileId, InFile}; 10use hir_expand::{name::Name, HirFileId, InFile};
11use ra_prof::profile; 11use ra_prof::profile;
12use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr}; 12use ra_syntax::{ast, AstPtr, SyntaxNodePtr};
13use stdx::format_to; 13use stdx::format_to;
14 14
15use crate::db::HirDatabase; 15use 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
49impl 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)]
60pub struct MissingFields { 50pub 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
82impl 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)]
93pub struct MissingPatFields { 83pub 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
152impl 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)]
163pub struct BreakOutsideOfLoop { 151pub 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
180impl 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)]
191pub struct MissingUnsafe { 169pub 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
208impl 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)]
219pub struct MismatchedArgCount { 187pub 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
242impl 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)]
252mod tests { 211mod 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: () }
326impl S { 290impl 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 {
448struct S { foo: i32, bar: () } 412struct S { foo: i32, bar: () }
449fn baz(s: S) { 413fn 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
7use std::cell::RefCell; 7use std::cell::RefCell;
8 8
9use hir::{ 9use hir::{diagnostics::DiagnosticSinkBuilder, Semantics};
10 diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSinkBuilder},
11 HasSource, HirDisplay, Semantics, VariantDef,
12};
13use itertools::Itertools; 10use itertools::Itertools;
14use ra_db::SourceDatabase; 11use ra_db::SourceDatabase;
15use ra_ide_db::RootDatabase; 12use ra_ide_db::RootDatabase;
16use ra_prof::profile; 13use ra_prof::profile;
17use ra_syntax::{ 14use 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};
22use ra_text_edit::{TextEdit, TextEditBuilder}; 18use ra_text_edit::{TextEdit, TextEditBuilder};
23 19
24use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceFileEdit}; 20use crate::{Diagnostic, FileId, Fix, SourceFileEdit};
21
22mod diagnostics_with_fix;
23use diagnostics_with_fix::DiagnosticWithFix;
25 24
26#[derive(Debug, Copy, Clone)] 25#[derive(Debug, Copy, Clone)]
27pub enum Severity { 26pub 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
144fn missing_struct_field_fix( 86fn 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.
3use crate::Fix;
4use ast::{edit::IndentLevel, make};
5use hir::{
6 db::AstDatabase,
7 diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule},
8 HasSource, HirDisplay, Semantics, VariantDef,
9};
10use ra_db::FileId;
11use ra_ide_db::{
12 source_change::{FileSystemEdit, SourceFileEdit},
13 RootDatabase,
14};
15use ra_syntax::{algo, ast, AstNode};
16use ra_text_edit::{TextEdit, TextEditBuilder};
17
18/// A [Diagnostic] that potentially has a fix available.
19///
20/// [Diagnostic]: hir::diagnostics::Diagnostic
21pub trait DiagnosticWithFix: Diagnostic {
22 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix>;
23}
24
25impl 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
41impl 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
52impl 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
86impl 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
98fn 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 {
112pub struct Fix { 112pub 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
117impl Fix { 119impl 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 {