Repository: rust-analyzer/rust-analyzer
Pull request #3905 (2020-04-09 03:27:53)

add ellipsis field to hir pat record
This PR corrects a `fixme`, adding an `ellipsis` field to the hir `Pat::Record` type. It will also be unlock some useful follow on work for #3894.

Additionally it adds a diagnostic for missing fields in record patterns.

~~Marking as a draft because I don't have any tests, and a small amount of manual testing on my branch from #3894 suggests it might *not* be working. Any thoughts on how I can best test this, or else pointers on where I might be going wrong?~~

JoshMcguigan: I created the `MissingPatFields` diagnostic, and I am seeing now why this wasn't actually working. [A trailing `..` is not treated as a DOT_DOT_PAT](https://github.com/rust-analyzer/rust-analyzer/blob/6a2dd7bafc24ac405aebf29f04120ca071019e92/crates/ra_parser/src/grammar/patterns.rs#L193) in a record field pat list. I believe it is parsed as the `DOTDOT` punctuation, and for this reason doesn't show up when we iterate over children. 

Any thoughts on the best course of action here?
matklad: >Any thoughts on the best course of action here?

using the `.dotdot_token()` accessor should work: https://github.com/rust-analyzer/rust-analyzer/blob/176f7f61175bc433c56083a758bd7a28a8ae31f8/crates/ra_syntax/src/ast/generated/nodes.rs#L1710

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 20e7e745be..34f05e83a3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -995,7 +995,6 @@ dependencies = [
  "ra_prof",
  "ra_syntax",
  "rustc-hash",
- "scoped-tls",
  "smallvec",
  "stdx",
  "test_utils",
@@ -1391,12 +1390,6 @@ dependencies = [
  "winapi-util",
 ]
 
-[[package]]
-name = "scoped-tls"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
-
 [[package]]
 name = "scopeguard"
 version = "1.1.0"
diff --git a/crates/ra_hir/src/source_analyzer.rs b/crates/ra_hir/src/source_analyzer.rs
index 45631f8fdf..226fb45348 100644
--- a/crates/ra_hir/src/source_analyzer.rs
+++ b/crates/ra_hir/src/source_analyzer.rs
@@ -255,7 +255,7 @@ impl SourceAnalyzer {
             _ => return None,
         };
 
-        let (variant, missing_fields) =
+        let (variant, missing_fields, _exhaustive) =
             record_pattern_missing_fields(db, infer, pat_id, &body[pat_id])?;
         let res = self.missing_fields(db, krate, substs, variant, missing_fields);
         Some(res)
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs
index b0d71eb3de..0855c1d3a2 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<'_> {
                 });
                 fields.extend(iter);
 
-                Pat::Record { path, args: fields }
+                let ellipsis = record_field_pat_list.dotdot_token().is_some();
+
+                Pat::Record { path, args: fields, ellipsis }
             }
             ast::Pat::SlicePat(p) => {
                 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 197bbe9bd0..e11bdf3ec2 100644
--- a/crates/ra_hir_def/src/expr.rs
+++ b/crates/ra_hir_def/src/expr.rs
@@ -376,35 +376,14 @@ pub enum Pat {
     Wild,
     Tuple(Vec<PatId>),
     Or(Vec<PatId>),
-    Record {
-        path: Option<Path>,
-        args: Vec<RecordFieldPat>,
-        // FIXME: 'ellipsis' option
-    },
-    Range {
-        start: ExprId,
-        end: ExprId,
-    },
-    Slice {
-        prefix: Vec<PatId>,
-        slice: Option<PatId>,
-        suffix: Vec<PatId>,
-    },
+    Record { path: Option<Path>, args: Vec<RecordFieldPat>, ellipsis: bool },
+    Range { start: ExprId, end: ExprId },
+    Slice { prefix: Vec<PatId>, slice: Option<PatId>, suffix: Vec<PatId> },
     Path(Path),
     Lit(ExprId),
-    Bind {
-        mode: BindingAnnotation,
-        name: Name,
-        subpat: Option<PatId>,
-    },
-    TupleStruct {
-        path: Option<Path>,
-        args: Vec<PatId>,
-    },
-    Ref {
-        pat: PatId,
-        mutability: Mutability,
-    },
+    Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> },
+    TupleStruct { path: Option<Path>, args: Vec<PatId> },
+    Ref { pat: PatId, mutability: Mutability },
 }
 
 impl Pat {
diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml
index 59efc1c31d..9a4a7aa6f7 100644
--- a/crates/ra_hir_ty/Cargo.toml
+++ b/crates/ra_hir_ty/Cargo.toml
@@ -24,8 +24,6 @@ ra_prof = { path = "../ra_prof" }
 ra_syntax = { path = "../ra_syntax" }
 test_utils = { path = "../test_utils" }
 
-scoped-tls = "1"
-
 chalk-solve =   { git = "https://github.com/rust-lang/chalk.git", rev = "039fc904a05f8cb3d0c682c9a57a63dda7a35356" }
 chalk-rust-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "039fc904a05f8cb3d0c682c9a57a63dda7a35356" }
 chalk-ir =      { git = "https://github.com/rust-lang/chalk.git", rev = "039fc904a05f8cb3d0c682c9a57a63dda7a35356" }
diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs
index 8cbce11686..3f18acf1da 100644
--- a/crates/ra_hir_ty/src/diagnostics.rs
+++ b/crates/ra_hir_ty/src/diagnostics.rs
@@ -62,6 +62,29 @@ impl AstDiagnostic for MissingFields {
     }
 }
 
+#[derive(Debug)]
+pub struct MissingPatFields {
+    pub file: HirFileId,
+    pub field_list: AstPtr<ast::RecordFieldPatList>,
+    pub missed_fields: Vec<Name>,
+}
+
+impl Diagnostic for MissingPatFields {
+    fn message(&self) -> String {
+        let mut buf = String::from("Missing structure fields:\n");
+        for field in &self.missed_fields {
+            format_to!(buf, "- {}", field);
+        }
+        buf
+    }
+    fn source(&self) -> InFile<SyntaxNodePtr> {
+        InFile { file_id: self.file, value: self.field_list.into() }
+    }
+    fn as_any(&self) -> &(dyn Any + Send + 'static) {
+        self
+    }
+}
+
 #[derive(Debug)]
 pub struct MissingMatchArms {
     pub file: HirFileId,
diff --git a/crates/ra_hir_ty/src/display.rs b/crates/ra_hir_ty/src/display.rs
index d03bbd5a7b..0e9313aa12 100644
--- a/crates/ra_hir_ty/src/display.rs
+++ b/crates/ra_hir_ty/src/display.rs
@@ -247,21 +247,19 @@ impl HirDisplay for ApplicationTy {
                 }
             }
             TypeCtor::Closure { .. } => {
-                let sig = self.parameters[0].callable_sig(f.db);
-                if let Some(sig) = sig {
-                    if sig.params().is_empty() {
-                        write!(f, "||")?;
-                    } else if f.omit_verbose_types() {
-                        write!(f, "|{}|", TYPE_HINT_TRUNCATION)?;
-                    } else {
-                        write!(f, "|")?;
-                        f.write_joined(sig.params(), ", ")?;
-                        write!(f, "|")?;
-                    };
-                    write!(f, " -> {}", sig.ret().display(f.db))?;
+                let sig = self.parameters[0]
+                    .callable_sig(f.db)
+                    .expect("first closure parameter should contain signature");
+                if sig.params().is_empty() {
+                    write!(f, "||")?;
+                } else if f.omit_verbose_types() {
+                    write!(f, "|{}|", TYPE_HINT_TRUNCATION)?;
                 } else {
-                    write!(f, "{{closure}}")?;
-                }
+                    write!(f, "|")?;
+                    f.write_joined(sig.params(), ", ")?;
+                    write!(f, "|")?;
+                };
+                write!(f, " -> {}", sig.ret().display(f.db))?;
             }
         }
         Ok(())
diff --git a/crates/ra_hir_ty/src/expr.rs b/crates/ra_hir_ty/src/expr.rs
index e45e9ea14d..a7c8d74abd 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;
 
 use crate::{
     db::HirDatabase,
-    diagnostics::{MissingFields, MissingMatchArms, MissingOkInTailExpr},
+    diagnostics::{MissingFields, MissingMatchArms, MissingOkInTailExpr, MissingPatFields},
     utils::variant_data,
     ApplicationTy, InferenceResult, Ty, TypeCtor,
     _match::{is_useful, MatchCheckCtx, Matrix, PatStack, Usefulness},
@@ -49,39 +49,97 @@ impl<'a, 'b> ExprValidator<'a, 'b> {
             if let Some((variant_def, missed_fields, true)) =
                 record_literal_missing_fields(db, &self.infer, id, expr)
             {
-                // XXX: only look at source_map if we do have missing fields
-                let (_, source_map) = db.body_with_source_map(self.func.into());
-
-                if let Ok(source_ptr) = source_map.expr_syntax(id) {
-                    if let Some(expr) = source_ptr.value.left() {
-                        let root = source_ptr.file_syntax(db.upcast());
-                        if let ast::Expr::RecordLit(record_lit) = expr.to_node(&root) {
-                            if let Some(field_list) = record_lit.record_field_list() {
-                                let variant_data = variant_data(db.upcast(), variant_def);
-                                let missed_fields = missed_fields
-                                    .into_iter()
-                                    .map(|idx| variant_data.fields()[idx].name.clone())
-                                    .collect();
-                                self.sink.push(MissingFields {
-                                    file: source_ptr.file_id,
-                                    field_list: AstPtr::new(&field_list),
-                                    missed_fields,
-                                })
-                            }
-                        }
-                    }
-                }
+                self.create_record_literal_missing_fields_diagnostic(
+                    id,
+                    db,
+                    variant_def,
+                    missed_fields,
+                );
             }
             if let Expr::Match { expr, arms } = expr {
                 self.validate_match(id, *expr, arms, db, self.infer.clone());
             }
         }
+        for (id, pat) in body.pats.iter() {
+            if let Some((variant_def, missed_fields, true)) =
+                record_pattern_missing_fields(db, &self.infer, id, pat)
+            {
+                self.create_record_pattern_missing_fields_diagnostic(
+                    id,
+                    db,
+                    variant_def,
+                    missed_fields,
+                );
+            }
+        }
         let body_expr = &body[body.body_expr];
         if let Expr::Block { tail: Some(t), .. } = body_expr {
             self.validate_results_in_tail_expr(body.body_expr, *t, db);
         }
     }
 
+    fn create_record_literal_missing_fields_diagnostic(
+        &mut self,
+        id: ExprId,
+        db: &dyn HirDatabase,
+        variant_def: VariantId,
+        missed_fields: Vec<LocalStructFieldId>,
+    ) {
+        // XXX: only look at source_map if we do have missing fields
+        let (_, source_map) = db.body_with_source_map(self.func.into());
+
+        if let Ok(source_ptr) = source_map.expr_syntax(id) {
+            if let Some(expr) = source_ptr.value.left() {
+                let root = source_ptr.file_syntax(db.upcast());
+                if let ast::Expr::RecordLit(record_lit) = expr.to_node(&root) {
+                    if let Some(field_list) = record_lit.record_field_list() {
+                        let variant_data = variant_data(db.upcast(), variant_def);
+                        let missed_fields = missed_fields
+                            .into_iter()
+                            .map(|idx| variant_data.fields()[idx].name.clone())
+                            .collect();
+                        self.sink.push(MissingFields {
+                            file: source_ptr.file_id,
+                            field_list: AstPtr::new(&field_list),
+                            missed_fields,
+                        })
+                    }
+                }
+            }
+        }
+    }
+
+    fn create_record_pattern_missing_fields_diagnostic(
+        &mut self,
+        id: PatId,
+        db: &dyn HirDatabase,
+        variant_def: VariantId,
+        missed_fields: Vec<LocalStructFieldId>,
+    ) {
+        // XXX: only look at source_map if we do have missing fields
+        let (_, source_map) = db.body_with_source_map(self.func.into());
+
+        if let Ok(source_ptr) = source_map.pat_syntax(id) {
+            if let Some(expr) = source_ptr.value.left() {
+                let root = source_ptr.file_syntax(db.upcast());
+                if let ast::Pat::RecordPat(record_pat) = expr.to_node(&root) {
+                    if let Some(field_list) = record_pat.record_field_pat_list() {
+                        let variant_data = variant_data(db.upcast(), variant_def);
+                        let missed_fields = missed_fields
+                            .into_iter()
+                            .map(|idx| variant_data.fields()[idx].name.clone())
+                            .collect();
+                        self.sink.push(MissingPatFields {
+                            file: source_ptr.file_id,
+                            field_list: AstPtr::new(&field_list),
+                            missed_fields,
+                        })
+                    }
+                }
+            }
+        }
+    }
+
     fn validate_match(
         &mut self,
         id: ExprId,
@@ -232,9 +290,9 @@ pub fn record_pattern_missing_fields(
     infer: &InferenceResult,
     id: PatId,
     pat: &Pat,
-) -> Option<(VariantId, Vec<LocalStructFieldId>)> {
-    let fields = match pat {
-        Pat::Record { path: _, args } => args,
+) -> Option<(VariantId, Vec<LocalStructFieldId>, /*exhaustive*/ bool)> {
+    let (fields, exhaustive) = match pat {
+        Pat::Record { path: _, args, ellipsis } => (args, !ellipsis),
         _ => return None,
     };
 
@@ -254,5 +312,5 @@ pub fn record_pattern_missing_fields(
     if missed_fields.is_empty() {
         return None;
     }
-    Some((variant_def, missed_fields))
+    Some((variant_def, missed_fields, exhaustive))
 }
diff --git a/crates/ra_hir_ty/src/infer/pat.rs b/crates/ra_hir_ty/src/infer/pat.rs
index 69bbb4307f..078476f76a 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> {
             Pat::TupleStruct { path: p, args: subpats } => {
                 self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm, pat)
             }
-            Pat::Record { path: p, args: fields } => {
+            Pat::Record { path: p, args: fields, ellipsis: _ } => {
                 self.infer_record_pat(p.as_ref(), fields, expected, default_bm, pat)
             }
             Pat::Path(path) => {
diff --git a/crates/ra_hir_ty/src/traits.rs b/crates/ra_hir_ty/src/traits.rs
index 21e2333799..5a1e12ce92 100644
--- a/crates/ra_hir_ty/src/traits.rs
+++ b/crates/ra_hir_ty/src/traits.rs
@@ -177,7 +177,7 @@ fn solve(
 
     let fuel = std::cell::Cell::new(CHALK_SOLVER_FUEL);
 
-    let should_continue = || {
+    let solution = solver.solve_limited(&context, goal, || {
         context.db.check_canceled();
         let remaining = fuel.get();
         fuel.set(remaining - 1);
@@ -185,21 +185,12 @@ fn solve(
             log::debug!("fuel exhausted");
         }
         remaining > 0
-    };
-    let mut solve = || solver.solve_limited(&context, goal, should_continue);
-    // don't set the TLS for Chalk unless Chalk debugging is active, to make
-    // extra sure we only use it for debugging
-    let solution =
-        if is_chalk_debug() { chalk::tls::set_current_program(db, solve) } else { solve() };
+    });
 
     log::debug!("solve({:?}) => {:?}", goal, solution);
     solution
 }
 
-fn is_chalk_debug() -> bool {
-    std::env::var("CHALK_DEBUG").is_ok()
-}
-
 fn solution_from_chalk(
     db: &dyn HirDatabase,
     solution: chalk_solve::Solution<Interner>,
diff --git a/crates/ra_hir_ty/src/traits/chalk.rs b/crates/ra_hir_ty/src/traits/chalk.rs
index c5f1b52324..1bc0f07135 100644
--- a/crates/ra_hir_ty/src/traits/chalk.rs
+++ b/crates/ra_hir_ty/src/traits/chalk.rs
@@ -20,8 +20,6 @@ use crate::{
     ProjectionTy, Substs, TraitRef, Ty, TypeCtor,
 };
 
-pub(super) mod tls;
-
 #[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
 pub struct Interner;
 
@@ -35,85 +33,90 @@ impl chalk_ir::interner::Interner for Interner {
     type Identifier = TypeAliasId;
     type DefId = InternId;
 
+    // FIXME: implement these
     fn debug_struct_id(
-        type_kind_id: StructId,
-        fmt: &mut fmt::Formatter<'_>,
+        _type_kind_id: chalk_ir::StructId<Self>,
+        _fmt: &mut fmt::Formatter<'_>,
     ) -> Option<fmt::Result> {
-        tls::with_current_program(|prog| Some(prog?.debug_struct_id(type_kind_id, fmt)))
+        None
     }
 
-    fn debug_trait_id(type_kind_id: TraitId, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> {
-        tls::with_current_program(|prog| Some(prog?.debug_trait_id(type_kind_id, fmt)))
+    fn debug_trait_id(
+        _type_kind_id: chalk_ir::TraitId<Self>,
+        _fmt: &mut fmt::Formatter<'_>,
+    ) -> Option<fmt::Result> {
+        None
     }
 
-    fn debug_assoc_type_id(id: AssocTypeId, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> {
-        tls::with_current_program(|prog| Some(prog?.debug_assoc_type_id(id, fmt)))
+    fn debug_assoc_type_id(
+        _id: chalk_ir::AssocTypeId<Self>,
+        _fmt: &mut fmt::Formatter<'_>,
+    ) -> Option<fmt::Result> {
+        None
     }
 
     fn debug_alias(
-        alias: &chalk_ir::AliasTy<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
+        _projection: &chalk_ir::AliasTy<Self>,
+        _fmt: &mut fmt::Formatter<'_>,
     ) -> Option<fmt::Result> {
-        tls::with_current_program(|prog| Some(prog?.debug_alias(alias, fmt)))
+        None
     }
 
-    fn debug_ty(ty: &chalk_ir::Ty<Interner>, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> {
-        tls::with_current_program(|prog| Some(prog?.debug_ty(ty, fmt)))
+    fn debug_ty(_ty: &chalk_ir::Ty<Self>, _fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> {
+        None
     }
 
     fn debug_lifetime(
-        lifetime: &chalk_ir::Lifetime<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
+        _lifetime: &chalk_ir::Lifetime<Self>,
+        _fmt: &mut fmt::Formatter<'_>,
     ) -> Option<fmt::Result> {
-        tls::with_current_program(|prog| Some(prog?.debug_lifetime(lifetime, fmt)))
+        None
     }
 
     fn debug_parameter(
-        parameter: &Parameter<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
+        _parameter: &Parameter<Self>,
+        _fmt: &mut fmt::Formatter<'_>,
     ) -> Option<fmt::Result> {
-        tls::with_current_program(|prog| Some(prog?.debug_parameter(parameter, fmt)))
+        None
     }
 
-    fn debug_goal(goal: &Goal<Interner>, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> {
-        tls::with_current_program(|prog| Some(prog?.debug_goal(goal, fmt)))
+    fn debug_goal(_goal: &Goal<Self>, _fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> {
+        None
     }
 
     fn debug_goals(
-        goals: &chalk_ir::Goals<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
+        _goals: &chalk_ir::Goals<Self>,
+        _fmt: &mut fmt::Formatter<'_>,
     ) -> Option<fmt::Result> {
-        tls::with_current_program(|prog| Some(prog?.debug_goals(goals, fmt)))
+        None
     }
 
     fn debug_program_clause_implication(
-        pci: &chalk_ir::ProgramClauseImplication<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
+        _pci: &chalk_ir::ProgramClauseImplication<Self>,
+        _fmt: &mut fmt::Formatter<'_>,
     ) -> Option<fmt::Result> {
-        tls::with_current_program(|prog| Some(prog?.debug_program_clause_implication(pci, fmt)))
+        None
     }
 
     fn debug_application_ty(
-        application_ty: &chalk_ir::ApplicationTy<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
+        _application_ty: &chalk_ir::ApplicationTy<Self>,
+        _fmt: &mut fmt::Formatter<'_>,
     ) -> Option<fmt::Result> {
-        tls::with_current_program(|prog| Some(prog?.debug_application_ty(application_ty, fmt)))
+        None
     }
 
     fn debug_substitution(
-        substitution: &chalk_ir::Substitution<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
+        _substitution: &chalk_ir::Substitution<Self>,
+        _fmt: &mut fmt::Formatter<'_>,
     ) -> Option<fmt::Result> {
-        tls::with_current_program(|prog| Some(prog?.debug_substitution(substitution, fmt)))
+        None
     }
 
     fn debug_separator_trait_ref(
-        separator_trait_ref: &chalk_ir::SeparatorTraitRef<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
+        _separator_trait_ref: &chalk_ir::SeparatorTraitRef<Self>,
+        _fmt: &mut fmt::Formatter<'_>,
     ) -> Option<fmt::Result> {
-        tls::with_current_program(|prog| {
-            Some(prog?.debug_separator_trait_ref(separator_trait_ref, fmt))
-        })
+        None
     }
 
     fn intern_ty(&self, ty: chalk_ir::TyData<Self>) -> Box<chalk_ir::TyData<Self>> {
diff --git a/crates/ra_hir_ty/src/traits/chalk/tls.rs b/crates/ra_hir_ty/src/traits/chalk/tls.rs
deleted file mode 100644
index d9bbb54a5b..0000000000
--- a/crates/ra_hir_ty/src/traits/chalk/tls.rs
+++ /dev/null
@@ -1,231 +0,0 @@
-//! Implementation of Chalk debug helper functions using TLS.
-use std::fmt;
-
-use chalk_ir::{AliasTy, Goal, Goals, Lifetime, Parameter, ProgramClauseImplication, TypeName};
-
-use super::{from_chalk, Interner};
-use crate::{db::HirDatabase, CallableDef, TypeCtor};
-use hir_def::{AdtId, AssocContainerId, Lookup, TypeAliasId};
-
-pub use unsafe_tls::{set_current_program, with_current_program};
-
-pub struct DebugContext<'a>(&'a (dyn HirDatabase + 'a));
-
-impl DebugContext<'_> {
-    pub fn debug_struct_id(
-        &self,
-        id: super::StructId,
-        f: &mut fmt::Formatter<'_>,
-    ) -> Result<(), fmt::Error> {
-        let type_ctor: TypeCtor = from_chalk(self.0, TypeName::Struct(id));
-        match type_ctor {
-            TypeCtor::Bool => write!(f, "bool")?,
-            TypeCtor::Char => write!(f, "char")?,
-            TypeCtor::Int(t) => write!(f, "{}", t)?,
-            TypeCtor::Float(t) => write!(f, "{}", t)?,
-            TypeCtor::Str => write!(f, "str")?,
-            TypeCtor::Slice => write!(f, "slice")?,
-            TypeCtor::Array => write!(f, "array")?,
-            TypeCtor::RawPtr(m) => write!(f, "*{}", m.as_keyword_for_ptr())?,
-            TypeCtor::Ref(m) => write!(f, "&{}", m.as_keyword_for_ref())?,
-            TypeCtor::Never => write!(f, "!")?,
-            TypeCtor::Tuple { .. } => {
-                write!(f, "()")?;
-            }
-            TypeCtor::FnPtr { .. } => {
-                write!(f, "fn")?;
-            }
-            TypeCtor::FnDef(def) => {
-                let name = match def {
-                    CallableDef::FunctionId(ff) => self.0.function_data(ff).name.clone(),
-                    CallableDef::StructId(s) => self.0.struct_data(s).name.clone(),
-                    CallableDef::EnumVariantId(e) => {
-                        let enum_data = self.0.enum_data(e.parent);
-                        enum_data.variants[e.local_id].name.clone()
-                    }
-                };
-                match def {
-                    CallableDef::FunctionId(_) => write!(f, "{{fn {}}}", name)?,
-                    CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => {
-                        write!(f, "{{ctor {}}}", name)?
-                    }
-                }
-            }
-            TypeCtor::Adt(def_id) => {
-                let name = match def_id {
-                    AdtId::StructId(it) => self.0.struct_data(it).name.clone(),
-                    AdtId::UnionId(it) => self.0.union_data(it).name.clone(),
-                    AdtId::EnumId(it) => self.0.enum_data(it).name.clone(),
-                };
-                write!(f, "{}", name)?;
-            }
-            TypeCtor::AssociatedType(type_alias) => {
-                let trait_ = match type_alias.lookup(self.0.upcast()).container {
-                    AssocContainerId::TraitId(it) => it,
-                    _ => panic!("not an associated type"),
-                };
-                let trait_name = self.0.trait_data(trait_).name.clone();
-                let name = self.0.type_alias_data(type_alias).name.clone();
-                write!(f, "{}::{}", trait_name, name)?;
-            }
-            TypeCtor::Closure { def, expr } => {
-                write!(f, "{{closure {:?} in {:?}}}", expr.into_raw(), def)?;
-            }
-        }
-        Ok(())
-    }
-
-    pub fn debug_trait_id(
-        &self,
-        id: super::TraitId,
-        fmt: &mut fmt::Formatter<'_>,
-    ) -> Result<(), fmt::Error> {
-        let trait_: hir_def::TraitId = from_chalk(self.0, id);
-        let trait_data = self.0.trait_data(trait_);
-        write!(fmt, "{}", trait_data.name)
-    }
-
-    pub fn debug_assoc_type_id(
-        &self,
-        id: super::AssocTypeId,
-        fmt: &mut fmt::Formatter<'_>,
-    ) -> Result<(), fmt::Error> {
-        let type_alias: TypeAliasId = from_chalk(self.0, id);
-        let type_alias_data = self.0.type_alias_data(type_alias);
-        let trait_ = match type_alias.lookup(self.0.upcast()).container {
-            AssocContainerId::TraitId(t) => t,
-            _ => panic!("associated type not in trait"),
-        };
-        let trait_data = self.0.trait_data(trait_);
-        write!(fmt, "{}::{}", trait_data.name, type_alias_data.name)
-    }
-
-    pub fn debug_alias(
-        &self,
-        alias: &AliasTy<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
-    ) -> Result<(), fmt::Error> {
-        let type_alias: TypeAliasId = from_chalk(self.0, alias.associated_ty_id);
-        let type_alias_data = self.0.type_alias_data(type_alias);
-        let trait_ = match type_alias.lookup(self.0.upcast()).container {
-            AssocContainerId::TraitId(t) => t,
-            _ => panic!("associated type not in trait"),
-        };
-        let trait_data = self.0.trait_data(trait_);
-        let params = alias.substitution.parameters(&Interner);
-        write!(
-            fmt,
-            "<{:?} as {}<{:?}>>::{}",
-            &params[0],
-            trait_data.name,
-            &params[1..],
-            type_alias_data.name
-        )
-    }
-
-    pub fn debug_ty(
-        &self,
-        ty: &chalk_ir::Ty<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
-    ) -> Result<(), fmt::Error> {
-        write!(fmt, "{:?}", ty.data(&Interner))
-    }
-
-    pub fn debug_lifetime(
-        &self,
-        lifetime: &Lifetime<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
-    ) -> Result<(), fmt::Error> {
-        write!(fmt, "{:?}", lifetime.data(&Interner))
-    }
-
-    pub fn debug_parameter(
-        &self,
-        parameter: &Parameter<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
-    ) -> Result<(), fmt::Error> {
-        write!(fmt, "{:?}", parameter.data(&Interner).inner_debug())
-    }
-
-    pub fn debug_goal(
-        &self,
-        goal: &Goal<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
-    ) -> Result<(), fmt::Error> {
-        let goal_data = goal.data(&Interner);
-        write!(fmt, "{:?}", goal_data)
-    }
-
-    pub fn debug_goals(
-        &self,
-        goals: &Goals<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
-    ) -> Result<(), fmt::Error> {
-        write!(fmt, "{:?}", goals.debug(&Interner))
-    }
-
-    pub fn debug_program_clause_implication(
-        &self,
-        pci: &ProgramClauseImplication<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
-    ) -> Result<(), fmt::Error> {
-        write!(fmt, "{:?}", pci.debug(&Interner))
-    }
-
-    pub fn debug_application_ty(
-        &self,
-        application_ty: &chalk_ir::ApplicationTy<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
-    ) -> Result<(), fmt::Error> {
-        write!(fmt, "{:?}", application_ty.debug(&Interner))
-    }
-
-    pub fn debug_substitution(
-        &self,
-        substitution: &chalk_ir::Substitution<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
-    ) -> Result<(), fmt::Error> {
-        write!(fmt, "{:?}", substitution.debug(&Interner))
-    }
-
-    pub fn debug_separator_trait_ref(
-        &self,
-        separator_trait_ref: &chalk_ir::SeparatorTraitRef<Interner>,
-        fmt: &mut fmt::Formatter<'_>,
-    ) -> Result<(), fmt::Error> {
-        write!(fmt, "{:?}", separator_trait_ref.debug(&Interner))
-    }
-}
-
-mod unsafe_tls {
-    use super::DebugContext;
-    use crate::db::HirDatabase;
-    use scoped_tls::scoped_thread_local;
-
-    scoped_thread_local!(static PROGRAM: DebugContext);
-
-    pub fn with_current_program<R>(
-        op: impl for<'a> FnOnce(Option<&'a DebugContext<'a>>) -> R,
-    ) -> R {
-        if PROGRAM.is_set() {
-            PROGRAM.with(|prog| op(Some(prog)))
-        } else {
-            op(None)
-        }
-    }
-
-    pub fn set_current_program<OP, R>(p: &dyn HirDatabase, op: OP) -> R
-    where
-        OP: FnOnce() -> R,
-    {
-        let ctx = DebugContext(p);
-        // we're transmuting the lifetime in the DebugContext to static. This is
-        // fine because we only keep the reference for the lifetime of this
-        // function, *and* the only way to access the context is through
-        // `with_current_program`, which hides the lifetime through the `for`
-        // type.
-        let static_p: &DebugContext<'static> =
-            unsafe { std::mem::transmute::<&DebugContext, &DebugContext<'static>>(&ctx) };
-        PROGRAM.set(static_p, || op())
-    }
-}


Test patch:
diff --git a/crates/ra_hir_ty/src/tests.rs b/crates/ra_hir_ty/src/tests.rs
index 002cffba66..47a7b9ffd6 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() {
 
     assert_snapshot!(diagnostics, @r###""###);
 }
+
+#[test]
+fn missing_record_pat_field_diagnostic() {
+    let diagnostics = TestDB::with_files(
+        r"
+        //- /lib.rs
+        struct S { foo: i32, bar: () }
+        fn baz(s: S) {
+            let S { foo: _ } = s;
+        }
+        ",
+    )
+    .diagnostics()
+    .0;
+
+    assert_snapshot!(diagnostics, @r###"
+    "{ foo: _ }": Missing structure fields:
+    - bar
+    "###
+    );
+}
+
+#[test]
+fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {
+    let diagnostics = TestDB::with_files(
+        r"
+        //- /lib.rs
+        struct S { foo: i32, bar: () }
+        fn baz(s: S) -> i32 {
+            match s {
+                S { foo, .. } => foo,
+            }
+        }
+        ",
+    )
+    .diagnostics()
+    .0;
+
+    assert_snapshot!(diagnostics, @"");
+}