Repository: rust-analyzer/rust-analyzer (rust)

Issue:
Assertion error in unification
> equating two type variables, both of which have known types: Tuple([]) and Tuple([])


backtrace:

```
thread '<unnamed>' panicked at 'equating two type variables, both of which have known types: Tuple([]) and Tuple([])', crates/ra_hir/src/ty.rs:93:67
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
stack backtrace:
   0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
             at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1: std::sys_common::backtrace::print
             at libstd/sys_common/backtrace.rs:71
             at libstd/sys_common/backtrace.rs:59
   2: std::panicking::default_hook::{{closure}}
             at libstd/panicking.rs:211
   3: std::panicking::default_hook
             at libstd/panicking.rs:227
   4: std::panicking::rust_panic_with_hook
             at libstd/panicking.rs:476
   5: std::panicking::continue_panic_fmt
             at libstd/panicking.rs:390
   6: std::panicking::begin_panic_fmt
             at libstd/panicking.rs:345
   7: <ra_hir::ty::TypeVarValue as ena::unify::UnifyValue>::unify_values
             at crates/ra_hir/src/ty.rs:93
   8: <ena::unify::UnificationTable<S>>::union_value
             at /home/matklad/.cargo/registry/src/github.com-1ecc6299db9ec823/ena-0.11.0/src/unify/mod.rs:493
             at /home/matklad/.cargo/registry/src/github.com-1ecc6299db9ec823/ena-0.11.0/src/unify/mod.rs:441
   9: <ra_hir::ty::InferenceContext<'a, D>>::unify
             at ./crates/ra_hir/src/ty.rs:697
  10: <ra_hir::ty::InferenceContext<'a, D>>::infer_expr
             at ./crates/ra_hir/src/ty.rs:983
  11: ra_hir::ty::infer
             at ./crates/ra_hir/src/ty.rs:1047
             at ./crates/ra_hir/src/ty.rs:1067
  12: <salsa::runtime::Runtime<DB>>::execute_query_implementation
             at ./<::salsa::query_group macros>:58
             at /home/matklad/.cargo/git/checkouts/salsa-3f2277f1661c3426/88313c8/src/derived.rs:346
             at /home/matklad/.cargo/git/checkouts/salsa-3f2277f1661c3426/88313c8/src/runtime.rs:310
  13: <salsa::derived::DerivedStorage<DB, Q, MP>>::read_upgrade
             at /home/matklad/.cargo/git/checkouts/salsa-3f2277f1661c3426/88313c8/src/derived.rs:339
  14: <salsa::derived::DerivedStorage<DB, Q, MP> as salsa::plumbing::QueryStorageOps<DB, Q>>::try_fetch
             at /home/matklad/.cargo/git/checkouts/salsa-3f2277f1661c3426/88313c8/src/derived.rs:268
             at /home/matklad/.cargo/git/checkouts/salsa-3f2277f1661c3426/88313c8/src/derived.rs:673
  15: <salsa::QueryTable<'_, DB, Q>>::get
             at /home/matklad/.cargo/git/checkouts/salsa-3f2277f1661c3426/88313c8/src/lib.rs:378
  16: ra_ide_api::completion::complete_dot::complete_dot
             at ./<::salsa::query_group macros>:25
             at ./crates/ra_hir/src/code_model_api.rs:253
             at crates/ra_ide_api/src/completion/complete_dot.rs:12
  17: ra_ide_api::completion::completions
             at crates/ra_ide_api/src/completion.rs:62
  18: std::panicking::try::do_call
             at crates/ra_ide_api/src/lib.rs:487
             at ./crates/ra_db/src/lib.rs:34
             at libstd/panicking.rs:310
  19: __rust_maybe_catch_panic
             at libpanic_unwind/lib.rs:102
  20: ra_db::BaseDatabase::catch_canceled
             at libstd/panicking.rs:289
             at libstd/panic.rs:392
             at ./crates/ra_db/src/lib.rs:34
  21: ra_ide_api::Analysis::completions
             at crates/ra_ide_api/src/lib.rs:485
  22: ra_lsp_server::main_loop::handlers::handle_completion
             at crates/ra_lsp_server/src/main_loop/handlers.rs:430
  23: <F as threadpool::FnBox>::call_box
             at crates/ra_lsp_server/src/main_loop.rs:421
             at /home/matklad/.cargo/registry/src/github.com-1ecc6299db9ec823/threadpool-1.7.1/src/lib.rs:95
```

cc @flodiebold 

Pull request description:
Fix assertion error in unification (hopefully)
Currently, all types that we handle during inference need to be resolved as far
as possible at the time. It's maybe too brittle of an invariant; I need to think
how we can do this better. This should fix #484 though, I hope (if
it's the same case as I managed to reproduce).

Patch:
diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs
index 0c24a06527..2d533eb6a9 100644
--- a/crates/ra_hir/src/ty.rs
+++ b/crates/ra_hir/src/ty.rs
@@ -18,6 +18,7 @@ mod primitive;
 #[cfg(test)]
 mod tests;
 
+use std::borrow::Cow;
 use std::ops::Index;
 use std::sync::Arc;
 use std::{fmt, mem};
@@ -671,7 +672,10 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
     }
 
     fn unify(&mut self, ty1: &Ty, ty2: &Ty) -> bool {
-        match (ty1, ty2) {
+        // try to resolve type vars first
+        let ty1 = self.resolve_ty_shallow(ty1);
+        let ty2 = self.resolve_ty_shallow(ty2);
+        match (&*ty1, &*ty2) {
             (Ty::Unknown, ..) => true,
             (.., Ty::Unknown) => true,
             (Ty::Bool, _)
@@ -698,10 +702,12 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
                 .zip(ts2.iter())
                 .all(|(t1, t2)| self.unify(t1, t2)),
             (Ty::Infer(InferTy::TypeVar(tv1)), Ty::Infer(InferTy::TypeVar(tv2))) => {
+                // both type vars are unknown since we tried to resolve them
                 self.var_unification_table.union(*tv1, *tv2);
                 true
             }
             (Ty::Infer(InferTy::TypeVar(tv)), other) | (other, Ty::Infer(InferTy::TypeVar(tv))) => {
+                // the type var is unknown since we tried to resolve it
                 self.var_unification_table
                     .union_value(*tv, TypeVarValue::Known(other.clone()));
                 true
@@ -746,6 +752,23 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
         })
     }
 
+    /// If `ty` is a type variable with known type, returns that type;
+    /// otherwise, return ty.
+    fn resolve_ty_shallow<'b>(&mut self, ty: &'b Ty) -> Cow<'b, Ty> {
+        match ty {
+            Ty::Infer(InferTy::TypeVar(tv)) => {
+                match self.var_unification_table.probe_value(*tv).known() {
+                    Some(known_ty) => {
+                        // The known_ty can't be a type var itself
+                        Cow::Owned(known_ty.clone())
+                    }
+                    _ => Cow::Borrowed(ty),
+                }
+            }
+            _ => Cow::Borrowed(ty),
+        }
+    }
+
     /// Resolves the type completely; type variables without known type are
     /// replaced by Ty::Unknown.
     fn resolve_ty_completely(&mut self, ty: Ty) -> Ty {
@@ -816,12 +839,15 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
                 // if let is desugared to match, so this is always simple if
                 self.infer_expr(*condition, &Expectation::has_type(Ty::Bool))?;
                 let then_ty = self.infer_expr(*then_branch, expected)?;
-                if let Some(else_branch) = else_branch {
-                    self.infer_expr(*else_branch, expected)?;
-                } else {
-                    // no else branch -> unit
-                    self.unify(&expected.ty, &Ty::unit()); // actually coerce
-                }
+                match else_branch {
+                    Some(else_branch) => {
+                        self.infer_expr(*else_branch, expected)?;
+                    }
+                    None => {
+                        // no else branch -> unit
+                        self.unify(&then_ty, &Ty::unit()); // actually coerce
+                    }
+                };
                 then_ty
             }
             Expr::Block { statements, tail } => self.infer_block(statements, *tail, expected)?,


Test patch:
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
index d8c0af3268..815aecda76 100644
--- a/crates/ra_hir/src/ty/tests.rs
+++ b/crates/ra_hir/src/ty/tests.rs
@@ -230,6 +230,18 @@ fn test2(a1: *const A, a2: *mut A) {
     );
 }
 
+#[test]
+fn infer_bug_484() {
+    check_inference(
+        r#"
+fn test() {
+   let x = if true {};
+}
+"#,
+        "bug_484.txt",
+    );
+}
+
 fn infer(content: &str) -> String {
     let (db, _, file_id) = MockDatabase::with_single_file(content);
     let source_file = db.source_file(file_id);
diff --git a/crates/ra_hir/src/ty/tests/data/bug_484.txt b/crates/ra_hir/src/ty/tests/data/bug_484.txt
new file mode 100644
index 0000000000..3005305513
--- /dev/null
+++ b/crates/ra_hir/src/ty/tests/data/bug_484.txt
@@ -0,0 +1,5 @@
+[11; 37) '{    l... {}; }': ()
+[20; 21) 'x': ()
+[24; 34) 'if true {}': ()
+[27; 31) 'true': bool
+[32; 34) '{}': ()