diff options
author | Timo Freiberg <[email protected]> | 2020-06-09 22:11:16 +0100 |
---|---|---|
committer | Timo Freiberg <[email protected]> | 2020-06-12 17:52:44 +0100 |
commit | f5ac3130001ab7fe64dd542fd866a18012ee9971 (patch) | |
tree | fb084408f5cce4addbb44806301f0526c5277356 /crates | |
parent | b56ad148db0c69eb279c225f45d324b4e80e7367 (diff) |
Add quickfix to add a struct field
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_hir/src/semantics.rs | 15 | ||||
-rw-r--r-- | crates/ra_hir/src/source_analyzer.rs | 10 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/diagnostics.rs | 12 | ||||
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 98 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/make.rs | 4 |
5 files changed, 134 insertions, 5 deletions
diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs index a232a5856..6a49c424a 100644 --- a/crates/ra_hir/src/semantics.rs +++ b/crates/ra_hir/src/semantics.rs | |||
@@ -6,9 +6,9 @@ use std::{cell::RefCell, fmt, iter::successors}; | |||
6 | 6 | ||
7 | use hir_def::{ | 7 | use hir_def::{ |
8 | resolver::{self, HasResolver, Resolver}, | 8 | resolver::{self, HasResolver, Resolver}, |
9 | AsMacroCall, TraitId, | 9 | AsMacroCall, TraitId, VariantId, |
10 | }; | 10 | }; |
11 | use hir_expand::{hygiene::Hygiene, ExpansionInfo}; | 11 | use hir_expand::{diagnostics::AstDiagnostic, hygiene::Hygiene, 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}; |
@@ -104,6 +104,13 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { | |||
104 | tree | 104 | tree |
105 | } | 105 | } |
106 | 106 | ||
107 | pub fn ast<T: AstDiagnostic + Diagnostic>(&self, d: &T) -> <T as AstDiagnostic>::AST { | ||
108 | let file_id = d.source().file_id; | ||
109 | let root = self.db.parse_or_expand(file_id).unwrap(); | ||
110 | self.cache(root, file_id); | ||
111 | d.ast(self.db) | ||
112 | } | ||
113 | |||
107 | pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> { | 114 | pub fn expand(&self, macro_call: &ast::MacroCall) -> Option<SyntaxNode> { |
108 | let macro_call = self.find_file(macro_call.syntax().clone()).with_value(macro_call); | 115 | let macro_call = self.find_file(macro_call.syntax().clone()).with_value(macro_call); |
109 | let sa = self.analyze2(macro_call.map(|it| it.syntax()), None); | 116 | let sa = self.analyze2(macro_call.map(|it| it.syntax()), None); |
@@ -247,6 +254,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { | |||
247 | self.analyze(path.syntax()).resolve_path(self.db, path) | 254 | self.analyze(path.syntax()).resolve_path(self.db, path) |
248 | } | 255 | } |
249 | 256 | ||
257 | pub fn resolve_variant(&self, record_lit: ast::RecordLit) -> Option<VariantId> { | ||
258 | self.analyze(record_lit.syntax()).resolve_variant(self.db, record_lit) | ||
259 | } | ||
260 | |||
250 | pub fn lower_path(&self, path: &ast::Path) -> Option<Path> { | 261 | pub fn lower_path(&self, path: &ast::Path) -> Option<Path> { |
251 | let src = self.find_file(path.syntax().clone()); | 262 | let src = self.find_file(path.syntax().clone()); |
252 | Path::from_src(path.clone(), &Hygiene::new(self.db.upcast(), src.file_id.into())) | 263 | Path::from_src(path.clone(), &Hygiene::new(self.db.upcast(), src.file_id.into())) |
diff --git a/crates/ra_hir/src/source_analyzer.rs b/crates/ra_hir/src/source_analyzer.rs index 7c6bbea13..757d1e397 100644 --- a/crates/ra_hir/src/source_analyzer.rs +++ b/crates/ra_hir/src/source_analyzer.rs | |||
@@ -313,6 +313,16 @@ impl SourceAnalyzer { | |||
313 | })?; | 313 | })?; |
314 | Some(macro_call_id.as_file()) | 314 | Some(macro_call_id.as_file()) |
315 | } | 315 | } |
316 | |||
317 | pub(crate) fn resolve_variant( | ||
318 | &self, | ||
319 | db: &dyn HirDatabase, | ||
320 | record_lit: ast::RecordLit, | ||
321 | ) -> Option<VariantId> { | ||
322 | let infer = self.infer.as_ref()?; | ||
323 | let expr_id = self.expr_id(db, &record_lit.into())?; | ||
324 | infer.variant_resolution_for_expr(expr_id) | ||
325 | } | ||
316 | } | 326 | } |
317 | 327 | ||
318 | fn scope_for( | 328 | fn scope_for( |
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs index 2c7298714..ebd9cb08f 100644 --- a/crates/ra_hir_ty/src/diagnostics.rs +++ b/crates/ra_hir_ty/src/diagnostics.rs | |||
@@ -6,7 +6,7 @@ use hir_expand::{db::AstDatabase, name::Name, HirFileId, InFile}; | |||
6 | use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr}; | 6 | use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr}; |
7 | use stdx::format_to; | 7 | use stdx::format_to; |
8 | 8 | ||
9 | pub use hir_def::{diagnostics::UnresolvedModule, expr::MatchArm}; | 9 | pub use hir_def::{diagnostics::UnresolvedModule, expr::MatchArm, path::Path}; |
10 | pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; | 10 | pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; |
11 | 11 | ||
12 | #[derive(Debug)] | 12 | #[derive(Debug)] |
@@ -29,6 +29,16 @@ impl Diagnostic for NoSuchField { | |||
29 | } | 29 | } |
30 | } | 30 | } |
31 | 31 | ||
32 | impl AstDiagnostic for NoSuchField { | ||
33 | type AST = ast::RecordField; | ||
34 | |||
35 | fn ast(&self, db: &impl AstDatabase) -> Self::AST { | ||
36 | let root = db.parse_or_expand(self.source().file_id).unwrap(); | ||
37 | let node = self.source().value.to_node(&root); | ||
38 | ast::RecordField::cast(node).unwrap() | ||
39 | } | ||
40 | } | ||
41 | |||
32 | #[derive(Debug)] | 42 | #[derive(Debug)] |
33 | pub struct MissingFields { | 43 | pub struct MissingFields { |
34 | pub file: HirFileId, | 44 | pub file: HirFileId, |
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index e1bfd72f9..365d52168 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -8,7 +8,7 @@ use std::cell::RefCell; | |||
8 | 8 | ||
9 | use hir::{ | 9 | use hir::{ |
10 | diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}, | 10 | diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}, |
11 | Semantics, | 11 | HasSource, HirDisplay, Semantics, VariantDef, |
12 | }; | 12 | }; |
13 | use itertools::Itertools; | 13 | use itertools::Itertools; |
14 | use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt}; | 14 | use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt}; |
@@ -16,7 +16,7 @@ use ra_ide_db::RootDatabase; | |||
16 | use ra_prof::profile; | 16 | use ra_prof::profile; |
17 | use ra_syntax::{ | 17 | use ra_syntax::{ |
18 | algo, | 18 | algo, |
19 | ast::{self, make, AstNode}, | 19 | ast::{self, edit::IndentLevel, make, AstNode}, |
20 | SyntaxNode, TextRange, T, | 20 | SyntaxNode, TextRange, T, |
21 | }; | 21 | }; |
22 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 22 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
@@ -123,7 +123,16 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
123 | severity: Severity::Error, | 123 | severity: Severity::Error, |
124 | fix: Some(fix), | 124 | fix: Some(fix), |
125 | }) | 125 | }) |
126 | }) | ||
127 | .on::<hir::diagnostics::NoSuchField, _>(|d| { | ||
128 | res.borrow_mut().push(Diagnostic { | ||
129 | range: sema.diagnostics_range(d).range, | ||
130 | message: d.message(), | ||
131 | severity: Severity::Error, | ||
132 | fix: missing_struct_field_fix(&sema, file_id, d), | ||
133 | }) | ||
126 | }); | 134 | }); |
135 | |||
127 | if let Some(m) = sema.to_module_def(file_id) { | 136 | if let Some(m) = sema.to_module_def(file_id) { |
128 | m.diagnostics(db, &mut sink); | 137 | m.diagnostics(db, &mut sink); |
129 | }; | 138 | }; |
@@ -131,6 +140,68 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
131 | res.into_inner() | 140 | res.into_inner() |
132 | } | 141 | } |
133 | 142 | ||
143 | fn missing_struct_field_fix( | ||
144 | sema: &Semantics<RootDatabase>, | ||
145 | file_id: FileId, | ||
146 | d: &hir::diagnostics::NoSuchField, | ||
147 | ) -> Option<Fix> { | ||
148 | let record_expr = sema.ast(d); | ||
149 | |||
150 | let record_lit = ast::RecordLit::cast(record_expr.syntax().parent()?.parent()?)?; | ||
151 | let def_id = sema.resolve_variant(record_lit)?; | ||
152 | let module; | ||
153 | let record_fields = match VariantDef::from(def_id) { | ||
154 | VariantDef::Struct(s) => { | ||
155 | module = s.module(sema.db); | ||
156 | let source = s.source(sema.db); | ||
157 | let fields = source.value.field_def_list()?; | ||
158 | record_field_def_list(fields)? | ||
159 | } | ||
160 | VariantDef::Union(u) => { | ||
161 | module = u.module(sema.db); | ||
162 | let source = u.source(sema.db); | ||
163 | source.value.record_field_def_list()? | ||
164 | } | ||
165 | VariantDef::EnumVariant(e) => { | ||
166 | module = e.module(sema.db); | ||
167 | let source = e.source(sema.db); | ||
168 | let fields = source.value.field_def_list()?; | ||
169 | record_field_def_list(fields)? | ||
170 | } | ||
171 | }; | ||
172 | |||
173 | let new_field_type = sema.type_of_expr(&record_expr.expr()?)?; | ||
174 | let new_field = make::record_field_def( | ||
175 | record_expr.field_name()?, | ||
176 | make::type_ref(&new_field_type.display_source_code(sema.db, module.into()).ok()?), | ||
177 | ); | ||
178 | |||
179 | let last_field = record_fields.fields().last()?; | ||
180 | let last_field_syntax = last_field.syntax(); | ||
181 | let indent = IndentLevel::from_node(last_field_syntax); | ||
182 | |||
183 | let mut new_field = format!("\n{}{}", indent, new_field); | ||
184 | |||
185 | let needs_comma = !last_field_syntax.to_string().ends_with(","); | ||
186 | if needs_comma { | ||
187 | new_field = format!(",{}", new_field); | ||
188 | } | ||
189 | |||
190 | let source_change = SourceFileEdit { | ||
191 | file_id, | ||
192 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), | ||
193 | }; | ||
194 | let fix = Fix::new("Create field", source_change.into()); | ||
195 | return Some(fix); | ||
196 | |||
197 | fn record_field_def_list(field_def_list: ast::FieldDefList) -> Option<ast::RecordFieldDefList> { | ||
198 | match field_def_list { | ||
199 | ast::FieldDefList::RecordFieldDefList(it) => Some(it), | ||
200 | ast::FieldDefList::TupleFieldDefList(_) => None, | ||
201 | } | ||
202 | } | ||
203 | } | ||
204 | |||
134 | fn check_unnecessary_braces_in_use_statement( | 205 | fn check_unnecessary_braces_in_use_statement( |
135 | acc: &mut Vec<Diagnostic>, | 206 | acc: &mut Vec<Diagnostic>, |
136 | file_id: FileId, | 207 | file_id: FileId, |
@@ -795,4 +866,27 @@ fn main() { | |||
795 | check_struct_shorthand_initialization, | 866 | check_struct_shorthand_initialization, |
796 | ); | 867 | ); |
797 | } | 868 | } |
869 | |||
870 | #[test] | ||
871 | fn test_add_field_from_usage() { | ||
872 | check_apply_diagnostic_fix( | ||
873 | r" | ||
874 | fn main() { | ||
875 | Foo { bar: 3, baz: false}; | ||
876 | } | ||
877 | struct Foo { | ||
878 | bar: i32 | ||
879 | } | ||
880 | ", | ||
881 | r" | ||
882 | fn main() { | ||
883 | Foo { bar: 3, baz: false}; | ||
884 | } | ||
885 | struct Foo { | ||
886 | bar: i32, | ||
887 | baz: bool | ||
888 | } | ||
889 | ", | ||
890 | ) | ||
891 | } | ||
798 | } | 892 | } |
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index da0eb0926..192c610f1 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs | |||
@@ -75,6 +75,10 @@ pub fn record_field(name: ast::NameRef, expr: Option<ast::Expr>) -> ast::RecordF | |||
75 | } | 75 | } |
76 | } | 76 | } |
77 | 77 | ||
78 | pub fn record_field_def(name: ast::NameRef, ty: ast::TypeRef) -> ast::RecordFieldDef { | ||
79 | ast_from_text(&format!("struct S {{ {}: {}, }}", name, ty)) | ||
80 | } | ||
81 | |||
78 | pub fn block_expr( | 82 | pub fn block_expr( |
79 | stmts: impl IntoIterator<Item = ast::Stmt>, | 83 | stmts: impl IntoIterator<Item = ast::Stmt>, |
80 | tail_expr: Option<ast::Expr>, | 84 | tail_expr: Option<ast::Expr>, |