aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2021-06-13 13:48:54 +0100
committerAleksey Kladov <[email protected]>2021-06-13 13:48:54 +0100
commit6383252cc2770545505d40217732f14e93a396c4 (patch)
treec68f9e02b8ef19b6d4bb70451c202bccbf5858f0
parentc6509a4592b67acc4a99a7ffd6dd688bc6cd29be (diff)
internal: unified missing fields diagnostic
-rw-r--r--crates/hir/src/diagnostics.rs52
-rw-r--r--crates/hir/src/lib.rs117
-rw-r--r--crates/hir_ty/src/diagnostics/expr.rs18
-rw-r--r--crates/ide/src/diagnostics.rs14
-rw-r--r--crates/ide/src/diagnostics/missing_fields.rs35
5 files changed, 94 insertions, 142 deletions
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index a5e982b75..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;
@@ -324,60 +325,11 @@ impl Diagnostic for MissingUnsafe {
324#[derive(Debug)] 325#[derive(Debug)]
325pub struct MissingFields { 326pub struct MissingFields {
326 pub file: HirFileId, 327 pub file: HirFileId,
327 pub field_list_parent: AstPtr<ast::RecordExpr>, 328 pub field_list_parent: Either<AstPtr<ast::RecordExpr>, AstPtr<ast::RecordPat>>,
328 pub field_list_parent_path: Option<AstPtr<ast::Path>>, 329 pub field_list_parent_path: Option<AstPtr<ast::Path>>,
329 pub missed_fields: Vec<Name>, 330 pub missed_fields: Vec<Name>,
330} 331}
331 332
332// Diagnostic: missing-pat-fields
333//
334// This diagnostic is triggered if pattern lacks some fields that exist in the corresponding structure.
335//
336// Example:
337//
338// ```rust
339// struct A { a: u8, b: u8 }
340//
341// let a = A { a: 10, b: 20 };
342//
343// if let A { a } = a {
344// // ...
345// }
346// ```
347#[derive(Debug)]
348pub struct MissingPatFields {
349 pub file: HirFileId,
350 pub field_list_parent: AstPtr<ast::RecordPat>,
351 pub field_list_parent_path: Option<AstPtr<ast::Path>>,
352 pub missed_fields: Vec<Name>,
353}
354
355impl Diagnostic for MissingPatFields {
356 fn code(&self) -> DiagnosticCode {
357 DiagnosticCode("missing-pat-fields")
358 }
359 fn message(&self) -> String {
360 let mut buf = String::from("Missing structure fields:\n");
361 for field in &self.missed_fields {
362 format_to!(buf, "- {}\n", field);
363 }
364 buf
365 }
366 fn display_source(&self) -> InFile<SyntaxNodePtr> {
367 InFile {
368 file_id: self.file,
369 value: self
370 .field_list_parent_path
371 .clone()
372 .map(SyntaxNodePtr::from)
373 .unwrap_or_else(|| self.field_list_parent.clone().into()),
374 }
375 }
376 fn as_any(&self) -> &(dyn Any + Send + 'static) {
377 self
378 }
379}
380
381// Diagnostic: replace-filter-map-next-with-find-map 333// Diagnostic: replace-filter-map-next-with-find-map
382// 334//
383// 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 583d92f20..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},
@@ -1098,67 +1098,70 @@ impl Function {
1098 BodyValidationDiagnostic::collect(db, self.id.into(), internal_diagnostics) 1098 BodyValidationDiagnostic::collect(db, self.id.into(), internal_diagnostics)
1099 { 1099 {
1100 match diagnostic { 1100 match diagnostic {
1101 BodyValidationDiagnostic::RecordLiteralMissingFields { 1101 BodyValidationDiagnostic::RecordMissingFields {
1102 record_expr, 1102 record,
1103 variant, 1103 variant,
1104 missed_fields, 1104 missed_fields,
1105 } => match source_map.expr_syntax(record_expr) { 1105 } => {
1106 Ok(source_ptr) => { 1106 let variant_data = variant.variant_data(db.upcast());
1107 let root = source_ptr.file_syntax(db.upcast()); 1107 let missed_fields = missed_fields
1108 if let ast::Expr::RecordExpr(record_expr) = &source_ptr.value.to_node(&root) 1108 .into_iter()
1109 { 1109 .map(|idx| variant_data.fields()[idx].name.clone())
1110 if let Some(_) = record_expr.record_expr_field_list() { 1110 .collect();
1111 let variant_data = variant.variant_data(db.upcast()); 1111
1112 let missed_fields = missed_fields 1112 match record {
1113 .into_iter() 1113 Either::Left(record_expr) => match source_map.expr_syntax(record_expr) {
1114 .map(|idx| variant_data.fields()[idx].name.clone()) 1114 Ok(source_ptr) => {
1115 .collect(); 1115 let root = source_ptr.file_syntax(db.upcast());
1116 acc.push( 1116 if let ast::Expr::RecordExpr(record_expr) =
1117 MissingFields { 1117 &source_ptr.value.to_node(&root)
1118 file: source_ptr.file_id, 1118 {
1119 field_list_parent: AstPtr::new(record_expr), 1119 if let Some(_) = record_expr.record_expr_field_list() {
1120 field_list_parent_path: record_expr 1120 acc.push(
1121 .path() 1121 MissingFields {
1122 .map(|path| AstPtr::new(&path)), 1122 file: source_ptr.file_id,
1123 missed_fields, 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 )
1124 } 1133 }
1125 .into(), 1134 }
1126 )
1127 } 1135 }
1128 } 1136 Err(SyntheticSyntax) => (),
1129 } 1137 },
1130 Err(SyntheticSyntax) => (), 1138 Either::Right(record_pat) => match source_map.pat_syntax(record_pat) {
1131 }, 1139 Ok(source_ptr) => {
1132 BodyValidationDiagnostic::RecordPatMissingFields { 1140 if let Some(expr) = source_ptr.value.as_ref().left() {
1133 record_pat, 1141 let root = source_ptr.file_syntax(db.upcast());
1134 variant, 1142 if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) {
1135 missed_fields, 1143 if let Some(_) = record_pat.record_pat_field_list() {
1136 } => match source_map.pat_syntax(record_pat) { 1144 acc.push(
1137 Ok(source_ptr) => { 1145 MissingFields {
1138 if let Some(expr) = source_ptr.value.as_ref().left() { 1146 file: source_ptr.file_id,
1139 let root = source_ptr.file_syntax(db.upcast()); 1147 field_list_parent: Either::Right(AstPtr::new(
1140 if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) { 1148 &record_pat,
1141 if let Some(_) = record_pat.record_pat_field_list() { 1149 )),
1142 let variant_data = variant.variant_data(db.upcast()); 1150 field_list_parent_path: record_pat
1143 let missed_fields = missed_fields 1151 .path()
1144 .into_iter() 1152 .map(|path| AstPtr::new(&path)),
1145 .map(|idx| variant_data.fields()[idx].name.clone()) 1153 missed_fields,
1146 .collect(); 1154 }
1147 sink.push(MissingPatFields { 1155 .into(),
1148 file: source_ptr.file_id, 1156 )
1149 field_list_parent: AstPtr::new(&record_pat), 1157 }
1150 field_list_parent_path: record_pat 1158 }
1151 .path()
1152 .map(|path| AstPtr::new(&path)),
1153 missed_fields,
1154 })
1155 } 1159 }
1156 } 1160 }
1157 } 1161 Err(SyntheticSyntax) => (),
1162 },
1158 } 1163 }
1159 Err(SyntheticSyntax) => (), 1164 }
1160 },
1161
1162 BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap { method_call_expr } => { 1165 BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap { method_call_expr } => {
1163 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) {
1164 sink.push(ReplaceFilterMapNextWithFindMap { 1167 sink.push(ReplaceFilterMapNextWithFindMap {
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 ef8c8044c..3307e240b 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -1056,20 +1056,6 @@ fn main() {
1056 } 1056 }
1057 1057
1058 #[test] 1058 #[test]
1059 fn missing_record_pat_field_diagnostic() {
1060 check_diagnostics(
1061 r#"
1062struct S { foo: i32, bar: () }
1063fn baz(s: S) {
1064 let S { foo: _ } = s;
1065 //^ Missing structure fields:
1066 //| - bar
1067}
1068"#,
1069 );
1070 }
1071
1072 #[test]
1073 fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() { 1059 fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
1074 check_diagnostics( 1060 check_diagnostics(
1075 r" 1061 r"
diff --git a/crates/ide/src/diagnostics/missing_fields.rs b/crates/ide/src/diagnostics/missing_fields.rs
index aee780972..66575f713 100644
--- a/crates/ide/src/diagnostics/missing_fields.rs
+++ b/crates/ide/src/diagnostics/missing_fields.rs
@@ -1,3 +1,4 @@
1use either::Either;
1use hir::{db::AstDatabase, InFile}; 2use hir::{db::AstDatabase, InFile};
2use ide_assists::Assist; 3use ide_assists::Assist;
3use ide_db::source_change::SourceChange; 4use ide_db::source_change::SourceChange;
@@ -7,7 +8,7 @@ use text_edit::TextEdit;
7 8
8use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext}; 9use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext};
9 10
10// Diagnostic: missing-structure-fields 11// Diagnostic: missing-fields
11// 12//
12// This diagnostic is triggered if record lacks some fields that exist in the corresponding structure. 13// This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
13// 14//
@@ -29,15 +30,11 @@ pub(super) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingField
29 d.field_list_parent_path 30 d.field_list_parent_path
30 .clone() 31 .clone()
31 .map(SyntaxNodePtr::from) 32 .map(SyntaxNodePtr::from)
32 .unwrap_or_else(|| d.field_list_parent.clone().into()), 33 .unwrap_or_else(|| d.field_list_parent.clone().either(|it| it.into(), |it| it.into())),
33 ); 34 );
34 35
35 Diagnostic::new( 36 Diagnostic::new("missing-fields", message, ctx.sema.diagnostics_display_range(ptr).range)
36 "missing-structure-fields", 37 .with_fixes(fixes(ctx, d))
37 message,
38 ctx.sema.diagnostics_display_range(ptr).range,
39 )
40 .with_fixes(fixes(ctx, d))
41} 38}
42 39
43fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Assist>> { 40fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Assist>> {
@@ -51,7 +48,11 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
51 } 48 }
52 49
53 let root = ctx.sema.db.parse_or_expand(d.file)?; 50 let root = ctx.sema.db.parse_or_expand(d.file)?;
54 let field_list_parent = d.field_list_parent.to_node(&root); 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 };
55 let old_field_list = field_list_parent.record_expr_field_list()?; 56 let old_field_list = field_list_parent.record_expr_field_list()?;
56 let new_field_list = old_field_list.clone_for_update(); 57 let new_field_list = old_field_list.clone_for_update();
57 for f in d.missed_fields.iter() { 58 for f in d.missed_fields.iter() {
@@ -76,7 +77,21 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass
76 77
77#[cfg(test)] 78#[cfg(test)]
78mod tests { 79mod tests {
79 use crate::diagnostics::tests::{check_fix, check_no_diagnostics}; 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 }
80 95
81 #[test] 96 #[test]
82 fn test_fill_struct_fields_empty() { 97 fn test_fill_struct_fields_empty() {