rustdoc-search: add support for associated types
This commit is contained in:
parent
9a66e4471f
commit
63c50712f4
@ -1651,6 +1651,13 @@ pub(crate) fn is_self_type(&self) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ pub(crate) fn is_assoc_ty(&self) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ pub(crate) fn is_empty(&self) -> bool {
|
||||
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)]
|
||||
|
@ -369,6 +369,7 @@ fn is_from_private_dep(tcx: TyCtxt<'_>, cache: &Cache, def_id: DefId) -> bool {
|
||||
&item,
|
||||
self.tcx,
|
||||
clean_impl_generics(self.cache.parent_stack.last()).as_ref(),
|
||||
parent,
|
||||
self.cache,
|
||||
),
|
||||
aliases: item.attrs.get_doc_aliases(),
|
||||
|
@ -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 {
|
||||
|
@ -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 @@ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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 @@ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
}
|
||||
}
|
||||
|
||||
#[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 @@ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
} 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 {
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
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 @@ fn convert_render_type(
|
||||
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 @@ fn convert_render_type(
|
||||
cache,
|
||||
&mut itemid_to_pathid,
|
||||
&mut primitives,
|
||||
&mut associated_types,
|
||||
&mut lastpathid,
|
||||
&mut crate_paths,
|
||||
);
|
||||
@ -171,6 +237,7 @@ fn convert_render_type(
|
||||
cache,
|
||||
&mut itemid_to_pathid,
|
||||
&mut primitives,
|
||||
&mut associated_types,
|
||||
&mut lastpathid,
|
||||
&mut crate_paths,
|
||||
);
|
||||
@ -182,6 +249,7 @@ fn convert_render_type(
|
||||
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(
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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(" ").join(" ");
|
||||
if (index % 2 === 1) {
|
||||
|
@ -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|})
|
||||
|
29
tests/rustdoc-js-std/iterator-type-signatures.js
Normal file
29
tests/rustdoc-js-std/iterator-type-signatures.js
Normal 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' },
|
||||
],
|
||||
},
|
||||
];
|
245
tests/rustdoc-js-std/parser-bindings.js
Normal file
245
tests/rustdoc-js-std/parser-bindings.js
Normal 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,
|
||||
},
|
||||
];
|
@ -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<->",
|
||||
|
163
tests/rustdoc-js/assoc-type-backtrack.js
Normal file
163
tests/rustdoc-js/assoc-type-backtrack.js
Normal 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': [],
|
||||
},
|
||||
];
|
38
tests/rustdoc-js/assoc-type-backtrack.rs
Normal file
38
tests/rustdoc-js/assoc-type-backtrack.rs
Normal 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;
|
||||
}
|
45
tests/rustdoc-js/assoc-type.js
Normal file
45
tests/rustdoc-js/assoc-type.js
Normal 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': [],
|
||||
},
|
||||
];
|
12
tests/rustdoc-js/assoc-type.rs
Normal file
12
tests/rustdoc-js/assoc-type.rs
Normal 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
57
tests/rustdoc-js/gat.js
Normal 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
8
tests/rustdoc-js/gat.rs
Normal 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 {} }
|
@ -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': [],
|
||||
},
|
||||
];
|
||||
|
12
tests/rustdoc-js/trait-methods.js
Normal file
12
tests/rustdoc-js/trait-methods.js
Normal 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' },
|
||||
],
|
||||
},
|
||||
];
|
4
tests/rustdoc-js/trait-methods.rs
Normal file
4
tests/rustdoc-js/trait-methods.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub trait MyTrait {
|
||||
type Item;
|
||||
fn next(&mut self) -> Option<Self::Item>;
|
||||
}
|
Loading…
Reference in New Issue
Block a user