Repository: rust-analyzer/rust-analyzer
Pull request #686 (2019-01-26 22:48:55)

Handle cycles in type vars
This might be the cause of #587.


Patch:
diff --git a/crates/ra_hir/src/marks.rs b/crates/ra_hir/src/marks.rs
index 338ed0516a..d704c3adb8 100644
--- a/crates/ra_hir/src/marks.rs
+++ b/crates/ra_hir/src/marks.rs
@@ -1,4 +1,6 @@
 test_utils::marks!(
     name_res_works_for_broken_modules
     item_map_enum_importing
+    type_var_cycles_resolve_completely
+    type_var_cycles_resolve_as_possible
 );
diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs
index 179ebddeec..31ea457062 100644
--- a/crates/ra_hir/src/ty.rs
+++ b/crates/ra_hir/src/ty.rs
@@ -29,6 +29,8 @@ use ra_arena::map::ArenaMap;
 use join_to_string::join;
 use rustc_hash::FxHashMap;
 
+use test_utils::tested_by;
+
 use crate::{
     Module, Function, Struct, StructField, Enum, EnumVariant, Path, Name, ImplBlock,
     FnSignature, FnScopes, ModuleDef, AdtDef,
@@ -862,14 +864,15 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
     }
 
     fn resolve_all(mut self) -> InferenceResult {
+        let mut tv_stack = Vec::new();
         let mut expr_types = mem::replace(&mut self.type_of_expr, ArenaMap::default());
         for ty in expr_types.values_mut() {
-            let resolved = self.resolve_ty_completely(mem::replace(ty, Ty::Unknown));
+            let resolved = self.resolve_ty_completely(&mut tv_stack, mem::replace(ty, Ty::Unknown));
             *ty = resolved;
         }
         let mut pat_types = mem::replace(&mut self.type_of_pat, ArenaMap::default());
         for ty in pat_types.values_mut() {
-            let resolved = self.resolve_ty_completely(mem::replace(ty, Ty::Unknown));
+            let resolved = self.resolve_ty_completely(&mut tv_stack, mem::replace(ty, Ty::Unknown));
             *ty = resolved;
         }
         InferenceResult {
@@ -1014,13 +1017,21 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
     /// by their known types. All types returned by the infer_* functions should
     /// be resolved as far as possible, i.e. contain no type variables with
     /// known type.
-    fn resolve_ty_as_possible(&mut self, ty: Ty) -> Ty {
+    fn resolve_ty_as_possible(&mut self, tv_stack: &mut Vec<TypeVarId>, ty: Ty) -> Ty {
         ty.fold(&mut |ty| match ty {
             Ty::Infer(tv) => {
                 let inner = tv.to_inner();
+                if tv_stack.contains(&inner) {
+                    tested_by!(type_var_cycles_resolve_as_possible);
+                    // recursive type
+                    return tv.fallback_value();
+                }
                 if let Some(known_ty) = self.var_unification_table.probe_value(inner).known() {
                     // known_ty may contain other variables that are known by now
-                    self.resolve_ty_as_possible(known_ty.clone())
+                    tv_stack.push(inner);
+                    let result = self.resolve_ty_as_possible(tv_stack, known_ty.clone());
+                    tv_stack.pop();
+                    result
                 } else {
                     ty
                 }
@@ -1049,13 +1060,21 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
 
     /// Resolves the type completely; type variables without known type are
     /// replaced by Ty::Unknown.
-    fn resolve_ty_completely(&mut self, ty: Ty) -> Ty {
+    fn resolve_ty_completely(&mut self, tv_stack: &mut Vec<TypeVarId>, ty: Ty) -> Ty {
         ty.fold(&mut |ty| match ty {
             Ty::Infer(tv) => {
                 let inner = tv.to_inner();
+                if tv_stack.contains(&inner) {
+                    tested_by!(type_var_cycles_resolve_completely);
+                    // recursive type
+                    return tv.fallback_value();
+                }
                 if let Some(known_ty) = self.var_unification_table.probe_value(inner).known() {
                     // known_ty may contain other variables that are known by now
-                    self.resolve_ty_completely(known_ty.clone())
+                    tv_stack.push(inner);
+                    let result = self.resolve_ty_completely(tv_stack, known_ty.clone());
+                    tv_stack.pop();
+                    result
                 } else {
                     tv.fallback_value()
                 }
@@ -1070,7 +1089,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
             let name = path.as_ident().cloned().unwrap_or_else(Name::self_param);
             if let Some(scope_entry) = self.scopes.resolve_local_name(expr, name) {
                 let ty = self.type_of_pat.get(scope_entry.pat())?;
-                let ty = self.resolve_ty_as_possible(ty.clone());
+                let ty = self.resolve_ty_as_possible(&mut vec![], ty.clone());
                 return Some(ty);
             };
         };
@@ -1239,7 +1258,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
         // use a new type variable if we got Ty::Unknown here
         let ty = self.insert_type_vars_shallow(ty);
         self.unify(&ty, expected);
-        let ty = self.resolve_ty_as_possible(ty);
+        let ty = self.resolve_ty_as_possible(&mut vec![], ty);
         self.write_pat_ty(pat, ty.clone());
         ty
     }
@@ -1538,7 +1557,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
         // use a new type variable if we got Ty::Unknown here
         let ty = self.insert_type_vars_shallow(ty);
         self.unify(&ty, &expected.ty);
-        let ty = self.resolve_ty_as_possible(ty);
+        let ty = self.resolve_ty_as_possible(&mut vec![], ty);
         self.write_expr_ty(tgt_expr, ty.clone());
         ty
     }


Test patch:
diff --git a/crates/ra_hir/src/ty/snapshots/tests__recursive_vars.snap b/crates/ra_hir/src/ty/snapshots/tests__recursive_vars.snap
new file mode 100644
index 0000000000..c3227ff7ea
--- /dev/null
+++ b/crates/ra_hir/src/ty/snapshots/tests__recursive_vars.snap
@@ -0,0 +1,14 @@
+---
+created: "2019-01-26T22:42:22.329980185+00:00"
+creator: insta@0.5.2
+expression: "&result"
+source: crates/ra_hir/src/ty/tests.rs
+---
+[11; 48) '{     ...&y]; }': ()
+[21; 22) 'y': &[unknown]
+[25; 32) 'unknown': &[unknown]
+[38; 45) '[y, &y]': [&&[unknown]]
+[39; 40) 'y': &[unknown]
+[42; 44) '&y': &&[unknown]
+[43; 44) 'y': &[unknown]
+
diff --git a/crates/ra_hir/src/ty/snapshots/tests__recursive_vars_2.snap b/crates/ra_hir/src/ty/snapshots/tests__recursive_vars_2.snap
new file mode 100644
index 0000000000..de124da5b1
--- /dev/null
+++ b/crates/ra_hir/src/ty/snapshots/tests__recursive_vars_2.snap
@@ -0,0 +1,21 @@
+---
+created: "2019-01-26T22:42:22.331805845+00:00"
+creator: insta@0.5.2
+expression: "&result"
+source: crates/ra_hir/src/ty/tests.rs
+---
+[11; 80) '{     ...x)]; }': ()
+[21; 22) 'x': &&[unknown]
+[25; 32) 'unknown': &&[unknown]
+[42; 43) 'y': &&[unknown]
+[46; 53) 'unknown': &&[unknown]
+[59; 77) '[(x, y..., &x)]': [(&&[unknown], &&[unknown])]
+[60; 66) '(x, y)': (&&[unknown], &&[unknown])
+[61; 62) 'x': &&[unknown]
+[64; 65) 'y': &&[unknown]
+[68; 76) '(&y, &x)': (&&&[unknown], &&&[unknown])
+[69; 71) '&y': &&&[unknown]
+[70; 71) 'y': &&[unknown]
+[73; 75) '&x': &&&[unknown]
+[74; 75) 'x': &&[unknown]
+
diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs
index e1165f6821..f74d6f5ea3 100644
--- a/crates/ra_hir/src/ty/tests.rs
+++ b/crates/ra_hir/src/ty/tests.rs
@@ -3,6 +3,7 @@ use std::fmt::Write;
 
 use ra_db::{SourceDatabase, salsa::Database};
 use ra_syntax::ast::{self, AstNode};
+use test_utils::covers;
 
 use crate::{
     source_binder,
@@ -562,6 +563,37 @@ fn quux() {
     );
 }
 
+#[test]
+fn recursive_vars() {
+    covers!(type_var_cycles_resolve_completely);
+    covers!(type_var_cycles_resolve_as_possible);
+    check_inference(
+        "recursive_vars",
+        r#"
+fn test() {
+    let y = unknown;
+    [y, &y];
+}
+"#,
+    );
+}
+
+#[test]
+fn recursive_vars_2() {
+    covers!(type_var_cycles_resolve_completely);
+    covers!(type_var_cycles_resolve_as_possible);
+    check_inference(
+        "recursive_vars_2",
+        r#"
+fn test() {
+    let x = unknown;
+    let y = unknown;
+    [(x, y), (&y, &x)];
+}
+"#,
+    );
+}
+
 fn infer(content: &str) -> String {
     let (db, _, file_id) = MockDatabase::with_single_file(content);
     let source_file = db.parse(file_id);