aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-06-13 13:49:37 +0100
committerGitHub <[email protected]>2021-06-13 13:49:37 +0100
commitcc6d761a99ab3b7e28ed13ca3839358f3341da4d (patch)
treed9d180d516ddbafc7eb950f401e9a8ab3f1e88fe
parent3f53a5dd724cbc7aa20280cddba44c7d2c0c8a6d (diff)
parent6383252cc2770545505d40217732f14e93a396c4 (diff)
Merge #9246
9246: internal: unified missing fields diagnostic r=matklad a=matklad bors r+ 🤖 Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r--crates/hir/src/diagnostics.rs95
-rw-r--r--crates/hir/src/lib.rs130
-rw-r--r--crates/hir_ty/src/diagnostics/expr.rs18
-rw-r--r--crates/ide/src/diagnostics.rs21
-rw-r--r--crates/ide/src/diagnostics/fixes.rs1
-rw-r--r--crates/ide/src/diagnostics/fixes/fill_missing_fields.rs217
-rw-r--r--crates/ide/src/diagnostics/missing_fields.rs256
-rw-r--r--crates/ide/src/diagnostics/unresolved_module.rs5
8 files changed, 342 insertions, 401 deletions
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index 5e2f94698..158626dc0 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -6,6 +6,7 @@
6use std::any::Any; 6use std::any::Any;
7 7
8use cfg::{CfgExpr, CfgOptions, DnfExpr}; 8use cfg::{CfgExpr, CfgOptions, DnfExpr};
9use either::Either;
9use hir_def::path::ModPath; 10use hir_def::path::ModPath;
10use hir_expand::{name::Name, HirFileId, InFile}; 11use hir_expand::{name::Name, HirFileId, InFile};
11use stdx::format_to; 12use stdx::format_to;
@@ -16,7 +17,7 @@ pub use crate::diagnostics_sink::{
16}; 17};
17 18
18macro_rules! diagnostics { 19macro_rules! diagnostics {
19 ($($diag:ident)*) => { 20 ($($diag:ident),*) => {
20 pub enum AnyDiagnostic {$( 21 pub enum AnyDiagnostic {$(
21 $diag(Box<$diag>), 22 $diag(Box<$diag>),
22 )*} 23 )*}
@@ -31,7 +32,7 @@ macro_rules! diagnostics {
31 }; 32 };
32} 33}
33 34
34diagnostics![UnresolvedModule]; 35diagnostics![UnresolvedModule, MissingFields];
35 36
36#[derive(Debug)] 37#[derive(Debug)]
37pub struct UnresolvedModule { 38pub struct UnresolvedModule {
@@ -321,102 +322,14 @@ impl Diagnostic for MissingUnsafe {
321 } 322 }
322} 323}
323 324
324// Diagnostic: missing-structure-fields
325//
326// This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
327//
328// Example:
329//
330// ```rust
331// struct A { a: u8, b: u8 }
332//
333// let a = A { a: 10 };
334// ```
335#[derive(Debug)] 325#[derive(Debug)]
336pub struct MissingFields { 326pub struct MissingFields {
337 pub file: HirFileId, 327 pub file: HirFileId,
338 pub field_list_parent: AstPtr<ast::RecordExpr>, 328 pub field_list_parent: Either<AstPtr<ast::RecordExpr>, AstPtr<ast::RecordPat>>,
339 pub field_list_parent_path: Option<AstPtr<ast::Path>>,
340 pub missed_fields: Vec<Name>,
341}
342
343impl Diagnostic for MissingFields {
344 fn code(&self) -> DiagnosticCode {
345 DiagnosticCode("missing-structure-fields")
346 }
347 fn message(&self) -> String {
348 let mut buf = String::from("Missing structure fields:\n");
349 for field in &self.missed_fields {
350 format_to!(buf, "- {}\n", field);
351 }
352 buf
353 }
354
355 fn display_source(&self) -> InFile<SyntaxNodePtr> {
356 InFile {
357 file_id: self.file,
358 value: self
359 .field_list_parent_path
360 .clone()
361 .map(SyntaxNodePtr::from)
362 .unwrap_or_else(|| self.field_list_parent.clone().into()),
363 }
364 }
365
366 fn as_any(&self) -> &(dyn Any + Send + 'static) {
367 self
368 }
369}
370
371// Diagnostic: missing-pat-fields
372//
373// This diagnostic is triggered if pattern lacks some fields that exist in the corresponding structure.
374//
375// Example:
376//
377// ```rust
378// struct A { a: u8, b: u8 }
379//
380// let a = A { a: 10, b: 20 };
381//
382// if let A { a } = a {
383// // ...
384// }
385// ```
386#[derive(Debug)]
387pub struct MissingPatFields {
388 pub file: HirFileId,
389 pub field_list_parent: AstPtr<ast::RecordPat>,
390 pub field_list_parent_path: Option<AstPtr<ast::Path>>, 329 pub field_list_parent_path: Option<AstPtr<ast::Path>>,
391 pub missed_fields: Vec<Name>, 330 pub missed_fields: Vec<Name>,
392} 331}
393 332
394impl Diagnostic for MissingPatFields {
395 fn code(&self) -> DiagnosticCode {
396 DiagnosticCode("missing-pat-fields")
397 }
398 fn message(&self) -> String {
399 let mut buf = String::from("Missing structure fields:\n");
400 for field in &self.missed_fields {
401 format_to!(buf, "- {}\n", field);
402 }
403 buf
404 }
405 fn display_source(&self) -> InFile<SyntaxNodePtr> {
406 InFile {
407 file_id: self.file,
408 value: self
409 .field_list_parent_path
410 .clone()
411 .map(SyntaxNodePtr::from)
412 .unwrap_or_else(|| self.field_list_parent.clone().into()),
413 }
414 }
415 fn as_any(&self) -> &(dyn Any + Send + 'static) {
416 self
417 }
418}
419
420// Diagnostic: replace-filter-map-next-with-find-map 333// Diagnostic: replace-filter-map-next-with-find-map
421// 334//
422// This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`. 335// This diagnostic is triggered when `.filter_map(..).next()` is used, rather than the more concise `.find_map(..)`.
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index ff6c68dbc..373134f62 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -88,9 +88,9 @@ pub use crate::{
88 diagnostics::{ 88 diagnostics::{
89 AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, InternalBailedOut, MacroError, 89 AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, InternalBailedOut, MacroError,
90 MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr, 90 MismatchedArgCount, MissingFields, MissingMatchArms, MissingOkOrSomeInTailExpr,
91 MissingPatFields, MissingUnsafe, NoSuchField, RemoveThisSemicolon, 91 MissingUnsafe, NoSuchField, RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap,
92 ReplaceFilterMapNextWithFindMap, UnimplementedBuiltinMacro, UnresolvedExternCrate, 92 UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
93 UnresolvedImport, UnresolvedMacroCall, UnresolvedModule, UnresolvedProcMacro, 93 UnresolvedModule, UnresolvedProcMacro,
94 }, 94 },
95 has_source::HasSource, 95 has_source::HasSource,
96 semantics::{PathResolution, Semantics, SemanticsScope}, 96 semantics::{PathResolution, Semantics, SemanticsScope},
@@ -609,23 +609,21 @@ impl Module {
609 } 609 }
610 for decl in self.declarations(db) { 610 for decl in self.declarations(db) {
611 match decl { 611 match decl {
612 crate::ModuleDef::Function(f) => f.diagnostics(db, sink, internal_diagnostics), 612 ModuleDef::Function(f) => acc.extend(f.diagnostics(db, sink, internal_diagnostics)),
613 crate::ModuleDef::Module(m) => { 613 ModuleDef::Module(m) => {
614 // Only add diagnostics from inline modules 614 // Only add diagnostics from inline modules
615 if def_map[m.id.local_id].origin.is_inline() { 615 if def_map[m.id.local_id].origin.is_inline() {
616 acc.extend(m.diagnostics(db, sink, internal_diagnostics)) 616 acc.extend(m.diagnostics(db, sink, internal_diagnostics))
617 } 617 }
618 } 618 }
619 _ => { 619 _ => decl.diagnostics(db, sink),
620 decl.diagnostics(db, sink);
621 }
622 } 620 }
623 } 621 }
624 622
625 for impl_def in self.impl_defs(db) { 623 for impl_def in self.impl_defs(db) {
626 for item in impl_def.items(db) { 624 for item in impl_def.items(db) {
627 if let AssocItem::Function(f) = item { 625 if let AssocItem::Function(f) = item {
628 f.diagnostics(db, sink, internal_diagnostics); 626 acc.extend(f.diagnostics(db, sink, internal_diagnostics));
629 } 627 }
630 } 628 }
631 } 629 }
@@ -1033,7 +1031,8 @@ impl Function {
1033 db: &dyn HirDatabase, 1031 db: &dyn HirDatabase,
1034 sink: &mut DiagnosticSink, 1032 sink: &mut DiagnosticSink,
1035 internal_diagnostics: bool, 1033 internal_diagnostics: bool,
1036 ) { 1034 ) -> Vec<AnyDiagnostic> {
1035 let mut acc: Vec<AnyDiagnostic> = Vec::new();
1037 let krate = self.module(db).id.krate(); 1036 let krate = self.module(db).id.krate();
1038 1037
1039 let source_map = db.body_with_source_map(self.id.into()).1; 1038 let source_map = db.body_with_source_map(self.id.into()).1;
@@ -1099,64 +1098,70 @@ impl Function {
1099 BodyValidationDiagnostic::collect(db, self.id.into(), internal_diagnostics) 1098 BodyValidationDiagnostic::collect(db, self.id.into(), internal_diagnostics)
1100 { 1099 {
1101 match diagnostic { 1100 match diagnostic {
1102 BodyValidationDiagnostic::RecordLiteralMissingFields { 1101 BodyValidationDiagnostic::RecordMissingFields {
1103 record_expr, 1102 record,
1104 variant, 1103 variant,
1105 missed_fields, 1104 missed_fields,
1106 } => match source_map.expr_syntax(record_expr) { 1105 } => {
1107 Ok(source_ptr) => { 1106 let variant_data = variant.variant_data(db.upcast());
1108 let root = source_ptr.file_syntax(db.upcast()); 1107 let missed_fields = missed_fields
1109 if let ast::Expr::RecordExpr(record_expr) = &source_ptr.value.to_node(&root) 1108 .into_iter()
1110 { 1109 .map(|idx| variant_data.fields()[idx].name.clone())
1111 if let Some(_) = record_expr.record_expr_field_list() { 1110 .collect();
1112 let variant_data = variant.variant_data(db.upcast()); 1111
1113 let missed_fields = missed_fields 1112 match record {
1114 .into_iter() 1113 Either::Left(record_expr) => match source_map.expr_syntax(record_expr) {
1115 .map(|idx| variant_data.fields()[idx].name.clone()) 1114 Ok(source_ptr) => {
1116 .collect(); 1115 let root = source_ptr.file_syntax(db.upcast());
1117 sink.push(MissingFields { 1116 if let ast::Expr::RecordExpr(record_expr) =
1118 file: source_ptr.file_id, 1117 &source_ptr.value.to_node(&root)
1119 field_list_parent: AstPtr::new(record_expr), 1118 {
1120 field_list_parent_path: record_expr 1119 if let Some(_) = record_expr.record_expr_field_list() {
1121 .path() 1120 acc.push(
1122 .map(|path| AstPtr::new(&path)), 1121 MissingFields {
1123 missed_fields, 1122 file: source_ptr.file_id,
1124 }) 1123 field_list_parent: Either::Left(AstPtr::new(
1124 record_expr,
1125 )),
1126 field_list_parent_path: record_expr
1127 .path()
1128 .map(|path| AstPtr::new(&path)),
1129 missed_fields,
1130 }
1131 .into(),
1132 )
1133 }
1134 }
1125 } 1135 }
1126 } 1136 Err(SyntheticSyntax) => (),
1127 } 1137 },
1128 Err(SyntheticSyntax) => (), 1138 Either::Right(record_pat) => match source_map.pat_syntax(record_pat) {
1129 }, 1139 Ok(source_ptr) => {
1130 BodyValidationDiagnostic::RecordPatMissingFields { 1140 if let Some(expr) = source_ptr.value.as_ref().left() {
1131 record_pat, 1141 let root = source_ptr.file_syntax(db.upcast());
1132 variant, 1142 if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) {
1133 missed_fields, 1143 if let Some(_) = record_pat.record_pat_field_list() {
1134 } => match source_map.pat_syntax(record_pat) { 1144 acc.push(
1135 Ok(source_ptr) => { 1145 MissingFields {
1136 if let Some(expr) = source_ptr.value.as_ref().left() { 1146 file: source_ptr.file_id,
1137 let root = source_ptr.file_syntax(db.upcast()); 1147 field_list_parent: Either::Right(AstPtr::new(
1138 if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) { 1148 &record_pat,
1139 if let Some(_) = record_pat.record_pat_field_list() { 1149 )),
1140 let variant_data = variant.variant_data(db.upcast()); 1150 field_list_parent_path: record_pat
1141 let missed_fields = missed_fields 1151 .path()
1142 .into_iter() 1152 .map(|path| AstPtr::new(&path)),
1143 .map(|idx| variant_data.fields()[idx].name.clone()) 1153 missed_fields,
1144 .collect(); 1154 }
1145 sink.push(MissingPatFields { 1155 .into(),
1146 file: source_ptr.file_id, 1156 )
1147 field_list_parent: AstPtr::new(&record_pat), 1157 }
1148 field_list_parent_path: record_pat 1158 }
1149 .path()
1150 .map(|path| AstPtr::new(&path)),
1151 missed_fields,
1152 })
1153 } 1159 }
1154 } 1160 }
1155 } 1161 Err(SyntheticSyntax) => (),
1162 },
1156 } 1163 }
1157 Err(SyntheticSyntax) => (), 1164 }
1158 },
1159
1160 BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap { method_call_expr } => { 1165 BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap { method_call_expr } => {
1161 if let Ok(next_source_ptr) = source_map.expr_syntax(method_call_expr) { 1166 if let Ok(next_source_ptr) = source_map.expr_syntax(method_call_expr) {
1162 sink.push(ReplaceFilterMapNextWithFindMap { 1167 sink.push(ReplaceFilterMapNextWithFindMap {
@@ -1234,6 +1239,7 @@ impl Function {
1234 for diag in hir_ty::diagnostics::validate_module_item(db, krate, self.id.into()) { 1239 for diag in hir_ty::diagnostics::validate_module_item(db, krate, self.id.into()) {
1235 sink.push(diag) 1240 sink.push(diag)
1236 } 1241 }
1242 acc
1237 } 1243 }
1238 1244
1239 /// Whether this function declaration has a definition. 1245 /// Whether this function declaration has a definition.
diff --git a/crates/hir_ty/src/diagnostics/expr.rs b/crates/hir_ty/src/diagnostics/expr.rs
index c480ed352..2a211fd8e 100644
--- a/crates/hir_ty/src/diagnostics/expr.rs
+++ b/crates/hir_ty/src/diagnostics/expr.rs
@@ -8,6 +8,7 @@ use hir_def::{
8 expr::Statement, path::path, resolver::HasResolver, AssocItemId, DefWithBodyId, HasModule, 8 expr::Statement, path::path, resolver::HasResolver, AssocItemId, DefWithBodyId, HasModule,
9}; 9};
10use hir_expand::name; 10use hir_expand::name;
11use itertools::Either;
11use rustc_hash::FxHashSet; 12use rustc_hash::FxHashSet;
12 13
13use crate::{ 14use crate::{
@@ -26,13 +27,8 @@ pub(crate) use hir_def::{
26}; 27};
27 28
28pub enum BodyValidationDiagnostic { 29pub enum BodyValidationDiagnostic {
29 RecordLiteralMissingFields { 30 RecordMissingFields {
30 record_expr: ExprId, 31 record: Either<ExprId, PatId>,
31 variant: VariantId,
32 missed_fields: Vec<LocalFieldId>,
33 },
34 RecordPatMissingFields {
35 record_pat: PatId,
36 variant: VariantId, 32 variant: VariantId,
37 missed_fields: Vec<LocalFieldId>, 33 missed_fields: Vec<LocalFieldId>,
38 }, 34 },
@@ -95,8 +91,8 @@ impl ExprValidator {
95 if let Some((variant, missed_fields, true)) = 91 if let Some((variant, missed_fields, true)) =
96 record_literal_missing_fields(db, &self.infer, id, expr) 92 record_literal_missing_fields(db, &self.infer, id, expr)
97 { 93 {
98 self.diagnostics.push(BodyValidationDiagnostic::RecordLiteralMissingFields { 94 self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields {
99 record_expr: id, 95 record: Either::Left(id),
100 variant, 96 variant,
101 missed_fields, 97 missed_fields,
102 }); 98 });
@@ -116,8 +112,8 @@ impl ExprValidator {
116 if let Some((variant, missed_fields, true)) = 112 if let Some((variant, missed_fields, true)) =
117 record_pattern_missing_fields(db, &self.infer, id, pat) 113 record_pattern_missing_fields(db, &self.infer, id, pat)
118 { 114 {
119 self.diagnostics.push(BodyValidationDiagnostic::RecordPatMissingFields { 115 self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields {
120 record_pat: id, 116 record: Either::Right(id),
121 variant, 117 variant,
122 missed_fields, 118 missed_fields,
123 }); 119 });
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 075aae8d5..3307e240b 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -5,6 +5,7 @@
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 unresolved_module;
8mod missing_fields;
8 9
9mod fixes; 10mod fixes;
10mod field_shorthand; 11mod field_shorthand;
@@ -123,9 +124,6 @@ pub(crate) fn diagnostics(
123 } 124 }
124 let res = RefCell::new(res); 125 let res = RefCell::new(res);
125 let sink_builder = DiagnosticSinkBuilder::new() 126 let sink_builder = DiagnosticSinkBuilder::new()
126 .on::<hir::diagnostics::MissingFields, _>(|d| {
127 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
128 })
129 .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| { 127 .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| {
130 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); 128 res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve));
131 }) 129 })
@@ -232,7 +230,8 @@ pub(crate) fn diagnostics(
232 let ctx = DiagnosticsContext { config, sema, resolve }; 230 let ctx = DiagnosticsContext { config, sema, resolve };
233 for diag in diags { 231 for diag in diags {
234 let d = match diag { 232 let d = match diag {
235 AnyDiagnostic::UnresolvedModule(d) => unresolved_module::render(&ctx, &d), 233 AnyDiagnostic::UnresolvedModule(d) => unresolved_module::unresolved_module(&ctx, &d),
234 AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d),
236 }; 235 };
237 if let Some(code) = d.code { 236 if let Some(code) = d.code {
238 if ctx.config.disabled.contains(code.as_str()) { 237 if ctx.config.disabled.contains(code.as_str()) {
@@ -1057,20 +1056,6 @@ fn main() {
1057 } 1056 }
1058 1057
1059 #[test] 1058 #[test]
1060 fn missing_record_pat_field_diagnostic() {
1061 check_diagnostics(
1062 r#"
1063struct S { foo: i32, bar: () }
1064fn baz(s: S) {
1065 let S { foo: _ } = s;
1066 //^ Missing structure fields:
1067 //| - bar
1068}
1069"#,
1070 );
1071 }
1072
1073 #[test]
1074 fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() { 1059 fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
1075 check_diagnostics( 1060 check_diagnostics(
1076 r" 1061 r"
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 8640d7231..a2e792b3b 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -2,7 +2,6 @@
2//! The same module also has all curret custom fixes for the diagnostics implemented. 2//! The same module also has all curret custom fixes for the diagnostics implemented.
3mod change_case; 3mod change_case;
4mod create_field; 4mod create_field;
5mod fill_missing_fields;
6mod remove_semicolon; 5mod remove_semicolon;
7mod replace_with_find_map; 6mod replace_with_find_map;
8mod wrap_tail_expr; 7mod wrap_tail_expr;
diff --git a/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs b/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs
deleted file mode 100644
index c76f6008a..000000000
--- a/crates/ide/src/diagnostics/fixes/fill_missing_fields.rs
+++ /dev/null
@@ -1,217 +0,0 @@
1use hir::{db::AstDatabase, diagnostics::MissingFields, Semantics};
2use ide_assists::AssistResolveStrategy;
3use ide_db::{source_change::SourceChange, RootDatabase};
4use syntax::{algo, ast::make, AstNode};
5use text_edit::TextEdit;
6
7use crate::{
8 diagnostics::{fix, fixes::DiagnosticWithFixes},
9 Assist,
10};
11
12impl DiagnosticWithFixes for MissingFields {
13 fn fixes(
14 &self,
15 sema: &Semantics<RootDatabase>,
16 _resolve: &AssistResolveStrategy,
17 ) -> Option<Vec<Assist>> {
18 // Note that although we could add a diagnostics to
19 // fill the missing tuple field, e.g :
20 // `struct A(usize);`
21 // `let a = A { 0: () }`
22 // but it is uncommon usage and it should not be encouraged.
23 if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
24 return None;
25 }
26
27 let root = sema.db.parse_or_expand(self.file)?;
28 let field_list_parent = self.field_list_parent.to_node(&root);
29 let old_field_list = field_list_parent.record_expr_field_list()?;
30 let new_field_list = old_field_list.clone_for_update();
31 for f in self.missed_fields.iter() {
32 let field =
33 make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()))
34 .clone_for_update();
35 new_field_list.add_field(field);
36 }
37
38 let edit = {
39 let mut builder = TextEdit::builder();
40 algo::diff(old_field_list.syntax(), new_field_list.syntax())
41 .into_text_edit(&mut builder);
42 builder.finish()
43 };
44 Some(vec![fix(
45 "fill_missing_fields",
46 "Fill struct fields",
47 SourceChange::from_text_edit(self.file.original_file(sema.db), edit),
48 sema.original_range(field_list_parent.syntax()).range,
49 )])
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use crate::diagnostics::tests::{check_fix, check_no_diagnostics};
56
57 #[test]
58 fn test_fill_struct_fields_empty() {
59 check_fix(
60 r#"
61struct TestStruct { one: i32, two: i64 }
62
63fn test_fn() {
64 let s = TestStruct {$0};
65}
66"#,
67 r#"
68struct TestStruct { one: i32, two: i64 }
69
70fn test_fn() {
71 let s = TestStruct { one: (), two: () };
72}
73"#,
74 );
75 }
76
77 #[test]
78 fn test_fill_struct_fields_self() {
79 check_fix(
80 r#"
81struct TestStruct { one: i32 }
82
83impl TestStruct {
84 fn test_fn() { let s = Self {$0}; }
85}
86"#,
87 r#"
88struct TestStruct { one: i32 }
89
90impl TestStruct {
91 fn test_fn() { let s = Self { one: () }; }
92}
93"#,
94 );
95 }
96
97 #[test]
98 fn test_fill_struct_fields_enum() {
99 check_fix(
100 r#"
101enum Expr {
102 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
103}
104
105impl Expr {
106 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
107 Expr::Bin {$0 }
108 }
109}
110"#,
111 r#"
112enum Expr {
113 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
114}
115
116impl Expr {
117 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
118 Expr::Bin { lhs: (), rhs: () }
119 }
120}
121"#,
122 );
123 }
124
125 #[test]
126 fn test_fill_struct_fields_partial() {
127 check_fix(
128 r#"
129struct TestStruct { one: i32, two: i64 }
130
131fn test_fn() {
132 let s = TestStruct{ two: 2$0 };
133}
134"#,
135 r"
136struct TestStruct { one: i32, two: i64 }
137
138fn test_fn() {
139 let s = TestStruct{ two: 2, one: () };
140}
141",
142 );
143 }
144
145 #[test]
146 fn test_fill_struct_fields_raw_ident() {
147 check_fix(
148 r#"
149struct TestStruct { r#type: u8 }
150
151fn test_fn() {
152 TestStruct { $0 };
153}
154"#,
155 r"
156struct TestStruct { r#type: u8 }
157
158fn test_fn() {
159 TestStruct { r#type: () };
160}
161",
162 );
163 }
164
165 #[test]
166 fn test_fill_struct_fields_no_diagnostic() {
167 check_no_diagnostics(
168 r#"
169struct TestStruct { one: i32, two: i64 }
170
171fn test_fn() {
172 let one = 1;
173 let s = TestStruct{ one, two: 2 };
174}
175 "#,
176 );
177 }
178
179 #[test]
180 fn test_fill_struct_fields_no_diagnostic_on_spread() {
181 check_no_diagnostics(
182 r#"
183struct TestStruct { one: i32, two: i64 }
184
185fn test_fn() {
186 let one = 1;
187 let s = TestStruct{ ..a };
188}
189"#,
190 );
191 }
192
193 #[test]
194 fn test_fill_struct_fields_blank_line() {
195 check_fix(
196 r#"
197struct S { a: (), b: () }
198
199fn f() {
200 S {
201 $0
202 };
203}
204"#,
205 r#"
206struct S { a: (), b: () }
207
208fn f() {
209 S {
210 a: (),
211 b: (),
212 };
213}
214"#,
215 );
216 }
217}
diff --git a/crates/ide/src/diagnostics/missing_fields.rs b/crates/ide/src/diagnostics/missing_fields.rs
new file mode 100644
index 000000000..66575f713
--- /dev/null
+++ b/crates/ide/src/diagnostics/missing_fields.rs
@@ -0,0 +1,256 @@
1use either::Either;
2use hir::{db::AstDatabase, InFile};
3use ide_assists::Assist;
4use ide_db::source_change::SourceChange;
5use stdx::format_to;
6use syntax::{algo, ast::make, AstNode, SyntaxNodePtr};
7use text_edit::TextEdit;
8
9use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext};
10
11// Diagnostic: missing-fields
12//
13// This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
14//
15// Example:
16//
17// ```rust
18// struct A { a: u8, b: u8 }
19//
20// let a = A { a: 10 };
21// ```
22pub(super) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic {
23 let mut message = String::from("Missing structure fields:\n");
24 for field in &d.missed_fields {
25 format_to!(message, "- {}\n", field);
26 }
27
28 let ptr = InFile::new(
29 d.file,
30 d.field_list_parent_path
31 .clone()
32 .map(SyntaxNodePtr::from)
33 .unwrap_or_else(|| d.field_list_parent.clone().either(|it| it.into(), |it| it.into())),
34 );
35
36 Diagnostic::new("missing-fields", message, ctx.sema.diagnostics_display_range(ptr).range)
37 .with_fixes(fixes(ctx, d))
38}
39
40fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Assist>> {
41 // Note that although we could add a diagnostics to
42 // fill the missing tuple field, e.g :
43 // `struct A(usize);`
44 // `let a = A { 0: () }`
45 // but it is uncommon usage and it should not be encouraged.
46 if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
47 return None;
48 }
49
50 let root = ctx.sema.db.parse_or_expand(d.file)?;
51 let field_list_parent = match &d.field_list_parent {
52 Either::Left(record_expr) => record_expr.to_node(&root),
53 // FIXE: patterns should be fixable as well.
54 Either::Right(_) => return None,
55 };
56 let old_field_list = field_list_parent.record_expr_field_list()?;
57 let new_field_list = old_field_list.clone_for_update();
58 for f in d.missed_fields.iter() {
59 let field =
60 make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()))
61 .clone_for_update();
62 new_field_list.add_field(field);
63 }
64
65 let edit = {
66 let mut builder = TextEdit::builder();
67 algo::diff(old_field_list.syntax(), new_field_list.syntax()).into_text_edit(&mut builder);
68 builder.finish()
69 };
70 Some(vec![fix(
71 "fill_missing_fields",
72 "Fill struct fields",
73 SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit),
74 ctx.sema.original_range(field_list_parent.syntax()).range,
75 )])
76}
77
78#[cfg(test)]
79mod tests {
80 use crate::diagnostics::tests::{check_diagnostics, check_fix, check_no_diagnostics};
81
82 #[test]
83 fn missing_record_pat_field_diagnostic() {
84 check_diagnostics(
85 r#"
86struct S { foo: i32, bar: () }
87fn baz(s: S) {
88 let S { foo: _ } = s;
89 //^ Missing structure fields:
90 //| - bar
91}
92"#,
93 );
94 }
95
96 #[test]
97 fn test_fill_struct_fields_empty() {
98 check_fix(
99 r#"
100struct TestStruct { one: i32, two: i64 }
101
102fn test_fn() {
103 let s = TestStruct {$0};
104}
105"#,
106 r#"
107struct TestStruct { one: i32, two: i64 }
108
109fn test_fn() {
110 let s = TestStruct { one: (), two: () };
111}
112"#,
113 );
114 }
115
116 #[test]
117 fn test_fill_struct_fields_self() {
118 check_fix(
119 r#"
120struct TestStruct { one: i32 }
121
122impl TestStruct {
123 fn test_fn() { let s = Self {$0}; }
124}
125"#,
126 r#"
127struct TestStruct { one: i32 }
128
129impl TestStruct {
130 fn test_fn() { let s = Self { one: () }; }
131}
132"#,
133 );
134 }
135
136 #[test]
137 fn test_fill_struct_fields_enum() {
138 check_fix(
139 r#"
140enum Expr {
141 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
142}
143
144impl Expr {
145 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
146 Expr::Bin {$0 }
147 }
148}
149"#,
150 r#"
151enum Expr {
152 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
153}
154
155impl Expr {
156 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
157 Expr::Bin { lhs: (), rhs: () }
158 }
159}
160"#,
161 );
162 }
163
164 #[test]
165 fn test_fill_struct_fields_partial() {
166 check_fix(
167 r#"
168struct TestStruct { one: i32, two: i64 }
169
170fn test_fn() {
171 let s = TestStruct{ two: 2$0 };
172}
173"#,
174 r"
175struct TestStruct { one: i32, two: i64 }
176
177fn test_fn() {
178 let s = TestStruct{ two: 2, one: () };
179}
180",
181 );
182 }
183
184 #[test]
185 fn test_fill_struct_fields_raw_ident() {
186 check_fix(
187 r#"
188struct TestStruct { r#type: u8 }
189
190fn test_fn() {
191 TestStruct { $0 };
192}
193"#,
194 r"
195struct TestStruct { r#type: u8 }
196
197fn test_fn() {
198 TestStruct { r#type: () };
199}
200",
201 );
202 }
203
204 #[test]
205 fn test_fill_struct_fields_no_diagnostic() {
206 check_no_diagnostics(
207 r#"
208struct TestStruct { one: i32, two: i64 }
209
210fn test_fn() {
211 let one = 1;
212 let s = TestStruct{ one, two: 2 };
213}
214 "#,
215 );
216 }
217
218 #[test]
219 fn test_fill_struct_fields_no_diagnostic_on_spread() {
220 check_no_diagnostics(
221 r#"
222struct TestStruct { one: i32, two: i64 }
223
224fn test_fn() {
225 let one = 1;
226 let s = TestStruct{ ..a };
227}
228"#,
229 );
230 }
231
232 #[test]
233 fn test_fill_struct_fields_blank_line() {
234 check_fix(
235 r#"
236struct S { a: (), b: () }
237
238fn f() {
239 S {
240 $0
241 };
242}
243"#,
244 r#"
245struct S { a: (), b: () }
246
247fn f() {
248 S {
249 a: (),
250 b: (),
251 };
252}
253"#,
254 );
255 }
256}
diff --git a/crates/ide/src/diagnostics/unresolved_module.rs b/crates/ide/src/diagnostics/unresolved_module.rs
index abf53a57c..4c8c74ff7 100644
--- a/crates/ide/src/diagnostics/unresolved_module.rs
+++ b/crates/ide/src/diagnostics/unresolved_module.rs
@@ -8,7 +8,10 @@ use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext};
8// Diagnostic: unresolved-module 8// Diagnostic: unresolved-module
9// 9//
10// This diagnostic is triggered if rust-analyzer is unable to discover referred module. 10// This diagnostic is triggered if rust-analyzer is unable to discover referred module.
11pub(super) fn render(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedModule) -> Diagnostic { 11pub(super) fn unresolved_module(
12 ctx: &DiagnosticsContext<'_>,
13 d: &hir::UnresolvedModule,
14) -> Diagnostic {
12 Diagnostic::new( 15 Diagnostic::new(
13 "unresolved-module", 16 "unresolved-module",
14 "unresolved module", 17 "unresolved module",