about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2024-05-05 16:42:46 +0200
committerGitHub <noreply@github.com>2024-05-05 16:42:46 +0200
commit042d0f5266f474491852b2055732698fceb41f14 (patch)
treec9069484e4795988474ceff0c90b2860ccd11569
parent02f7806ecd641d67c8f046b073323c7e176ee6d2 (diff)
parentc51447160109a7da12e582d4b36d8c5a66a870a7 (diff)
downloadrust-042d0f5266f474491852b2055732698fceb41f14.tar.gz
rust-042d0f5266f474491852b2055732698fceb41f14.zip
Rollup merge of #124148 - notriddle:notriddle/reference, r=GuillaumeGomez
rustdoc-search: search for references

This feature extends rustdoc with syntax and search index information for searching borrow references. Part of https://github.com/rust-lang/rust/issues/60485

## Preview

- [`&mut`](https://notriddle.com/rustdoc-html-demo-11/reference/std/index.html?search=%26mut)
- [`&Option<T> -> Option<&T>`](https://notriddle.com/rustdoc-html-demo-11/reference/std/index.html?search=%26Option%3CT%3E%20-%3E%20Option%3C%26T%3E)
- [`&mut Option<T> -> Option<&mut T>`](https://notriddle.com/rustdoc-html-demo-11/reference/std/index.html?search=%26mut%20Option%3CT%3E%20-%3E%20Option%3C%26mut%20T%3E)

Updated chapter of the book: https://notriddle.com/rustdoc-html-demo-11/reference/rustdoc/read-documentation/search.html

## Motivation

See https://github.com/rust-lang/rust/pull/119676

## Guide-level explanation

You can't search by lifetimes, but other than that it's the same syntax references normally use.

## Reference-level description

<table>
<thead>
  <tr>
    <th>Shorthand</th>
    <th>Explicit names</th>
  </tr>
</thead>
<tbody>
  <tr><td colspan="2">Before this PR</td></tr>
  <tr>
    <td><code>[]</code></td>
    <td><code>primitive:slice</code> and/or <code>primitive:array</code></td>
  </tr>
  <tr>
    <td><code>[T]</code></td>
    <td><code>primitive:slice&lt;T&gt;</code> and/or <code>primitive:array&lt;T&gt;</code></td>
  </tr>
  <tr>
    <td><code>!</code></td>
    <td><code>primitive:never</code></td>
  </tr>
  <tr>
    <td><code>()</code></td>
    <td><code>primitive:unit</code> and/or <code>primitive:tuple</code></td>
  </tr>
  <tr>
    <td><code>(T)</code></td>
    <td><code>T</code></td>
  </tr>
  <tr>
    <td><code>(T,)</code></td>
    <td><code>primitive:tuple&lt;T&gt;</code></td>
  </tr>
  <tr>
    <td><code>(T, U -> V, W)</code></td>
    <td><code>fn(T, U) -> (V, W)</code>, Fn, FnMut, and FnOnce</td>
  </tr>
  <tr><td colspan="2">New additions with this PR</td></tr>
  <tr>
    <td><code>&</code></td>
    <td><code>primitive:reference</td>
  </tr>
  <tr>
    <td><code>&mut</code></td>
    <td><code>primitive:reference&lt;keyword:mut&gt;</td>
  </tr>
  <tr>
    <td><code>&T</code></td>
    <td><code>primitive:reference&lt;T&gt;</td>
  </tr>
  <tr>
    <td><code>&mut T</code></td>
    <td><code>primitive:reference&lt;keyword:mut, T&gt;</td>
  </tr>
</tbody>
</table>

### Search query grammar

<code><pre><strong>borrow-ref = AMP *WS [MUT] *WS [arg]</strong>
arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like / <strong>borrow-ref</strong>)</pre></code>

```
AMP = "&"
MUT = "mut"
```

## Future direction

As described in https://github.com/rust-lang/rust/pull/118194 and https://github.com/rust-lang/rust/pull/119676

* The remaining type expression grammar (this is another step in the type expression grammar: `ReferenceType` is now supported)
* Search subtyping and traits
-rw-r--r--src/doc/rustdoc/src/read-documentation/search.md10
-rw-r--r--src/librustdoc/html/render/mod.rs1
-rw-r--r--src/librustdoc/html/render/search_index.rs61
-rw-r--r--src/librustdoc/html/static/js/search.js31
-rw-r--r--tests/rustdoc-js-std/parser-reference.js527
-rw-r--r--tests/rustdoc-js/reference.js236
-rw-r--r--tests/rustdoc-js/reference.rs32
7 files changed, 874 insertions, 24 deletions
diff --git a/src/doc/rustdoc/src/read-documentation/search.md b/src/doc/rustdoc/src/read-documentation/search.md
index e2def14b357..e912ca0fe5b 100644
--- a/src/doc/rustdoc/src/read-documentation/search.md
+++ b/src/doc/rustdoc/src/read-documentation/search.md
@@ -146,6 +146,7 @@ and number of matches. For example, a function with the signature
 `fn read_all(&mut self: impl Read) -> Result<Vec<u8>, Error>`
 will match these queries:
 
+* `&mut Read -> Result<Vec<u8>, Error>`
 * `Read -> Result<Vec<u8>, Error>`
 * `Read -> Result<Error, Vec>`
 * `Read -> Result<Vec<u8>>`
@@ -166,6 +167,10 @@ but you need to know which one you want.
 
 | Shorthand        | Explicit names                                    |
 | ---------------- | ------------------------------------------------- |
+| `&`              | `primitive:reference`                             |
+| `&T`             | `primitive:reference<T>`                          |
+| `&mut`           | `primitive:reference<keyword:mut>`                |
+| `&mut T`         | `primitive:reference<keyword:mut, T>`             |
 | `[]`             | `primitive:slice` and/or `primitive:array`        |
 | `[T]`            | `primitive:slice<T>` and/or `primitive:array<T>`  |
 | `()`             | `primitive:unit` and/or `primitive:tuple`         |
@@ -253,7 +258,8 @@ ident = *(ALPHA / DIGIT / "_")
 path = ident *(DOUBLE-COLON ident) [BANG]
 slice-like = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
 tuple-like = OPEN-PAREN [ nonempty-arg-list ] CLOSE-PAREN
-arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like)
+borrow-ref = AMP *WS [MUT] *WS [arg]
+arg = [type-filter *WS COLON *WS] (path [generics] / slice-like / tuple-like / borrow-ref)
 type-sep = COMMA/WS *(COMMA/WS)
 nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep) [ return-args ]
 generic-arg-list = *(type-sep) arg [ EQUAL arg ] *(type-sep arg [ EQUAL arg ]) *(type-sep)
@@ -310,6 +316,8 @@ COMMA = ","
 RETURN-ARROW = "->"
 EQUAL = "="
 BANG = "!"
+AMP = "&"
+MUT = "mut"
 
 ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
 DIGIT = %x30-39
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 09a53affb14..075d94bd337 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -182,6 +182,7 @@ pub(crate) enum RenderTypeId {
     Primitive(clean::PrimitiveType),
     AssociatedType(Symbol),
     Index(isize),
+    Mut,
 }
 
 impl RenderTypeId {
diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs
index 6d2bb31ee13..e635c1e611d 100644
--- a/src/librustdoc/html/render/search_index.rs
+++ b/src/librustdoc/html/render/search_index.rs
@@ -7,7 +7,7 @@ use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
 use rustc_middle::ty::TyCtxt;
 use rustc_span::def_id::DefId;
 use rustc_span::sym;
-use rustc_span::symbol::Symbol;
+use rustc_span::symbol::{kw, Symbol};
 use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer};
 use thin_vec::ThinVec;
 
@@ -163,6 +163,15 @@ pub(crate) fn build_index<'tcx>(
         ) -> Option<RenderTypeId> {
             let Cache { ref paths, ref external_paths, ref exact_paths, .. } = *cache;
             match id {
+                RenderTypeId::Mut => Some(insert_into_map(
+                    primitives,
+                    kw::Mut,
+                    lastpathid,
+                    crate_paths,
+                    ItemType::Keyword,
+                    &[kw::Mut],
+                    None,
+                )),
                 RenderTypeId::DefId(defid) => {
                     if let Some(&(ref fqp, item_type)) =
                         paths.get(&defid).or_else(|| external_paths.get(&defid))
@@ -765,9 +774,8 @@ fn get_index_type_id(
             bounds.get(0).map(|b| RenderTypeId::DefId(b.trait_.def_id()))
         }
         clean::Primitive(p) => Some(RenderTypeId::Primitive(p)),
-        clean::BorrowedRef { ref type_, .. } | clean::RawPointer(_, ref type_) => {
-            get_index_type_id(type_, rgen)
-        }
+        clean::BorrowedRef { .. } => Some(RenderTypeId::Primitive(clean::PrimitiveType::Reference)),
+        clean::RawPointer(_, ref 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)),
@@ -833,28 +841,14 @@ 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() => {
-                is_self = true;
-                self_
-            }
-            type_ if type_.is_self_type() => {
-                is_self = true;
-                self_
-            }
-            arg => arg,
-        }
+    let (is_self, arg) = if let Some(self_) = self_
+        && arg.is_self_type()
+    {
+        (true, self_)
     } else {
-        arg
+        (false, arg)
     };
 
-    // strip references from the argument type
-    while let Type::BorrowedRef { type_, .. } = &*arg {
-        arg = &*type_;
-    }
-
     // If this argument is a type parameter and not a trait bound or a type, we need to look
     // for its bounds.
     if let Type::Generic(arg_s) = *arg {
@@ -1027,6 +1021,27 @@ fn simplify_fn_type<'tcx, 'a>(
             bindings: Some(ty_bindings),
             generics: Some(ty_generics),
         });
+    } else if let Type::BorrowedRef { lifetime: _, mutability, ref type_ } = *arg {
+        let mut ty_generics = Vec::new();
+        if mutability.is_mut() {
+            ty_generics.push(RenderType {
+                id: Some(RenderTypeId::Mut),
+                generics: None,
+                bindings: None,
+            });
+        }
+        simplify_fn_type(
+            self_,
+            generics,
+            &type_,
+            tcx,
+            recurse + 1,
+            &mut ty_generics,
+            rgen,
+            is_return,
+            cache,
+        );
+        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.
diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js
index 41a9897de6d..76a6fc9008e 100644
--- a/src/librustdoc/html/static/js/search.js
+++ b/src/librustdoc/html/static/js/search.js
@@ -786,6 +786,37 @@ function initSearch(rawSearchIndex) {
                 }
                 elems.push(makePrimitiveElement(name, { bindingName, generics }));
             }
+        } else if (parserState.userQuery[parserState.pos] === "&") {
+            if (parserState.typeFilter !== null && parserState.typeFilter !== "primitive") {
+                throw [
+                    "Invalid search type: primitive ",
+                    "&",
+                    " and ",
+                    parserState.typeFilter,
+                    " both specified",
+                ];
+            }
+            parserState.typeFilter = null;
+            parserState.pos += 1;
+            let c = parserState.userQuery[parserState.pos];
+            while (c === " " && parserState.pos < parserState.length) {
+                parserState.pos += 1;
+                c = parserState.userQuery[parserState.pos];
+            }
+            const generics = [];
+            if (parserState.userQuery.slice(parserState.pos, parserState.pos + 3) === "mut") {
+                generics.push(makePrimitiveElement("mut", { typeFilter: "keyword"}));
+                parserState.pos += 3;
+                c = parserState.userQuery[parserState.pos];
+            }
+            while (c === " " && parserState.pos < parserState.length) {
+                parserState.pos += 1;
+                c = parserState.userQuery[parserState.pos];
+            }
+            if (!isEndCharacter(c) && parserState.pos < parserState.length) {
+                getFilteredNextElem(query, parserState, generics, isInGenerics);
+            }
+            elems.push(makePrimitiveElement("reference", { generics }));
         } else {
             const isStringElem = parserState.userQuery[start] === "\"";
             // We handle the strings on their own mostly to make code easier to follow.
diff --git a/tests/rustdoc-js-std/parser-reference.js b/tests/rustdoc-js-std/parser-reference.js
new file mode 100644
index 00000000000..6b1250146be
--- /dev/null
+++ b/tests/rustdoc-js-std/parser-reference.js
@@ -0,0 +1,527 @@
+const PARSED = [
+    {
+        query: '&[',
+        elems: [],
+        foundElems: 0,
+        original: '&[',
+        returned: [],
+        userQuery: '&[',
+        error: 'Unclosed `[`',
+    },
+    {
+        query: '[&',
+        elems: [],
+        foundElems: 0,
+        original: '[&',
+        returned: [],
+        userQuery: '[&',
+        error: 'Unclosed `[`',
+    },
+    {
+        query: '&&&D, []',
+        elems: [
+            {
+                name: "reference",
+                fullPath: ["reference"],
+                pathWithoutLast: [],
+                pathLast: "reference",
+                generics: [
+                    {
+                        name: "reference",
+                        fullPath: ["reference"],
+                        pathWithoutLast: [],
+                        pathLast: "reference",
+                        generics: [
+                            {
+                                name: "reference",
+                                fullPath: ["reference"],
+                                pathWithoutLast: [],
+                                pathLast: "reference",
+                                generics: [
+                                    {
+                                        name: "d",
+                                        fullPath: ["d"],
+                                        pathWithoutLast: [],
+                                        pathLast: "d",
+                                        generics: [],
+                                        typeFilter: -1,
+                                    },
+                                ],
+                                typeFilter: 1,
+                            },
+                        ],
+                        typeFilter: 1,
+                    },
+                ],
+                typeFilter: 1,
+            },
+            {
+                name: "[]",
+                fullPath: ["[]"],
+                pathWithoutLast: [],
+                pathLast: "[]",
+                generics: [],
+                typeFilter: 1,
+            },
+        ],
+        foundElems: 2,
+        original: '&&&D, []',
+        returned: [],
+        userQuery: '&&&d, []',
+        error: null,
+    },
+    {
+        query: '&&&[D]',
+        elems: [
+            {
+                name: "reference",
+                fullPath: ["reference"],
+                pathWithoutLast: [],
+                pathLast: "reference",
+                generics: [
+                    {
+                        name: "reference",
+                        fullPath: ["reference"],
+                        pathWithoutLast: [],
+                        pathLast: "reference",
+                        generics: [
+                            {
+                                name: "reference",
+                                fullPath: ["reference"],
+                                pathWithoutLast: [],
+                                pathLast: "reference",
+                                generics: [
+                                    {
+                                        name: "[]",
+                                        fullPath: ["[]"],
+                                        pathWithoutLast: [],
+                                        pathLast: "[]",
+                                        generics: [
+                                            {
+                                                name: "d",
+                                                fullPath: ["d"],
+                                                pathWithoutLast: [],
+                                                pathLast: "d",
+                                                generics: [],
+                                                typeFilter: -1,
+                                            },
+                                        ],
+                                        typeFilter: 1,
+                                    },
+                                ],
+                                typeFilter: 1,
+                            },
+                        ],
+                        typeFilter: 1,
+                    },
+                ],
+                typeFilter: 1,
+            },
+        ],
+        foundElems: 1,
+        original: '&&&[D]',
+        returned: [],
+        userQuery: '&&&[d]',
+        error: null,
+    },
+    {
+        query: '&',
+        elems: [
+            {
+                name: "reference",
+                fullPath: ["reference"],
+                pathWithoutLast: [],
+                pathLast: "reference",
+                generics: [],
+                typeFilter: 1,
+            },
+        ],
+        foundElems: 1,
+        original: '&',
+        returned: [],
+        userQuery: '&',
+        error: null,
+    },
+    {
+        query: '&mut',
+        elems: [
+            {
+                name: "reference",
+                fullPath: ["reference"],
+                pathWithoutLast: [],
+                pathLast: "reference",
+                generics: [
+                    {
+                        name: "mut",
+                        fullPath: ["mut"],
+                        pathWithoutLast: [],
+                        pathLast: "mut",
+                        generics: [],
+                        typeFilter: 0,
+                    },
+                ],
+                typeFilter: 1,
+            },
+        ],
+        foundElems: 1,
+        original: '&mut',
+        returned: [],
+        userQuery: '&mut',
+        error: null,
+    },
+    {
+        query: '&,u8',
+        elems: [
+            {
+                name: "reference",
+                fullPath: ["reference"],
+                pathWithoutLast: [],
+                pathLast: "reference",
+                generics: [],
+                typeFilter: 1,
+            },
+            {
+                name: "u8",
+                fullPath: ["u8"],
+                pathWithoutLast: [],
+                pathLast: "u8",
+                generics: [],
+                typeFilter: -1,
+            },
+        ],
+        foundElems: 2,
+        original: "&,u8",
+        returned: [],
+        userQuery: "&,u8",
+        error: null,
+    },
+    {
+        query: '&mut,u8',
+        elems: [
+            {
+                name: "reference",
+                fullPath: ["reference"],
+                pathWithoutLast: [],
+                pathLast: "reference",
+                generics: [
+                    {
+                        name: "mut",
+                        fullPath: ["mut"],
+                        pathWithoutLast: [],
+                        pathLast: "mut",
+                        generics: [],
+                        typeFilter: 0,
+                    },
+                ],
+                typeFilter: 1,
+            },
+            {
+                name: "u8",
+                fullPath: ["u8"],
+                pathWithoutLast: [],
+                pathLast: "u8",
+                generics: [],
+                typeFilter: -1,
+            },
+        ],
+        foundElems: 2,
+        original: "&mut,u8",
+        returned: [],
+        userQuery: "&mut,u8",
+        error: null,
+    },
+    {
+        query: '&u8',
+        elems: [
+            {
+                name: "reference",
+                fullPath: ["reference"],
+                pathWithoutLast: [],
+                pathLast: "reference",
+                generics: [
+                    {
+                        name: "u8",
+                        fullPath: ["u8"],
+                        pathWithoutLast: [],
+                        pathLast: "u8",
+                        generics: [],
+                        typeFilter: -1,
+                    },
+                ],
+                typeFilter: 1,
+            },
+        ],
+        foundElems: 1,
+        original: "&u8",
+        returned: [],
+        userQuery: "&u8",
+        error: null,
+    },
+    {
+        query: '&u8<u8>',
+        elems: [
+            {
+                name: "reference",
+                fullPath: ["reference"],
+                pathWithoutLast: [],
+                pathLast: "reference",
+                generics: [
+                    {
+                        name: "u8",
+                        fullPath: ["u8"],
+                        pathWithoutLast: [],
+                        pathLast: "u8",
+                        generics: [
+                            {
+                                name: "u8",
+                                fullPath: ["u8"],
+                                pathWithoutLast: [],
+                                pathLast: "u8",
+                                generics: [],
+                                typeFilter: -1,
+                            },
+                        ],
+                        typeFilter: -1,
+                    },
+                ],
+                typeFilter: 1,
+            },
+        ],
+        foundElems: 1,
+        original: "&u8<u8>",
+        returned: [],
+        userQuery: "&u8<u8>",
+        error: null,
+    },
+    {
+        query: 'u8<&u8>',
+        elems: [
+            {
+                name: "u8",
+                fullPath: ["u8"],
+                pathWithoutLast: [],
+                pathLast: "u8",
+                generics: [
+                    {
+                        name: "reference",
+                        fullPath: ["reference"],
+                        pathWithoutLast: [],
+                        pathLast: "reference",
+                        generics: [
+                            {
+                                name: "u8",
+                                fullPath: ["u8"],
+                                pathWithoutLast: [],
+                                pathLast: "u8",
+                                generics: [],
+                                typeFilter: -1,
+                            },
+                        ],
+                        typeFilter: 1,
+                    },
+                ],
+                typeFilter: -1,
+            },
+        ],
+        foundElems: 1,
+        original: "u8<&u8>",
+        returned: [],
+        userQuery: "u8<&u8>",
+        error: null,
+    },
+    {
+        query: 'u8<&u8, u8>',
+        elems: [
+            {
+                name: "u8",
+                fullPath: ["u8"],
+                pathWithoutLast: [],
+                pathLast: "u8",
+                generics: [
+                    {
+                        name: "reference",
+                        fullPath: ["reference"],
+                        pathWithoutLast: [],
+                        pathLast: "reference",
+                        generics: [
+                            {
+                                name: "u8",
+                                fullPath: ["u8"],
+                                pathWithoutLast: [],
+                                pathLast: "u8",
+                                generics: [],
+                                typeFilter: -1,
+                            },
+                        ],
+                        typeFilter: 1,
+                    },
+                    {
+                        name: "u8",
+                        fullPath: ["u8"],
+                        pathWithoutLast: [],
+                        pathLast: "u8",
+                        generics: [],
+                        typeFilter: -1,
+                    },
+                ],
+                typeFilter: -1,
+            },
+        ],
+        foundElems: 1,
+        original: "u8<&u8, u8>",
+        returned: [],
+        userQuery: "u8<&u8, u8>",
+        error: null,
+    },
+    {
+        query: 'u8<&u8>',
+        elems: [
+            {
+                name: "u8",
+                fullPath: ["u8"],
+                pathWithoutLast: [],
+                pathLast: "u8",
+                generics: [
+                    {
+                        name: "reference",
+                        fullPath: ["reference"],
+                        pathWithoutLast: [],
+                        pathLast: "reference",
+                        generics: [
+                            {
+                                name: "u8",
+                                fullPath: ["u8"],
+                                pathWithoutLast: [],
+                                pathLast: "u8",
+                                generics: [],
+                                typeFilter: -1,
+                            },
+                        ],
+                        typeFilter: 1,
+                    },
+                ],
+                typeFilter: -1,
+            },
+        ],
+        foundElems: 1,
+        original: "u8<&u8>",
+        returned: [],
+        userQuery: "u8<&u8>",
+        error: null,
+    },
+    {
+        query: 'u8<&mut u8, u8>',
+        elems: [
+            {
+                name: "u8",
+                fullPath: ["u8"],
+                pathWithoutLast: [],
+                pathLast: "u8",
+                generics: [
+                    {
+                        name: "reference",
+                        fullPath: ["reference"],
+                        pathWithoutLast: [],
+                        pathLast: "reference",
+                        generics: [
+                            {
+                                name: "mut",
+                                fullPath: ["mut"],
+                                pathWithoutLast: [],
+                                pathLast: "mut",
+                                generics: [],
+                                typeFilter: 0,
+                            },
+                            {
+                                name: "u8",
+                                fullPath: ["u8"],
+                                pathWithoutLast: [],
+                                pathLast: "u8",
+                                generics: [],
+                                typeFilter: -1,
+                            },
+                        ],
+                        typeFilter: 1,
+                    },
+                    {
+                        name: "u8",
+                        fullPath: ["u8"],
+                        pathWithoutLast: [],
+                        pathLast: "u8",
+                        generics: [],
+                        typeFilter: -1,
+                    },
+                ],
+                typeFilter: -1,
+            },
+        ],
+        foundElems: 1,
+        original: "u8<&mut u8, u8>",
+        returned: [],
+        userQuery: "u8<&mut u8, u8>",
+        error: null,
+    },
+    {
+        query: 'primitive:&u8',
+        elems: [
+            {
+                name: "reference",
+                fullPath: ["reference"],
+                pathWithoutLast: [],
+                pathLast: "reference",
+                generics: [
+                    {
+                        name: "u8",
+                        fullPath: ["u8"],
+                        pathWithoutLast: [],
+                        pathLast: "u8",
+                        generics: [],
+                        typeFilter: -1,
+                    },
+                ],
+                typeFilter: 1,
+            },
+        ],
+        foundElems: 1,
+        original: "primitive:&u8",
+        returned: [],
+        userQuery: "primitive:&u8",
+        error: null,
+    },
+    {
+        query: 'macro:&u8',
+        elems: [],
+        foundElems: 0,
+        original: "macro:&u8",
+        returned: [],
+        userQuery: "macro:&u8",
+        error: "Invalid search type: primitive `&` and `macro` both specified",
+    },
+    {
+        query: '&macro:u8',
+        elems: [
+            {
+                name: "reference",
+                fullPath: ["reference"],
+                pathWithoutLast: [],
+                pathLast: "reference",
+                generics: [
+                    {
+                        name: "u8",
+                        fullPath: ["u8"],
+                        pathWithoutLast: [],
+                        pathLast: "u8",
+                        generics: [],
+                        typeFilter: 16,
+                    },
+                ],
+                typeFilter: 1,
+            },
+        ],
+        foundElems: 1,
+        original: "&macro:u8",
+        returned: [],
+        userQuery: "&macro:u8",
+        error: null,
+    },
+];
diff --git a/tests/rustdoc-js/reference.js b/tests/rustdoc-js/reference.js
new file mode 100644
index 00000000000..b4a1fb15d36
--- /dev/null
+++ b/tests/rustdoc-js/reference.js
@@ -0,0 +1,236 @@
+// exact-check
+
+const EXPECTED = [
+    // pinkie with explicit names
+    {
+        'query': 'usize, usize -> ()',
+        'others': [
+            { 'path': 'reference', 'name': 'pinky' },
+        ],
+    },
+    {
+        'query': 'reference<usize>, usize -> ()',
+        'others': [
+            { 'path': 'reference', 'name': 'pinky' },
+        ],
+    },
+    {
+        'query': 'reference<usize>, reference<usize> -> ()',
+        'others': [],
+    },
+    {
+        'query': 'reference<mut, usize>, usize -> ()',
+        'others': [],
+    },
+    // thumb with explicit names
+    {
+        'query': 'thumb, thumb -> ()',
+        'others': [
+            { 'path': 'reference::Thumb', 'name': 'up' },
+        ],
+    },
+    {
+        'query': 'reference<thumb>, thumb -> ()',
+        'others': [
+            { 'path': 'reference::Thumb', 'name': 'up' },
+        ],
+    },
+    {
+        'query': 'reference<thumb>, reference<thumb> -> ()',
+        'others': [],
+    },
+    {
+        'query': 'reference<mut, thumb>, thumb -> ()',
+        'others': [],
+    },
+    // index with explicit names
+    {
+        'query': 'index, index -> ()',
+        'others': [
+            { 'path': 'reference::Index', 'name': 'point' },
+        ],
+    },
+    {
+        'query': 'reference<index>, index -> ()',
+        'others': [
+            { 'path': 'reference::Index', 'name': 'point' },
+        ],
+    },
+    {
+        'query': 'reference<index>, reference<index> -> ()',
+        'others': [],
+    },
+    {
+        'query': 'reference<mut, index>, index -> ()',
+        'others': [],
+    },
+    // ring with explicit names
+    {
+        'query': 'ring, ring -> ()',
+        'others': [
+            { 'path': 'reference::Ring', 'name': 'wear' },
+        ],
+    },
+    {
+        'query': 'reference<ring>, ring -> ()',
+        'others': [
+            { 'path': 'reference::Ring', 'name': 'wear' },
+        ],
+    },
+    {
+        'query': 'reference<ring>, reference<ring> -> ()',
+        'others': [
+            { 'path': 'reference::Ring', 'name': 'wear' },
+        ],
+    },
+    {
+        'query': 'reference<mut, ring>, reference<ring> -> ()',
+        'others': [
+            { 'path': 'reference::Ring', 'name': 'wear' },
+        ],
+    },
+    {
+        'query': 'reference<mut, ring>, reference<mut, ring> -> ()',
+        'others': [],
+    },
+    // middle with explicit names
+    {
+        'query': 'middle, middle -> ()',
+        'others': [
+            { 'path': 'reference', 'name': 'show' },
+        ],
+    },
+    {
+        'query': 'reference<middle>, reference<middle> -> ()',
+        'others': [
+            { 'path': 'reference', 'name': 'show' },
+        ],
+    },
+    {
+        'query': 'reference<mut, middle>, reference<mut, middle> -> ()',
+        'others': [
+            { 'path': 'reference', 'name': 'show' },
+        ],
+    },
+    {
+        'query': 'reference<reference<mut, middle>>, reference<mut, reference<middle>> -> ()',
+        'others': [
+            { 'path': 'reference', 'name': 'show' },
+        ],
+    },
+    {
+        'query': 'reference<mut, reference<middle>>, reference<reference<mut, middle>> -> ()',
+        'others': [
+            { 'path': 'reference', 'name': 'show' },
+        ],
+    },
+    {
+        'query': 'reference<reference<mut, middle>>, reference<reference<mut, middle>> -> ()',
+        'others': [],
+    },
+    {
+        'query': 'reference<mut, reference<middle>>, reference<mut, reference<middle>> -> ()',
+        'others': [],
+    },
+    // pinkie with shorthand
+    {
+        'query': '&usize, usize -> ()',
+        'others': [
+            { 'path': 'reference', 'name': 'pinky' },
+        ],
+    },
+    {
+        'query': '&usize, &usize -> ()',
+        'others': [],
+    },
+    {
+        'query': '&mut usize, usize -> ()',
+        'others': [],
+    },
+    // thumb with shorthand
+    {
+        'query': '&thumb, thumb -> ()',
+        'others': [
+            { 'path': 'reference::Thumb', 'name': 'up' },
+        ],
+    },
+    {
+        'query': '&thumb, &thumb -> ()',
+        'others': [],
+    },
+    {
+        'query': '&mut thumb, thumb -> ()',
+        'others': [],
+    },
+    // index with explicit names
+    {
+        'query': '&index, index -> ()',
+        'others': [
+            { 'path': 'reference::Index', 'name': 'point' },
+        ],
+    },
+    {
+        'query': '&index, &index -> ()',
+        'others': [],
+    },
+    {
+        'query': '&mut index, index -> ()',
+        'others': [],
+    },
+    // ring with shorthand
+    {
+        'query': '&ring, ring -> ()',
+        'others': [
+            { 'path': 'reference::Ring', 'name': 'wear' },
+        ],
+    },
+    {
+        'query': '&ring, ring -> ()',
+        'others': [
+            { 'path': 'reference::Ring', 'name': 'wear' },
+        ],
+    },
+    {
+        'query': '&mut ring, &ring -> ()',
+        'others': [
+            { 'path': 'reference::Ring', 'name': 'wear' },
+        ],
+    },
+    {
+        'query': '&mut ring, &mut ring -> ()',
+        'others': [],
+    },
+    // middle with shorthand
+    {
+        'query': '&middle, &middle -> ()',
+        'others': [
+            { 'path': 'reference', 'name': 'show' },
+        ],
+    },
+    {
+        'query': '&mut middle, &mut middle -> ()',
+        'others': [
+            { 'path': 'reference', 'name': 'show' },
+        ],
+    },
+    {
+        'query': '&&mut middle, &mut &middle -> ()',
+        'others': [
+            { 'path': 'reference', 'name': 'show' },
+        ],
+    },
+    {
+        'query': '&mut &middle, &&mut middle -> ()',
+        'others': [
+            { 'path': 'reference', 'name': 'show' },
+        ],
+    },
+    {
+        'query': '&&mut middle, &&mut middle -> ()',
+        'others': [],
+    },
+    {
+        'query': '&mut &middle, &mut &middle -> ()',
+        'others': [],
+    },
+];
diff --git a/tests/rustdoc-js/reference.rs b/tests/rustdoc-js/reference.rs
new file mode 100644
index 00000000000..3a0a23c65d5
--- /dev/null
+++ b/tests/rustdoc-js/reference.rs
@@ -0,0 +1,32 @@
+#![feature(extern_types)]
+
+pub fn pinky(input: &usize, manage: usize) {
+    unimplemented!()
+}
+
+pub struct Thumb;
+
+impl Thumb {
+    pub fn up(&self, finger: Thumb) { unimplemented!() }
+}
+
+pub enum Index {}
+
+impl Index {
+    pub fn point(self, data: &Index) { unimplemented!() }
+}
+
+pub union Ring {
+    magic: u32,
+    marriage: f32,
+}
+
+impl Ring {
+    pub fn wear(&mut self, extra: &Ring) { unimplemented!() }
+}
+
+extern "C" {
+    pub type Middle;
+}
+
+pub fn show(left: &&mut Middle, right: &mut &Middle) { unimplemented!() }