Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rustdoc-search: add support for traits and associated types #116085

Merged
merged 2 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 43 additions & 4 deletions src/doc/rustdoc/src/read-documentation/search.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ the standard library and functions that are included in the results list:
| [`stdout, [u8]`][stdoutu8] | `Stdout::write` |
| [`any -> !`][] | `panic::panic_any` |
| [`vec::intoiter<T> -> [T]`][iterasslice] | `IntoIter::as_slice` and `IntoIter::next_chunk` |
| [`iterator<T>, fnmut -> T`][iterreduce] | `Iterator::reduce` and `Iterator::find` |

[`usize -> vec`]: ../../std/vec/struct.Vec.html?search=usize%20-%3E%20vec&filter-crate=std
[`vec, vec -> bool`]: ../../std/vec/struct.Vec.html?search=vec,%20vec%20-%3E%20bool&filter-crate=std
Expand All @@ -81,6 +82,7 @@ the standard library and functions that are included in the results list:
[`any -> !`]: ../../std/vec/struct.Vec.html?search=any%20-%3E%20!&filter-crate=std
[stdoutu8]: ../../std/vec/struct.Vec.html?search=stdout%2C%20[u8]&filter-crate=std
[iterasslice]: ../../std/vec/struct.Vec.html?search=vec%3A%3Aintoiter<T>%20->%20[T]&filter-crate=std
[iterreduce]: ../../std/index.html?search=iterator<T>%2C%20fnmut%20->%20T&filter-crate=std

### How type-based search works

Expand All @@ -95,16 +97,47 @@ After deciding which items are type parameters and which are actual types, it
then searches by matching up the function parameters (written before the `->`)
and the return types (written after the `->`). Type matching is order-agnostic,
and allows items to be left out of the query, but items that are present in the
query must be present in the function for it to match.
query must be present in the function for it to match. The `self` parameter is
treated the same as any other parameter, and `Self` is resolved to the
underlying type's name.

Function signature searches can query generics, wrapped in angle brackets, and
traits will be normalized like types in the search engine if no type parameters
match them. For example, a function with the signature
`fn my_function<I: Iterator<Item=u32>>(input: I) -> usize`
can be matched with the following queries:

* `Iterator<u32> -> usize`
* `Iterator -> usize`
* `Iterator<Item=u32> -> usize`
* `Iterator<u32> -> usize` (you can leave out the `Item=` part)
* `Iterator -> usize` (you can leave out iterator's generic entirely)
* `T -> usize` (you can match with a generic parameter)

Each of the above queries is progressively looser, except the last one
would not match `dyn Iterator`, since that's not a type parameter.

If a bound has multiple associated types, specifying the name allows you to
pick which one gets matched. If no name is specified, then the query will
match of any of them. For example,

```rust
pub trait MyTrait {
type First;
type Second;
}

/// This function can be found using the following search queries:
///
/// MyTrait<First=u8, Second=u32> -> bool
/// MyTrait<u32, First=u8> -> bool
/// MyTrait<Second=u32> -> bool
/// MyTrait<u32, u8> -> bool
///
/// The following queries, however, will *not* match it:
///
/// MyTrait<First=u32> -> bool
/// MyTrait<u32, u32> -> bool
pub fn my_fn(x: impl MyTrait<First=u8, Second=u32>) -> bool { true }
```

Generics and function parameters are order-agnostic, but sensitive to nesting
and number of matches. For example, a function with the signature
Expand Down Expand Up @@ -134,6 +167,10 @@ Most of these limitations should be addressed in future version of Rustdoc.
with that bound, it'll match, but `option<T> -> T where T: Default`
cannot be precisely searched for (use `option<Default> -> Default`).

* Supertraits, type aliases, and Deref are all ignored. Search mostly
operates on type signatures *as written*, and not as they are
represented within the compiler.

* Type parameters match type parameters, such that `Option<A>` matches
`Option<T>`, but never match concrete types in function signatures.
A trait named as if it were a type, such as `Option<Read>`, will match
Expand Down Expand Up @@ -183,7 +220,8 @@ slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
arg = [type-filter *WS COLON *WS] (path [generics] / slice / [!])
type-sep = COMMA/WS *(COMMA/WS)
nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep)
generic-arg-list = *(type-sep) arg [ EQUAL arg ] *(type-sep arg [ EQUAL arg ]) *(type-sep)
generics = OPEN-ANGLE-BRACKET [ generic-arg-list ] *(type-sep)
CLOSE-ANGLE-BRACKET
return-args = RETURN-ARROW *(type-sep) nonempty-arg-list

Expand Down Expand Up @@ -230,6 +268,7 @@ DOUBLE-COLON = "::"
QUOTE = %x22
COMMA = ","
RETURN-ARROW = "->"
EQUAL = "="

ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
DIGIT = %x30-39
Expand Down
44 changes: 44 additions & 0 deletions src/librustdoc/clean/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)]
Expand Down
1 change: 1 addition & 0 deletions src/librustdoc/formats/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
2 changes: 2 additions & 0 deletions src/librustdoc/formats/item_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
39 changes: 34 additions & 5 deletions src/librustdoc/html/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -129,24 +130,47 @@ 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)
}
}
}

#[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 {
Expand All @@ -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 {
Expand Down
Loading
Loading