diff options
author | Josh Mcguigan <[email protected]> | 2020-04-09 04:23:51 +0100 |
---|---|---|
committer | Josh Mcguigan <[email protected]> | 2020-04-10 14:35:52 +0100 |
commit | e63315b8f189396cf556f21d4ca27ae4281d17d7 (patch) | |
tree | 48d6f6bae33a58d378939f766a1725c73bac7f2e /crates/ra_hir_ty/src | |
parent | 176f7f61175bc433c56083a758bd7a28a8ae31f8 (diff) |
add record pat missing field diagnostic
Diffstat (limited to 'crates/ra_hir_ty/src')
-rw-r--r-- | crates/ra_hir_ty/src/diagnostics.rs | 23 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/expr.rs | 112 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/infer/pat.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir_ty/src/tests.rs | 40 |
4 files changed, 149 insertions, 28 deletions
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs index 8cbce1168..3f18acf1d 100644 --- a/crates/ra_hir_ty/src/diagnostics.rs +++ b/crates/ra_hir_ty/src/diagnostics.rs | |||
@@ -63,6 +63,29 @@ impl AstDiagnostic for MissingFields { | |||
63 | } | 63 | } |
64 | 64 | ||
65 | #[derive(Debug)] | 65 | #[derive(Debug)] |
66 | pub struct MissingPatFields { | ||
67 | pub file: HirFileId, | ||
68 | pub field_list: AstPtr<ast::RecordFieldPatList>, | ||
69 | pub missed_fields: Vec<Name>, | ||
70 | } | ||
71 | |||
72 | impl Diagnostic for MissingPatFields { | ||
73 | fn message(&self) -> String { | ||
74 | let mut buf = String::from("Missing structure fields:\n"); | ||
75 | for field in &self.missed_fields { | ||
76 | format_to!(buf, "- {}", field); | ||
77 | } | ||
78 | buf | ||
79 | } | ||
80 | fn source(&self) -> InFile<SyntaxNodePtr> { | ||
81 | InFile { file_id: self.file, value: self.field_list.into() } | ||
82 | } | ||
83 | fn as_any(&self) -> &(dyn Any + Send + 'static) { | ||
84 | self | ||
85 | } | ||
86 | } | ||
87 | |||
88 | #[derive(Debug)] | ||
66 | pub struct MissingMatchArms { | 89 | pub struct MissingMatchArms { |
67 | pub file: HirFileId, | 90 | pub file: HirFileId, |
68 | pub match_expr: AstPtr<ast::Expr>, | 91 | pub match_expr: AstPtr<ast::Expr>, |
diff --git a/crates/ra_hir_ty/src/expr.rs b/crates/ra_hir_ty/src/expr.rs index e45e9ea14..a7c8d74ab 100644 --- a/crates/ra_hir_ty/src/expr.rs +++ b/crates/ra_hir_ty/src/expr.rs | |||
@@ -9,7 +9,7 @@ use rustc_hash::FxHashSet; | |||
9 | 9 | ||
10 | use crate::{ | 10 | use crate::{ |
11 | db::HirDatabase, | 11 | db::HirDatabase, |
12 | diagnostics::{MissingFields, MissingMatchArms, MissingOkInTailExpr}, | 12 | diagnostics::{MissingFields, MissingMatchArms, MissingOkInTailExpr, MissingPatFields}, |
13 | utils::variant_data, | 13 | utils::variant_data, |
14 | ApplicationTy, InferenceResult, Ty, TypeCtor, | 14 | ApplicationTy, InferenceResult, Ty, TypeCtor, |
15 | _match::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness}, | 15 | _match::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness}, |
@@ -49,39 +49,97 @@ impl<'a, 'b> ExprValidator<'a, 'b> { | |||
49 | if let Some((variant_def, missed_fields, true)) = | 49 | if let Some((variant_def, missed_fields, true)) = |
50 | record_literal_missing_fields(db, &self.infer, id, expr) | 50 | record_literal_missing_fields(db, &self.infer, id, expr) |
51 | { | 51 | { |
52 | // XXX: only look at source_map if we do have missing fields | 52 | self.create_record_literal_missing_fields_diagnostic( |
53 | let (_, source_map) = db.body_with_source_map(self.func.into()); | 53 | id, |
54 | 54 | db, | |
55 | if let Ok(source_ptr) = source_map.expr_syntax(id) { | 55 | variant_def, |
56 | if let Some(expr) = source_ptr.value.left() { | 56 | missed_fields, |
57 | let root = source_ptr.file_syntax(db.upcast()); | 57 | ); |
58 | if let ast::Expr::RecordLit(record_lit) = expr.to_node(&root) { | ||
59 | if let Some(field_list) = record_lit.record_field_list() { | ||
60 | let variant_data = variant_data(db.upcast(), variant_def); | ||
61 | let missed_fields = missed_fields | ||
62 | .into_iter() | ||
63 | .map(|idx| variant_data.fields()[idx].name.clone()) | ||
64 | .collect(); | ||
65 | self.sink.push(MissingFields { | ||
66 | file: source_ptr.file_id, | ||
67 | field_list: AstPtr::new(&field_list), | ||
68 | missed_fields, | ||
69 | }) | ||
70 | } | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | } | 58 | } |
75 | if let Expr::Match { expr, arms } = expr { | 59 | if let Expr::Match { expr, arms } = expr { |
76 | self.validate_match(id, *expr, arms, db, self.infer.clone()); | 60 | self.validate_match(id, *expr, arms, db, self.infer.clone()); |
77 | } | 61 | } |
78 | } | 62 | } |
63 | for (id, pat) in body.pats.iter() { | ||
64 | if let Some((variant_def, missed_fields, true)) = | ||
65 | record_pattern_missing_fields(db, &self.infer, id, pat) | ||
66 | { | ||
67 | self.create_record_pattern_missing_fields_diagnostic( | ||
68 | id, | ||
69 | db, | ||
70 | variant_def, | ||
71 | missed_fields, | ||
72 | ); | ||
73 | } | ||
74 | } | ||
79 | let body_expr = &body[body.body_expr]; | 75 | let body_expr = &body[body.body_expr]; |
80 | if let Expr::Block { tail: Some(t), .. } = body_expr { | 76 | if let Expr::Block { tail: Some(t), .. } = body_expr { |
81 | self.validate_results_in_tail_expr(body.body_expr, *t, db); | 77 | self.validate_results_in_tail_expr(body.body_expr, *t, db); |
82 | } | 78 | } |
83 | } | 79 | } |
84 | 80 | ||
81 | fn create_record_literal_missing_fields_diagnostic( | ||
82 | &mut self, | ||
83 | id: ExprId, | ||
84 | db: &dyn HirDatabase, | ||
85 | variant_def: VariantId, | ||
86 | missed_fields: Vec<LocalStructFieldId>, | ||
87 | ) { | ||
88 | // XXX: only look at source_map if we do have missing fields | ||
89 | let (_, source_map) = db.body_with_source_map(self.func.into()); | ||
90 | |||
91 | if let Ok(source_ptr) = source_map.expr_syntax(id) { | ||
92 | if let Some(expr) = source_ptr.value.left() { | ||
93 | let root = source_ptr.file_syntax(db.upcast()); | ||
94 | if let ast::Expr::RecordLit(record_lit) = expr.to_node(&root) { | ||
95 | if let Some(field_list) = record_lit.record_field_list() { | ||
96 | let variant_data = variant_data(db.upcast(), variant_def); | ||
97 | let missed_fields = missed_fields | ||
98 | .into_iter() | ||
99 | .map(|idx| variant_data.fields()[idx].name.clone()) | ||
100 | .collect(); | ||
101 | self.sink.push(MissingFields { | ||
102 | file: source_ptr.file_id, | ||
103 | field_list: AstPtr::new(&field_list), | ||
104 | missed_fields, | ||
105 | }) | ||
106 | } | ||
107 | } | ||
108 | } | ||
109 | } | ||
110 | } | ||
111 | |||
112 | fn create_record_pattern_missing_fields_diagnostic( | ||
113 | &mut self, | ||
114 | id: PatId, | ||
115 | db: &dyn HirDatabase, | ||
116 | variant_def: VariantId, | ||
117 | missed_fields: Vec<LocalStructFieldId>, | ||
118 | ) { | ||
119 | // XXX: only look at source_map if we do have missing fields | ||
120 | let (_, source_map) = db.body_with_source_map(self.func.into()); | ||
121 | |||
122 | if let Ok(source_ptr) = source_map.pat_syntax(id) { | ||
123 | if let Some(expr) = source_ptr.value.left() { | ||
124 | let root = source_ptr.file_syntax(db.upcast()); | ||
125 | if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) { | ||
126 | if let Some(field_list) = record_pat.record_field_pat_list() { | ||
127 | let variant_data = variant_data(db.upcast(), variant_def); | ||
128 | let missed_fields = missed_fields | ||
129 | .into_iter() | ||
130 | .map(|idx| variant_data.fields()[idx].name.clone()) | ||
131 | .collect(); | ||
132 | self.sink.push(MissingPatFields { | ||
133 | file: source_ptr.file_id, | ||
134 | field_list: AstPtr::new(&field_list), | ||
135 | missed_fields, | ||
136 | }) | ||
137 | } | ||
138 | } | ||
139 | } | ||
140 | } | ||
141 | } | ||
142 | |||
85 | fn validate_match( | 143 | fn validate_match( |
86 | &mut self, | 144 | &mut self, |
87 | id: ExprId, | 145 | id: ExprId, |
@@ -232,9 +290,9 @@ pub fn record_pattern_missing_fields( | |||
232 | infer: &InferenceResult, | 290 | infer: &InferenceResult, |
233 | id: PatId, | 291 | id: PatId, |
234 | pat: &Pat, | 292 | pat: &Pat, |
235 | ) -> Option<(VariantId, Vec<LocalStructFieldId>)> { | 293 | ) -> Option<(VariantId, Vec<LocalStructFieldId>, /*exhaustive*/ bool)> { |
236 | let fields = match pat { | 294 | let (fields, exhaustive) = match pat { |
237 | Pat::Record { path: _, args } => args, | 295 | Pat::Record { path: _, args, ellipsis } => (args, !ellipsis), |
238 | _ => return None, | 296 | _ => return None, |
239 | }; | 297 | }; |
240 | 298 | ||
@@ -254,5 +312,5 @@ pub fn record_pattern_missing_fields( | |||
254 | if missed_fields.is_empty() { | 312 | if missed_fields.is_empty() { |
255 | return None; | 313 | return None; |
256 | } | 314 | } |
257 | Some((variant_def, missed_fields)) | 315 | Some((variant_def, missed_fields, exhaustive)) |
258 | } | 316 | } |
diff --git a/crates/ra_hir_ty/src/infer/pat.rs b/crates/ra_hir_ty/src/infer/pat.rs index 69bbb4307..078476f76 100644 --- a/crates/ra_hir_ty/src/infer/pat.rs +++ b/crates/ra_hir_ty/src/infer/pat.rs | |||
@@ -158,7 +158,7 @@ impl<'a> InferenceContext<'a> { | |||
158 | Pat::TupleStruct { path: p, args: subpats } => { | 158 | Pat::TupleStruct { path: p, args: subpats } => { |
159 | self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm, pat) | 159 | self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm, pat) |
160 | } | 160 | } |
161 | Pat::Record { path: p, args: fields } => { | 161 | Pat::Record { path: p, args: fields, ellipsis: _ } => { |
162 | self.infer_record_pat(p.as_ref(), fields, expected, default_bm, pat) | 162 | self.infer_record_pat(p.as_ref(), fields, expected, default_bm, pat) |
163 | } | 163 | } |
164 | Pat::Path(path) => { | 164 | Pat::Path(path) => { |
diff --git a/crates/ra_hir_ty/src/tests.rs b/crates/ra_hir_ty/src/tests.rs index 002cffba6..47a7b9ffd 100644 --- a/crates/ra_hir_ty/src/tests.rs +++ b/crates/ra_hir_ty/src/tests.rs | |||
@@ -409,3 +409,43 @@ fn no_such_field_with_feature_flag_diagnostics_on_struct_fields() { | |||
409 | 409 | ||
410 | assert_snapshot!(diagnostics, @r###""###); | 410 | assert_snapshot!(diagnostics, @r###""###); |
411 | } | 411 | } |
412 | |||
413 | #[test] | ||
414 | fn missing_record_pat_field_diagnostic() { | ||
415 | let diagnostics = TestDB::with_files( | ||
416 | r" | ||
417 | //- /lib.rs | ||
418 | struct S { foo: i32, bar: () } | ||
419 | fn baz(s: S) { | ||
420 | let S { foo: _ } = s; | ||
421 | } | ||
422 | ", | ||
423 | ) | ||
424 | .diagnostics() | ||
425 | .0; | ||
426 | |||
427 | assert_snapshot!(diagnostics, @r###" | ||
428 | "{ foo: _ }": Missing structure fields: | ||
429 | - bar | ||
430 | "### | ||
431 | ); | ||
432 | } | ||
433 | |||
434 | #[test] | ||
435 | fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() { | ||
436 | let diagnostics = TestDB::with_files( | ||
437 | r" | ||
438 | //- /lib.rs | ||
439 | struct S { foo: i32, bar: () } | ||
440 | fn baz(s: S) -> i32 { | ||
441 | match s { | ||
442 | S { foo, .. } => foo, | ||
443 | } | ||
444 | } | ||
445 | ", | ||
446 | ) | ||
447 | .diagnostics() | ||
448 | .0; | ||
449 | |||
450 | assert_snapshot!(diagnostics, @""); | ||
451 | } | ||