aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_hir_ty/src
diff options
context:
space:
mode:
authorJosh Mcguigan <[email protected]>2020-04-09 04:23:51 +0100
committerJosh Mcguigan <[email protected]>2020-04-10 14:35:52 +0100
commite63315b8f189396cf556f21d4ca27ae4281d17d7 (patch)
tree48d6f6bae33a58d378939f766a1725c73bac7f2e /crates/ra_hir_ty/src
parent176f7f61175bc433c56083a758bd7a28a8ae31f8 (diff)
add record pat missing field diagnostic
Diffstat (limited to 'crates/ra_hir_ty/src')
-rw-r--r--crates/ra_hir_ty/src/diagnostics.rs23
-rw-r--r--crates/ra_hir_ty/src/expr.rs112
-rw-r--r--crates/ra_hir_ty/src/infer/pat.rs2
-rw-r--r--crates/ra_hir_ty/src/tests.rs40
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)]
66pub struct MissingPatFields {
67 pub file: HirFileId,
68 pub field_list: AstPtr<ast::RecordFieldPatList>,
69 pub missed_fields: Vec<Name>,
70}
71
72impl 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)]
66pub struct MissingMatchArms { 89pub 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
10use crate::{ 10use 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]
414fn 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]
435fn 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}