about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLukas Wirth <lukastw97@gmail.com>2023-09-09 10:45:29 +0200
committerLukas Wirth <lukastw97@gmail.com>2023-09-09 10:45:29 +0200
commit8f5fee4a5aab72399da267fca256090c03c55ff6 (patch)
tree162d32458bf6ff867c0fc5df1c4cb68e87ca7a62
parent55c75450fb3143f5624c4c59d9c420cde6a54448 (diff)
downloadrust-8f5fee4a5aab72399da267fca256090c03c55ff6.tar.gz
rust-8f5fee4a5aab72399da267fca256090c03c55ff6.zip
Diagnose incorrect and private fields in record structs
-rw-r--r--crates/hir-def/src/body.rs9
-rw-r--r--crates/hir-def/src/body/lower.rs15
-rw-r--r--crates/hir-ty/src/infer.rs2
-rw-r--r--crates/hir-ty/src/infer/expr.rs12
-rw-r--r--crates/hir-ty/src/infer/pat.rs117
-rw-r--r--crates/hir-ty/src/mir.rs4
-rw-r--r--crates/hir/src/diagnostics.rs2
-rw-r--r--crates/hir/src/lib.rs19
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_match_arms.rs1
-rw-r--r--crates/ide-diagnostics/src/handlers/no_such_field.rs94
10 files changed, 193 insertions, 82 deletions
diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs
index f8d492d0e52..26fd58c0a40 100644
--- a/crates/hir-def/src/body.rs
+++ b/crates/hir-def/src/body.rs
@@ -65,6 +65,8 @@ pub type LabelSource = InFile<LabelPtr>;
 
 pub type FieldPtr = AstPtr<ast::RecordExprField>;
 pub type FieldSource = InFile<FieldPtr>;
+pub type PatFieldPtr = AstPtr<ast::RecordPatField>;
+pub type PatFieldSource = InFile<PatFieldPtr>;
 
 /// An item body together with the mapping from syntax nodes to HIR expression
 /// IDs. This is needed to go from e.g. a position in a file to the HIR
@@ -90,8 +92,8 @@ pub struct BodySourceMap {
 
     /// We don't create explicit nodes for record fields (`S { record_field: 92 }`).
     /// Instead, we use id of expression (`92`) to identify the field.
-    field_map: FxHashMap<FieldSource, ExprId>,
     field_map_back: FxHashMap<ExprId, FieldSource>,
+    pat_field_map_back: FxHashMap<PatId, PatFieldSource>,
 
     expansions: FxHashMap<InFile<AstPtr<ast::MacroCall>>, HirFileId>,
 
@@ -375,9 +377,8 @@ impl BodySourceMap {
         self.field_map_back[&expr].clone()
     }
 
-    pub fn node_field(&self, node: InFile<&ast::RecordExprField>) -> Option<ExprId> {
-        let src = node.map(AstPtr::new);
-        self.field_map.get(&src).cloned()
+    pub fn pat_field_syntax(&self, pat: PatId) -> PatFieldSource {
+        self.pat_field_map_back[&pat].clone()
     }
 
     pub fn macro_expansion_expr(&self, node: InFile<&ast::MacroExpr>) -> Option<ExprId> {
diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs
index 38073ce3da5..cc02df80a8f 100644
--- a/crates/hir-def/src/body/lower.rs
+++ b/crates/hir-def/src/body/lower.rs
@@ -446,7 +446,6 @@ impl ExprCollector<'_> {
                                 None => self.missing_expr(),
                             };
                             let src = self.expander.to_source(AstPtr::new(&field));
-                            self.source_map.field_map.insert(src.clone(), expr);
                             self.source_map.field_map_back.insert(expr, src);
                             Some(RecordLitField { name, expr })
                         })
@@ -1330,23 +1329,21 @@ impl ExprCollector<'_> {
             ast::Pat::RecordPat(p) => {
                 let path =
                     p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new);
-                let args = p
-                    .record_pat_field_list()
-                    .expect("every struct should have a field list")
+                let record_pat_field_list =
+                    &p.record_pat_field_list().expect("every struct should have a field list");
+                let args = record_pat_field_list
                     .fields()
                     .filter_map(|f| {
                         let ast_pat = f.pat()?;
                         let pat = self.collect_pat(ast_pat, binding_list);
                         let name = f.field_name()?.as_name();
+                        let src = self.expander.to_source(AstPtr::new(&f));
+                        self.source_map.pat_field_map_back.insert(pat, src);
                         Some(RecordFieldPat { name, pat })
                     })
                     .collect();
 
-                let ellipsis = p
-                    .record_pat_field_list()
-                    .expect("every struct should have a field list")
-                    .rest_pat()
-                    .is_some();
+                let ellipsis = record_pat_field_list.rest_pat().is_some();
 
                 Pat::Record { path, args, ellipsis }
             }
diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs
index 3423e0f0120..78d3c667a1f 100644
--- a/crates/hir-ty/src/infer.rs
+++ b/crates/hir-ty/src/infer.rs
@@ -194,7 +194,7 @@ pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>;
 #[derive(Debug, PartialEq, Eq, Clone)]
 pub enum InferenceDiagnostic {
     NoSuchField {
-        expr: ExprId,
+        field: ExprOrPatId,
         private: bool,
     },
     PrivateField {
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index e283db81fe8..0c3c725a7c7 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -527,7 +527,7 @@ impl InferenceContext<'_> {
                     self.write_variant_resolution(tgt_expr.into(), variant);
                 }
                 match def_id {
-                    Some(_) if fields.is_empty() => {}
+                    _ if fields.is_empty() => {}
                     Some(def) => {
                         let field_types = self.db.field_types(def);
                         let variant_data = def.variant_data(self.db.upcast());
@@ -542,16 +542,16 @@ impl InferenceContext<'_> {
                                         ) {
                                             self.push_diagnostic(
                                                 InferenceDiagnostic::NoSuchField {
-                                                    expr: field.expr,
+                                                    field: field.expr.into(),
                                                     private: true,
                                                 },
                                             );
                                         }
-                                        Some(FieldId { parent: def, local_id })
+                                        Some(local_id)
                                     }
                                     None => {
                                         self.push_diagnostic(InferenceDiagnostic::NoSuchField {
-                                            expr: field.expr,
+                                            field: field.expr.into(),
                                             private: false,
                                         });
                                         None
@@ -559,7 +559,7 @@ impl InferenceContext<'_> {
                                 }
                             };
                             let field_ty = field_def.map_or(self.err_ty(), |it| {
-                                field_types[it.local_id].clone().substitute(Interner, &substs)
+                                field_types[it].clone().substitute(Interner, &substs)
                             });
 
                             // Field type might have some unknown types
@@ -1154,7 +1154,7 @@ impl InferenceContext<'_> {
             Expr::Underscore => rhs_ty.clone(),
             _ => {
                 // `lhs` is a place expression, a unit struct, or an enum variant.
-                let lhs_ty = self.infer_expr(lhs, &Expectation::none());
+                let lhs_ty = self.infer_expr_inner(lhs, &Expectation::none());
 
                 // This is the only branch where this function may coerce any type.
                 // We are returning early to avoid the unifiability check below.
diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs
index 7d88e42e52a..4e28ec06023 100644
--- a/crates/hir-ty/src/infer/pat.rs
+++ b/crates/hir-ty/src/infer/pat.rs
@@ -92,24 +92,51 @@ impl InferenceContext<'_> {
         let substs =
             ty.as_adt().map(|(_, s)| s.clone()).unwrap_or_else(|| Substitution::empty(Interner));
 
-        let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default();
-        let (pre, post) = match ellipsis {
-            Some(idx) => subs.split_at(idx),
-            None => (subs, &[][..]),
-        };
-        let post_idx_offset = field_tys.iter().count().saturating_sub(post.len());
-
-        let pre_iter = pre.iter().enumerate();
-        let post_iter = (post_idx_offset..).zip(post.iter());
-        for (i, &subpat) in pre_iter.chain(post_iter) {
-            let expected_ty = var_data
-                .as_ref()
-                .and_then(|d| d.field(&Name::new_tuple_field(i)))
-                .map_or(self.err_ty(), |field| {
-                    field_tys[field].clone().substitute(Interner, &substs)
-                });
-            let expected_ty = self.normalize_associated_types_in(expected_ty);
-            T::infer(self, subpat, &expected_ty, default_bm);
+        match def {
+            _ if subs.len() == 0 => {}
+            Some(def) => {
+                let field_types = self.db.field_types(def);
+                let variant_data = def.variant_data(self.db.upcast());
+                let visibilities = self.db.field_visibilities(def);
+
+                let (pre, post) = match ellipsis {
+                    Some(idx) => subs.split_at(idx),
+                    None => (subs, &[][..]),
+                };
+                let post_idx_offset = field_types.iter().count().saturating_sub(post.len());
+
+                let pre_iter = pre.iter().enumerate();
+                let post_iter = (post_idx_offset..).zip(post.iter());
+
+                for (i, &subpat) in pre_iter.chain(post_iter) {
+                    let field_def = {
+                        match variant_data.field(&Name::new_tuple_field(i)) {
+                            Some(local_id) => {
+                                if !visibilities[local_id]
+                                    .is_visible_from(self.db.upcast(), self.resolver.module())
+                                {
+                                    // FIXME(DIAGNOSE): private tuple field
+                                }
+                                Some(local_id)
+                            }
+                            None => None,
+                        }
+                    };
+
+                    let expected_ty = field_def.map_or(self.err_ty(), |f| {
+                        field_types[f].clone().substitute(Interner, &substs)
+                    });
+                    let expected_ty = self.normalize_associated_types_in(expected_ty);
+
+                    T::infer(self, subpat, &expected_ty, default_bm);
+                }
+            }
+            None => {
+                let err_ty = self.err_ty();
+                for &inner in subs {
+                    T::infer(self, inner, &err_ty, default_bm);
+                }
+            }
         }
 
         ty
@@ -122,7 +149,7 @@ impl InferenceContext<'_> {
         expected: &Ty,
         default_bm: T::BindingMode,
         id: T,
-        subs: impl Iterator<Item = (Name, T)>,
+        subs: impl Iterator<Item = (Name, T)> + ExactSizeIterator,
     ) -> Ty {
         let (ty, def) = self.resolve_variant(path, false);
         if let Some(variant) = def {
@@ -134,17 +161,51 @@ impl InferenceContext<'_> {
         let substs =
             ty.as_adt().map(|(_, s)| s.clone()).unwrap_or_else(|| Substitution::empty(Interner));
 
-        let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default();
-        let var_data = def.map(|it| it.variant_data(self.db.upcast()));
+        match def {
+            _ if subs.len() == 0 => {}
+            Some(def) => {
+                let field_types = self.db.field_types(def);
+                let variant_data = def.variant_data(self.db.upcast());
+                let visibilities = self.db.field_visibilities(def);
+
+                for (name, inner) in subs {
+                    let field_def = {
+                        match variant_data.field(&name) {
+                            Some(local_id) => {
+                                if !visibilities[local_id]
+                                    .is_visible_from(self.db.upcast(), self.resolver.module())
+                                {
+                                    self.push_diagnostic(InferenceDiagnostic::NoSuchField {
+                                        field: inner.into(),
+                                        private: true,
+                                    });
+                                }
+                                Some(local_id)
+                            }
+                            None => {
+                                self.push_diagnostic(InferenceDiagnostic::NoSuchField {
+                                    field: inner.into(),
+                                    private: false,
+                                });
+                                None
+                            }
+                        }
+                    };
 
-        for (name, inner) in subs {
-            let expected_ty = var_data
-                .as_ref()
-                .and_then(|it| it.field(&name))
-                .map_or(self.err_ty(), |f| field_tys[f].clone().substitute(Interner, &substs));
-            let expected_ty = self.normalize_associated_types_in(expected_ty);
+                    let expected_ty = field_def.map_or(self.err_ty(), |f| {
+                        field_types[f].clone().substitute(Interner, &substs)
+                    });
+                    let expected_ty = self.normalize_associated_types_in(expected_ty);
 
-            T::infer(self, inner, &expected_ty, default_bm);
+                    T::infer(self, inner, &expected_ty, default_bm);
+                }
+            }
+            None => {
+                let err_ty = self.err_ty();
+                for (_, inner) in subs {
+                    T::infer(self, inner, &err_ty, default_bm);
+                }
+            }
         }
 
         ty
diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs
index 3ee141b5536..e953058ccca 100644
--- a/crates/hir-ty/src/mir.rs
+++ b/crates/hir-ty/src/mir.rs
@@ -166,8 +166,8 @@ impl<V, T> ProjectionElem<V, T> {
                 TyKind::Adt(_, subst) => {
                     db.field_types(f.parent)[f.local_id].clone().substitute(Interner, subst)
                 }
-                _ => {
-                    never!("Only adt has field");
+                ty => {
+                    never!("Only adt has field, found {:?}", ty);
                     return TyKind::Error.intern(Interner);
                 }
             },
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index d9a6e6fcd55..479138b67f8 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -173,7 +173,7 @@ pub struct MalformedDerive {
 
 #[derive(Debug)]
 pub struct NoSuchField {
-    pub field: InFile<AstPtr<ast::RecordExprField>>,
+    pub field: InFile<Either<AstPtr<ast::RecordExprField>, AstPtr<ast::RecordPatField>>>,
     pub private: bool,
 }
 
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index ebb8539a66d..453f9b93737 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -1503,11 +1503,19 @@ impl DefWithBody {
         let infer = db.infer(self.into());
         let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1);
         let expr_syntax = |expr| source_map.expr_syntax(expr).expect("unexpected synthetic");
+        let pat_syntax = |pat| source_map.pat_syntax(pat).expect("unexpected synthetic");
         for d in &infer.diagnostics {
             match d {
-                &hir_ty::InferenceDiagnostic::NoSuchField { expr, private } => {
-                    let field = source_map.field_syntax(expr);
-                    acc.push(NoSuchField { field, private }.into())
+                &hir_ty::InferenceDiagnostic::NoSuchField { field: expr, private } => {
+                    let expr_or_pat = match expr {
+                        ExprOrPatId::ExprId(expr) => {
+                            source_map.field_syntax(expr).map(Either::Left)
+                        }
+                        ExprOrPatId::PatId(pat) => {
+                            source_map.pat_field_syntax(pat).map(Either::Right)
+                        }
+                    };
+                    acc.push(NoSuchField { field: expr_or_pat, private }.into())
                 }
                 &hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
                     acc.push(
@@ -1523,10 +1531,7 @@ impl DefWithBody {
                 &hir_ty::InferenceDiagnostic::PrivateAssocItem { id, item } => {
                     let expr_or_pat = match id {
                         ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(Either::Left),
-                        ExprOrPatId::PatId(pat) => source_map
-                            .pat_syntax(pat)
-                            .expect("unexpected synthetic")
-                            .map(Either::Right),
+                        ExprOrPatId::PatId(pat) => pat_syntax(pat).map(Either::Right),
                     };
                     let item = item.into();
                     acc.push(PrivateAssocItem { expr_or_pat, item }.into())
diff --git a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
index d5115c2192b..06b03d3d198 100644
--- a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
@@ -849,6 +849,7 @@ fn main() {
 struct Foo { }
 fn main(f: Foo) {
     match f { Foo { bar } => () }
+                 // ^^^ error: no such field
 }
 "#,
         );
diff --git a/crates/ide-diagnostics/src/handlers/no_such_field.rs b/crates/ide-diagnostics/src/handlers/no_such_field.rs
index 34f2b7bbf15..290c16c9d24 100644
--- a/crates/ide-diagnostics/src/handlers/no_such_field.rs
+++ b/crates/ide-diagnostics/src/handlers/no_such_field.rs
@@ -1,3 +1,4 @@
+use either::Either;
 use hir::{db::ExpandDatabase, HasSource, HirDisplay, Semantics};
 use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
 use syntax::{
@@ -12,30 +13,39 @@ use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext};
 //
 // This diagnostic is triggered if created structure does not have field provided in record.
 pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic {
-    Diagnostic::new_with_syntax_node_ptr(
-        ctx,
-        if d.private {
-            DiagnosticCode::RustcHardError("E0451")
-        } else {
-            DiagnosticCode::RustcHardError("E0559")
-        },
-        if d.private { "field is private" } else { "no such field" },
-        d.field.clone().map(|it| it.into()),
-    )
-    .with_fixes(fixes(ctx, d))
+    let node = d.field.clone().map(|it| it.either(Into::into, Into::into));
+    if d.private {
+        // FIXME: quickfix to add required visibility
+        Diagnostic::new_with_syntax_node_ptr(
+            ctx,
+            DiagnosticCode::RustcHardError("E0451"),
+            "field is private",
+            node,
+        )
+    } else {
+        Diagnostic::new_with_syntax_node_ptr(
+            ctx,
+            DiagnosticCode::RustcHardError("E0559"),
+            "no such field",
+            node,
+        )
+        .with_fixes(fixes(ctx, d))
+    }
 }
 
 fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
-    if d.private {
-        // FIXME: quickfix to add required visibility
-        return None;
+    // FIXME: quickfix for pattern
+    match &d.field.value {
+        Either::Left(ptr) => {
+            let root = ctx.sema.db.parse_or_expand(d.field.file_id);
+            missing_record_expr_field_fixes(
+                &ctx.sema,
+                d.field.file_id.original_file(ctx.sema.db),
+                &ptr.to_node(&root),
+            )
+        }
+        _ => None,
     }
-    let root = ctx.sema.db.parse_or_expand(d.field.file_id);
-    missing_record_expr_field_fixes(
-        &ctx.sema,
-        d.field.file_id.original_file(ctx.sema.db),
-        &d.field.value.to_node(&root),
-    )
 }
 
 fn missing_record_expr_field_fixes(
@@ -126,13 +136,34 @@ mod tests {
             r#"
 struct S { foo: i32, bar: () }
 impl S {
-    fn new() -> S {
+    fn new(
+        s@S {
+        //^ 💡 error: missing structure fields:
+        //|    - bar
+            foo,
+            baz: baz2,
+          //^^^^^^^^^ error: no such field
+            qux
+          //^^^ error: no such field
+        }: S
+    ) -> S {
+        S {
+      //^ 💡 error: missing structure fields:
+      //|    - bar
+            foo,
+            baz: baz2,
+          //^^^^^^^^^ error: no such field
+            qux
+          //^^^ error: no such field
+        } = s;
         S {
       //^ 💡 error: missing structure fields:
       //|    - bar
             foo: 92,
             baz: 62,
           //^^^^^^^ 💡 error: no such field
+            qux
+          //^^^ error: no such field
         }
     }
 }
@@ -310,13 +341,28 @@ fn main() {
             r#"
 mod m {
     pub struct Struct {
-        field: u32
+        field: u32,
+        field2: u32,
     }
 }
-fn main() {
+fn f(s@m::Struct {
+    field: f,
+  //^^^^^^^^ error: field is private
+    field2
+  //^^^^^^ error: field is private
+}: m::Struct) {
+    // assignee expression
+    m::Struct {
+        field: 0,
+      //^^^^^^^^ error: field is private
+        field2
+      //^^^^^^ error: field is private
+    } = s;
     m::Struct {
-        field: 0
+        field: 0,
       //^^^^^^^^ error: field is private
+        field2
+      //^^^^^^ error: field is private
     };
 }
 "#,