rustdoc-search: add support for associated types

This commit is contained in:
Michael Howell 2023-09-22 17:27:06 -07:00
parent 9a66e4471f
commit 63c50712f4
21 changed files with 1390 additions and 124 deletions

View File

@ -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)]

View File

@ -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(),

View File

@ -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 {

View File

@ -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 {

View File

@ -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,31 +85,81 @@ 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_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>)>,
) -> Option<RenderTypeId> {
let Cache { ref paths, ref external_paths, .. } = *cache;
match id {
RenderTypeId::DefId(defid) => {
if let Some(&(ref fqp, item_type)) =
paths.get(&defid).or_else(|| external_paths.get(&defid))
{
Some(insert_into_map(
itemid_to_pathid,
ItemId::DefId(defid),
lastpathid,
crate_paths,
item_type,
fqp,
))
} else {
None
}
}
RenderTypeId::Primitive(primitive) => {
let sym = primitive.as_sym();
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>)>,
) {
@ -111,48 +170,54 @@ pub(crate) fn build_index<'tcx>(
cache,
itemid_to_pathid,
primitives,
associated_types,
lastpathid,
crate_paths,
);
}
}
let Cache { ref paths, ref external_paths, .. } = *cache;
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;
};
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,
itemid_to_pathid,
ItemId::DefId(defid),
lastpathid,
crate_paths,
item_type,
fqp,
);
} else {
ty.id = None;
}
}
RenderTypeId::Primitive(primitive) => {
let sym = primitive.as_sym();
insert_into_map(
ty,
primitives,
sym,
lastpathid,
crate_paths,
ItemType::Primitive,
&[sym],
);
}
RenderTypeId::Index(_) => {}
}
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,
);
}
}
let id = get_index_type_id(&arg);
// 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, 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(

View File

@ -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;

View File

@ -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,
};
}

View File

@ -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) {

View File

@ -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|})

View File

@ -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' },
],
},
];

View File

@ -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,
},
];

View File

@ -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<->",

View File

@ -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': [],
},
];

View File

@ -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;
}

View File

@ -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': [],
},
];

View File

@ -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
}
}

57
tests/rustdoc-js/gat.js Normal file
View File

@ -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' },
],
},
];

8
tests/rustdoc-js/gat.rs Normal file
View File

@ -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 {} }

View File

@ -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': [],
},
];

View File

@ -0,0 +1,12 @@
// exact-check
const EXPECTED = [
{
'query': 'mytrait<t> -> option<t>',
'correction': null,
'in_args': [],
'others': [
{ 'path': 'trait_methods::MyTrait', 'name': 'next' },
],
},
];

View File

@ -0,0 +1,4 @@
pub trait MyTrait {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}