aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2021-06-13 17:45:16 +0100
committerAleksey Kladov <[email protected]>2021-06-13 17:45:16 +0100
commit7166e8549bad95b05f66acd508d07a6cd97d3dc0 (patch)
treecf85d27f8eda6afdf4f9a92bcf646a7e1ce77397 /crates/ide/src
parentd3621eeb02652038a8185f60d78fb4791a732dc6 (diff)
internal: refactor NoSuchField diagnostic
Diffstat (limited to 'crates/ide/src')
-rw-r--r--crates/ide/src/diagnostics.rs146
-rw-r--r--crates/ide/src/diagnostics/fixes/create_field.rs155
-rw-r--r--crates/ide/src/diagnostics/no_such_field.rs286
3 files changed, 297 insertions, 290 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 3fbd21c30..caaa89e0a 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -4,15 +4,16 @@
4//! macro-expanded files, but we need to present them to the users in terms of 4//! macro-expanded files, but we need to present them to the users in terms of
5//! original files. So we need to map the ranges. 5//! original files. So we need to map the ranges.
6 6
7mod unresolved_module; 7mod inactive_code;
8mod macro_error;
9mod missing_fields;
10mod no_such_field;
11mod unimplemented_builtin_macro;
8mod unresolved_extern_crate; 12mod unresolved_extern_crate;
9mod unresolved_import; 13mod unresolved_import;
10mod unresolved_macro_call; 14mod unresolved_macro_call;
15mod unresolved_module;
11mod unresolved_proc_macro; 16mod unresolved_proc_macro;
12mod unimplemented_builtin_macro;
13mod macro_error;
14mod inactive_code;
15mod missing_fields;
16 17
17mod fixes; 18mod fixes;
18mod field_shorthand; 19mod field_shorthand;
@@ -161,9 +162,6 @@ pub(crate) fn diagnostics(
161 .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| { 162 .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| {
162 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); 163 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
163 }) 164 })
164 .on::<hir::diagnostics::NoSuchField, _>(|d| {
165 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
166 })
167 .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| { 165 .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| {
168 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); 166 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
169 }) 167 })
@@ -220,14 +218,15 @@ pub(crate) fn diagnostics(
220 for diag in diags { 218 for diag in diags {
221 #[rustfmt::skip] 219 #[rustfmt::skip]
222 let d = match diag { 220 let d = match diag {
223 AnyDiagnostic::UnresolvedModule(d) => unresolved_module::unresolved_module(&ctx, &d), 221 AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d),
222 AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d),
223 AnyDiagnostic::NoSuchField(d) => no_such_field::no_such_field(&ctx, &d),
224 AnyDiagnostic::UnimplementedBuiltinMacro(d) => unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d),
224 AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d), 225 AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d),
225 AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d), 226 AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d),
226 AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d), 227 AnyDiagnostic::UnresolvedMacroCall(d) => unresolved_macro_call::unresolved_macro_call(&ctx, &d),
228 AnyDiagnostic::UnresolvedModule(d) => unresolved_module::unresolved_module(&ctx, &d),
227 AnyDiagnostic::UnresolvedProcMacro(d) => unresolved_proc_macro::unresolved_proc_macro(&ctx, &d), 229 AnyDiagnostic::UnresolvedProcMacro(d) => unresolved_proc_macro::unresolved_proc_macro(&ctx, &d),
228 AnyDiagnostic::UnimplementedBuiltinMacro(d) => unimplemented_builtin_macro::unimplemented_builtin_macro(&ctx, &d),
229 AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d),
230 AnyDiagnostic::MacroError(d) => macro_error::macro_error(&ctx, &d),
231 230
232 AnyDiagnostic::InactiveCode(d) => match inactive_code::inactive_code(&ctx, &d) { 231 AnyDiagnostic::InactiveCode(d) => match inactive_code::inactive_code(&ctx, &d) {
233 Some(it) => it, 232 Some(it) => it,
@@ -723,129 +722,6 @@ fn foo() { break; }
723 } 722 }
724 723
725 #[test] 724 #[test]
726 fn no_such_field_diagnostics() {
727 check_diagnostics(
728 r#"
729struct S { foo: i32, bar: () }
730impl S {
731 fn new() -> S {
732 S {
733 //^ Missing structure fields:
734 //| - bar
735 foo: 92,
736 baz: 62,
737 //^^^^^^^ no such field
738 }
739 }
740}
741"#,
742 );
743 }
744 #[test]
745 fn no_such_field_with_feature_flag_diagnostics() {
746 check_diagnostics(
747 r#"
748//- /lib.rs crate:foo cfg:feature=foo
749struct MyStruct {
750 my_val: usize,
751 #[cfg(feature = "foo")]
752 bar: bool,
753}
754
755impl MyStruct {
756 #[cfg(feature = "foo")]
757 pub(crate) fn new(my_val: usize, bar: bool) -> Self {
758 Self { my_val, bar }
759 }
760 #[cfg(not(feature = "foo"))]
761 pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
762 Self { my_val }
763 }
764}
765"#,
766 );
767 }
768
769 #[test]
770 fn no_such_field_enum_with_feature_flag_diagnostics() {
771 check_diagnostics(
772 r#"
773//- /lib.rs crate:foo cfg:feature=foo
774enum Foo {
775 #[cfg(not(feature = "foo"))]
776 Buz,
777 #[cfg(feature = "foo")]
778 Bar,
779 Baz
780}
781
782fn test_fn(f: Foo) {
783 match f {
784 Foo::Bar => {},
785 Foo::Baz => {},
786 }
787}
788"#,
789 );
790 }
791
792 #[test]
793 fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
794 check_diagnostics(
795 r#"
796//- /lib.rs crate:foo cfg:feature=foo
797struct S {
798 #[cfg(feature = "foo")]
799 foo: u32,
800 #[cfg(not(feature = "foo"))]
801 bar: u32,
802}
803
804impl S {
805 #[cfg(feature = "foo")]
806 fn new(foo: u32) -> Self {
807 Self { foo }
808 }
809 #[cfg(not(feature = "foo"))]
810 fn new(bar: u32) -> Self {
811 Self { bar }
812 }
813 fn new2(bar: u32) -> Self {
814 #[cfg(feature = "foo")]
815 { Self { foo: bar } }
816 #[cfg(not(feature = "foo"))]
817 { Self { bar } }
818 }
819 fn new2(val: u32) -> Self {
820 Self {
821 #[cfg(feature = "foo")]
822 foo: val,
823 #[cfg(not(feature = "foo"))]
824 bar: val,
825 }
826 }
827}
828"#,
829 );
830 }
831
832 #[test]
833 fn no_such_field_with_type_macro() {
834 check_diagnostics(
835 r#"
836macro_rules! Type { () => { u32 }; }
837struct Foo { bar: Type![] }
838
839impl Foo {
840 fn new() -> Self {
841 Foo { bar: 0 }
842 }
843}
844"#,
845 );
846 }
847
848 #[test]
849 fn missing_unsafe_diagnostic_with_raw_ptr() { 725 fn missing_unsafe_diagnostic_with_raw_ptr() {
850 check_diagnostics( 726 check_diagnostics(
851 r#" 727 r#"
diff --git a/crates/ide/src/diagnostics/fixes/create_field.rs b/crates/ide/src/diagnostics/fixes/create_field.rs
index f6e45967a..8b1378917 100644
--- a/crates/ide/src/diagnostics/fixes/create_field.rs
+++ b/crates/ide/src/diagnostics/fixes/create_field.rs
@@ -1,156 +1 @@
1use hir::{db::AstDatabase, diagnostics::NoSuchField, HasSource, HirDisplay, Semantics};
2use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
3use syntax::{
4 ast::{self, edit::IndentLevel, make},
5 AstNode,
6};
7use text_edit::TextEdit;
8
9use crate::{
10 diagnostics::{fix, DiagnosticWithFixes},
11 Assist, AssistResolveStrategy,
12};
13impl DiagnosticWithFixes for NoSuchField {
14 fn fixes(
15 &self,
16 sema: &Semantics<RootDatabase>,
17 _resolve: &AssistResolveStrategy,
18 ) -> Option<Vec<Assist>> {
19 let root = sema.db.parse_or_expand(self.file)?;
20 missing_record_expr_field_fixes(
21 sema,
22 self.file.original_file(sema.db),
23 &self.field.to_node(&root),
24 )
25 }
26}
27
28fn missing_record_expr_field_fixes(
29 sema: &Semantics<RootDatabase>,
30 usage_file_id: FileId,
31 record_expr_field: &ast::RecordExprField,
32) -> Option<Vec<Assist>> {
33 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
34 let def_id = sema.resolve_variant(record_lit)?;
35 let module;
36 let def_file_id;
37 let record_fields = match def_id {
38 hir::VariantDef::Struct(s) => {
39 module = s.module(sema.db);
40 let source = s.source(sema.db)?;
41 def_file_id = source.file_id;
42 let fields = source.value.field_list()?;
43 record_field_list(fields)?
44 }
45 hir::VariantDef::Union(u) => {
46 module = u.module(sema.db);
47 let source = u.source(sema.db)?;
48 def_file_id = source.file_id;
49 source.value.record_field_list()?
50 }
51 hir::VariantDef::Variant(e) => {
52 module = e.module(sema.db);
53 let source = e.source(sema.db)?;
54 def_file_id = source.file_id;
55 let fields = source.value.field_list()?;
56 record_field_list(fields)?
57 }
58 };
59 let def_file_id = def_file_id.original_file(sema.db);
60
61 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
62 if new_field_type.is_unknown() {
63 return None;
64 }
65 let new_field = make::record_field(
66 None,
67 make::name(&record_expr_field.field_name()?.text()),
68 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
69 );
70
71 let last_field = record_fields.fields().last()?;
72 let last_field_syntax = last_field.syntax();
73 let indent = IndentLevel::from_node(last_field_syntax);
74
75 let mut new_field = new_field.to_string();
76 if usage_file_id != def_file_id {
77 new_field = format!("pub(crate) {}", new_field);
78 }
79 new_field = format!("\n{}{}", indent, new_field);
80
81 let needs_comma = !last_field_syntax.to_string().ends_with(',');
82 if needs_comma {
83 new_field = format!(",{}", new_field);
84 }
85
86 let source_change = SourceChange::from_text_edit(
87 def_file_id,
88 TextEdit::insert(last_field_syntax.text_range().end(), new_field),
89 );
90
91 return Some(vec![fix(
92 "create_field",
93 "Create field",
94 source_change,
95 record_expr_field.syntax().text_range(),
96 )]);
97
98 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
99 match field_def_list {
100 ast::FieldList::RecordFieldList(it) => Some(it),
101 ast::FieldList::TupleFieldList(_) => None,
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use crate::diagnostics::tests::check_fix;
109
110 #[test]
111 fn test_add_field_from_usage() {
112 check_fix(
113 r"
114fn main() {
115 Foo { bar: 3, baz$0: false};
116}
117struct Foo {
118 bar: i32
119}
120",
121 r"
122fn main() {
123 Foo { bar: 3, baz: false};
124}
125struct Foo {
126 bar: i32,
127 baz: bool
128}
129",
130 )
131 }
132
133 #[test]
134 fn test_add_field_in_other_file_from_usage() {
135 check_fix(
136 r#"
137//- /main.rs
138mod foo;
139
140fn main() {
141 foo::Foo { bar: 3, $0baz: false};
142}
143//- /foo.rs
144struct Foo {
145 bar: i32
146}
147"#,
148 r#"
149struct Foo {
150 bar: i32,
151 pub(crate) baz: bool
152}
153"#,
154 )
155 }
156}
diff --git a/crates/ide/src/diagnostics/no_such_field.rs b/crates/ide/src/diagnostics/no_such_field.rs
new file mode 100644
index 000000000..61962de28
--- /dev/null
+++ b/crates/ide/src/diagnostics/no_such_field.rs
@@ -0,0 +1,286 @@
1use hir::{db::AstDatabase, HasSource, HirDisplay, Semantics};
2use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
3use syntax::{
4 ast::{self, edit::IndentLevel, make},
5 AstNode,
6};
7use text_edit::TextEdit;
8
9use crate::{
10 diagnostics::{fix, Diagnostic, DiagnosticsContext},
11 Assist,
12};
13
14// Diagnostic: no-such-field
15//
16// This diagnostic is triggered if created structure does not have field provided in record.
17pub(super) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic {
18 Diagnostic::new(
19 "no-such-field",
20 "no such field".to_string(),
21 ctx.sema.diagnostics_display_range(d.field.clone().map(|it| it.into())).range,
22 )
23 .with_fixes(fixes(ctx, d))
24}
25
26fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
27 let root = ctx.sema.db.parse_or_expand(d.field.file_id)?;
28 missing_record_expr_field_fixes(
29 &ctx.sema,
30 d.field.file_id.original_file(ctx.sema.db),
31 &d.field.value.to_node(&root),
32 )
33}
34
35fn missing_record_expr_field_fixes(
36 sema: &Semantics<RootDatabase>,
37 usage_file_id: FileId,
38 record_expr_field: &ast::RecordExprField,
39) -> Option<Vec<Assist>> {
40 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
41 let def_id = sema.resolve_variant(record_lit)?;
42 let module;
43 let def_file_id;
44 let record_fields = match def_id {
45 hir::VariantDef::Struct(s) => {
46 module = s.module(sema.db);
47 let source = s.source(sema.db)?;
48 def_file_id = source.file_id;
49 let fields = source.value.field_list()?;
50 record_field_list(fields)?
51 }
52 hir::VariantDef::Union(u) => {
53 module = u.module(sema.db);
54 let source = u.source(sema.db)?;
55 def_file_id = source.file_id;
56 source.value.record_field_list()?
57 }
58 hir::VariantDef::Variant(e) => {
59 module = e.module(sema.db);
60 let source = e.source(sema.db)?;
61 def_file_id = source.file_id;
62 let fields = source.value.field_list()?;
63 record_field_list(fields)?
64 }
65 };
66 let def_file_id = def_file_id.original_file(sema.db);
67
68 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
69 if new_field_type.is_unknown() {
70 return None;
71 }
72 let new_field = make::record_field(
73 None,
74 make::name(&record_expr_field.field_name()?.text()),
75 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
76 );
77
78 let last_field = record_fields.fields().last()?;
79 let last_field_syntax = last_field.syntax();
80 let indent = IndentLevel::from_node(last_field_syntax);
81
82 let mut new_field = new_field.to_string();
83 if usage_file_id != def_file_id {
84 new_field = format!("pub(crate) {}", new_field);
85 }
86 new_field = format!("\n{}{}", indent, new_field);
87
88 let needs_comma = !last_field_syntax.to_string().ends_with(',');
89 if needs_comma {
90 new_field = format!(",{}", new_field);
91 }
92
93 let source_change = SourceChange::from_text_edit(
94 def_file_id,
95 TextEdit::insert(last_field_syntax.text_range().end(), new_field),
96 );
97
98 return Some(vec![fix(
99 "create_field",
100 "Create field",
101 source_change,
102 record_expr_field.syntax().text_range(),
103 )]);
104
105 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
106 match field_def_list {
107 ast::FieldList::RecordFieldList(it) => Some(it),
108 ast::FieldList::TupleFieldList(_) => None,
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use crate::diagnostics::tests::{check_diagnostics, check_fix};
116
117 #[test]
118 fn no_such_field_diagnostics() {
119 check_diagnostics(
120 r#"
121struct S { foo: i32, bar: () }
122impl S {
123 fn new() -> S {
124 S {
125 //^ Missing structure fields:
126 //| - bar
127 foo: 92,
128 baz: 62,
129 //^^^^^^^ no such field
130 }
131 }
132}
133"#,
134 );
135 }
136 #[test]
137 fn no_such_field_with_feature_flag_diagnostics() {
138 check_diagnostics(
139 r#"
140//- /lib.rs crate:foo cfg:feature=foo
141struct MyStruct {
142 my_val: usize,
143 #[cfg(feature = "foo")]
144 bar: bool,
145}
146
147impl MyStruct {
148 #[cfg(feature = "foo")]
149 pub(crate) fn new(my_val: usize, bar: bool) -> Self {
150 Self { my_val, bar }
151 }
152 #[cfg(not(feature = "foo"))]
153 pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
154 Self { my_val }
155 }
156}
157"#,
158 );
159 }
160
161 #[test]
162 fn no_such_field_enum_with_feature_flag_diagnostics() {
163 check_diagnostics(
164 r#"
165//- /lib.rs crate:foo cfg:feature=foo
166enum Foo {
167 #[cfg(not(feature = "foo"))]
168 Buz,
169 #[cfg(feature = "foo")]
170 Bar,
171 Baz
172}
173
174fn test_fn(f: Foo) {
175 match f {
176 Foo::Bar => {},
177 Foo::Baz => {},
178 }
179}
180"#,
181 );
182 }
183
184 #[test]
185 fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
186 check_diagnostics(
187 r#"
188//- /lib.rs crate:foo cfg:feature=foo
189struct S {
190 #[cfg(feature = "foo")]
191 foo: u32,
192 #[cfg(not(feature = "foo"))]
193 bar: u32,
194}
195
196impl S {
197 #[cfg(feature = "foo")]
198 fn new(foo: u32) -> Self {
199 Self { foo }
200 }
201 #[cfg(not(feature = "foo"))]
202 fn new(bar: u32) -> Self {
203 Self { bar }
204 }
205 fn new2(bar: u32) -> Self {
206 #[cfg(feature = "foo")]
207 { Self { foo: bar } }
208 #[cfg(not(feature = "foo"))]
209 { Self { bar } }
210 }
211 fn new2(val: u32) -> Self {
212 Self {
213 #[cfg(feature = "foo")]
214 foo: val,
215 #[cfg(not(feature = "foo"))]
216 bar: val,
217 }
218 }
219}
220"#,
221 );
222 }
223
224 #[test]
225 fn no_such_field_with_type_macro() {
226 check_diagnostics(
227 r#"
228macro_rules! Type { () => { u32 }; }
229struct Foo { bar: Type![] }
230
231impl Foo {
232 fn new() -> Self {
233 Foo { bar: 0 }
234 }
235}
236"#,
237 );
238 }
239
240 #[test]
241 fn test_add_field_from_usage() {
242 check_fix(
243 r"
244fn main() {
245 Foo { bar: 3, baz$0: false};
246}
247struct Foo {
248 bar: i32
249}
250",
251 r"
252fn main() {
253 Foo { bar: 3, baz: false};
254}
255struct Foo {
256 bar: i32,
257 baz: bool
258}
259",
260 )
261 }
262
263 #[test]
264 fn test_add_field_in_other_file_from_usage() {
265 check_fix(
266 r#"
267//- /main.rs
268mod foo;
269
270fn main() {
271 foo::Foo { bar: 3, $0baz: false};
272}
273//- /foo.rs
274struct Foo {
275 bar: i32
276}
277"#,
278 r#"
279struct Foo {
280 bar: i32,
281 pub(crate) baz: bool
282}
283"#,
284 )
285 }
286}