diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-07-11 10:51:31 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-07-11 10:51:31 +0100 |
commit | 6c1546c3a495785cf293dbbfb7f65ea124602528 (patch) | |
tree | dfad676d6188fcd77e7f7e2c913c74368178557b | |
parent | 0366a85052904c5c6efa3b3ecddc35dce85337c4 (diff) | |
parent | 43079ba80d79a75851f64ffc68d60711191f8307 (diff) |
Merge #4996
4996: Correctly generate new struct field in file containing struct def r=matklad a=TimoFreiberg
WIP because the test doesn't pass.
Testing the fix by hand looked good, although quickfixes seem to not support setting the editor cursor yet, which i think we want for "generate missing defs from usage" fixes.
Co-authored-by: Timo Freiberg <[email protected]>
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 58 |
1 files changed, 55 insertions, 3 deletions
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 71fd45e5d..3afe5381a 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -131,7 +131,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
131 | 131 | ||
132 | fn missing_struct_field_fix( | 132 | fn missing_struct_field_fix( |
133 | sema: &Semantics<RootDatabase>, | 133 | sema: &Semantics<RootDatabase>, |
134 | file_id: FileId, | 134 | usage_file_id: FileId, |
135 | d: &hir::diagnostics::NoSuchField, | 135 | d: &hir::diagnostics::NoSuchField, |
136 | ) -> Option<Fix> { | 136 | ) -> Option<Fix> { |
137 | let record_expr = sema.ast(d); | 137 | let record_expr = sema.ast(d); |
@@ -139,25 +139,30 @@ fn missing_struct_field_fix( | |||
139 | let record_lit = ast::RecordLit::cast(record_expr.syntax().parent()?.parent()?)?; | 139 | let record_lit = ast::RecordLit::cast(record_expr.syntax().parent()?.parent()?)?; |
140 | let def_id = sema.resolve_variant(record_lit)?; | 140 | let def_id = sema.resolve_variant(record_lit)?; |
141 | let module; | 141 | let module; |
142 | let def_file_id; | ||
142 | let record_fields = match VariantDef::from(def_id) { | 143 | let record_fields = match VariantDef::from(def_id) { |
143 | VariantDef::Struct(s) => { | 144 | VariantDef::Struct(s) => { |
144 | module = s.module(sema.db); | 145 | module = s.module(sema.db); |
145 | let source = s.source(sema.db); | 146 | let source = s.source(sema.db); |
147 | def_file_id = source.file_id; | ||
146 | let fields = source.value.field_def_list()?; | 148 | let fields = source.value.field_def_list()?; |
147 | record_field_def_list(fields)? | 149 | record_field_def_list(fields)? |
148 | } | 150 | } |
149 | VariantDef::Union(u) => { | 151 | VariantDef::Union(u) => { |
150 | module = u.module(sema.db); | 152 | module = u.module(sema.db); |
151 | let source = u.source(sema.db); | 153 | let source = u.source(sema.db); |
154 | def_file_id = source.file_id; | ||
152 | source.value.record_field_def_list()? | 155 | source.value.record_field_def_list()? |
153 | } | 156 | } |
154 | VariantDef::EnumVariant(e) => { | 157 | VariantDef::EnumVariant(e) => { |
155 | module = e.module(sema.db); | 158 | module = e.module(sema.db); |
156 | let source = e.source(sema.db); | 159 | let source = e.source(sema.db); |
160 | def_file_id = source.file_id; | ||
157 | let fields = source.value.field_def_list()?; | 161 | let fields = source.value.field_def_list()?; |
158 | record_field_def_list(fields)? | 162 | record_field_def_list(fields)? |
159 | } | 163 | } |
160 | }; | 164 | }; |
165 | let def_file_id = def_file_id.original_file(sema.db); | ||
161 | 166 | ||
162 | let new_field_type = sema.type_of_expr(&record_expr.expr()?)?; | 167 | let new_field_type = sema.type_of_expr(&record_expr.expr()?)?; |
163 | if new_field_type.is_unknown() { | 168 | if new_field_type.is_unknown() { |
@@ -172,7 +177,11 @@ fn missing_struct_field_fix( | |||
172 | let last_field_syntax = last_field.syntax(); | 177 | let last_field_syntax = last_field.syntax(); |
173 | let indent = IndentLevel::from_node(last_field_syntax); | 178 | let indent = IndentLevel::from_node(last_field_syntax); |
174 | 179 | ||
175 | let mut new_field = format!("\n{}{}", indent, new_field); | 180 | let mut new_field = new_field.to_string(); |
181 | if usage_file_id != def_file_id { | ||
182 | new_field = format!("pub(crate) {}", new_field); | ||
183 | } | ||
184 | new_field = format!("\n{}{}", indent, new_field); | ||
176 | 185 | ||
177 | let needs_comma = !last_field_syntax.to_string().ends_with(","); | 186 | let needs_comma = !last_field_syntax.to_string().ends_with(","); |
178 | if needs_comma { | 187 | if needs_comma { |
@@ -180,7 +189,7 @@ fn missing_struct_field_fix( | |||
180 | } | 189 | } |
181 | 190 | ||
182 | let source_change = SourceFileEdit { | 191 | let source_change = SourceFileEdit { |
183 | file_id, | 192 | file_id: def_file_id, |
184 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), | 193 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), |
185 | }; | 194 | }; |
186 | let fix = Fix::new("Create field", source_change.into()); | 195 | let fix = Fix::new("Create field", source_change.into()); |
@@ -309,6 +318,25 @@ mod tests { | |||
309 | ); | 318 | ); |
310 | } | 319 | } |
311 | 320 | ||
321 | /// Checks that a diagnostic applies to the file containing the `<|>` cursor marker | ||
322 | /// which has a fix that can apply to other files. | ||
323 | fn check_apply_diagnostic_fix_in_other_file(ra_fixture_before: &str, ra_fixture_after: &str) { | ||
324 | let ra_fixture_after = &trim_indent(ra_fixture_after); | ||
325 | let (analysis, file_pos) = analysis_and_position(ra_fixture_before); | ||
326 | let current_file_id = file_pos.file_id; | ||
327 | let diagnostic = analysis.diagnostics(current_file_id).unwrap().pop().unwrap(); | ||
328 | let mut fix = diagnostic.fix.unwrap(); | ||
329 | let edit = fix.source_change.source_file_edits.pop().unwrap(); | ||
330 | let changed_file_id = edit.file_id; | ||
331 | let before = analysis.file_text(changed_file_id).unwrap(); | ||
332 | let actual = { | ||
333 | let mut actual = before.to_string(); | ||
334 | edit.edit.apply(&mut actual); | ||
335 | actual | ||
336 | }; | ||
337 | assert_eq_text!(ra_fixture_after, &actual); | ||
338 | } | ||
339 | |||
312 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics | 340 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics |
313 | /// apply to the file containing the cursor. | 341 | /// apply to the file containing the cursor. |
314 | fn check_no_diagnostics(ra_fixture: &str) { | 342 | fn check_no_diagnostics(ra_fixture: &str) { |
@@ -732,4 +760,28 @@ struct Foo { | |||
732 | ", | 760 | ", |
733 | ) | 761 | ) |
734 | } | 762 | } |
763 | |||
764 | #[test] | ||
765 | fn test_add_field_in_other_file_from_usage() { | ||
766 | check_apply_diagnostic_fix_in_other_file( | ||
767 | r" | ||
768 | //- /main.rs | ||
769 | mod foo; | ||
770 | |||
771 | fn main() { | ||
772 | <|>foo::Foo { bar: 3, baz: false}; | ||
773 | } | ||
774 | //- /foo.rs | ||
775 | struct Foo { | ||
776 | bar: i32 | ||
777 | } | ||
778 | ", | ||
779 | r" | ||
780 | struct Foo { | ||
781 | bar: i32, | ||
782 | pub(crate) baz: bool | ||
783 | } | ||
784 | ", | ||
785 | ) | ||
786 | } | ||
735 | } | 787 | } |