about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMichael Howell <michael@notriddle.com>2023-09-22 17:27:06 -0700
committerMichael Howell <michael@notriddle.com>2023-11-19 18:54:36 -0700
commit63c50712f40055a60aefc8069661c3847bd27df4 (patch)
treefd35e82b0a00c87a0f383f8b1d37d8dc7f3a4dcd
parent9a66e4471f71283fd54d80ef8147630d34756332 (diff)
downloadrust-63c50712f40055a60aefc8069661c3847bd27df4.tar.gz
rust-63c50712f40055a60aefc8069661c3847bd27df4.zip
rustdoc-search: add support for associated types
-rw-r--r--src/librustdoc/clean/types.rs44
-rw-r--r--src/librustdoc/formats/cache.rs1
-rw-r--r--src/librustdoc/formats/item_type.rs2
-rw-r--r--src/librustdoc/html/render/mod.rs39
-rw-r--r--src/librustdoc/html/render/search_index.rs422
-rw-r--r--src/librustdoc/html/static/js/externs.js5
-rw-r--r--src/librustdoc/html/static/js/search.js331
-rw-r--r--src/tools/rustdoc-js/tester.js29
-rw-r--r--tests/rustdoc-gui/search-tab.goml2
-rw-r--r--tests/rustdoc-js-std/iterator-type-signatures.js29
-rw-r--r--tests/rustdoc-js-std/parser-bindings.js245
-rw-r--r--tests/rustdoc-js-std/parser-errors.js2
-rw-r--r--tests/rustdoc-js/assoc-type-backtrack.js163
-rw-r--r--tests/rustdoc-js/assoc-type-backtrack.rs38
-rw-r--r--tests/rustdoc-js/assoc-type.js45
-rw-r--r--tests/rustdoc-js/assoc-type.rs12
-rw-r--r--tests/rustdoc-js/gat.js57
-rw-r--r--tests/rustdoc-js/gat.rs8
-rw-r--r--tests/rustdoc-js/never-search.js10
-rw-r--r--tests/rustdoc-js/trait-methods.js12
-rw-r--r--tests/rustdoc-js/trait-methods.rs4
21 files changed, 1383 insertions, 117 deletions
diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs
index 871738cdc07..ded256fd75c 100644
--- a/src/librustdoc/clean/types.rs
+++ b/src/librustdoc/clean/types.rs
@@ -1651,6 +1651,13 @@ impl Type {
         }
     }
 
+    pub(crate) fn generic_args(&self) -> Option<&GenericArgs> {
+        match self {
+            Type::Path { path, .. } => path.generic_args(),
+            _ => None,
+        }
+    }
+
     pub(crate) fn generics(&self) -> Option<Vec<&Type>> {
         match self {
             Type::Path { path, .. } => path.generics(),
@@ -2191,6 +2198,10 @@ impl Path {
         }
     }
 
+    pub(crate) fn generic_args(&self) -> Option<&GenericArgs> {
+        self.segments.last().map(|seg| &seg.args)
+    }
+
     pub(crate) fn generics(&self) -> Option<Vec<&Type>> {
         self.segments.last().and_then(|seg| {
             if let GenericArgs::AngleBracketed { ref args, .. } = seg.args {
@@ -2232,6 +2243,39 @@ impl GenericArgs {
             GenericArgs::Parenthesized { inputs, output } => inputs.is_empty() && output.is_none(),
         }
     }
+    pub(crate) fn bindings<'a>(&'a self) -> Box<dyn Iterator<Item = TypeBinding> + 'a> {
+        match self {
+            &GenericArgs::AngleBracketed { ref bindings, .. } => Box::new(bindings.iter().cloned()),
+            &GenericArgs::Parenthesized { ref output, .. } => Box::new(
+                output
+                    .as_ref()
+                    .map(|ty| TypeBinding {
+                        assoc: PathSegment {
+                            name: sym::Output,
+                            args: GenericArgs::AngleBracketed {
+                                args: Vec::new().into_boxed_slice(),
+                                bindings: ThinVec::new(),
+                            },
+                        },
+                        kind: TypeBindingKind::Equality { term: Term::Type((**ty).clone()) },
+                    })
+                    .into_iter(),
+            ),
+        }
+    }
+}
+
+impl<'a> IntoIterator for &'a GenericArgs {
+    type IntoIter = Box<dyn Iterator<Item = GenericArg> + 'a>;
+    type Item = GenericArg;
+    fn into_iter(self) -> Self::IntoIter {
+        match self {
+            &GenericArgs::AngleBracketed { ref args, .. } => Box::new(args.iter().cloned()),
+            &GenericArgs::Parenthesized { ref inputs, .. } => {
+                Box::new(inputs.iter().cloned().map(GenericArg::Type))
+            }
+        }
+    }
 }
 
 #[derive(Clone, PartialEq, Eq, Debug, Hash)]
diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs
index d63bbe5896e..9b1b6899751 100644
--- a/src/librustdoc/formats/cache.rs
+++ b/src/librustdoc/formats/cache.rs
@@ -369,6 +369,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
                                 &item,
                                 self.tcx,
                                 clean_impl_generics(self.cache.parent_stack.last()).as_ref(),
+                                parent,
                                 self.cache,
                             ),
                             aliases: item.attrs.get_doc_aliases(),
diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs
index def3a90c8e8..22527876b76 100644
--- a/src/librustdoc/formats/item_type.rs
+++ b/src/librustdoc/formats/item_type.rs
@@ -49,6 +49,8 @@ pub(crate) enum ItemType {
     ProcAttribute = 23,
     ProcDerive = 24,
     TraitAlias = 25,
+    // This number is reserved for use in JavaScript
+    // Generic = 26,
 }
 
 impl Serialize for ItemType {
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 90691411f44..8b3cd9ca6fc 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -113,6 +113,7 @@ pub(crate) struct IndexItem {
 pub(crate) struct RenderType {
     id: Option<RenderTypeId>,
     generics: Option<Vec<RenderType>>,
+    bindings: Option<Vec<(RenderTypeId, Vec<RenderType>)>>,
 }
 
 impl Serialize for RenderType {
@@ -129,10 +130,15 @@ impl Serialize for RenderType {
             Some(RenderTypeId::Index(idx)) => *idx,
             _ => panic!("must convert render types to indexes before serializing"),
         };
-        if let Some(generics) = &self.generics {
+        if self.generics.is_some() || self.bindings.is_some() {
             let mut seq = serializer.serialize_seq(None)?;
             seq.serialize_element(&id)?;
-            seq.serialize_element(generics)?;
+            seq.serialize_element(self.generics.as_ref().map(Vec::as_slice).unwrap_or_default())?;
+            if self.bindings.is_some() {
+                seq.serialize_element(
+                    self.bindings.as_ref().map(Vec::as_slice).unwrap_or_default(),
+                )?;
+            }
             seq.end()
         } else {
             id.serialize(serializer)
@@ -140,13 +146,31 @@ impl Serialize for RenderType {
     }
 }
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Copy, Debug)]
 pub(crate) enum RenderTypeId {
     DefId(DefId),
     Primitive(clean::PrimitiveType),
+    AssociatedType(Symbol),
     Index(isize),
 }
 
+impl Serialize for RenderTypeId {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let id = match &self {
+            // 0 is a sentinel, everything else is one-indexed
+            // concrete type
+            RenderTypeId::Index(idx) if *idx >= 0 => idx + 1,
+            // generic type parameter
+            RenderTypeId::Index(idx) => *idx,
+            _ => panic!("must convert render types to indexes before serializing"),
+        };
+        id.serialize(serializer)
+    }
+}
+
 /// Full type of functions/methods in the search index.
 #[derive(Debug)]
 pub(crate) struct IndexItemFunctionType {
@@ -171,17 +195,22 @@ impl Serialize for IndexItemFunctionType {
         } else {
             let mut seq = serializer.serialize_seq(None)?;
             match &self.inputs[..] {
-                [one] if one.generics.is_none() => seq.serialize_element(one)?,
+                [one] if one.generics.is_none() && one.bindings.is_none() => {
+                    seq.serialize_element(one)?
+                }
                 _ => seq.serialize_element(&self.inputs)?,
             }
             match &self.output[..] {
                 [] if self.where_clause.is_empty() => {}
-                [one] if one.generics.is_none() => seq.serialize_element(one)?,
+                [one] if one.generics.is_none() && one.bindings.is_none() => {
+                    seq.serialize_element(one)?
+                }
                 _ => seq.serialize_element(&self.output)?,
             }
             for constraint in &self.where_clause {
                 if let [one] = &constraint[..]
                     && one.generics.is_none()
+                    && one.bindings.is_none()
                 {
                     seq.serialize_element(one)?;
                 } else {
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index 1284f69e5d7..b3ae720fcf6 100644
--- a/src/librustdoc/html/render/search_index.rs
+++ b/src/librustdoc/html/render/search_index.rs
@@ -3,8 +3,10 @@ use std::collections::BTreeMap;
 
 use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
 use rustc_middle::ty::TyCtxt;
+use rustc_span::def_id::DefId;
 use rustc_span::symbol::Symbol;
 use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer};
+use thin_vec::ThinVec;
 
 use crate::clean;
 use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate};
@@ -22,6 +24,7 @@ pub(crate) fn build_index<'tcx>(
 ) -> String {
     let mut itemid_to_pathid = FxHashMap::default();
     let mut primitives = FxHashMap::default();
+    let mut associated_types = FxHashMap::default();
     let mut crate_paths = vec![];
 
     // Attach all orphan items to the type's definition if the type
@@ -38,7 +41,13 @@ pub(crate) fn build_index<'tcx>(
                 parent: Some(parent),
                 parent_idx: None,
                 impl_id,
-                search_type: get_function_type_for_search(item, tcx, impl_generics.as_ref(), cache),
+                search_type: get_function_type_for_search(
+                    item,
+                    tcx,
+                    impl_generics.as_ref(),
+                    Some(parent),
+                    cache,
+                ),
                 aliases: item.attrs.get_doc_aliases(),
                 deprecation: item.deprecation(tcx),
             });
@@ -76,83 +85,139 @@ pub(crate) fn build_index<'tcx>(
     let mut search_index = std::mem::replace(&mut cache.search_index, Vec::new());
     for item in search_index.iter_mut() {
         fn insert_into_map<F: std::hash::Hash + Eq>(
-            ty: &mut RenderType,
             map: &mut FxHashMap<F, isize>,
             itemid: F,
             lastpathid: &mut isize,
             crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
             item_type: ItemType,
             path: &[Symbol],
-        ) {
+        ) -> RenderTypeId {
             match map.entry(itemid) {
-                Entry::Occupied(entry) => ty.id = Some(RenderTypeId::Index(*entry.get())),
+                Entry::Occupied(entry) => RenderTypeId::Index(*entry.get()),
                 Entry::Vacant(entry) => {
                     let pathid = *lastpathid;
                     entry.insert(pathid);
                     *lastpathid += 1;
                     crate_paths.push((item_type, path.to_vec()));
-                    ty.id = Some(RenderTypeId::Index(pathid));
+                    RenderTypeId::Index(pathid)
                 }
             }
         }
 
-        fn convert_render_type(
-            ty: &mut RenderType,
+        fn convert_render_type_id(
+            id: RenderTypeId,
             cache: &mut Cache,
             itemid_to_pathid: &mut FxHashMap<ItemId, isize>,
             primitives: &mut FxHashMap<Symbol, isize>,
+            associated_types: &mut FxHashMap<Symbol, isize>,
             lastpathid: &mut isize,
             crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
-        ) {
-            if let Some(generics) = &mut ty.generics {
-                for item in generics {
-                    convert_render_type(
-                        item,
-                        cache,
-                        itemid_to_pathid,
-                        primitives,
-                        lastpathid,
-                        crate_paths,
-                    );
-                }
-            }
+        ) -> Option<RenderTypeId> {
             let Cache { ref paths, ref external_paths, .. } = *cache;
-            let Some(id) = ty.id.clone() else {
-                assert!(ty.generics.is_some());
-                return;
-            };
             match id {
                 RenderTypeId::DefId(defid) => {
                     if let Some(&(ref fqp, item_type)) =
                         paths.get(&defid).or_else(|| external_paths.get(&defid))
                     {
-                        insert_into_map(
-                            ty,
+                        Some(insert_into_map(
                             itemid_to_pathid,
                             ItemId::DefId(defid),
                             lastpathid,
                             crate_paths,
                             item_type,
                             fqp,
-                        );
+                        ))
                     } else {
-                        ty.id = None;
+                        None
                     }
                 }
                 RenderTypeId::Primitive(primitive) => {
                     let sym = primitive.as_sym();
-                    insert_into_map(
-                        ty,
+                    Some(insert_into_map(
                         primitives,
                         sym,
                         lastpathid,
                         crate_paths,
                         ItemType::Primitive,
                         &[sym],
+                    ))
+                }
+                RenderTypeId::Index(_) => Some(id),
+                RenderTypeId::AssociatedType(sym) => Some(insert_into_map(
+                    associated_types,
+                    sym,
+                    lastpathid,
+                    crate_paths,
+                    ItemType::AssocType,
+                    &[sym],
+                )),
+            }
+        }
+
+        fn convert_render_type(
+            ty: &mut RenderType,
+            cache: &mut Cache,
+            itemid_to_pathid: &mut FxHashMap<ItemId, isize>,
+            primitives: &mut FxHashMap<Symbol, isize>,
+            associated_types: &mut FxHashMap<Symbol, isize>,
+            lastpathid: &mut isize,
+            crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
+        ) {
+            if let Some(generics) = &mut ty.generics {
+                for item in generics {
+                    convert_render_type(
+                        item,
+                        cache,
+                        itemid_to_pathid,
+                        primitives,
+                        associated_types,
+                        lastpathid,
+                        crate_paths,
                     );
                 }
-                RenderTypeId::Index(_) => {}
             }
+            if let Some(bindings) = &mut ty.bindings {
+                bindings.retain_mut(|(associated_type, constraints)| {
+                    let converted_associated_type = convert_render_type_id(
+                        *associated_type,
+                        cache,
+                        itemid_to_pathid,
+                        primitives,
+                        associated_types,
+                        lastpathid,
+                        crate_paths,
+                    );
+                    let Some(converted_associated_type) = converted_associated_type else {
+                        return false;
+                    };
+                    *associated_type = converted_associated_type;
+                    for constraint in constraints {
+                        convert_render_type(
+                            constraint,
+                            cache,
+                            itemid_to_pathid,
+                            primitives,
+                            associated_types,
+                            lastpathid,
+                            crate_paths,
+                        );
+                    }
+                    true
+                });
+            }
+            let Some(id) = ty.id.clone() else {
+                assert!(ty.generics.is_some());
+                return;
+            };
+            ty.id = convert_render_type_id(
+                id,
+                cache,
+                itemid_to_pathid,
+                primitives,
+                associated_types,
+                lastpathid,
+                crate_paths,
+            );
         }
         if let Some(search_type) = &mut item.search_type {
             for item in &mut search_type.inputs {
@@ -161,6 +226,7 @@ pub(crate) fn build_index<'tcx>(
                     cache,
                     &mut itemid_to_pathid,
                     &mut primitives,
+                    &mut associated_types,
                     &mut lastpathid,
                     &mut crate_paths,
                 );
@@ -171,6 +237,7 @@ pub(crate) fn build_index<'tcx>(
                     cache,
                     &mut itemid_to_pathid,
                     &mut primitives,
+                    &mut associated_types,
                     &mut lastpathid,
                     &mut crate_paths,
                 );
@@ -182,6 +249,7 @@ pub(crate) fn build_index<'tcx>(
                         cache,
                         &mut itemid_to_pathid,
                         &mut primitives,
+                        &mut associated_types,
                         &mut lastpathid,
                         &mut crate_paths,
                     );
@@ -442,12 +510,39 @@ pub(crate) fn get_function_type_for_search<'tcx>(
     item: &clean::Item,
     tcx: TyCtxt<'tcx>,
     impl_generics: Option<&(clean::Type, clean::Generics)>,
+    parent: Option<DefId>,
     cache: &Cache,
 ) -> Option<IndexItemFunctionType> {
+    let mut trait_info = None;
+    let impl_or_trait_generics = impl_generics.or_else(|| {
+        if let Some(def_id) = parent
+            && let Some(trait_) = cache.traits.get(&def_id)
+            && let Some((path, _)) =
+                cache.paths.get(&def_id).or_else(|| cache.external_paths.get(&def_id))
+        {
+            let path = clean::Path {
+                res: rustc_hir::def::Res::Def(rustc_hir::def::DefKind::Trait, def_id),
+                segments: path
+                    .iter()
+                    .map(|name| clean::PathSegment {
+                        name: *name,
+                        args: clean::GenericArgs::AngleBracketed {
+                            args: Vec::new().into_boxed_slice(),
+                            bindings: ThinVec::new(),
+                        },
+                    })
+                    .collect(),
+            };
+            trait_info = Some((clean::Type::Path { path }, trait_.generics.clone()));
+            Some(trait_info.as_ref().unwrap())
+        } else {
+            None
+        }
+    });
     let (mut inputs, mut output, where_clause) = match *item.kind {
-        clean::FunctionItem(ref f) => get_fn_inputs_and_outputs(f, tcx, impl_generics, cache),
-        clean::MethodItem(ref m, _) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache),
-        clean::TyMethodItem(ref m) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache),
+        clean::FunctionItem(ref f) | clean::MethodItem(ref f, _) | clean::TyMethodItem(ref f) => {
+            get_fn_inputs_and_outputs(f, tcx, impl_or_trait_generics, cache)
+        }
         _ => return None,
     };
 
@@ -457,14 +552,23 @@ pub(crate) fn get_function_type_for_search<'tcx>(
     Some(IndexItemFunctionType { inputs, output, where_clause })
 }
 
-fn get_index_type(clean_type: &clean::Type, generics: Vec<RenderType>) -> RenderType {
+fn get_index_type(
+    clean_type: &clean::Type,
+    generics: Vec<RenderType>,
+    rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>,
+) -> RenderType {
     RenderType {
-        id: get_index_type_id(clean_type),
+        id: get_index_type_id(clean_type, rgen),
         generics: if generics.is_empty() { None } else { Some(generics) },
+        bindings: None,
     }
 }
 
-fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> {
+fn get_index_type_id(
+    clean_type: &clean::Type,
+    rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>,
+) -> Option<RenderTypeId> {
+    use rustc_hir::def::{DefKind, Res};
     match *clean_type {
         clean::Type::Path { ref path, .. } => Some(RenderTypeId::DefId(path.def_id())),
         clean::DynTrait(ref bounds, _) => {
@@ -472,18 +576,27 @@ fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> {
         }
         clean::Primitive(p) => Some(RenderTypeId::Primitive(p)),
         clean::BorrowedRef { ref type_, .. } | clean::RawPointer(_, ref type_) => {
-            get_index_type_id(type_)
+            get_index_type_id(type_, rgen)
         }
         // The type parameters are converted to generics in `simplify_fn_type`
         clean::Slice(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Slice)),
         clean::Array(_, _) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Array)),
         clean::Tuple(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Tuple)),
+        clean::QPath(ref data) => {
+            if data.self_type.is_self_type()
+                && let Some(clean::Path { res: Res::Def(DefKind::Trait, trait_), .. }) = data.trait_
+            {
+                let idx = -isize::try_from(rgen.len() + 1).unwrap();
+                let (idx, _) = rgen
+                    .entry(SimplifiedParam::AssociatedType(trait_, data.assoc.name))
+                    .or_insert_with(|| (idx, Vec::new()));
+                Some(RenderTypeId::Index(*idx))
+            } else {
+                None
+            }
+        }
         // Not supported yet
-        clean::BareFunction(_)
-        | clean::Generic(_)
-        | clean::ImplTrait(_)
-        | clean::QPath { .. }
-        | clean::Infer => None,
+        clean::BareFunction(_) | clean::Generic(_) | clean::ImplTrait(_) | clean::Infer => None,
     }
 }
 
@@ -493,6 +606,9 @@ enum SimplifiedParam {
     Symbol(Symbol),
     // every argument-position impl trait is its own type parameter
     Anonymous(isize),
+    // in a trait definition, the associated types are all bound to
+    // their own type parameter
+    AssociatedType(DefId, Symbol),
 }
 
 /// The point of this function is to lower generics and types into the simplified form that the
@@ -523,10 +639,17 @@ fn simplify_fn_type<'tcx, 'a>(
     }
 
     // First, check if it's "Self".
+    let mut is_self = false;
     let mut arg = if let Some(self_) = self_ {
         match &*arg {
-            Type::BorrowedRef { type_, .. } if type_.is_self_type() => self_,
-            type_ if type_.is_self_type() => self_,
+            Type::BorrowedRef { type_, .. } if type_.is_self_type() => {
+                is_self = true;
+                self_
+            }
+            type_ if type_.is_self_type() => {
+                is_self = true;
+                self_
+            }
             arg => arg,
         }
     } else {
@@ -585,11 +708,19 @@ fn simplify_fn_type<'tcx, 'a>(
             }
         }
         if let Some((idx, _)) = rgen.get(&SimplifiedParam::Symbol(arg_s)) {
-            res.push(RenderType { id: Some(RenderTypeId::Index(*idx)), generics: None });
+            res.push(RenderType {
+                id: Some(RenderTypeId::Index(*idx)),
+                generics: None,
+                bindings: None,
+            });
         } else {
             let idx = -isize::try_from(rgen.len() + 1).unwrap();
             rgen.insert(SimplifiedParam::Symbol(arg_s), (idx, type_bounds));
-            res.push(RenderType { id: Some(RenderTypeId::Index(idx)), generics: None });
+            res.push(RenderType {
+                id: Some(RenderTypeId::Index(idx)),
+                generics: None,
+                bindings: None,
+            });
         }
     } else if let Type::ImplTrait(ref bounds) = *arg {
         let mut type_bounds = Vec::new();
@@ -611,12 +742,16 @@ fn simplify_fn_type<'tcx, 'a>(
         }
         if is_return && !type_bounds.is_empty() {
             // In parameter position, `impl Trait` is a unique thing.
-            res.push(RenderType { id: None, generics: Some(type_bounds) });
+            res.push(RenderType { id: None, generics: Some(type_bounds), bindings: None });
         } else {
             // In parameter position, `impl Trait` is the same as an unnamed generic parameter.
             let idx = -isize::try_from(rgen.len() + 1).unwrap();
             rgen.insert(SimplifiedParam::Anonymous(idx), (idx, type_bounds));
-            res.push(RenderType { id: Some(RenderTypeId::Index(idx)), generics: None });
+            res.push(RenderType {
+                id: Some(RenderTypeId::Index(idx)),
+                generics: None,
+                bindings: None,
+            });
         }
     } else if let Type::Slice(ref ty) = *arg {
         let mut ty_generics = Vec::new();
@@ -631,7 +766,7 @@ fn simplify_fn_type<'tcx, 'a>(
             is_return,
             cache,
         );
-        res.push(get_index_type(arg, ty_generics));
+        res.push(get_index_type(arg, ty_generics, rgen));
     } else if let Type::Array(ref ty, _) = *arg {
         let mut ty_generics = Vec::new();
         simplify_fn_type(
@@ -645,7 +780,7 @@ fn simplify_fn_type<'tcx, 'a>(
             is_return,
             cache,
         );
-        res.push(get_index_type(arg, ty_generics));
+        res.push(get_index_type(arg, ty_generics, rgen));
     } else if let Type::Tuple(ref tys) = *arg {
         let mut ty_generics = Vec::new();
         for ty in tys {
@@ -661,7 +796,7 @@ fn simplify_fn_type<'tcx, 'a>(
                 cache,
             );
         }
-        res.push(get_index_type(arg, ty_generics));
+        res.push(get_index_type(arg, ty_generics, rgen));
     } else {
         // This is not a type parameter. So for example if we have `T, U: Option<T>`, and we're
         // looking at `Option`, we enter this "else" condition, otherwise if it's `T`, we don't.
@@ -669,12 +804,16 @@ fn simplify_fn_type<'tcx, 'a>(
         // So in here, we can add it directly and look for its own type parameters (so for `Option`,
         // we will look for them but not for `T`).
         let mut ty_generics = Vec::new();
-        if let Some(arg_generics) = arg.generics() {
-            for gen in arg_generics.iter() {
+        let mut ty_bindings = Vec::new();
+        if let Some(arg_generics) = arg.generic_args() {
+            for ty in arg_generics.into_iter().filter_map(|gen| match gen {
+                clean::GenericArg::Type(ty) => Some(ty),
+                _ => None,
+            }) {
                 simplify_fn_type(
                     self_,
                     generics,
-                    gen,
+                    &ty,
                     tcx,
                     recurse + 1,
                     &mut ty_generics,
@@ -683,17 +822,180 @@ fn simplify_fn_type<'tcx, 'a>(
                     cache,
                 );
             }
+            for binding in arg_generics.bindings() {
+                simplify_fn_binding(
+                    self_,
+                    generics,
+                    &binding,
+                    tcx,
+                    recurse + 1,
+                    &mut ty_bindings,
+                    rgen,
+                    is_return,
+                    cache,
+                );
+            }
+        }
+        // Every trait associated type on self gets assigned to a type parameter index
+        // this same one is used later for any appearances of these types
+        //
+        // for example, Iterator::next is:
+        //
+        //     trait Iterator {
+        //         fn next(&mut self) -> Option<Self::Item>
+        //     }
+        //
+        // Self is technically just Iterator, but we want to pretend it's more like this:
+        //
+        //     fn next<T>(self: Iterator<Item=T>) -> Option<T>
+        if is_self
+            && let Type::Path { path } = arg
+            && let def_id = path.def_id()
+            && let Some(trait_) = cache.traits.get(&def_id)
+            && trait_.items.iter().any(|at| at.is_ty_associated_type())
+        {
+            for assoc_ty in &trait_.items {
+                if let clean::ItemKind::TyAssocTypeItem(_generics, bounds) = &*assoc_ty.kind
+                    && let Some(name) = assoc_ty.name
+                {
+                    let idx = -isize::try_from(rgen.len() + 1).unwrap();
+                    let (idx, stored_bounds) = rgen
+                        .entry(SimplifiedParam::AssociatedType(def_id, name))
+                        .or_insert_with(|| (idx, Vec::new()));
+                    let idx = *idx;
+                    if stored_bounds.is_empty() {
+                        // Can't just pass stored_bounds to simplify_fn_type,
+                        // because it also accepts rgen as a parameter.
+                        // Instead, have it fill in this local, then copy it into the map afterward.
+                        let mut type_bounds = Vec::new();
+                        for bound in bounds {
+                            if let Some(path) = bound.get_trait_path() {
+                                let ty = Type::Path { path };
+                                simplify_fn_type(
+                                    self_,
+                                    generics,
+                                    &ty,
+                                    tcx,
+                                    recurse + 1,
+                                    &mut type_bounds,
+                                    rgen,
+                                    is_return,
+                                    cache,
+                                );
+                            }
+                        }
+                        let stored_bounds = &mut rgen
+                            .get_mut(&SimplifiedParam::AssociatedType(def_id, name))
+                            .unwrap()
+                            .1;
+                        if stored_bounds.is_empty() {
+                            *stored_bounds = type_bounds;
+                        }
+                    }
+                    ty_bindings.push((
+                        RenderTypeId::AssociatedType(name),
+                        vec![RenderType {
+                            id: Some(RenderTypeId::Index(idx)),
+                            generics: None,
+                            bindings: None,
+                        }],
+                    ))
+                }
+            }
         }
-        let id = get_index_type_id(&arg);
+        let id = get_index_type_id(&arg, rgen);
         if id.is_some() || !ty_generics.is_empty() {
             res.push(RenderType {
                 id,
+                bindings: if ty_bindings.is_empty() { None } else { Some(ty_bindings) },
                 generics: if ty_generics.is_empty() { None } else { Some(ty_generics) },
             });
         }
     }
 }
 
+fn simplify_fn_binding<'tcx, 'a>(
+    self_: Option<&'a Type>,
+    generics: &Generics,
+    binding: &'a clean::TypeBinding,
+    tcx: TyCtxt<'tcx>,
+    recurse: usize,
+    res: &mut Vec<(RenderTypeId, Vec<RenderType>)>,
+    rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>,
+    is_return: bool,
+    cache: &Cache,
+) {
+    let mut ty_binding_constraints = Vec::new();
+    let ty_binding_assoc = RenderTypeId::AssociatedType(binding.assoc.name);
+    for gen in &binding.assoc.args {
+        match gen {
+            clean::GenericArg::Type(arg) => simplify_fn_type(
+                self_,
+                generics,
+                &arg,
+                tcx,
+                recurse + 1,
+                &mut ty_binding_constraints,
+                rgen,
+                is_return,
+                cache,
+            ),
+            clean::GenericArg::Lifetime(_)
+            | clean::GenericArg::Const(_)
+            | clean::GenericArg::Infer => {}
+        }
+    }
+    for binding in binding.assoc.args.bindings() {
+        simplify_fn_binding(
+            self_,
+            generics,
+            &binding,
+            tcx,
+            recurse + 1,
+            res,
+            rgen,
+            is_return,
+            cache,
+        );
+    }
+    match &binding.kind {
+        clean::TypeBindingKind::Equality { term } => {
+            if let clean::Term::Type(arg) = &term {
+                simplify_fn_type(
+                    self_,
+                    generics,
+                    arg,
+                    tcx,
+                    recurse + 1,
+                    &mut ty_binding_constraints,
+                    rgen,
+                    is_return,
+                    cache,
+                );
+            }
+        }
+        clean::TypeBindingKind::Constraint { bounds } => {
+            for bound in &bounds[..] {
+                if let Some(path) = bound.get_trait_path() {
+                    let ty = Type::Path { path };
+                    simplify_fn_type(
+                        self_,
+                        generics,
+                        &ty,
+                        tcx,
+                        recurse + 1,
+                        &mut ty_binding_constraints,
+                        rgen,
+                        is_return,
+                        cache,
+                    );
+                }
+            }
+        }
+    }
+    res.push((ty_binding_assoc, ty_binding_constraints));
+}
+
 /// Return the full list of types when bounds have been resolved.
 ///
 /// i.e. `fn foo<A: Display, B: Option<A>>(x: u32, y: B)` will return
@@ -701,13 +1003,15 @@ fn simplify_fn_type<'tcx, 'a>(
 fn get_fn_inputs_and_outputs<'tcx>(
     func: &Function,
     tcx: TyCtxt<'tcx>,
-    impl_generics: Option<&(clean::Type, clean::Generics)>,
+    impl_or_trait_generics: Option<&(clean::Type, clean::Generics)>,
     cache: &Cache,
 ) -> (Vec<RenderType>, Vec<RenderType>, Vec<Vec<RenderType>>) {
     let decl = &func.decl;
 
+    let mut rgen: FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default();
+
     let combined_generics;
-    let (self_, generics) = if let Some((impl_self, impl_generics)) = impl_generics {
+    let (self_, generics) = if let Some((impl_self, impl_generics)) = impl_or_trait_generics {
         match (impl_generics.is_empty(), func.generics.is_empty()) {
             (true, _) => (Some(impl_self), &func.generics),
             (_, true) => (Some(impl_self), impl_generics),
@@ -729,8 +1033,6 @@ fn get_fn_inputs_and_outputs<'tcx>(
         (None, &func.generics)
     };
 
-    let mut rgen: FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default();
-
     let mut arg_types = Vec::new();
     for arg in decl.inputs.values.iter() {
         simplify_fn_type(
diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js
index c7811b43d16..2338931a18f 100644
--- a/src/librustdoc/html/static/js/externs.js
+++ b/src/librustdoc/html/static/js/externs.js
@@ -14,6 +14,7 @@ function initSearch(searchIndex){}
  *     pathWithoutLast: Array<string>,
  *     pathLast: string,
  *     generics: Array<QueryElement>,
+ *     bindings: Map<(string|integer), Array<QueryElement>>,
  * }}
  */
 let QueryElement;
@@ -24,6 +25,7 @@ let QueryElement;
  *      totalElems: number,
  *      typeFilter: (null|string),
  *      userQuery: string,
+ *      isInBinding: (null|string),
  * }}
  */
 let ParserState;
@@ -191,8 +193,9 @@ let FunctionSearchType;
 /**
  * @typedef {{
  *     id: (null|number),
- *     ty: (null|number),
+ *     ty: number,
  *     generics: Array<FunctionType>,
+ *     bindings: Map<integer, Array<FunctionType>>,
  * }}
  */
 let FunctionType;
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index e9dd3c439b3..22dcb870143 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -23,27 +23,27 @@ const itemTypes = [
     "import",
     "struct",
     "enum",
-    "fn",
+    "fn", // 5
     "type",
     "static",
     "trait",
     "impl",
-    "tymethod",
+    "tymethod", // 10
     "method",
     "structfield",
     "variant",
     "macro",
-    "primitive",
+    "primitive", // 15
     "associatedtype",
     "constant",
     "associatedconstant",
     "union",
-    "foreigntype",
+    "foreigntype", // 20
     "keyword",
     "existential",
     "attr",
     "derive",
-    "traitalias",
+    "traitalias", // 25
     "generic",
 ];
 
@@ -298,7 +298,7 @@ function initSearch(rawSearchIndex) {
     }
 
     function isEndCharacter(c) {
-        return ",>-]".indexOf(c) !== -1;
+        return "=,>-]".indexOf(c) !== -1;
     }
 
     function isStopCharacter(c) {
@@ -398,7 +398,7 @@ function initSearch(rawSearchIndex) {
      * @return {boolean}
      */
     function isSeparatorCharacter(c) {
-        return c === ",";
+        return c === "," || c === "=";
     }
 
 /**
@@ -500,6 +500,8 @@ function initSearch(rawSearchIndex) {
                     " does not accept generic parameters",
                 ];
             }
+            const bindingName = parserState.isInBinding;
+            parserState.isInBinding = null;
             return {
                 name: "never",
                 id: null,
@@ -507,7 +509,9 @@ function initSearch(rawSearchIndex) {
                 pathWithoutLast: [],
                 pathLast: "never",
                 generics: [],
+                bindings: new Map(),
                 typeFilter: "primitive",
+                bindingName,
             };
         }
         if (path.startsWith("::")) {
@@ -542,14 +546,27 @@ function initSearch(rawSearchIndex) {
         if (isInGenerics) {
             parserState.genericsElems += 1;
         }
+        const bindingName = parserState.isInBinding;
+        parserState.isInBinding = null;
+        const bindings = new Map();
         return {
             name: name.trim(),
             id: null,
             fullPath: pathSegments,
             pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1),
             pathLast: pathSegments[pathSegments.length - 1],
-            generics: generics,
+            generics: generics.filter(gen => {
+                // Syntactically, bindings are parsed as generics,
+                // but the query engine treats them differently.
+                if (gen.bindingName !== null) {
+                    bindings.set(gen.bindingName.name, [gen, ...gen.bindingName.generics]);
+                    return false;
+                }
+                return true;
+            }),
+            bindings,
             typeFilter,
+            bindingName,
         };
     }
 
@@ -608,6 +625,7 @@ function initSearch(rawSearchIndex) {
                     }
                 } else if (
                     c === "[" ||
+                    c === "=" ||
                     isStopCharacter(c) ||
                     isSpecialStartCharacter(c) ||
                     isSeparatorCharacter(c)
@@ -657,6 +675,7 @@ function initSearch(rawSearchIndex) {
             parserState.pos += 1;
             getItemsBefore(query, parserState, generics, "]");
             const typeFilter = parserState.typeFilter;
+            const isInBinding = parserState.isInBinding;
             if (typeFilter !== null && typeFilter !== "primitive") {
                 throw [
                     "Invalid search type: primitive ",
@@ -667,10 +686,16 @@ function initSearch(rawSearchIndex) {
                 ];
             }
             parserState.typeFilter = null;
+            parserState.isInBinding = null;
             parserState.totalElems += 1;
             if (isInGenerics) {
                 parserState.genericsElems += 1;
             }
+            for (const gen of generics) {
+                if (gen.bindingName !== null) {
+                    throw ["Type parameter ", "=", " cannot be within slice ", "[]"];
+                }
+            }
             elems.push({
                 name: "[]",
                 id: null,
@@ -679,6 +704,8 @@ function initSearch(rawSearchIndex) {
                 pathLast: "[]",
                 generics,
                 typeFilter: "primitive",
+                bindingName: isInBinding,
+                bindings: new Map(),
             });
         } else {
             const isStringElem = parserState.userQuery[start] === "\"";
@@ -705,15 +732,38 @@ function initSearch(rawSearchIndex) {
             if (start >= end && generics.length === 0) {
                 return;
             }
-            elems.push(
-                createQueryElement(
-                    query,
-                    parserState,
-                    parserState.userQuery.slice(start, end),
-                    generics,
-                    isInGenerics
-                )
-            );
+            if (parserState.userQuery[parserState.pos] === "=") {
+                if (parserState.isInBinding) {
+                    throw ["Cannot write ", "=", " twice in a binding"];
+                }
+                if (!isInGenerics) {
+                    throw ["Type parameter ", "=", " must be within generics list"];
+                }
+                const name = parserState.userQuery.slice(start, end).trim();
+                if (name === "!") {
+                    throw ["Type parameter ", "=", " key cannot be ", "!", " never type"];
+                }
+                if (name.includes("!")) {
+                    throw ["Type parameter ", "=", " key cannot be ", "!", " macro"];
+                }
+                if (name.includes("::")) {
+                    throw ["Type parameter ", "=", " key cannot contain ", "::", " path"];
+                }
+                if (name.includes(":")) {
+                    throw ["Type parameter ", "=", " key cannot contain ", ":", " type"];
+                }
+                parserState.isInBinding = { name, generics };
+            } else {
+                elems.push(
+                    createQueryElement(
+                        query,
+                        parserState,
+                        parserState.userQuery.slice(start, end),
+                        generics,
+                        isInGenerics
+                    )
+                );
+            }
         }
     }
 
@@ -737,6 +787,8 @@ function initSearch(rawSearchIndex) {
         // If this is a generic, keep the outer item's type filter around.
         const oldTypeFilter = parserState.typeFilter;
         parserState.typeFilter = null;
+        const oldIsInBinding = parserState.isInBinding;
+        parserState.isInBinding = null;
 
         let extra = "";
         if (endChar === ">") {
@@ -752,6 +804,9 @@ function initSearch(rawSearchIndex) {
         while (parserState.pos < parserState.length) {
             const c = parserState.userQuery[parserState.pos];
             if (c === endChar) {
+                if (parserState.isInBinding) {
+                    throw ["Unexpected ", endChar, " after ", "="];
+                }
                 break;
             } else if (isSeparatorCharacter(c)) {
                 parserState.pos += 1;
@@ -791,7 +846,9 @@ function initSearch(rawSearchIndex) {
                     throw [
                         "Expected ",
                         ",",
-                        " or ",
+                        ", ",
+                        "=",
+                        ", or ",
                         endChar,
                         ...extra,
                         ", found ",
@@ -801,6 +858,8 @@ function initSearch(rawSearchIndex) {
                 throw [
                     "Expected ",
                     ",",
+                    " or ",
+                    "=",
                     ...extra,
                     ", found ",
                     c,
@@ -828,6 +887,7 @@ function initSearch(rawSearchIndex) {
         parserState.pos += 1;
 
         parserState.typeFilter = oldTypeFilter;
+        parserState.isInBinding = oldIsInBinding;
     }
 
     /**
@@ -1054,6 +1114,11 @@ function initSearch(rawSearchIndex) {
             for (const elem2 of elem.generics) {
                 convertTypeFilterOnElem(elem2);
             }
+            for (const constraints of elem.bindings.values()) {
+                for (const constraint of constraints) {
+                    convertTypeFilterOnElem(constraint);
+                }
+            }
         }
         userQuery = userQuery.trim();
         const parserState = {
@@ -1063,6 +1128,7 @@ function initSearch(rawSearchIndex) {
             totalElems: 0,
             genericsElems: 0,
             typeFilter: null,
+            isInBinding: null,
             userQuery: userQuery.toLowerCase(),
         };
         let query = newParsedQuery(userQuery);
@@ -1342,7 +1408,8 @@ function initSearch(rawSearchIndex) {
             const fl = fnTypesIn.length;
 
             // One element fast path / base case
-            if (ql === 1 && queryElems[0].generics.length === 0) {
+            if (ql === 1 && queryElems[0].generics.length === 0
+                && queryElems[0].bindings.size === 0) {
                 const queryElem = queryElems[0];
                 for (const fnType of fnTypesIn) {
                     if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) {
@@ -1453,16 +1520,33 @@ function initSearch(rawSearchIndex) {
                     whereClause,
                     mgensScratch,
                     mgensScratch => {
-                        if (fnType.generics.length === 0 && queryElem.generics.length === 0) {
+                        if (fnType.generics.length === 0 && queryElem.generics.length === 0
+                            && fnType.bindings.size === 0 && queryElem.bindings.size === 0) {
                             return !solutionCb || solutionCb(mgensScratch);
                         }
-                        return unifyFunctionTypes(
-                            fnType.generics,
-                            queryElem.generics,
+                        const solution = unifyFunctionTypeCheckBindings(
+                            fnType,
+                            queryElem,
                             whereClause,
-                            mgensScratch,
-                            solutionCb
+                            mgensScratch
                         );
+                        if (!solution) {
+                            return false;
+                        }
+                        const simplifiedGenerics = solution.simplifiedGenerics;
+                        for (const simplifiedMgens of solution.mgens) {
+                            const passesUnification = unifyFunctionTypes(
+                                simplifiedGenerics,
+                                queryElem.generics,
+                                whereClause,
+                                simplifiedMgens,
+                                solutionCb
+                            );
+                            if (passesUnification) {
+                                return true;
+                            }
+                        }
+                        return false;
                     }
                 );
                 if (passesUnification) {
@@ -1491,8 +1575,11 @@ function initSearch(rawSearchIndex) {
                 const generics = fnType.id < 0 ?
                     whereClause[(-fnType.id) - 1] :
                     fnType.generics;
+                const bindings = fnType.bindings ?
+                    Array.from(fnType.bindings.values()).flat() :
+                    [];
                 const passesUnification = unifyFunctionTypes(
-                    fnTypes.toSpliced(i, 1, ...generics),
+                    fnTypes.toSpliced(i, 1, ...generics, ...bindings),
                     queryElems,
                     whereClause,
                     mgensScratch,
@@ -1504,22 +1591,37 @@ function initSearch(rawSearchIndex) {
             }
             return false;
         }
-        function unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens) {
+        /**
+         * Check if this function is a match candidate.
+         *
+         * This function is all the fast checks that don't require backtracking.
+         * It checks that two items are not named differently, and is load-bearing for that.
+         * It also checks that, if the query has generics, the function type must have generics
+         * or associated type bindings: that's not load-bearing, but it prevents unnecessary
+         * backtracking later.
+         *
+         * @param {FunctionType} fnType
+         * @param {QueryElement} queryElem
+         * @param {[FunctionSearchType]} whereClause - Trait bounds for generic items.
+         * @param {Map<number,number>|null} mgensIn - Map functions generics to query generics.
+         * @returns {boolean}
+         */
+        function unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgensIn) {
             // type filters look like `trait:Read` or `enum:Result`
             if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) {
                 return false;
             }
             // fnType.id < 0 means generic
             // queryElem.id < 0 does too
-            // mgens[fnType.id] = queryElem.id
-            // or, if mgens[fnType.id] = 0, then we've matched this generic with a bare trait
+            // mgensIn[fnType.id] = queryElem.id
+            // or, if mgensIn[fnType.id] = 0, then we've matched this generic with a bare trait
             // and should make that same decision everywhere it appears
             if (fnType.id < 0 && queryElem.id < 0) {
-                if (mgens !== null) {
-                    if (mgens.has(fnType.id) && mgens.get(fnType.id) !== queryElem.id) {
+                if (mgensIn) {
+                    if (mgensIn.has(fnType.id) && mgensIn.get(fnType.id) !== queryElem.id) {
                         return false;
                     }
-                    for (const [fid, qid] of mgens.entries()) {
+                    for (const [fid, qid] of mgensIn.entries()) {
                         if (fnType.id !== fid && queryElem.id === qid) {
                             return false;
                         }
@@ -1528,6 +1630,7 @@ function initSearch(rawSearchIndex) {
                         }
                     }
                 }
+                return true;
             } else {
                 if (queryElem.id === typeNameIdOfArrayOrSlice &&
                     (fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray)
@@ -1539,7 +1642,12 @@ function initSearch(rawSearchIndex) {
                 }
                 // If the query elem has generics, and the function doesn't,
                 // it can't match.
-                if (fnType.generics.length === 0 && queryElem.generics.length !== 0) {
+                if ((fnType.generics.length + fnType.bindings.size) === 0 &&
+                    queryElem.generics.length !== 0
+                ) {
+                    return false;
+                }
+                if (fnType.bindings.size < queryElem.bindings.size) {
                     return false;
                 }
                 // If the query element is a path (it contains `::`), we need to check if this
@@ -1568,9 +1676,87 @@ function initSearch(rawSearchIndex) {
                         return false;
                     }
                 }
+                return true;
             }
-            return true;
         }
+        /**
+         * This function checks the associated type bindings. Any that aren't matched get converted
+         * to generics, and this function returns an array of the function's generics with these
+         * simplified bindings added to them. That is, it takes a path like this:
+         *
+         *     Iterator<Item=u32>
+         *
+         * ... if queryElem itself has an `Item=` in it, then this function returns an empty array.
+         * But if queryElem contains no Item=, then this function returns a one-item array with the
+         * ID of u32 in it, and the rest of the matching engine acts as if `Iterator<u32>` were
+         * the type instead.
+         *
+         * @param {FunctionType} fnType
+         * @param {QueryElement} queryElem
+         * @param {[FunctionType]} whereClause - Trait bounds for generic items.
+         * @param {Map<number,number>} mgensIn - Map functions generics to query generics.
+         *                                            Never modified.
+         * @returns {false|{mgens: [Map<number,number>], simplifiedGenerics: [FunctionType]}}
+         */
+        function unifyFunctionTypeCheckBindings(fnType, queryElem, whereClause, mgensIn) {
+            if (fnType.bindings.size < queryElem.bindings.size) {
+                return false;
+            }
+            let simplifiedGenerics = fnType.generics || [];
+            if (fnType.bindings.size > 0) {
+                let mgensSolutionSet = [mgensIn];
+                for (const [name, constraints] of queryElem.bindings.entries()) {
+                    if (mgensSolutionSet.length === 0) {
+                        return false;
+                    }
+                    if (!fnType.bindings.has(name)) {
+                        return false;
+                    }
+                    const fnTypeBindings = fnType.bindings.get(name);
+                    mgensSolutionSet = mgensSolutionSet.flatMap(mgens => {
+                        const newSolutions = [];
+                        unifyFunctionTypes(
+                            fnTypeBindings,
+                            constraints,
+                            whereClause,
+                            mgens,
+                            newMgens => {
+                                newSolutions.push(newMgens);
+                                // return `false` makes unifyFunctionTypes return the full set of
+                                // possible solutions
+                                return false;
+                            }
+                        );
+                        return newSolutions;
+                    });
+                }
+                if (mgensSolutionSet.length === 0) {
+                    return false;
+                }
+                const binds = Array.from(fnType.bindings.entries()).flatMap(entry => {
+                    const [name, constraints] = entry;
+                    if (queryElem.bindings.has(name)) {
+                        return [];
+                    } else {
+                        return constraints;
+                    }
+                });
+                if (simplifiedGenerics.length > 0) {
+                    simplifiedGenerics = [...simplifiedGenerics, ...binds];
+                } else {
+                    simplifiedGenerics = binds;
+                }
+                return { simplifiedGenerics, mgens: mgensSolutionSet };
+            }
+            return { simplifiedGenerics, mgens: [mgensIn] };
+        }
+        /**
+         * @param {FunctionType} fnType
+         * @param {QueryElement} queryElem
+         * @param {[FunctionType]} whereClause - Trait bounds for generic items.
+         * @param {Map<number,number>|null} mgens - Map functions generics to query generics.
+         * @returns {boolean}
+         */
         function unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens) {
             if (fnType.id < 0 && queryElem.id >= 0) {
                 if (!whereClause) {
@@ -1578,7 +1764,7 @@ function initSearch(rawSearchIndex) {
                 }
                 // mgens[fnType.id] === 0 indicates that we committed to unboxing this generic
                 // mgens[fnType.id] === null indicates that we haven't decided yet
-                if (mgens !== null && mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) {
+                if (mgens && mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) {
                     return false;
                 }
                 // This is only a potential unbox if the search query appears in the where clause
@@ -1586,8 +1772,12 @@ function initSearch(rawSearchIndex) {
                 // `fn read_all<R: Read>(R) -> Result<usize>`
                 // generic `R` is considered "unboxed"
                 return checkIfInList(whereClause[(-fnType.id) - 1], queryElem, whereClause);
-            } else if (fnType.generics && fnType.generics.length > 0) {
-                return checkIfInList(fnType.generics, queryElem, whereClause);
+            } else if (fnType.generics.length > 0 || fnType.bindings.size > 0) {
+                const simplifiedGenerics = [
+                    ...fnType.generics,
+                    ...Array.from(fnType.bindings.values()).flat(),
+                ];
+                return checkIfInList(simplifiedGenerics, queryElem, whereClause);
             }
             return false;
         }
@@ -1622,15 +1812,17 @@ function initSearch(rawSearchIndex) {
           * @return {boolean} - Returns true if the type matches, false otherwise.
           */
         function checkType(row, elem, whereClause) {
-            if (elem.id < 0) {
-                return row.id < 0 || checkIfInList(row.generics, elem, whereClause);
-            }
-            if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 &&
-                typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 &&
-                // special case
-                elem.id !== typeNameIdOfArrayOrSlice
-            ) {
-                return row.id === elem.id || checkIfInList(row.generics, elem, whereClause);
+            if (row.bindings.size === 0 && elem.bindings.size === 0) {
+                if (elem.id < 0) {
+                    return row.id < 0 || checkIfInList(row.generics, elem, whereClause);
+                }
+                if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 &&
+                    typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 &&
+                    // special case
+                    elem.id !== typeNameIdOfArrayOrSlice
+                ) {
+                    return row.id === elem.id || checkIfInList(row.generics, elem, whereClause);
+                }
             }
             return unifyFunctionTypes([row], [elem], whereClause);
         }
@@ -1977,7 +2169,7 @@ function initSearch(rawSearchIndex) {
                     elem.id = match;
                 }
                 if ((elem.id === null && parsedQuery.totalElems > 1 && elem.typeFilter === -1
-                     && elem.generics.length === 0)
+                     && elem.generics.length === 0 && elem.bindings.size === 0)
                     || elem.typeFilter === TY_GENERIC) {
                     if (genericSymbols.has(elem.name)) {
                         elem.id = genericSymbols.get(elem.name);
@@ -2020,6 +2212,23 @@ function initSearch(rawSearchIndex) {
                 for (const elem2 of elem.generics) {
                     convertNameToId(elem2);
                 }
+                elem.bindings = new Map(Array.from(elem.bindings.entries())
+                    .map(entry => {
+                        const [name, constraints] = entry;
+                        if (!typeNameIdMap.has(name)) {
+                            parsedQuery.error = [
+                                "Type parameter ",
+                                name,
+                                " does not exist",
+                            ];
+                        }
+                        for (const elem2 of constraints) {
+                            convertNameToId(elem2);
+                        }
+
+                        return [typeNameIdMap.get(name), constraints];
+                    })
+                );
             }
 
             for (const elem of parsedQuery.elems) {
@@ -2536,16 +2745,39 @@ ${item.displayPath}<span class="${type}">${name}</span>\
     function buildItemSearchType(type, lowercasePaths) {
         const PATH_INDEX_DATA = 0;
         const GENERICS_DATA = 1;
-        let pathIndex, generics;
+        const BINDINGS_DATA = 2;
+        let pathIndex, generics, bindings;
         if (typeof type === "number") {
             pathIndex = type;
             generics = [];
+            bindings = new Map();
         } else {
             pathIndex = type[PATH_INDEX_DATA];
             generics = buildItemSearchTypeAll(
                 type[GENERICS_DATA],
                 lowercasePaths
             );
+            if (type.length > BINDINGS_DATA) {
+                bindings = new Map(type[BINDINGS_DATA].map(binding => {
+                    const [assocType, constraints] = binding;
+                    // Associated type constructors are represented sloppily in rustdoc's
+                    // type search, to make the engine simpler.
+                    //
+                    // MyType<Output<T>=Result<T>> is equivalent to MyType<Output<Result<T>>=T>
+                    // and both are, essentially
+                    // MyType<Output=(T, Result<T>)>, except the tuple isn't actually there.
+                    // It's more like the value of a type binding is naturally an array,
+                    // which rustdoc calls "constraints".
+                    //
+                    // As a result, the key should never have generics on it.
+                    return [
+                        buildItemSearchType(assocType, lowercasePaths).id,
+                        buildItemSearchTypeAll(constraints, lowercasePaths),
+                    ];
+                }));
+            } else {
+                bindings = new Map();
+            }
         }
         if (pathIndex < 0) {
             // types less than 0 are generic parameters
@@ -2555,6 +2787,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
                 ty: TY_GENERIC,
                 path: null,
                 generics,
+                bindings,
             };
         }
         if (pathIndex === 0) {
@@ -2564,6 +2797,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
                 ty: null,
                 path: null,
                 generics,
+                bindings,
             };
         }
         const item = lowercasePaths[pathIndex - 1];
@@ -2572,6 +2806,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
             ty: item.ty,
             path: item.path,
             generics,
+            bindings,
         };
     }
 
diff --git a/src/tools/rustdoc-js/tester.js b/src/tools/rustdoc-js/tester.js
index c7e6dd3615e..6e630a80454 100644
--- a/src/tools/rustdoc-js/tester.js
+++ b/src/tools/rustdoc-js/tester.js
@@ -122,7 +122,31 @@ function checkNeededFields(fullPath, expected, error_text, queryName, position)
 }
 
 function valueCheck(fullPath, expected, result, error_text, queryName) {
-    if (Array.isArray(expected)) {
+    if (Array.isArray(expected) && result instanceof Map) {
+        const expected_set = new Set();
+        for (const [key, expected_value] of expected) {
+            expected_set.add(key);
+            checkNeededFields(fullPath, expected_value, error_text, queryName, key);
+            if (result.has(key)) {
+                valueCheck(
+                    fullPath + "[" + key + "]",
+                    expected_value,
+                    result.get(key),
+                    error_text,
+                    queryName
+                );
+            } else {
+                error_text.push(`${queryName}==> EXPECTED has extra key in map from field ` +
+                    `\`${fullPath}\` (key ${key}): \`${JSON.stringify(expected_value)}\``);
+            }
+        }
+        for (const [key, result_value] of result.entries()) {
+            if (!expected_set.has(key)) {
+                error_text.push(`${queryName}==> EXPECTED missing key in map from field ` +
+                    `\`${fullPath}\` (key ${key}): \`${JSON.stringify(result_value)}\``);
+            }
+        }
+    } else if (Array.isArray(expected)) {
         let i;
         for (i = 0; i < expected.length; ++i) {
             checkNeededFields(fullPath, expected[i], error_text, queryName, i);
@@ -153,6 +177,9 @@ function valueCheck(fullPath, expected, result, error_text, queryName) {
             }
             let result_v = result[key];
             if (result_v !== null && key === "error") {
+                if (!result_v.forEach) {
+                    throw result_v;
+                }
                 result_v.forEach((value, index) => {
                     value = value.split("&nbsp;").join(" ");
                     if (index % 2 === 1) {
diff --git a/tests/rustdoc-gui/search-tab.goml b/tests/rustdoc-gui/search-tab.goml
index db1605ff220..b52bb0688c1 100644
--- a/tests/rustdoc-gui/search-tab.goml
+++ b/tests/rustdoc-gui/search-tab.goml
@@ -80,7 +80,7 @@ set-window-size: (851, 600)
 
 // Check the size and count in tabs
 assert-text: ("#search-tabs > button:nth-child(1) > .count", " (25) ")
-assert-text: ("#search-tabs > button:nth-child(2) > .count", " (5)  ")
+assert-text: ("#search-tabs > button:nth-child(2) > .count", " (6)  ")
 assert-text: ("#search-tabs > button:nth-child(3) > .count", " (0)  ")
 store-property: ("#search-tabs > button:nth-child(1)", {"offsetWidth": buttonWidth})
 assert-property: ("#search-tabs > button:nth-child(2)", {"offsetWidth": |buttonWidth|})
diff --git a/tests/rustdoc-js-std/iterator-type-signatures.js b/tests/rustdoc-js-std/iterator-type-signatures.js
new file mode 100644
index 00000000000..c18ffc1651c
--- /dev/null
+++ b/tests/rustdoc-js-std/iterator-type-signatures.js
@@ -0,0 +1,29 @@
+// ignore-order
+
+const FILTER_CRATE = "std";
+
+const EXPECTED = [
+    {
+        'query': 'iterator<t> -> option<t>',
+        'others': [
+            { 'path': 'std::iter::Iterator', 'name': 'max' },
+            { 'path': 'std::iter::Iterator', 'name': 'min' },
+            { 'path': 'std::iter::Iterator', 'name': 'last' },
+            { 'path': 'std::iter::Iterator', 'name': 'next' },
+        ],
+    },
+    {
+        'query': 'iterator<t>, usize -> option<t>',
+        'others': [
+            { 'path': 'std::iter::Iterator', 'name': 'nth' },
+        ],
+    },
+    {
+        // Something should be done so that intoiterator is considered a match
+        // for plain iterator.
+        'query': 'iterator<t>, intoiterator<t> -> ordering',
+        'others': [
+            { 'path': 'std::iter::Iterator', 'name': 'cmp' },
+        ],
+    },
+];
diff --git a/tests/rustdoc-js-std/parser-bindings.js b/tests/rustdoc-js-std/parser-bindings.js
new file mode 100644
index 00000000000..faf880c8116
--- /dev/null
+++ b/tests/rustdoc-js-std/parser-bindings.js
@@ -0,0 +1,245 @@
+const PARSED = [
+    {
+        query: 'A<B=C>',
+        elems: [
+            {
+                name: "a",
+                fullPath: ["a"],
+                pathWithoutLast: [],
+                pathLast: "a",
+                generics: [],
+                bindings: [
+                    [
+                        'b',
+                        [
+                            {
+                                name: "c",
+                                fullPath: ["c"],
+                                pathWithoutLast: [],
+                                pathLast: "c",
+                                generics: [],
+                                typeFilter: -1,
+                            },
+                        ]
+                    ],
+                ],
+                typeFilter: -1,
+            },
+        ],
+        foundElems: 1,
+        original: 'A<B=C>',
+        returned: [],
+        userQuery: 'a<b=c>',
+        error: null,
+    },
+    {
+        query: 'A<B = C>',
+        elems: [
+            {
+                name: "a",
+                fullPath: ["a"],
+                pathWithoutLast: [],
+                pathLast: "a",
+                generics: [],
+                bindings: [
+                    [
+                        'b',
+                        [{
+                            name: "c",
+                            fullPath: ["c"],
+                            pathWithoutLast: [],
+                            pathLast: "c",
+                            generics: [],
+                            typeFilter: -1,
+                        }]
+                    ],
+                ],
+                typeFilter: -1,
+            },
+        ],
+        foundElems: 1,
+        original: 'A<B = C>',
+        returned: [],
+        userQuery: 'a<b = c>',
+        error: null,
+    },
+    {
+        query: 'A<B=!>',
+        elems: [
+            {
+                name: "a",
+                fullPath: ["a"],
+                pathWithoutLast: [],
+                pathLast: "a",
+                generics: [],
+                bindings: [
+                    [
+                        'b',
+                        [{
+                            name: "never",
+                            fullPath: ["never"],
+                            pathWithoutLast: [],
+                            pathLast: "never",
+                            generics: [],
+                            typeFilter: 15,
+                        }]
+                    ],
+                ],
+                typeFilter: -1,
+            },
+        ],
+        foundElems: 1,
+        original: 'A<B=!>',
+        returned: [],
+        userQuery: 'a<b=!>',
+        error: null,
+    },
+    {
+        query: 'A<B=[]>',
+        elems: [
+            {
+                name: "a",
+                fullPath: ["a"],
+                pathWithoutLast: [],
+                pathLast: "a",
+                generics: [],
+                bindings: [
+                    [
+                        'b',
+                        [{
+                            name: "[]",
+                            fullPath: ["[]"],
+                            pathWithoutLast: [],
+                            pathLast: "[]",
+                            generics: [],
+                            typeFilter: 15,
+                        }]
+                    ],
+                ],
+                typeFilter: -1,
+            },
+        ],
+        foundElems: 1,
+        original: 'A<B=[]>',
+        returned: [],
+        userQuery: 'a<b=[]>',
+        error: null,
+    },
+    {
+        query: 'A<B=[!]>',
+        elems: [
+            {
+                name: "a",
+                fullPath: ["a"],
+                pathWithoutLast: [],
+                pathLast: "a",
+                generics: [],
+                bindings: [
+                    [
+                        'b',
+                        [{
+                            name: "[]",
+                            fullPath: ["[]"],
+                            pathWithoutLast: [],
+                            pathLast: "[]",
+                            generics: [
+                                {
+                                    name: "never",
+                                    fullPath: ["never"],
+                                    pathWithoutLast: [],
+                                    pathLast: "never",
+                                    generics: [],
+                                    typeFilter: 15,
+                                },
+                            ],
+                            typeFilter: 15,
+                        }]
+                    ],
+                ],
+                typeFilter: -1,
+            },
+        ],
+        foundElems: 1,
+        original: 'A<B=[!]>',
+        returned: [],
+        userQuery: 'a<b=[!]>',
+        error: null,
+    },
+    {
+        query: 'A<B=C=>',
+        elems: [],
+        foundElems: 0,
+        original: 'A<B=C=>',
+        returned: [],
+        userQuery: 'a<b=c=>',
+        error: "Cannot write `=` twice in a binding",
+    },
+    {
+        query: 'A<B=>',
+        elems: [],
+        foundElems: 0,
+        original: 'A<B=>',
+        returned: [],
+        userQuery: 'a<b=>',
+        error: "Unexpected `>` after `=`",
+    },
+    {
+        query: 'B=C',
+        elems: [],
+        foundElems: 0,
+        original: 'B=C',
+        returned: [],
+        userQuery: 'b=c',
+        error: "Type parameter `=` must be within generics list",
+    },
+    {
+        query: '[B=C]',
+        elems: [],
+        foundElems: 0,
+        original: '[B=C]',
+        returned: [],
+        userQuery: '[b=c]',
+        error: "Type parameter `=` cannot be within slice `[]`",
+    },
+    {
+        query: 'A<B<X>=C>',
+        elems: [
+            {
+                name: "a",
+                fullPath: ["a"],
+                pathWithoutLast: [],
+                pathLast: "a",
+                generics: [],
+                bindings: [
+                    [
+                        'b',
+                        [
+                            {
+                                name: "c",
+                                fullPath: ["c"],
+                                pathWithoutLast: [],
+                                pathLast: "c",
+                                generics: [],
+                                typeFilter: -1,
+                            },
+                            {
+                                name: "x",
+                                fullPath: ["x"],
+                                pathWithoutLast: [],
+                                pathLast: "x",
+                                generics: [],
+                                typeFilter: -1,
+                            },
+                        ],
+                    ],
+                ],
+                typeFilter: -1,
+            },
+        ],
+        foundElems: 1,
+        original: 'A<B<X>=C>',
+        returned: [],
+        userQuery: 'a<b<x>=c>',
+        error: null,
+    },
+];
diff --git a/tests/rustdoc-js-std/parser-errors.js b/tests/rustdoc-js-std/parser-errors.js
index b32bfea5439..ab8d72bf71b 100644
--- a/tests/rustdoc-js-std/parser-errors.js
+++ b/tests/rustdoc-js-std/parser-errors.js
@@ -303,7 +303,7 @@ const PARSED = [
         original: '->a<>b',
         returned: [],
         userQuery: '->a<>b',
-        error: 'Expected `,` after `>`, found `b`',
+        error: 'Expected `,` or `=` after `>`, found `b`',
     },
     {
         query: "a<->",
diff --git a/tests/rustdoc-js/assoc-type-backtrack.js b/tests/rustdoc-js/assoc-type-backtrack.js
new file mode 100644
index 00000000000..493e1a9910d
--- /dev/null
+++ b/tests/rustdoc-js/assoc-type-backtrack.js
@@ -0,0 +1,163 @@
+// exact-check
+
+const EXPECTED = [
+    {
+        'query': 'mytrait, mytrait2 -> T',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' },
+            { 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' },
+        ],
+    },
+    {
+        'query': 'mytrait<U>, mytrait2 -> T',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' },
+            { 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' },
+        ],
+    },
+    {
+        'query': 'mytrait<Item=U>, mytrait2 -> T',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::MyTrait', 'name': 'fold' },
+            { 'path': 'assoc_type_backtrack::Cloned', 'name': 'fold' },
+        ],
+    },
+    {
+        'query': 'mytrait<T>, mytrait2 -> T',
+        'correction': null,
+        'others': [],
+    },
+    {
+        'query': 'mytrait<Item=T>, mytrait2 -> T',
+        'correction': null,
+        'others': [],
+    },
+    {
+        'query': 'mytrait<T> -> Option<T>',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::MyTrait', 'name': 'next' },
+        ],
+    },
+    {
+        'query': 'mytrait<Item=T> -> Option<T>',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::MyTrait', 'name': 'next' },
+        ],
+    },
+    {
+        'query': 'mytrait<U> -> Option<T>',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' },
+        ],
+    },
+    {
+        'query': 'mytrait<Item=U> -> Option<T>',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::Cloned', 'name': 'next' },
+        ],
+    },
+    // The first two define the base case.
+    {
+        'query': 'myintofuture<fut=myfuture<t>> -> myfuture<t>',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
+            { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
+        ],
+    },
+    {
+        'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
+        ],
+    },
+    // Unboxings of the one-argument case.
+    {
+        'query': 'myfuture<t> -> myfuture<t>',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
+            { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
+        ],
+    },
+    {
+        'query': 'myintofuture<myfuture<t>> -> myfuture<t>',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future' },
+            { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
+        ],
+    },
+    // Invalid unboxing of the one-argument case.
+    // If you unbox one of the myfutures, you need to unbox both of them.
+    {
+        'query': 'myintofuture<fut=t> -> myfuture<t>',
+        'correction': null,
+        'others': [],
+    },
+    // Unboxings of the two-argument case.
+    {
+        'query': 'myintofuture<fut=t>, myintofuture<fut=t> -> t',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
+        ],
+    },
+    {
+        'query': 'myintofuture<fut=myfuture>, myintofuture<fut=myfuture> -> myfuture',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
+        ],
+    },
+    {
+        'query': 'myintofuture<myfuture>, myintofuture<myfuture> -> myfuture',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
+        ],
+    },
+    {
+        'query': 'myfuture<t>, myfuture<t> -> myfuture<t>',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type_backtrack::MyIntoFuture', 'name': 'into_future_2' },
+        ],
+    },
+    // Invalid unboxings of the two-argument case.
+    // If you unbox one of the myfutures, you need to unbox all of them.
+    {
+        'query': 'myintofuture<fut=t>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
+        'correction': null,
+        'others': [],
+    },
+    {
+        'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=t> -> myfuture<t>',
+        'correction': null,
+        'others': [],
+    },
+    {
+        'query': 'myintofuture<fut=myfuture<t>>, myintofuture<fut=myfuture<t>> -> t',
+        'correction': null,
+        'others': [],
+    },
+    // different generics don't match up either
+    {
+        'query': 'myintofuture<fut=myfuture<u>>, myintofuture<fut=myfuture<t>> -> myfuture<t>',
+        'correction': null,
+        'others': [],
+    },
+    {
+        'query': 'myintofuture<output=t> -> myfuture<tt>',
+        'correction': null,
+        'others': [],
+    },
+];
diff --git a/tests/rustdoc-js/assoc-type-backtrack.rs b/tests/rustdoc-js/assoc-type-backtrack.rs
new file mode 100644
index 00000000000..c3cdd78c6e1
--- /dev/null
+++ b/tests/rustdoc-js/assoc-type-backtrack.rs
@@ -0,0 +1,38 @@
+pub trait MyTrait2<X> {
+    type Output;
+}
+
+pub trait MyTrait {
+    type Item;
+    fn next(&mut self) -> Option<Self::Item>;
+    fn fold<B, F>(self, init: B, f: F) -> B where
+        Self: Sized,
+        F: MyTrait2<(B, Self::Item), Output=B>;
+}
+
+pub struct Cloned<I>(I);
+
+impl<'a, T, I> MyTrait for Cloned<I> where
+    T: 'a + Clone,
+    I: MyTrait<Item = &'a T>
+{
+    type Item = T;
+    fn next(&mut self) -> Option<Self::Item> { loop {} }
+    fn fold<B, F>(self, init: B, f: F) -> B where
+        Self: Sized,
+        F: MyTrait2<(B, Self::Item), Output=B>
+    {
+        loop {}
+    }
+}
+
+pub trait MyFuture {
+    type Output;
+}
+
+pub trait MyIntoFuture {
+    type Output;
+    type Fut: MyFuture<Output=Self::Output>;
+    fn into_future(self) -> Self::Fut;
+    fn into_future_2(self, other: Self) -> Self::Fut;
+}
diff --git a/tests/rustdoc-js/assoc-type.js b/tests/rustdoc-js/assoc-type.js
new file mode 100644
index 00000000000..cc3afaa17c0
--- /dev/null
+++ b/tests/rustdoc-js/assoc-type.js
@@ -0,0 +1,45 @@
+// exact-check
+
+const EXPECTED = [
+    // if I just use generics, then the generics version
+    // and the type binding version both show up
+    {
+        'query': 'iterator<something> -> u32',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type', 'name': 'my_fn' },
+            { 'path': 'assoc_type::my', 'name': 'other_fn' },
+        ],
+    },
+    {
+        'query': 'iterator<something>',
+        'correction': null,
+        'in_args': [
+            { 'path': 'assoc_type', 'name': 'my_fn' },
+            { 'path': 'assoc_type::my', 'name': 'other_fn' },
+        ],
+    },
+    // if I write an explicit binding, only it shows up
+    {
+        'query': 'iterator<item=something> -> u32',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type', 'name': 'my_fn' },
+        ],
+    },
+    // case insensitivity
+    {
+        'query': 'iterator<ItEm=sOmEtHiNg> -> u32',
+        'correction': null,
+        'others': [
+            { 'path': 'assoc_type', 'name': 'my_fn' },
+        ],
+    },
+    // wrong binding name, no result
+    {
+        'query': 'iterator<something=something> -> u32',
+        'correction': null,
+        'in_args': [],
+        'others': [],
+    },
+];
diff --git a/tests/rustdoc-js/assoc-type.rs b/tests/rustdoc-js/assoc-type.rs
new file mode 100644
index 00000000000..e12e73cb546
--- /dev/null
+++ b/tests/rustdoc-js/assoc-type.rs
@@ -0,0 +1,12 @@
+pub fn my_fn<X: Iterator<Item = Something>>(_x: X) -> u32 {
+    3
+}
+
+pub struct Something;
+
+pub mod my {
+    pub trait Iterator<T> {}
+    pub fn other_fn<X: Iterator<crate::Something>>(_: X) -> u32 {
+        3
+    }
+}
diff --git a/tests/rustdoc-js/gat.js b/tests/rustdoc-js/gat.js
new file mode 100644
index 00000000000..7cb6a85d135
--- /dev/null
+++ b/tests/rustdoc-js/gat.js
@@ -0,0 +1,57 @@
+// exact-check
+
+const EXPECTED = [
+    {
+        'query': 'foo<assoc<u8>=u8> -> u32',
+        'correction': null,
+        'in_args': [],
+        'others': [
+            { 'path': 'gat', 'name': 'sample' },
+        ],
+    },
+    {
+        'query': 'foo<assoc<u8>=u8> -> !',
+        'correction': null,
+        'in_args': [],
+        'others': [
+            { 'path': 'gat', 'name': 'synergy' },
+        ],
+    },
+    {
+        'query': 'foo<assoc<u8>=u8>',
+        'correction': null,
+        'in_args': [
+            { 'path': 'gat', 'name': 'sample' },
+            { 'path': 'gat', 'name': 'synergy' },
+        ],
+    },
+    {
+        'query': 'foo<assoc<u8>=u32>',
+        'correction': null,
+        'in_args': [
+            { 'path': 'gat', 'name': 'consider' },
+        ],
+    },
+    {
+        // This one is arguably a bug, because the way rustdoc
+        // stores GATs in the search index is sloppy, but it's
+        // precise enough to match most of the samples in the
+        // GAT initiative repo
+        'query': 'foo<assoc<u32>=u8>',
+        'correction': null,
+        'in_args': [
+            { 'path': 'gat', 'name': 'consider' },
+        ],
+    },
+    {
+        // This one is arguably a bug, because the way rustdoc
+        // stores GATs in the search index is sloppy, but it's
+        // precise enough to match most of the samples in the
+        // GAT initiative repo
+        'query': 'foo<assoc<T>=T>',
+        'correction': null,
+        'in_args': [
+            { 'path': 'gat', 'name': 'integrate' },
+        ],
+    },
+];
diff --git a/tests/rustdoc-js/gat.rs b/tests/rustdoc-js/gat.rs
new file mode 100644
index 00000000000..b4861cc683f
--- /dev/null
+++ b/tests/rustdoc-js/gat.rs
@@ -0,0 +1,8 @@
+pub trait Foo {
+    type Assoc<T>;
+}
+
+pub fn sample<X: Foo<Assoc<u8> = u8>>(_: X) -> u32 { loop {} }
+pub fn synergy(_: impl Foo<Assoc<u8> = u8>) -> ! { loop {} }
+pub fn consider(_: impl Foo<Assoc<u8> = u32>) -> bool { loop {} }
+pub fn integrate<T>(_: impl Foo<Assoc<T> = T>) -> T { loop {} }
diff --git a/tests/rustdoc-js/never-search.js b/tests/rustdoc-js/never-search.js
index ed24d693133..9f18370c2f5 100644
--- a/tests/rustdoc-js/never-search.js
+++ b/tests/rustdoc-js/never-search.js
@@ -43,4 +43,14 @@ const EXPECTED = [
             { 'path': 'never_search', 'name': 'box_uninteresting' },
         ],
     },
+    {
+        'query': 'box<item=!>',
+        'in_args': [],
+        'returned': [],
+    },
+    {
+        'query': 'box<item=never>',
+        'in_args': [],
+        'returned': [],
+    },
 ];
diff --git a/tests/rustdoc-js/trait-methods.js b/tests/rustdoc-js/trait-methods.js
new file mode 100644
index 00000000000..dafad5e4378
--- /dev/null
+++ b/tests/rustdoc-js/trait-methods.js
@@ -0,0 +1,12 @@
+// exact-check
+
+const EXPECTED = [
+    {
+        'query': 'mytrait<t> -> option<t>',
+        'correction': null,
+        'in_args': [],
+        'others': [
+            { 'path': 'trait_methods::MyTrait', 'name': 'next' },
+        ],
+    },
+];
diff --git a/tests/rustdoc-js/trait-methods.rs b/tests/rustdoc-js/trait-methods.rs
new file mode 100644
index 00000000000..c88f5edfd55
--- /dev/null
+++ b/tests/rustdoc-js/trait-methods.rs
@@ -0,0 +1,4 @@
+pub trait MyTrait {
+    type Item;
+    fn next(&mut self) -> Option<Self::Item>;
+}