Repository: rust-lang/rust-analyzer
Pull request #12680 (2022-07-02 18:43:18)

fix regressions on assignment expressions
This is a follow-up PR on #12428. I'm not sure if this is everything I overlooked, so if there are more things that are not right, we may want to revert #12428.

This should also fix the increase of the type mismatches and the unknown types in diesel in the [metrics](https://rust-analyzer.github.io/metrics/?start=2022-06-23&end=2022-07-01) introduced by #12428.

The regressions are:

- some coercions don't work in the ordinary (i.e. non-destructuring) assignments

    In order for coercions on ADT fields instantiations to work, lhs type has to be known before inferring rhs. #12428 changed the inference order, making rhs inferred before lhs, breaking the coercion, so I restored the original inference mechanism for the ordinary assignments.

    Note that this kind of coercion doesn't happen in destructuring assigments, because when they are desugared, the struct expression is first assigned to a temporary, which is then assigned to the assignee, which is not coercion site anymore.

- type mismatches on individual identifiers are not reported


Patch:
diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs
index 3cb7afef74..6dee0322a9 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(_)) => {
@@ -891,7 +911,15 @@ impl<'a> InferenceContext<'a> {
                 let lhs_ty = self.insert_type_vars_shallow(lhs_ty);
                 let ty = match self.coerce(None, &rhs_ty, &lhs_ty) {
                     Ok(ty) => ty,
-                    Err(_) => self.err_ty(),
+                    Err(_) => {
+                        self.result.type_mismatches.insert(
+                            lhs.into(),
+                            TypeMismatch { expected: rhs_ty.clone(), actual: lhs_ty.clone() },
+                        );
+                        // `rhs_ty` is returned so no further type mismatches are
+                        // reported because of this mismatch.
+                        rhs_ty
+                    }
                 };
                 self.write_expr_ty(lhs, ty.clone());
                 return ty;


Test patch:
diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs
index 0e512ef5ec..bf59fadc2c 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>,)
+}
+        "#,
+    );
+}
diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs
index 535b948371..5b08f55210 100644
--- a/crates/hir-ty/src/tests/simple.rs
+++ b/crates/hir-ty/src/tests/simple.rs
@@ -3043,3 +3043,30 @@ fn main() {
         "#,
     );
 }
+
+#[test]
+fn destructuring_assignment_type_mismatch_on_identifier() {
+    check(
+        r#"
+struct S { v: i64 }
+struct TS(i64);
+fn main() {
+    let mut a: usize = 0;
+    (a,) = (0i64,);
+   //^expected i64, got usize
+
+    let mut a: usize = 0;
+    [a,] = [0i64,];
+   //^expected i64, got usize
+
+    let mut a: usize = 0;
+    S { v: a } = S { v: 0 };
+         //^expected i64, got usize
+
+    let mut a: usize = 0;
+    TS(a) = TS(0);
+     //^expected i64, got usize
+}
+        "#,
+    );
+}