Repository: rust-analyzer/rust-analyzer
Pull request #3965 (2020-04-13 13:08:15)

Implement inline associated type bounds
Like `Iterator<Item: SomeTrait>`.

This is an unstable feature, but it's used in the standard library e.g. in the definition of Flatten, so we can't get away with not implementing it :)

(This is cherry-picked from my recursive solver branch, where it works better, but I did manage to write a test that works with the current Chalk solver as well...)


Patch:
diff --git a/crates/ra_hir_def/src/data.rs b/crates/ra_hir_def/src/data.rs
index 56a20c5bd1..b3c91fea2a 100644
--- a/crates/ra_hir_def/src/data.rs
+++ b/crates/ra_hir_def/src/data.rs
@@ -15,7 +15,7 @@ use ra_syntax::ast::{
 use crate::{
     attr::Attrs,
     db::DefDatabase,
-    path::{path, GenericArgs, Path},
+    path::{path, AssociatedTypeBinding, GenericArgs, Path},
     src::HasSource,
     type_ref::{Mutability, TypeBound, TypeRef},
     visibility::RawVisibility,
@@ -95,7 +95,11 @@ fn desugar_future_path(orig: TypeRef) -> Path {
     let path = path![std::future::Future];
     let mut generic_args: Vec<_> = std::iter::repeat(None).take(path.segments.len() - 1).collect();
     let mut last = GenericArgs::empty();
-    last.bindings.push((name![Output], orig));
+    last.bindings.push(AssociatedTypeBinding {
+        name: name![Output],
+        type_ref: Some(orig),
+        bounds: Vec::new(),
+    });
     generic_args.push(Some(Arc::new(last)));
 
     Path::from_known_path(path, generic_args)
diff --git a/crates/ra_hir_def/src/path.rs b/crates/ra_hir_def/src/path.rs
index 91c7b3e094..162b3c8c74 100644
--- a/crates/ra_hir_def/src/path.rs
+++ b/crates/ra_hir_def/src/path.rs
@@ -14,7 +14,10 @@ use hir_expand::{
 use ra_db::CrateId;
 use ra_syntax::ast;
 
-use crate::{type_ref::TypeRef, InFile};
+use crate::{
+    type_ref::{TypeBound, TypeRef},
+    InFile,
+};
 
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct ModPath {
@@ -111,7 +114,21 @@ pub struct GenericArgs {
     /// is left out.
     pub has_self_type: bool,
     /// Associated type bindings like in `Iterator<Item = T>`.
-    pub bindings: Vec<(Name, TypeRef)>,
+    pub bindings: Vec<AssociatedTypeBinding>,
+}
+
+/// An associated type binding like in `Iterator<Item = T>`.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct AssociatedTypeBinding {
+    /// The name of the associated type.
+    pub name: Name,
+    /// The type bound to this associated type (in `Item = T`, this would be the
+    /// `T`). This can be `None` if there are bounds instead.
+    pub type_ref: Option<TypeRef>,
+    /// Bounds for the associated type, like in `Iterator<Item:
+    /// SomeOtherTrait>`. (This is the unstable `associated_type_bounds`
+    /// feature.)
+    pub bounds: Vec<TypeBound>,
 }
 
 /// A single generic argument.
diff --git a/crates/ra_hir_def/src/path/lower.rs b/crates/ra_hir_def/src/path/lower.rs
index 0f806d6fb5..9ec2e0dcdf 100644
--- a/crates/ra_hir_def/src/path/lower.rs
+++ b/crates/ra_hir_def/src/path/lower.rs
@@ -9,11 +9,12 @@ use hir_expand::{
     hygiene::Hygiene,
     name::{name, AsName},
 };
-use ra_syntax::ast::{self, AstNode, TypeAscriptionOwner};
+use ra_syntax::ast::{self, AstNode, TypeAscriptionOwner, TypeBoundsOwner};
 
+use super::AssociatedTypeBinding;
 use crate::{
     path::{GenericArg, GenericArgs, ModPath, Path, PathKind},
-    type_ref::TypeRef,
+    type_ref::{TypeBound, TypeRef},
 };
 
 pub(super) use lower_use::lower_use_tree;
@@ -136,10 +137,16 @@ pub(super) fn lower_generic_args(node: ast::TypeArgList) -> Option<GenericArgs>
     // lifetimes ignored for now
     let mut bindings = Vec::new();
     for assoc_type_arg in node.assoc_type_args() {
+        let assoc_type_arg: ast::AssocTypeArg = assoc_type_arg;
         if let Some(name_ref) = assoc_type_arg.name_ref() {
             let name = name_ref.as_name();
-            let type_ref = TypeRef::from_ast_opt(assoc_type_arg.type_ref());
-            bindings.push((name, type_ref));
+            let type_ref = assoc_type_arg.type_ref().map(TypeRef::from_ast);
+            let bounds = if let Some(l) = assoc_type_arg.type_bound_list() {
+                l.bounds().map(TypeBound::from_ast).collect()
+            } else {
+                Vec::new()
+            };
+            bindings.push(AssociatedTypeBinding { name, type_ref, bounds });
         }
     }
     if args.is_empty() && bindings.is_empty() {
@@ -168,7 +175,11 @@ fn lower_generic_args_from_fn_path(
     }
     if let Some(ret_type) = ret_type {
         let type_ref = TypeRef::from_ast_opt(ret_type.type_ref());
-        bindings.push((name![Output], type_ref))
+        bindings.push(AssociatedTypeBinding {
+            name: name![Output],
+            type_ref: Some(type_ref),
+            bounds: Vec::new(),
+        });
     }
     if args.is_empty() && bindings.is_empty() {
         None
diff --git a/crates/ra_hir_def/src/type_ref.rs b/crates/ra_hir_def/src/type_ref.rs
index ea29c4176c..f308c6bdfc 100644
--- a/crates/ra_hir_def/src/type_ref.rs
+++ b/crates/ra_hir_def/src/type_ref.rs
@@ -163,8 +163,16 @@ impl TypeRef {
                         let crate::path::GenericArg::Type(type_ref) = arg;
                         go(type_ref, f);
                     }
-                    for (_, type_ref) in &args_and_bindings.bindings {
-                        go(type_ref, f);
+                    for binding in &args_and_bindings.bindings {
+                        if let Some(type_ref) = &binding.type_ref {
+                            go(type_ref, f);
+                        }
+                        for bound in &binding.bounds {
+                            match bound {
+                                TypeBound::Path(path) => go_path(path, f),
+                                TypeBound::Error => (),
+                            }
+                        }
                     }
                 }
             }
diff --git a/crates/ra_hir_ty/src/lower.rs b/crates/ra_hir_ty/src/lower.rs
index 6c7bbc4489..21e7baf8b4 100644
--- a/crates/ra_hir_ty/src/lower.rs
+++ b/crates/ra_hir_ty/src/lower.rs
@@ -8,6 +8,8 @@
 use std::iter;
 use std::sync::Arc;
 
+use smallvec::SmallVec;
+
 use hir_def::{
     adt::StructKind,
     builtin_type::BuiltinType,
@@ -596,21 +598,35 @@ fn assoc_type_bindings_from_type_bound<'a>(
         .into_iter()
         .flat_map(|segment| segment.args_and_bindings.into_iter())
         .flat_map(|args_and_bindings| args_and_bindings.bindings.iter())
-        .map(move |(name, type_ref)| {
+        .flat_map(move |binding| {
             let associated_ty = associated_type_by_name_including_super_traits(
                 ctx.db.upcast(),
                 trait_ref.trait_,
-                &name,
+                &binding.name,
             );
             let associated_ty = match associated_ty {
-                None => return GenericPredicate::Error,
+                None => return SmallVec::<[GenericPredicate; 1]>::new(),
                 Some(t) => t,
             };
             let projection_ty =
                 ProjectionTy { associated_ty, parameters: trait_ref.substs.clone() };
-            let ty = Ty::from_hir(ctx, type_ref);
-            let projection_predicate = ProjectionPredicate { projection_ty, ty };
-            GenericPredicate::Projection(projection_predicate)
+            let mut preds = SmallVec::with_capacity(
+                binding.type_ref.as_ref().map_or(0, |_| 1) + binding.bounds.len(),
+            );
+            if let Some(type_ref) = &binding.type_ref {
+                let ty = Ty::from_hir(ctx, type_ref);
+                let projection_predicate =
+                    ProjectionPredicate { projection_ty: projection_ty.clone(), ty };
+                preds.push(GenericPredicate::Projection(projection_predicate));
+            }
+            for bound in &binding.bounds {
+                preds.extend(GenericPredicate::from_type_bound(
+                    ctx,
+                    bound,
+                    Ty::Projection(projection_ty.clone()),
+                ));
+            }
+            preds
         })
 }
 


Test patch:
diff --git a/crates/ra_hir_ty/src/tests/traits.rs b/crates/ra_hir_ty/src/tests/traits.rs
index 22ae6ca909..53461bdbb6 100644
--- a/crates/ra_hir_ty/src/tests/traits.rs
+++ b/crates/ra_hir_ty/src/tests/traits.rs
@@ -1923,6 +1923,53 @@ fn test<T, U>() where T: Trait<U::Item>, U: Trait<T::Item> {
     assert_eq!(t, "{unknown}");
 }
 
+#[test]
+fn inline_assoc_type_bounds_1() {
+    let t = type_at(
+        r#"
+//- /main.rs
+trait Iterator {
+    type Item;
+}
+trait OtherTrait<T> {
+    fn foo(&self) -> T;
+}
+
+// workaround for Chalk assoc type normalization problems
+pub struct S<T>;
+impl<T: Iterator> Iterator for S<T> {
+    type Item = <T as Iterator>::Item;
+}
+
+fn test<I: Iterator<Item: OtherTrait<u32>>>() {
+    let x: <S<I> as Iterator>::Item;
+    x.foo()<|>;
+}
+"#,
+    );
+    assert_eq!(t, "u32");
+}
+
+#[test]
+fn inline_assoc_type_bounds_2() {
+    let t = type_at(
+        r#"
+//- /main.rs
+trait Iterator {
+    type Item;
+}
+
+fn test<I: Iterator<Item: Iterator<Item = u32>>>() {
+    let x: <<I as Iterator>::Item as Iterator>::Item;
+    x<|>;
+}
+"#,
+    );
+    // assert_eq!(t, "u32");
+    // doesn't currently work, Chalk #234
+    assert_eq!(t, "{unknown}");
+}
+
 #[test]
 fn unify_impl_trait() {
     assert_snapshot!(