aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_hir/src/source_analyzer.rs2
-rw-r--r--crates/ra_hir_def/src/body/lower.rs4
-rw-r--r--crates/ra_hir_def/src/expr.rs33
-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
7 files changed, 159 insertions, 57 deletions
diff --git a/crates/ra_hir/src/source_analyzer.rs b/crates/ra_hir/src/source_analyzer.rs
index 45631f8fd..226fb4534 100644
--- a/crates/ra_hir/src/source_analyzer.rs
+++ b/crates/ra_hir/src/source_analyzer.rs
@@ -255,7 +255,7 @@ impl SourceAnalyzer {
255 _ => return None, 255 _ => return None,
256 }; 256 };
257 257
258 let (variant, missing_fields) = 258 let (variant, missing_fields, _exhaustive) =
259 record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])?; 259 record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])?;
260 let res = self.missing_fields(db, krate, substs, variant, missing_fields); 260 let res = self.missing_fields(db, krate, substs, variant, missing_fields);
261 Some(res) 261 Some(res)
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs
index b0d71eb3d..0855c1d3a 100644
--- a/crates/ra_hir_def/src/body/lower.rs
+++ b/crates/ra_hir_def/src/body/lower.rs
@@ -668,7 +668,9 @@ impl ExprCollector<'_> {
668 }); 668 });
669 fields.extend(iter); 669 fields.extend(iter);
670 670
671 Pat::Record { path, args: fields } 671 let ellipsis = record_field_pat_list.dotdot_token().is_some();
672
673 Pat::Record { path, args: fields, ellipsis }
672 } 674 }
673 ast::Pat::SlicePat(p) => { 675 ast::Pat::SlicePat(p) => {
674 let SlicePatComponents { prefix, slice, suffix } = p.components(); 676 let SlicePatComponents { prefix, slice, suffix } = p.components();
diff --git a/crates/ra_hir_def/src/expr.rs b/crates/ra_hir_def/src/expr.rs
index 197bbe9bd..e11bdf3ec 100644
--- a/crates/ra_hir_def/src/expr.rs
+++ b/crates/ra_hir_def/src/expr.rs
@@ -376,35 +376,14 @@ pub enum Pat {
376 Wild, 376 Wild,
377 Tuple(Vec<PatId>), 377 Tuple(Vec<PatId>),
378 Or(Vec<PatId>), 378 Or(Vec<PatId>),
379 Record { 379 Record { path: Option<Path>, args: Vec<RecordFieldPat>, ellipsis: bool },
380 path: Option<Path>, 380 Range { start: ExprId, end: ExprId },
381 args: Vec<RecordFieldPat>, 381 Slice { prefix: Vec<PatId>, slice: Option<PatId>, suffix: Vec<PatId> },
382 // FIXME: 'ellipsis' option
383 },
384 Range {
385 start: ExprId,
386 end: ExprId,
387 },
388 Slice {
389 prefix: Vec<PatId>,
390 slice: Option<PatId>,
391 suffix: Vec<PatId>,
392 },
393 Path(Path), 382 Path(Path),
394 Lit(ExprId), 383 Lit(ExprId),
395 Bind { 384 Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> },
396 mode: BindingAnnotation, 385 TupleStruct { path: Option<Path>, args: Vec<PatId> },
397 name: Name, 386 Ref { pat: PatId, mutability: Mutability },
398 subpat: Option<PatId>,
399 },
400 TupleStruct {
401 path: Option<Path>,
402 args: Vec<PatId>,
403 },
404 Ref {
405 pat: PatId,
406 mutability: Mutability,
407 },
408} 387}
409 388
410impl Pat { 389impl Pat {
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}