about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/hir-ty/src/infer/expr.rs24
-rw-r--r--crates/hir-ty/src/tests/coercion.rs44
2 files changed, 66 insertions, 2 deletions
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index 3cb7afef749..08abf8a461b 100644
--- a/crates/hir-ty/src/infer/expr.rs
+++ b/crates/hir-ty/src/infer/expr.rs
@@ -593,8 +593,28 @@ impl<'a> InferenceContext<'a> {
             }
             Expr::BinaryOp { lhs, rhs, op } => match op {
                 Some(BinaryOp::Assignment { op: None }) => {
-                    let rhs_ty = self.infer_expr(*rhs, &Expectation::none());
-                    self.infer_assignee_expr(*lhs, &rhs_ty);
+                    let lhs = *lhs;
+                    let is_ordinary = match &self.body[lhs] {
+                        Expr::Array(_)
+                        | Expr::RecordLit { .. }
+                        | Expr::Tuple { .. }
+                        | Expr::Underscore => false,
+                        Expr::Call { callee, .. } => !matches!(&self.body[*callee], Expr::Path(_)),
+                        _ => true,
+                    };
+
+                    // In ordinary (non-destructuring) assignments, the type of
+                    // `lhs` must be inferred first so that the ADT fields
+                    // instantiations in RHS can be coerced to it. Note that this
+                    // cannot happen in destructuring assignments because of how
+                    // they are desugared.
+                    if is_ordinary {
+                        let lhs_ty = self.infer_expr(lhs, &Expectation::none());
+                        self.infer_expr_coerce(*rhs, &Expectation::has_type(lhs_ty));
+                    } else {
+                        let rhs_ty = self.infer_expr(*rhs, &Expectation::none());
+                        self.infer_assignee_expr(lhs, &rhs_ty);
+                    }
                     self.result.standard_types.unit.clone()
                 }
                 Some(BinaryOp::LogicOp(_)) => {
diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs
index 0e512ef5ec9..bf59fadc2c3 100644
--- a/crates/hir-ty/src/tests/coercion.rs
+++ b/crates/hir-ty/src/tests/coercion.rs
@@ -709,3 +709,47 @@ fn test() {
         "#,
     );
 }
+
+#[test]
+fn assign_coerce_struct_fields() {
+    check_no_mismatches(
+        r#"
+//- minicore: coerce_unsized
+struct S;
+trait Tr {}
+impl Tr for S {}
+struct V<T> { t: T }
+
+fn main() {
+    let a: V<&dyn Tr>;
+    a = V { t: &S };
+
+    let mut a: V<&dyn Tr> = V { t: &S };
+    a = V { t: &S };
+}
+        "#,
+    );
+}
+
+#[test]
+fn destructuring_assign_coerce_struct_fields() {
+    check(
+        r#"
+//- minicore: coerce_unsized
+struct S;
+trait Tr {}
+impl Tr for S {}
+struct V<T> { t: T }
+
+fn main() {
+    let a: V<&dyn Tr>;
+    (a,) = V { t: &S };
+  //^^^^expected V<&S>, got (V<&dyn Tr>,)
+
+    let mut a: V<&dyn Tr> = V { t: &S };
+    (a,) = V { t: &S };
+  //^^^^expected V<&S>, got (V<&dyn Tr>,)
+}
+        "#,
+    );
+}