aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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 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
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::{RelativePath, SourceDatabase, SourceDatabaseExt}; 14use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt};
@@ -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};
@@ -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
143fn 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
134fn check_unnecessary_braces_in_use_statement( 205fn 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
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>,