aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-06-19 16:08:52 +0100
committerGitHub <[email protected]>2020-06-19 16:08:52 +0100
commit90a5c4626a4c46f7d5973e79dc68ab734d3b9349 (patch)
tree8334808a94de667dfdf19f1b3545831f8a45895a
parentec6df5d3e866c215f7e699366ba55a7112ab4149 (diff)
parentf5ac3130001ab7fe64dd542fd866a18012ee9971 (diff)
Merge #4851
4851: Add quickfix to add a struct field r=TimoFreiberg a=TimoFreiberg Related to #4563 I created a quickfix for record literals first because the NoSuchField diagnostic was already there. To offer that quickfix for FieldExprs with unknown fields I'd need to add a new diagnostic (or create a `NoSuchField` diagnostic for those cases) I think it'd make sense to make this a snippet completion (to select the generated type), but this would require changing the `Analysis` API and I'd like some feedback before I touch that. Co-authored-by: Timo Freiberg <[email protected]>
-rw-r--r--crates/ra_hir/src/semantics.rs15
-rw-r--r--crates/ra_hir/src/source_analyzer.rs10
-rw-r--r--crates/ra_hir_ty/src/diagnostics.rs12
-rw-r--r--crates/ra_ide/src/diagnostics.rs98
-rw-r--r--crates/ra_syntax/src/ast/make.rs4
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
7use hir_def::{ 7use hir_def::{
8 resolver::{self, HasResolver, Resolver}, 8 resolver::{self, HasResolver, Resolver},
9 AsMacroCall, TraitId, 9 AsMacroCall, TraitId, VariantId,
10}; 10};
11use hir_expand::{hygiene::Hygiene, ExpansionInfo}; 11use hir_expand::{diagnostics::AstDiagnostic, hygiene::Hygiene, 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};
@@ -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
318fn scope_for( 328fn 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};
6use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr}; 6use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr};
7use stdx::format_to; 7use stdx::format_to;
8 8
9pub use hir_def::{diagnostics::UnresolvedModule, expr::MatchArm}; 9pub use hir_def::{diagnostics::UnresolvedModule, expr::MatchArm, path::Path};
10pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; 10pub 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
32impl 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)]
33pub struct MissingFields { 43pub 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 fd9abb55b..a88a978d7 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
9use hir::{ 9use hir::{
10 diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}, 10 diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink},
11 Semantics, 11 HasSource, HirDisplay, Semantics, VariantDef,
12}; 12};
13use itertools::Itertools; 13use itertools::Itertools;
14use ra_db::SourceDatabase; 14use ra_db::SourceDatabase;
@@ -16,7 +16,7 @@ use ra_ide_db::RootDatabase;
16use ra_prof::profile; 16use ra_prof::profile;
17use ra_syntax::{ 17use 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};
22use ra_text_edit::{TextEdit, TextEditBuilder}; 22use ra_text_edit::{TextEdit, TextEditBuilder};
@@ -119,7 +119,16 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
119 severity: Severity::Error, 119 severity: Severity::Error,
120 fix: Some(fix), 120 fix: Some(fix),
121 }) 121 })
122 })
123 .on::<hir::diagnostics::NoSuchField, _>(|d| {
124 res.borrow_mut().push(Diagnostic {
125 range: sema.diagnostics_range(d).range,
126 message: d.message(),
127 severity: Severity::Error,
128 fix: missing_struct_field_fix(&sema, file_id, d),
129 })
122 }); 130 });
131
123 if let Some(m) = sema.to_module_def(file_id) { 132 if let Some(m) = sema.to_module_def(file_id) {
124 m.diagnostics(db, &mut sink); 133 m.diagnostics(db, &mut sink);
125 }; 134 };
@@ -127,6 +136,68 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
127 res.into_inner() 136 res.into_inner()
128} 137}
129 138
139fn missing_struct_field_fix(
140 sema: &Semantics<RootDatabase>,
141 file_id: FileId,
142 d: &hir::diagnostics::NoSuchField,
143) -> Option<Fix> {
144 let record_expr = sema.ast(d);
145
146 let record_lit = ast::RecordLit::cast(record_expr.syntax().parent()?.parent()?)?;
147 let def_id = sema.resolve_variant(record_lit)?;
148 let module;
149 let record_fields = match VariantDef::from(def_id) {
150 VariantDef::Struct(s) => {
151 module = s.module(sema.db);
152 let source = s.source(sema.db);
153 let fields = source.value.field_def_list()?;
154 record_field_def_list(fields)?
155 }
156 VariantDef::Union(u) => {
157 module = u.module(sema.db);
158 let source = u.source(sema.db);
159 source.value.record_field_def_list()?
160 }
161 VariantDef::EnumVariant(e) => {
162 module = e.module(sema.db);
163 let source = e.source(sema.db);
164 let fields = source.value.field_def_list()?;
165 record_field_def_list(fields)?
166 }
167 };
168
169 let new_field_type = sema.type_of_expr(&record_expr.expr()?)?;
170 let new_field = make::record_field_def(
171 record_expr.field_name()?,
172 make::type_ref(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
173 );
174
175 let last_field = record_fields.fields().last()?;
176 let last_field_syntax = last_field.syntax();
177 let indent = IndentLevel::from_node(last_field_syntax);
178
179 let mut new_field = format!("\n{}{}", indent, new_field);
180
181 let needs_comma = !last_field_syntax.to_string().ends_with(",");
182 if needs_comma {
183 new_field = format!(",{}", new_field);
184 }
185
186 let source_change = SourceFileEdit {
187 file_id,
188 edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field),
189 };
190 let fix = Fix::new("Create field", source_change.into());
191 return Some(fix);
192
193 fn record_field_def_list(field_def_list: ast::FieldDefList) -> Option<ast::RecordFieldDefList> {
194 match field_def_list {
195 ast::FieldDefList::RecordFieldDefList(it) => Some(it),
196 ast::FieldDefList::TupleFieldDefList(_) => None,
197 }
198 }
199}
200
130fn check_unnecessary_braces_in_use_statement( 201fn check_unnecessary_braces_in_use_statement(
131 acc: &mut Vec<Diagnostic>, 202 acc: &mut Vec<Diagnostic>,
132 file_id: FileId, 203 file_id: FileId,
@@ -791,4 +862,27 @@ fn main() {
791 check_struct_shorthand_initialization, 862 check_struct_shorthand_initialization,
792 ); 863 );
793 } 864 }
865
866 #[test]
867 fn test_add_field_from_usage() {
868 check_apply_diagnostic_fix(
869 r"
870 fn main() {
871 Foo { bar: 3, baz: false};
872 }
873 struct Foo {
874 bar: i32
875 }
876 ",
877 r"
878 fn main() {
879 Foo { bar: 3, baz: false};
880 }
881 struct Foo {
882 bar: i32,
883 baz: bool
884 }
885 ",
886 )
887 }
794} 888}
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
78pub fn record_field_def(name: ast::NameRef, ty: ast::TypeRef) -> ast::RecordFieldDef {
79 ast_from_text(&format!("struct S {{ {}: {}, }}", name, ty))
80}
81
78pub fn block_expr( 82pub 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>,