Repository: rust-analyzer/rust-analyzer
Pull request #2567 (2019-12-15 18:00:55)

Handle impl Trait more correctly
When calling a function, argument-position impl Trait is transparent; same for return-position impl Trait when inside the function. 

So in these cases, we need to represent that type not by `Ty::Opaque`, but by a type variable that can be unified with whatever flows into there.


Patch:
diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs
index af42854cce..98ba05fc27 100644
--- a/crates/ra_hir_ty/src/infer.rs
+++ b/crates/ra_hir_ty/src/infer.rs
@@ -32,6 +32,7 @@ use hir_def::{
 use hir_expand::{diagnostics::DiagnosticSink, name::name};
 use ra_arena::map::ArenaMap;
 use ra_prof::profile;
+use test_utils::tested_by;
 
 use super::{
     primitive::{FloatTy, IntTy},
@@ -274,6 +275,29 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
         self.normalize_associated_types_in(ty)
     }
 
+    /// Replaces `impl Trait` in `ty` by type variables and obligations for
+    /// those variables. This is done for function arguments when calling a
+    /// function, and for return types when inside the function body, i.e. in
+    /// the cases where the `impl Trait` is 'transparent'. In other cases, `impl
+    /// Trait` is represented by `Ty::Opaque`.
+    fn insert_vars_for_impl_trait(&mut self, ty: Ty) -> Ty {
+        ty.fold(&mut |ty| match ty {
+            Ty::Opaque(preds) => {
+                tested_by!(insert_vars_for_impl_trait);
+                let var = self.table.new_type_var();
+                let var_subst = Substs::builder(1).push(var.clone()).build();
+                self.obligations.extend(
+                    preds
+                        .iter()
+                        .map(|pred| pred.clone().subst_bound_vars(&var_subst))
+                        .filter_map(Obligation::from_predicate),
+                );
+                var
+            }
+            _ => ty,
+        })
+    }
+
     /// Replaces Ty::Unknown by a new type var, so we can maybe still infer it.
     fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty {
         match ty {
@@ -414,7 +438,8 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
 
             self.infer_pat(*pat, &ty, BindingMode::default());
         }
-        self.return_ty = self.make_ty(&data.ret_type);
+        let return_ty = self.make_ty(&data.ret_type);
+        self.return_ty = self.insert_vars_for_impl_trait(return_ty);
     }
 
     fn infer_body(&mut self) {
diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs
index 2e3cdd53a0..924ad3e813 100644
--- a/crates/ra_hir_ty/src/infer/expr.rs
+++ b/crates/ra_hir_ty/src/infer/expr.rs
@@ -613,6 +613,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
                     continue;
                 }
 
+                let param_ty = self.insert_vars_for_impl_trait(param_ty);
                 let param_ty = self.normalize_associated_types_in(param_ty);
                 self.infer_expr_coerce(arg, &Expectation::has_type(param_ty.clone()));
             }
diff --git a/crates/ra_hir_ty/src/marks.rs b/crates/ra_hir_ty/src/marks.rs
index 0f754eb9c7..fe74acf119 100644
--- a/crates/ra_hir_ty/src/marks.rs
+++ b/crates/ra_hir_ty/src/marks.rs
@@ -6,4 +6,5 @@ test_utils::marks!(
     type_var_resolves_to_int_var
     match_ergonomics_ref
     coerce_merge_fail_fallback
+    insert_vars_for_impl_trait
 );


Test patch:
diff --git a/crates/ra_hir_ty/src/tests/traits.rs b/crates/ra_hir_ty/src/tests/traits.rs
index 6139adb72e..802937cb02 100644
--- a/crates/ra_hir_ty/src/tests/traits.rs
+++ b/crates/ra_hir_ty/src/tests/traits.rs
@@ -1,7 +1,10 @@
-use super::{infer, type_at, type_at_pos};
-use crate::test_db::TestDB;
 use insta::assert_snapshot;
+
 use ra_db::fixture::WithFixture;
+use test_utils::covers;
+
+use super::{infer, infer_with_mismatches, type_at, type_at_pos};
+use crate::test_db::TestDB;
 
 #[test]
 fn infer_await() {
@@ -1486,3 +1489,61 @@ fn test<T, U>() where T: Trait<U::Item>, U: Trait<T::Item> {
     // this is a legitimate cycle
     assert_eq!(t, "{unknown}");
 }
+
+#[test]
+fn unify_impl_trait() {
+    covers!(insert_vars_for_impl_trait);
+    assert_snapshot!(
+        infer_with_mismatches(r#"
+trait Trait<T> {}
+
+fn foo(x: impl Trait<u32>) { loop {} }
+fn bar<T>(x: impl Trait<T>) -> T { loop {} }
+
+struct S<T>(T);
+impl<T> Trait<T> for S<T> {}
+
+fn default<T>() -> T { loop {} }
+
+fn test() -> impl Trait<i32> {
+    let s1 = S(default());
+    foo(s1);
+    let x: i32 = bar(S(default()));
+    S(default())
+}
+"#, true),
+        @r###"
+    [27; 28) 'x': impl Trait<u32>
+    [47; 58) '{ loop {} }': ()
+    [49; 56) 'loop {}': !
+    [54; 56) '{}': ()
+    [69; 70) 'x': impl Trait<T>
+    [92; 103) '{ loop {} }': T
+    [94; 101) 'loop {}': !
+    [99; 101) '{}': ()
+    [172; 183) '{ loop {} }': T
+    [174; 181) 'loop {}': !
+    [179; 181) '{}': ()
+    [214; 310) '{     ...t()) }': S<i32>
+    [224; 226) 's1': S<u32>
+    [229; 230) 'S': S<u32>(T) -> S<T>
+    [229; 241) 'S(default())': S<u32>
+    [231; 238) 'default': fn default<u32>() -> T
+    [231; 240) 'default()': u32
+    [247; 250) 'foo': fn foo(impl Trait<u32>) -> ()
+    [247; 254) 'foo(s1)': ()
+    [251; 253) 's1': S<u32>
+    [264; 265) 'x': i32
+    [273; 276) 'bar': fn bar<i32>(impl Trait<T>) -> T
+    [273; 290) 'bar(S(...lt()))': i32
+    [277; 278) 'S': S<i32>(T) -> S<T>
+    [277; 289) 'S(default())': S<i32>
+    [279; 286) 'default': fn default<i32>() -> T
+    [279; 288) 'default()': i32
+    [296; 297) 'S': S<i32>(T) -> S<T>
+    [296; 308) 'S(default())': S<i32>
+    [298; 305) 'default': fn default<i32>() -> T
+    [298; 307) 'default()': i32
+    "###
+    );
+}