rustdoc-search: add support for type parameters
When writing a type-driven search query in rustdoc, specifically one with more than one query element, non-existent types become generic parameters instead of auto-correcting (which is currently only done for single-element queries) or giving no result. You can also force a generic type parameter by writing `generic:T` (and can force it to not use a generic type parameter with something like `struct:T` or whatever, though if this happens it means the thing you're looking for doesn't exist and will give you no results). There is no syntax provided for specifying type constraints for generic type parameters. When you have a generic type parameter in a search query, it will only match up with generic type parameters in the actual function, not concrete types that match, not concrete types that implement a trait. It also strictly matches based on when they're the same or different, so `option<T>, option<U> -> option<U>` matches `Option::and`, but not `Option::or`. Similarly, `option<T>, option<T> -> option<T>`` matches `Option::or`, but not `Option::and`.
This commit is contained in:
parent
217fe24e52
commit
0b3c617ec0
9 changed files with 670 additions and 291 deletions
|
@ -1637,10 +1637,6 @@ impl Type {
|
|||
matches!(self, Type::Generic(_))
|
||||
}
|
||||
|
||||
pub(crate) fn is_impl_trait(&self) -> bool {
|
||||
matches!(self, Type::ImplTrait(_))
|
||||
}
|
||||
|
||||
pub(crate) fn is_unit(&self) -> bool {
|
||||
matches!(self, Type::Tuple(v) if v.is_empty())
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ pub(crate) struct IndexItem {
|
|||
pub(crate) path: String,
|
||||
pub(crate) desc: String,
|
||||
pub(crate) parent: Option<DefId>,
|
||||
pub(crate) parent_idx: Option<usize>,
|
||||
pub(crate) parent_idx: Option<isize>,
|
||||
pub(crate) search_type: Option<IndexItemFunctionType>,
|
||||
pub(crate) aliases: Box<[Symbol]>,
|
||||
pub(crate) deprecation: Option<Deprecation>,
|
||||
|
@ -122,7 +122,10 @@ impl Serialize for RenderType {
|
|||
let id = match &self.id {
|
||||
// 0 is a sentinel, everything else is one-indexed
|
||||
None => 0,
|
||||
Some(RenderTypeId::Index(idx)) => idx + 1,
|
||||
// concrete type
|
||||
Some(RenderTypeId::Index(idx)) if *idx >= 0 => idx + 1,
|
||||
// generic type parameter
|
||||
Some(RenderTypeId::Index(idx)) => *idx,
|
||||
_ => panic!("must convert render types to indexes before serializing"),
|
||||
};
|
||||
if let Some(generics) = &self.generics {
|
||||
|
@ -140,7 +143,7 @@ impl Serialize for RenderType {
|
|||
pub(crate) enum RenderTypeId {
|
||||
DefId(DefId),
|
||||
Primitive(clean::PrimitiveType),
|
||||
Index(usize),
|
||||
Index(isize),
|
||||
}
|
||||
|
||||
/// Full type of functions/methods in the search index.
|
||||
|
@ -148,6 +151,7 @@ pub(crate) enum RenderTypeId {
|
|||
pub(crate) struct IndexItemFunctionType {
|
||||
inputs: Vec<RenderType>,
|
||||
output: Vec<RenderType>,
|
||||
where_clause: Vec<Vec<RenderType>>,
|
||||
}
|
||||
|
||||
impl Serialize for IndexItemFunctionType {
|
||||
|
@ -170,10 +174,17 @@ impl Serialize for IndexItemFunctionType {
|
|||
_ => seq.serialize_element(&self.inputs)?,
|
||||
}
|
||||
match &self.output[..] {
|
||||
[] => {}
|
||||
[] if self.where_clause.is_empty() => {}
|
||||
[one] if one.generics.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() {
|
||||
seq.serialize_element(one)?;
|
||||
} else {
|
||||
seq.serialize_element(constraint)?;
|
||||
}
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,16 +68,16 @@ pub(crate) fn build_index<'tcx>(
|
|||
// Reduce `DefId` in paths into smaller sequential numbers,
|
||||
// and prune the paths that do not appear in the index.
|
||||
let mut lastpath = "";
|
||||
let mut lastpathid = 0usize;
|
||||
let mut lastpathid = 0isize;
|
||||
|
||||
// First, on function signatures
|
||||
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, usize>,
|
||||
map: &mut FxHashMap<F, isize>,
|
||||
itemid: F,
|
||||
lastpathid: &mut usize,
|
||||
lastpathid: &mut isize,
|
||||
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
|
||||
item_type: ItemType,
|
||||
path: &[Symbol],
|
||||
|
@ -97,9 +97,9 @@ pub(crate) fn build_index<'tcx>(
|
|||
fn convert_render_type(
|
||||
ty: &mut RenderType,
|
||||
cache: &mut Cache,
|
||||
itemid_to_pathid: &mut FxHashMap<ItemId, usize>,
|
||||
primitives: &mut FxHashMap<Symbol, usize>,
|
||||
lastpathid: &mut usize,
|
||||
itemid_to_pathid: &mut FxHashMap<ItemId, isize>,
|
||||
primitives: &mut FxHashMap<Symbol, isize>,
|
||||
lastpathid: &mut isize,
|
||||
crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>,
|
||||
) {
|
||||
if let Some(generics) = &mut ty.generics {
|
||||
|
@ -173,6 +173,18 @@ pub(crate) fn build_index<'tcx>(
|
|||
&mut crate_paths,
|
||||
);
|
||||
}
|
||||
for constraint in &mut search_type.where_clause {
|
||||
for trait_ in &mut constraint[..] {
|
||||
convert_render_type(
|
||||
trait_,
|
||||
cache,
|
||||
&mut itemid_to_pathid,
|
||||
&mut primitives,
|
||||
&mut lastpathid,
|
||||
&mut crate_paths,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -402,7 +414,7 @@ pub(crate) fn get_function_type_for_search<'tcx>(
|
|||
impl_generics: Option<&(clean::Type, clean::Generics)>,
|
||||
cache: &Cache,
|
||||
) -> Option<IndexItemFunctionType> {
|
||||
let (mut inputs, mut output) = match *item.kind {
|
||||
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),
|
||||
|
@ -412,7 +424,7 @@ pub(crate) fn get_function_type_for_search<'tcx>(
|
|||
inputs.retain(|a| a.id.is_some() || a.generics.is_some());
|
||||
output.retain(|a| a.id.is_some() || a.generics.is_some());
|
||||
|
||||
Some(IndexItemFunctionType { inputs, output })
|
||||
Some(IndexItemFunctionType { inputs, output, where_clause })
|
||||
}
|
||||
|
||||
fn get_index_type(clean_type: &clean::Type, generics: Vec<RenderType>) -> RenderType {
|
||||
|
@ -432,96 +444,48 @@ fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> {
|
|||
clean::BorrowedRef { ref type_, .. } | clean::RawPointer(_, ref type_) => {
|
||||
get_index_type_id(type_)
|
||||
}
|
||||
// The type parameters are converted to generics in `add_generics_and_bounds_as_types`
|
||||
// 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)),
|
||||
// Not supported yet
|
||||
clean::BareFunction(_)
|
||||
| clean::Generic(_)
|
||||
| clean::ImplTrait(_)
|
||||
| clean::Tuple(_)
|
||||
| clean::QPath { .. }
|
||||
| clean::Infer => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The point of this function is to replace bounds with types.
|
||||
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
|
||||
enum SimplifiedParam {
|
||||
// other kinds of type parameters are identified by their name
|
||||
Symbol(Symbol),
|
||||
// every argument-position impl trait is its own type parameter
|
||||
Anonymous(isize),
|
||||
}
|
||||
|
||||
/// The point of this function is to lower generics and types into the simplified form that the
|
||||
/// frontend search engine can use.
|
||||
///
|
||||
/// i.e. `[T, U]` when you have the following bounds: `T: Display, U: Option<T>` will return
|
||||
/// `[Display, Option]`. If a type parameter has no trait bound, it is discarded.
|
||||
/// For example, `[T, U, i32]]` where you have the bounds: `T: Display, U: Option<T>` will return
|
||||
/// `[-1, -2, i32] where -1: Display, -2: Option<-1>`. If a type parameter has no traid bound, it
|
||||
/// will still get a number. If a constraint is present but not used in the actual types, it will
|
||||
/// not be added to the map.
|
||||
///
|
||||
/// Important note: It goes through generics recursively. So if you have
|
||||
/// `T: Option<Result<(), ()>>`, it'll go into `Option` and then into `Result`.
|
||||
#[instrument(level = "trace", skip(tcx, res, cache))]
|
||||
fn add_generics_and_bounds_as_types<'tcx, 'a>(
|
||||
/// This function also works recursively.
|
||||
#[instrument(level = "trace", skip(tcx, res, rgen, cache))]
|
||||
fn simplify_fn_type<'tcx, 'a>(
|
||||
self_: Option<&'a Type>,
|
||||
generics: &Generics,
|
||||
arg: &'a Type,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
recurse: usize,
|
||||
res: &mut Vec<RenderType>,
|
||||
rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>,
|
||||
is_return: bool,
|
||||
cache: &Cache,
|
||||
) {
|
||||
fn insert_ty(res: &mut Vec<RenderType>, ty: Type, mut generics: Vec<RenderType>) {
|
||||
// generics and impl trait are both identified by their generics,
|
||||
// rather than a type name itself
|
||||
let anonymous = ty.is_full_generic() || ty.is_impl_trait();
|
||||
let generics_empty = generics.is_empty();
|
||||
|
||||
if anonymous {
|
||||
if generics_empty {
|
||||
// This is a type parameter with no trait bounds (for example: `T` in
|
||||
// `fn f<T>(p: T)`, so not useful for the rustdoc search because we would end up
|
||||
// with an empty type with an empty name. Let's just discard it.
|
||||
return;
|
||||
} else if generics.len() == 1 {
|
||||
// In this case, no need to go through an intermediate state if the type parameter
|
||||
// contains only one trait bound.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// `fn foo<T: Display>(r: Option<T>) {}`
|
||||
//
|
||||
// In this case, it would contain:
|
||||
//
|
||||
// ```
|
||||
// [{
|
||||
// name: "option",
|
||||
// generics: [{
|
||||
// name: "",
|
||||
// generics: [
|
||||
// name: "Display",
|
||||
// generics: []
|
||||
// }]
|
||||
// }]
|
||||
// }]
|
||||
// ```
|
||||
//
|
||||
// After removing the intermediate (unnecessary) type parameter, it'll become:
|
||||
//
|
||||
// ```
|
||||
// [{
|
||||
// name: "option",
|
||||
// generics: [{
|
||||
// name: "Display",
|
||||
// generics: []
|
||||
// }]
|
||||
// }]
|
||||
// ```
|
||||
//
|
||||
// To be noted that it can work if there is ONLY ONE trait bound, otherwise we still
|
||||
// need to keep it as is!
|
||||
res.push(generics.pop().unwrap());
|
||||
return;
|
||||
}
|
||||
}
|
||||
let index_ty = get_index_type(&ty, generics);
|
||||
if index_ty.id.is_none() && generics_empty {
|
||||
return;
|
||||
}
|
||||
res.push(index_ty);
|
||||
}
|
||||
|
||||
if recurse >= 10 {
|
||||
// FIXME: remove this whole recurse thing when the recursion bug is fixed
|
||||
// See #59502 for the original issue.
|
||||
|
@ -548,88 +512,126 @@ fn add_generics_and_bounds_as_types<'tcx, 'a>(
|
|||
// for its bounds.
|
||||
if let Type::Generic(arg_s) = *arg {
|
||||
// First we check if the bounds are in a `where` predicate...
|
||||
let mut type_bounds = Vec::new();
|
||||
for where_pred in generics.where_predicates.iter().filter(|g| match g {
|
||||
WherePredicate::BoundPredicate { ty: Type::Generic(ty_s), .. } => *ty_s == arg_s,
|
||||
_ => false,
|
||||
}) {
|
||||
let mut ty_generics = Vec::new();
|
||||
let bounds = where_pred.get_bounds().unwrap_or_else(|| &[]);
|
||||
for bound in bounds.iter() {
|
||||
if let Some(path) = bound.get_trait_path() {
|
||||
let ty = Type::Path { path };
|
||||
add_generics_and_bounds_as_types(
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
&ty,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_generics,
|
||||
&mut type_bounds,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
}
|
||||
insert_ty(res, arg.clone(), ty_generics);
|
||||
}
|
||||
// Otherwise we check if the trait bounds are "inlined" like `T: Option<u32>`...
|
||||
if let Some(bound) = generics.params.iter().find(|g| g.is_type() && g.name == arg_s) {
|
||||
let mut ty_generics = Vec::new();
|
||||
for bound in bound.get_bounds().unwrap_or(&[]) {
|
||||
if let Some(path) = bound.get_trait_path() {
|
||||
let ty = Type::Path { path };
|
||||
add_generics_and_bounds_as_types(
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
&ty,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_generics,
|
||||
&mut type_bounds,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
}
|
||||
insert_ty(res, arg.clone(), ty_generics);
|
||||
}
|
||||
if let Some((idx, _)) = rgen.get(&SimplifiedParam::Symbol(arg_s)) {
|
||||
res.push(RenderType { id: Some(RenderTypeId::Index(*idx)), generics: 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 });
|
||||
}
|
||||
} else if let Type::ImplTrait(ref bounds) = *arg {
|
||||
let mut ty_generics = Vec::new();
|
||||
let mut type_bounds = Vec::new();
|
||||
for bound in bounds {
|
||||
if let Some(path) = bound.get_trait_path() {
|
||||
let ty = Type::Path { path };
|
||||
add_generics_and_bounds_as_types(
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
&ty,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_generics,
|
||||
&mut type_bounds,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
}
|
||||
insert_ty(res, arg.clone(), ty_generics);
|
||||
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) });
|
||||
} 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 });
|
||||
}
|
||||
} else if let Type::Slice(ref ty) = *arg {
|
||||
let mut ty_generics = Vec::new();
|
||||
add_generics_and_bounds_as_types(
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
&ty,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_generics,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
insert_ty(res, arg.clone(), ty_generics);
|
||||
res.push(get_index_type(arg, ty_generics));
|
||||
} else if let Type::Array(ref ty, _) = *arg {
|
||||
let mut ty_generics = Vec::new();
|
||||
add_generics_and_bounds_as_types(
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
&ty,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_generics,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
insert_ty(res, arg.clone(), ty_generics);
|
||||
res.push(get_index_type(arg, ty_generics));
|
||||
} else if let Type::Tuple(ref tys) = *arg {
|
||||
let mut ty_generics = Vec::new();
|
||||
for ty in tys {
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
&ty,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_generics,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
res.push(get_index_type(arg, ty_generics));
|
||||
} 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.
|
||||
|
@ -639,18 +641,26 @@ fn add_generics_and_bounds_as_types<'tcx, 'a>(
|
|||
let mut ty_generics = Vec::new();
|
||||
if let Some(arg_generics) = arg.generics() {
|
||||
for gen in arg_generics.iter() {
|
||||
add_generics_and_bounds_as_types(
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
gen,
|
||||
tcx,
|
||||
recurse + 1,
|
||||
&mut ty_generics,
|
||||
rgen,
|
||||
is_return,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
}
|
||||
insert_ty(res, arg.clone(), ty_generics);
|
||||
let id = get_index_type_id(&arg);
|
||||
if id.is_some() || !ty_generics.is_empty() {
|
||||
res.push(RenderType {
|
||||
id,
|
||||
generics: if ty_generics.is_empty() { None } else { Some(ty_generics) },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -663,7 +673,7 @@ fn get_fn_inputs_and_outputs<'tcx>(
|
|||
tcx: TyCtxt<'tcx>,
|
||||
impl_generics: Option<&(clean::Type, clean::Generics)>,
|
||||
cache: &Cache,
|
||||
) -> (Vec<RenderType>, Vec<RenderType>) {
|
||||
) -> (Vec<RenderType>, Vec<RenderType>, Vec<Vec<RenderType>>) {
|
||||
let decl = &func.decl;
|
||||
|
||||
let combined_generics;
|
||||
|
@ -689,21 +699,27 @@ fn get_fn_inputs_and_outputs<'tcx>(
|
|||
(None, &func.generics)
|
||||
};
|
||||
|
||||
let mut all_types = Vec::new();
|
||||
let mut rgen: FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default();
|
||||
|
||||
let mut arg_types = Vec::new();
|
||||
for arg in decl.inputs.values.iter() {
|
||||
let mut args = Vec::new();
|
||||
add_generics_and_bounds_as_types(self_, generics, &arg.type_, tcx, 0, &mut args, cache);
|
||||
if !args.is_empty() {
|
||||
all_types.extend(args);
|
||||
} else {
|
||||
all_types.push(get_index_type(&arg.type_, vec![]));
|
||||
}
|
||||
simplify_fn_type(
|
||||
self_,
|
||||
generics,
|
||||
&arg.type_,
|
||||
tcx,
|
||||
0,
|
||||
&mut arg_types,
|
||||
&mut rgen,
|
||||
false,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
|
||||
let mut ret_types = Vec::new();
|
||||
add_generics_and_bounds_as_types(self_, generics, &decl.output, tcx, 0, &mut ret_types, cache);
|
||||
if ret_types.is_empty() {
|
||||
ret_types.push(get_index_type(&decl.output, vec![]));
|
||||
}
|
||||
(all_types, ret_types)
|
||||
simplify_fn_type(self_, generics, &decl.output, tcx, 0, &mut ret_types, &mut rgen, true, cache);
|
||||
|
||||
let mut simplified_params = rgen.into_values().collect::<Vec<_>>();
|
||||
simplified_params.sort_by_key(|(idx, _)| -idx);
|
||||
(arg_types, ret_types, simplified_params.into_iter().map(|(_idx, traits)| traits).collect())
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ let ResultObject;
|
|||
*
|
||||
* fn something() -> Result<usize, usize>
|
||||
*
|
||||
* If output was allowed to be any RawFunctionType, it would look like this
|
||||
* If output was allowed to be any RawFunctionType, it would look like thi
|
||||
*
|
||||
* [[], [50, [3, 3]]]
|
||||
*
|
||||
|
@ -113,10 +113,56 @@ let ResultObject;
|
|||
* in favor of the pair of types interpretation. This is why the `(number|Array<RawFunctionType>)`
|
||||
* is used instead of `(RawFunctionType|Array<RawFunctionType>)`.
|
||||
*
|
||||
* The output can be skipped if it's actually unit and there's no type constraints. If thi
|
||||
* function accepts constrained generics, then the output will be unconditionally emitted, and
|
||||
* after it will come a list of trait constraints. The position of the item in the list will
|
||||
* determine which type parameter it is. For example:
|
||||
*
|
||||
* [1, 2, 3, 4, 5]
|
||||
* ^ ^ ^ ^ ^
|
||||
* | | | | - generic parameter (-3) of trait 5
|
||||
* | | | - generic parameter (-2) of trait 4
|
||||
* | | - generic parameter (-1) of trait 3
|
||||
* | - this function returns a single value (type 2)
|
||||
* - this function takes a single input parameter (type 1)
|
||||
*
|
||||
* Or, for a less contrived version:
|
||||
*
|
||||
* [[[4, -1], 3], [[5, -1]], 11]
|
||||
* -^^^^^^^---- ^^^^^^^ ^^
|
||||
* | | | - generic parameter, roughly `where -1: 11`
|
||||
* | | | since -1 is the type parameter and 11 the trait
|
||||
* | | - function output 5<-1>
|
||||
* | - the overall function signature is something like
|
||||
* | `fn(4<-1>, 3) -> 5<-1> where -1: 11`
|
||||
* - function input, corresponds roughly to 4<-1>
|
||||
* 4 is an index into the `p` array for a type
|
||||
* -1 is the generic parameter, given by 11
|
||||
*
|
||||
* If a generic parameter has multiple trait constraints, it gets wrapped in an array, just like
|
||||
* function inputs and outputs:
|
||||
*
|
||||
* [-1, -1, [4, 3]]
|
||||
* ^^^^^^ where -1: 4 + 3
|
||||
*
|
||||
* If a generic parameter's trait constraint has generic parameters, it gets wrapped in the array
|
||||
* even if only one exists. In other words, the ambiguity of `4<3>` and `4 + 3` is resolved in
|
||||
* favor of `4 + 3`:
|
||||
*
|
||||
* [-1, -1, [[4, 3]]]
|
||||
* ^^^^^^^^ where -1: 4 + 3
|
||||
*
|
||||
* [-1, -1, [5, [4, 3]]]
|
||||
* ^^^^^^^^^^^ where -1: 5, -2: 4 + 3
|
||||
*
|
||||
* If a generic parameter has no trait constraints (like in Rust, the `Sized` constraint i
|
||||
* implied and a fake `?Sized` constraint used to note its absence), it will be filled in with 0.
|
||||
*
|
||||
* @typedef {(
|
||||
* 0 |
|
||||
* [(number|Array<RawFunctionType>)] |
|
||||
* [(number|Array<RawFunctionType>), (number|Array<RawFunctionType>)]
|
||||
* [(number|Array<RawFunctionType>), (number|Array<RawFunctionType>)] |
|
||||
* Array<(number|Array<RawFunctionType>)>
|
||||
* )}
|
||||
*/
|
||||
let RawFunctionSearchType;
|
||||
|
@ -136,6 +182,7 @@ let RawFunctionType;
|
|||
* @typedef {{
|
||||
* inputs: Array<FunctionType>,
|
||||
* output: Array<FunctionType>,
|
||||
* where_clause: Array<Array<FunctionType>>,
|
||||
* }}
|
||||
*/
|
||||
let FunctionSearchType;
|
||||
|
|
|
@ -33,6 +33,7 @@ const itemTypes = [
|
|||
"attr",
|
||||
"derive",
|
||||
"traitalias",
|
||||
"generic",
|
||||
];
|
||||
|
||||
const longItemTypes = [
|
||||
|
@ -67,6 +68,7 @@ const longItemTypes = [
|
|||
// used for special search precedence
|
||||
const TY_PRIMITIVE = itemTypes.indexOf("primitive");
|
||||
const TY_KEYWORD = itemTypes.indexOf("keyword");
|
||||
const TY_GENERIC = itemTypes.indexOf("generic");
|
||||
const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";
|
||||
|
||||
function hasOwnPropertyRustdoc(obj, property) {
|
||||
|
@ -974,6 +976,8 @@ function initSearch(rawSearchIndex) {
|
|||
literalSearch: false,
|
||||
error: null,
|
||||
correction: null,
|
||||
proposeCorrectionFrom: null,
|
||||
proposeCorrectionTo: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1014,64 +1018,10 @@ function initSearch(rawSearchIndex) {
|
|||
/**
|
||||
* Parses the query.
|
||||
*
|
||||
* The supported syntax by this parser is as follow:
|
||||
* The supported syntax by this parser is given in the rustdoc book chapter
|
||||
* /src/doc/rustdoc/src/read-documentation/search.md
|
||||
*
|
||||
* ident = *(ALPHA / DIGIT / "_")
|
||||
* path = ident *(DOUBLE-COLON/{WS} ident) [!]
|
||||
* slice = OPEN-SQUARE-BRACKET [ nonempty-arg-list ] CLOSE-SQUARE-BRACKET
|
||||
* arg = [type-filter *WS COLON *WS] (path [generics] / slice)
|
||||
* type-sep = *WS COMMA *(COMMA)
|
||||
* nonempty-arg-list = *(type-sep) arg *(type-sep arg) *(type-sep)
|
||||
* generics = OPEN-ANGLE-BRACKET [ nonempty-arg-list ] *(type-sep)
|
||||
* CLOSE-ANGLE-BRACKET
|
||||
* return-args = RETURN-ARROW *(type-sep) nonempty-arg-list
|
||||
*
|
||||
* exact-search = [type-filter *WS COLON] [ RETURN-ARROW ] *WS QUOTE ident QUOTE [ generics ]
|
||||
* type-search = [ nonempty-arg-list ] [ return-args ]
|
||||
*
|
||||
* query = *WS (exact-search / type-search) *WS
|
||||
*
|
||||
* type-filter = (
|
||||
* "mod" /
|
||||
* "externcrate" /
|
||||
* "import" /
|
||||
* "struct" /
|
||||
* "enum" /
|
||||
* "fn" /
|
||||
* "type" /
|
||||
* "static" /
|
||||
* "trait" /
|
||||
* "impl" /
|
||||
* "tymethod" /
|
||||
* "method" /
|
||||
* "structfield" /
|
||||
* "variant" /
|
||||
* "macro" /
|
||||
* "primitive" /
|
||||
* "associatedtype" /
|
||||
* "constant" /
|
||||
* "associatedconstant" /
|
||||
* "union" /
|
||||
* "foreigntype" /
|
||||
* "keyword" /
|
||||
* "existential" /
|
||||
* "attr" /
|
||||
* "derive" /
|
||||
* "traitalias")
|
||||
*
|
||||
* OPEN-ANGLE-BRACKET = "<"
|
||||
* CLOSE-ANGLE-BRACKET = ">"
|
||||
* OPEN-SQUARE-BRACKET = "["
|
||||
* CLOSE-SQUARE-BRACKET = "]"
|
||||
* COLON = ":"
|
||||
* DOUBLE-COLON = "::"
|
||||
* QUOTE = %x22
|
||||
* COMMA = ","
|
||||
* RETURN-ARROW = "->"
|
||||
*
|
||||
* ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
|
||||
* DIGIT = %x30-39
|
||||
* WS = %x09 / " "
|
||||
* When adding new things to the parser, add them there, too!
|
||||
*
|
||||
* @param {string} val - The user query
|
||||
*
|
||||
|
@ -1350,11 +1300,13 @@ function initSearch(rawSearchIndex) {
|
|||
*
|
||||
* @param {FunctionType} fnType - The object to check.
|
||||
* @param {QueryElement} queryElem - The element from the parsed query.
|
||||
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
|
||||
* @param {Map<number,number>|null} mgensIn - Map functions generics to query generics.
|
||||
*
|
||||
* @return {boolean} - Returns true if a match, false otherwise.
|
||||
*/
|
||||
function checkGenerics(fnType, queryElem) {
|
||||
return unifyFunctionTypes(fnType.generics, queryElem.generics);
|
||||
function checkGenerics(fnType, queryElem, whereClause, mgensIn) {
|
||||
return unifyFunctionTypes(fnType.generics, queryElem.generics, whereClause, mgensIn);
|
||||
}
|
||||
/**
|
||||
* This function checks if a list of search query `queryElems` can all be found in the
|
||||
|
@ -1362,10 +1314,12 @@ function initSearch(rawSearchIndex) {
|
|||
*
|
||||
* @param {Array<FunctionType>} fnTypes - The objects to check.
|
||||
* @param {Array<QueryElement>} queryElems - The elements from the parsed query.
|
||||
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
|
||||
* @param {Map<number,number>|null} mgensIn - Map function generics to query generics.
|
||||
*
|
||||
* @return {boolean} - Returns true if a match, false otherwise.
|
||||
*/
|
||||
function unifyFunctionTypes(fnTypes, queryElems) {
|
||||
function unifyFunctionTypes(fnTypes, queryElems, whereClause, mgensIn) {
|
||||
// This search engine implements order-agnostic unification. There
|
||||
// should be no missing duplicates (generics have "bag semantics"),
|
||||
// and the row is allowed to have extras.
|
||||
|
@ -1379,13 +1333,15 @@ function initSearch(rawSearchIndex) {
|
|||
* @type Map<integer, QueryElement[]>
|
||||
*/
|
||||
const queryElemSet = new Map();
|
||||
const mgens = new Map(mgensIn);
|
||||
const addQueryElemToQueryElemSet = queryElem => {
|
||||
let currentQueryElemList;
|
||||
if (queryElemSet.has(queryElem.id)) {
|
||||
currentQueryElemList = queryElemSet.get(queryElem.id);
|
||||
const qid = queryElem.id;
|
||||
if (queryElemSet.has(qid)) {
|
||||
currentQueryElemList = queryElemSet.get(qid);
|
||||
} else {
|
||||
currentQueryElemList = [];
|
||||
queryElemSet.set(queryElem.id, currentQueryElemList);
|
||||
queryElemSet.set(qid, currentQueryElemList);
|
||||
}
|
||||
currentQueryElemList.push(queryElem);
|
||||
};
|
||||
|
@ -1396,44 +1352,98 @@ function initSearch(rawSearchIndex) {
|
|||
* @type Map<integer, FunctionType[]>
|
||||
*/
|
||||
const fnTypeSet = new Map();
|
||||
const addFnTypeToFnTypeSet = fnType => {
|
||||
// Pure generic, or an item that's not matched by any query elems.
|
||||
// Try [unboxing] it.
|
||||
//
|
||||
// [unboxing]:
|
||||
// http://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
|
||||
const queryContainsArrayOrSliceElem = queryElemSet.has(typeNameIdOfArrayOrSlice);
|
||||
if (fnType.id === null || !(
|
||||
queryElemSet.has(fnType.id) ||
|
||||
(fnType.id === typeNameIdOfSlice && queryContainsArrayOrSliceElem) ||
|
||||
(fnType.id === typeNameIdOfArray && queryContainsArrayOrSliceElem)
|
||||
)) {
|
||||
/**
|
||||
* If `fnType` is a concrete type with generics, replace it with its generics.
|
||||
*
|
||||
* If `fnType` is a generic type parameter, replace it with any traits that it's
|
||||
* constrained by. This converts `T where T: Trait` into the same representation
|
||||
* that's directly used by return-position `impl Trait` and by `dyn Trait` in
|
||||
* all positions, so you can directly write `-> Trait` and get all three.
|
||||
*
|
||||
* This seems to correspond to two transforms described in
|
||||
* http://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
|
||||
* One of them is actual unboxing (turning `Maybe a` into `a`), while the other is
|
||||
* equivalent to converting a type parameter into an existential, which Hoogle doesn't
|
||||
* seem to actually do? I notice that these two searches produce the same result:
|
||||
* https://hoogle.haskell.org/?hoogle=Eq+-%3E+Set+Eq+-%3E+Set+Eq
|
||||
* https://hoogle.haskell.org/?hoogle=Ord+-%3E+Set+Ord+-%3E+Set+Ord
|
||||
*
|
||||
* Haskell is a bit of a foreign language to me. I just want to be able to look up
|
||||
* Option combinators without having to actually remember their (mostly arbitrary)
|
||||
* names, and Haskell claims to already have that problem solved...
|
||||
*
|
||||
* @type Map<integer, FunctionType[]>
|
||||
*/
|
||||
const unbox = function unbox(fnType) {
|
||||
if (fnType.id < 0) {
|
||||
const genid = (-fnType.id) - 1;
|
||||
if (whereClause && whereClause[genid]) {
|
||||
for (const trait of whereClause[genid]) {
|
||||
addFnTypeToFnTypeSet(trait);
|
||||
}
|
||||
}
|
||||
mgens.set(fnType.id, 0);
|
||||
} else {
|
||||
for (const innerFnType of fnType.generics) {
|
||||
addFnTypeToFnTypeSet(innerFnType);
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @param {FunctionType} fnType
|
||||
*/
|
||||
const addFnTypeToFnTypeSet = function addFnTypeToFnTypeSet(fnType) {
|
||||
const queryContainsArrayOrSliceElem = queryElemSet.has(typeNameIdOfArrayOrSlice);
|
||||
// qsid is an index into the `queryElemSet`, while `fnType.id` is an index into
|
||||
// the `fnTypeSet`. These are the same, except for generics, where they get mapped.
|
||||
// If qsid = 0, it means the generic type parameter has undergone instance
|
||||
// substitution, and
|
||||
let qsid = fnType.id;
|
||||
if (fnType.id < 0) {
|
||||
if (mgens.has(fnType.id)) {
|
||||
qsid = mgens.get(fnType.id);
|
||||
} else {
|
||||
qsid = null;
|
||||
searching: for (const qid of queryElemSet.keys()) {
|
||||
if (qid < 0) {
|
||||
for (const qidMapped of mgens.values()) {
|
||||
if (qid === qidMapped) {
|
||||
continue searching;
|
||||
}
|
||||
}
|
||||
mgens.set(fnType.id, qid);
|
||||
qsid = qid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (qsid === null || !(
|
||||
queryElemSet.has(qsid) ||
|
||||
(qsid === typeNameIdOfSlice && queryContainsArrayOrSliceElem) ||
|
||||
(qsid === typeNameIdOfArray && queryContainsArrayOrSliceElem)
|
||||
)) {
|
||||
unbox(fnType);
|
||||
return;
|
||||
}
|
||||
let currentQueryElemList = queryElemSet.get(fnType.id) || [];
|
||||
let currentQueryElemList = queryElemSet.get(qsid) || [];
|
||||
let matchIdx = currentQueryElemList.findIndex(queryElem => {
|
||||
return typePassesFilter(queryElem.typeFilter, fnType.ty) &&
|
||||
checkGenerics(fnType, queryElem);
|
||||
checkGenerics(fnType, queryElem, whereClause, mgens);
|
||||
});
|
||||
if (matchIdx === -1 &&
|
||||
(fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray) &&
|
||||
(qsid === typeNameIdOfSlice || qsid === typeNameIdOfArray) &&
|
||||
queryContainsArrayOrSliceElem
|
||||
) {
|
||||
currentQueryElemList = queryElemSet.get(typeNameIdOfArrayOrSlice) || [];
|
||||
matchIdx = currentQueryElemList.findIndex(queryElem => {
|
||||
return typePassesFilter(queryElem.typeFilter, fnType.ty) &&
|
||||
checkGenerics(fnType, queryElem);
|
||||
checkGenerics(fnType, queryElem, whereClause, mgens);
|
||||
});
|
||||
}
|
||||
// None of the query elems match the function type.
|
||||
// Try [unboxing] it.
|
||||
if (matchIdx === -1) {
|
||||
for (const innerFnType of fnType.generics) {
|
||||
addFnTypeToFnTypeSet(innerFnType);
|
||||
}
|
||||
unbox(fnType);
|
||||
return;
|
||||
}
|
||||
let currentFnTypeList;
|
||||
|
@ -1488,7 +1498,9 @@ function initSearch(rawSearchIndex) {
|
|||
continue;
|
||||
}
|
||||
}
|
||||
if (queryElem.generics.length === 0 || checkGenerics(fnType, queryElem)) {
|
||||
if (queryElem.generics.length === 0
|
||||
|| checkGenerics(fnType, queryElem, whereClause, mgens)
|
||||
) {
|
||||
currentFnTypeList.splice(i, 1);
|
||||
const result = doHandleQueryElemList(currentFnTypeList, queryElemList);
|
||||
if (result) {
|
||||
|
@ -1499,15 +1511,32 @@ function initSearch(rawSearchIndex) {
|
|||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* @param {number} id
|
||||
* @param {[QueryElement]} queryElemList
|
||||
*/
|
||||
const handleQueryElemList = (id, queryElemList) => {
|
||||
if (!fnTypeSet.has(id)) {
|
||||
if (id === typeNameIdOfArrayOrSlice) {
|
||||
let fsid = id;
|
||||
if (fsid < 0) {
|
||||
fsid = null;
|
||||
if (!mgens) {
|
||||
return false;
|
||||
}
|
||||
for (const [fid, qsid] of mgens) {
|
||||
if (id === qsid) {
|
||||
fsid = fid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fsid === null || !fnTypeSet.has(fsid)) {
|
||||
if (fsid === typeNameIdOfArrayOrSlice) {
|
||||
return handleQueryElemList(typeNameIdOfSlice, queryElemList) ||
|
||||
handleQueryElemList(typeNameIdOfArray, queryElemList);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const currentFnTypeList = fnTypeSet.get(id);
|
||||
const currentFnTypeList = fnTypeSet.get(fsid);
|
||||
if (currentFnTypeList.length < queryElemList.length) {
|
||||
// It's not possible for all the query elems to find a match.
|
||||
return false;
|
||||
|
@ -1518,14 +1547,14 @@ function initSearch(rawSearchIndex) {
|
|||
// Any items that weren't used for it can be unboxed, and might form
|
||||
// part of the solution for another item.
|
||||
for (const innerFnType of currentFnTypeList) {
|
||||
addFnTypeToFnTypeSet(innerFnType);
|
||||
unbox(innerFnType);
|
||||
}
|
||||
fnTypeSet.delete(id);
|
||||
fnTypeSet.delete(fsid);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
let queryElemSetSize = -1;
|
||||
while (queryElemSetSize !== queryElemSet.size) {
|
||||
let queryElemSetSize = Number.MAX_VALUE;
|
||||
while (queryElemSetSize > queryElemSet.size) {
|
||||
queryElemSetSize = queryElemSet.size;
|
||||
for (const [id, queryElemList] of queryElemSet) {
|
||||
if (handleQueryElemList(id, queryElemList)) {
|
||||
|
@ -1533,7 +1562,14 @@ function initSearch(rawSearchIndex) {
|
|||
}
|
||||
}
|
||||
}
|
||||
return queryElemSetSize === 0;
|
||||
if (queryElemSetSize === 0) {
|
||||
for (const [fid, qid] of mgens) {
|
||||
mgensIn.set(fid, qid);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1542,12 +1578,13 @@ function initSearch(rawSearchIndex) {
|
|||
*
|
||||
* @param {Array<FunctionType>} list
|
||||
* @param {QueryElement} elem - The element from the parsed query.
|
||||
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
|
||||
*
|
||||
* @return {boolean} - Returns true if found, false otherwise.
|
||||
*/
|
||||
function checkIfInList(list, elem) {
|
||||
function checkIfInList(list, elem, whereClause) {
|
||||
for (const entry of list) {
|
||||
if (checkType(entry, elem)) {
|
||||
if (checkType(entry, elem, whereClause)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1560,13 +1597,25 @@ function initSearch(rawSearchIndex) {
|
|||
*
|
||||
* @param {Row} row
|
||||
* @param {QueryElement} elem - The element from the parsed query.
|
||||
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
|
||||
*
|
||||
* @return {boolean} - Returns true if the type matches, false otherwise.
|
||||
*/
|
||||
function checkType(row, elem) {
|
||||
function checkType(row, elem, whereClause) {
|
||||
if (row.id === null) {
|
||||
// This is a pure "generic" search, no need to run other checks.
|
||||
return row.generics.length > 0 ? checkIfInList(row.generics, elem) : false;
|
||||
return row.generics.length > 0
|
||||
? checkIfInList(row.generics, elem, whereClause)
|
||||
: false;
|
||||
}
|
||||
|
||||
if (row.id < 0 && elem.id >= 0) {
|
||||
const gid = (-row.id) - 1;
|
||||
return checkIfInList(whereClause[gid], elem, whereClause);
|
||||
}
|
||||
|
||||
if (row.id < 0 && elem.id < 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const matchesExact = row.id === elem.id;
|
||||
|
@ -1576,7 +1625,7 @@ function initSearch(rawSearchIndex) {
|
|||
if ((matchesExact || matchesArrayOrSlice) &&
|
||||
typePassesFilter(elem.typeFilter, row.ty)) {
|
||||
if (elem.generics.length > 0) {
|
||||
return checkGenerics(row, elem);
|
||||
return checkGenerics(row, elem, whereClause, new Map());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1584,7 +1633,7 @@ function initSearch(rawSearchIndex) {
|
|||
// If the current item does not match, try [unboxing] the generic.
|
||||
// [unboxing]:
|
||||
// https://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf
|
||||
return checkIfInList(row.generics, elem);
|
||||
return checkIfInList(row.generics, elem, whereClause);
|
||||
}
|
||||
|
||||
function checkPath(contains, ty, maxEditDistance) {
|
||||
|
@ -1785,13 +1834,15 @@ function initSearch(rawSearchIndex) {
|
|||
const fullId = row.id;
|
||||
const searchWord = searchWords[pos];
|
||||
|
||||
const in_args = row.type && row.type.inputs && checkIfInList(row.type.inputs, elem);
|
||||
const in_args = row.type && row.type.inputs
|
||||
&& checkIfInList(row.type.inputs, elem, row.type.where_clause);
|
||||
if (in_args) {
|
||||
// path_dist is 0 because no parent path information is currently stored
|
||||
// in the search index
|
||||
addIntoResults(results_in_args, fullId, pos, -1, 0, 0, maxEditDistance);
|
||||
}
|
||||
const returned = row.type && row.type.output && checkIfInList(row.type.output, elem);
|
||||
const returned = row.type && row.type.output
|
||||
&& checkIfInList(row.type.output, elem, row.type.where_clause);
|
||||
if (returned) {
|
||||
addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance);
|
||||
}
|
||||
|
@ -1853,10 +1904,24 @@ function initSearch(rawSearchIndex) {
|
|||
}
|
||||
|
||||
// If the result is too "bad", we return false and it ends this search.
|
||||
if (!unifyFunctionTypes(row.type.inputs, parsedQuery.elems)) {
|
||||
let mgens;
|
||||
if (row.type.where_clause && row.type.where_clause.length > 0) {
|
||||
mgens = new Map();
|
||||
}
|
||||
if (!unifyFunctionTypes(
|
||||
row.type.inputs,
|
||||
parsedQuery.elems,
|
||||
row.type.where_clause,
|
||||
mgens
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
if (!unifyFunctionTypes(row.type.output, parsedQuery.returned)) {
|
||||
if (!unifyFunctionTypes(
|
||||
row.type.output,
|
||||
parsedQuery.returned,
|
||||
row.type.where_clause,
|
||||
mgens
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1875,6 +1940,11 @@ function initSearch(rawSearchIndex) {
|
|||
}
|
||||
const maxEditDistance = Math.floor(queryLen / 3);
|
||||
|
||||
/**
|
||||
* @type {Map<string, integer>}
|
||||
*/
|
||||
const genericSymbols = new Map();
|
||||
|
||||
/**
|
||||
* Convert names to ids in parsed query elements.
|
||||
* This is not used for the "In Names" tab, but is used for the
|
||||
|
@ -1910,6 +1980,46 @@ function initSearch(rawSearchIndex) {
|
|||
}
|
||||
elem.id = match;
|
||||
}
|
||||
if ((elem.id === null && parsedQuery.foundElems > 1 && elem.typeFilter === -1)
|
||||
|| elem.typeFilter === TY_GENERIC) {
|
||||
if (genericSymbols.has(elem.name)) {
|
||||
elem.id = genericSymbols.get(elem.name);
|
||||
} else {
|
||||
elem.id = -(genericSymbols.size + 1);
|
||||
genericSymbols.set(elem.name, elem.id);
|
||||
}
|
||||
if (elem.typeFilter === -1 && elem.name.length >= 3) {
|
||||
// Silly heuristic to catch if the user probably meant
|
||||
// to not write a generic parameter. We don't use it,
|
||||
// just bring it up.
|
||||
const maxPartDistance = Math.floor(elem.name.length / 3);
|
||||
let matchDist = maxPartDistance + 1;
|
||||
let matchName = "";
|
||||
for (const name of typeNameIdMap.keys()) {
|
||||
const dist = editDistance(name, elem.name, maxPartDistance);
|
||||
if (dist <= matchDist && dist <= maxPartDistance) {
|
||||
if (dist === matchDist && matchName > name) {
|
||||
continue;
|
||||
}
|
||||
matchDist = dist;
|
||||
matchName = name;
|
||||
}
|
||||
}
|
||||
if (matchName !== "") {
|
||||
parsedQuery.proposeCorrectionFrom = elem.name;
|
||||
parsedQuery.proposeCorrectionTo = matchName;
|
||||
}
|
||||
}
|
||||
elem.typeFilter = TY_GENERIC;
|
||||
}
|
||||
if (elem.generics.length > 0 && elem.typeFilter === TY_GENERIC) {
|
||||
// Rust does not have HKT
|
||||
parsedQuery.error = [
|
||||
"Generic type parameter ",
|
||||
elem.name,
|
||||
" does not accept generic parameters",
|
||||
];
|
||||
}
|
||||
for (const elem2 of elem.generics) {
|
||||
convertNameToId(elem2);
|
||||
}
|
||||
|
@ -1943,8 +2053,11 @@ function initSearch(rawSearchIndex) {
|
|||
elem = parsedQuery.returned[0];
|
||||
for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) {
|
||||
row = searchIndex[i];
|
||||
in_returned = row.type &&
|
||||
unifyFunctionTypes(row.type.output, parsedQuery.returned);
|
||||
in_returned = row.type && unifyFunctionTypes(
|
||||
row.type.output,
|
||||
parsedQuery.returned,
|
||||
row.type.where_clause, new Map()
|
||||
);
|
||||
if (in_returned) {
|
||||
addIntoResults(
|
||||
results_others,
|
||||
|
@ -2295,6 +2408,13 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
|||
"Showing results for closest type name " +
|
||||
`"${results.query.correction}" instead.</h3>`;
|
||||
}
|
||||
if (results.query.proposeCorrectionFrom !== null) {
|
||||
const orig = results.query.proposeCorrectionFrom;
|
||||
const targ = results.query.proposeCorrectionTo;
|
||||
output += "<h3 class=\"search-corrections\">" +
|
||||
`Type "${orig}" not found and used as generic parameter. ` +
|
||||
`Consider searching for "${targ}" instead.</h3>`;
|
||||
}
|
||||
|
||||
const resultsElem = document.createElement("div");
|
||||
resultsElem.id = "results";
|
||||
|
@ -2396,9 +2516,17 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
|||
* @return {Array<FunctionSearchType>}
|
||||
*/
|
||||
function buildItemSearchTypeAll(types, lowercasePaths) {
|
||||
return types.map(type => buildItemSearchType(type, lowercasePaths));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single type.
|
||||
*
|
||||
* @param {RawFunctionType} type
|
||||
*/
|
||||
function buildItemSearchType(type, lowercasePaths) {
|
||||
const PATH_INDEX_DATA = 0;
|
||||
const GENERICS_DATA = 1;
|
||||
return types.map(type => {
|
||||
let pathIndex, generics;
|
||||
if (typeof type === "number") {
|
||||
pathIndex = type;
|
||||
|
@ -2410,13 +2538,23 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
|||
lowercasePaths
|
||||
);
|
||||
}
|
||||
// `0` is used as a sentinel because it's fewer bytes than `null`
|
||||
if (pathIndex < 0) {
|
||||
// types less than 0 are generic parameters
|
||||
// the actual names of generic parameters aren't stored, since they aren't API
|
||||
return {
|
||||
id: pathIndex,
|
||||
ty: TY_GENERIC,
|
||||
path: null,
|
||||
generics,
|
||||
};
|
||||
}
|
||||
if (pathIndex === 0) {
|
||||
// `0` is used as a sentinel because it's fewer bytes than `null`
|
||||
return {
|
||||
id: null,
|
||||
ty: null,
|
||||
path: null,
|
||||
generics: generics,
|
||||
generics,
|
||||
};
|
||||
}
|
||||
const item = lowercasePaths[pathIndex - 1];
|
||||
|
@ -2424,9 +2562,8 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
|||
id: buildTypeMapIndex(item.name),
|
||||
ty: item.ty,
|
||||
path: item.path,
|
||||
generics: generics,
|
||||
generics,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2454,23 +2591,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
|||
}
|
||||
let inputs, output;
|
||||
if (typeof functionSearchType[INPUTS_DATA] === "number") {
|
||||
const pathIndex = functionSearchType[INPUTS_DATA];
|
||||
if (pathIndex === 0) {
|
||||
inputs = [{
|
||||
id: null,
|
||||
ty: null,
|
||||
path: null,
|
||||
generics: [],
|
||||
}];
|
||||
} else {
|
||||
const item = lowercasePaths[pathIndex - 1];
|
||||
inputs = [{
|
||||
id: buildTypeMapIndex(item.name),
|
||||
ty: item.ty,
|
||||
path: item.path,
|
||||
generics: [],
|
||||
}];
|
||||
}
|
||||
inputs = [buildItemSearchType(functionSearchType[INPUTS_DATA], lowercasePaths)];
|
||||
} else {
|
||||
inputs = buildItemSearchTypeAll(
|
||||
functionSearchType[INPUTS_DATA],
|
||||
|
@ -2479,23 +2600,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
|||
}
|
||||
if (functionSearchType.length > 1) {
|
||||
if (typeof functionSearchType[OUTPUT_DATA] === "number") {
|
||||
const pathIndex = functionSearchType[OUTPUT_DATA];
|
||||
if (pathIndex === 0) {
|
||||
output = [{
|
||||
id: null,
|
||||
ty: null,
|
||||
path: null,
|
||||
generics: [],
|
||||
}];
|
||||
} else {
|
||||
const item = lowercasePaths[pathIndex - 1];
|
||||
output = [{
|
||||
id: buildTypeMapIndex(item.name),
|
||||
ty: item.ty,
|
||||
path: item.path,
|
||||
generics: [],
|
||||
}];
|
||||
}
|
||||
output = [buildItemSearchType(functionSearchType[OUTPUT_DATA], lowercasePaths)];
|
||||
} else {
|
||||
output = buildItemSearchTypeAll(
|
||||
functionSearchType[OUTPUT_DATA],
|
||||
|
@ -2505,8 +2610,15 @@ ${item.displayPath}<span class="${type}">${name}</span>\
|
|||
} else {
|
||||
output = [];
|
||||
}
|
||||
const where_clause = [];
|
||||
const l = functionSearchType.length;
|
||||
for (let i = 2; i < l; ++i) {
|
||||
where_clause.push(typeof functionSearchType[i] === "number"
|
||||
? [buildItemSearchType(functionSearchType[i], lowercasePaths)]
|
||||
: buildItemSearchTypeAll(functionSearchType[i], lowercasePaths));
|
||||
}
|
||||
return {
|
||||
inputs, output,
|
||||
inputs, output, where_clause,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -54,3 +54,45 @@ assert-text: (
|
|||
".search-corrections",
|
||||
"Type \"notablestructwithlongnamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead."
|
||||
)
|
||||
|
||||
// Now, generic correction
|
||||
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
|
||||
// Intentionally wrong spelling of "NotableStructWithLongName"
|
||||
write: (".search-input", "NotableStructWithLongNamr, NotableStructWithLongNamr")
|
||||
// To be SURE that the search will be run.
|
||||
press-key: 'Enter'
|
||||
// Waiting for the search results to appear...
|
||||
wait-for: "#search-tabs"
|
||||
|
||||
assert-css: (".search-corrections", {
|
||||
"display": "block"
|
||||
})
|
||||
assert-text: (
|
||||
".search-corrections",
|
||||
"Type \"notablestructwithlongnamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead."
|
||||
)
|
||||
|
||||
// Now, generic correction plus error
|
||||
go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
|
||||
// Intentionally wrong spelling of "NotableStructWithLongName"
|
||||
write: (".search-input", "NotableStructWithLongNamr<x>,y")
|
||||
// To be SURE that the search will be run.
|
||||
press-key: 'Enter'
|
||||
// Waiting for the search results to appear...
|
||||
wait-for: "#search-tabs"
|
||||
|
||||
assert-css: (".search-corrections", {
|
||||
"display": "block"
|
||||
})
|
||||
assert-text: (
|
||||
".search-corrections",
|
||||
"Type \"notablestructwithlongnamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead."
|
||||
)
|
||||
|
||||
assert-css: (".error", {
|
||||
"display": "block"
|
||||
})
|
||||
assert-text: (
|
||||
".error",
|
||||
"Query parser error: \"Generic type parameter notablestructwithlongnamr does not accept generic parameters\"."
|
||||
)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
const FILTER_CRATE = "std";
|
||||
|
||||
const EXPECTED = [
|
||||
{
|
||||
'query': 'option, fnonce -> option',
|
||||
|
@ -19,4 +21,55 @@ const EXPECTED = [
|
|||
{ 'path': 'std::option::Option', 'name': 'as_mut_slice' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'option<t>, option<t> -> option<t>',
|
||||
'others': [
|
||||
{ 'path': 'std::option::Option', 'name': 'or' },
|
||||
{ 'path': 'std::option::Option', 'name': 'xor' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'option<t>, option<u> -> option<u>',
|
||||
'others': [
|
||||
{ 'path': 'std::option::Option', 'name': 'and' },
|
||||
{ 'path': 'std::option::Option', 'name': 'zip' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'option<t>, option<u> -> option<t, u>',
|
||||
'others': [
|
||||
{ 'path': 'std::option::Option', 'name': 'zip' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'option<t>, option<u> -> option<t, u>',
|
||||
'others': [
|
||||
{ 'path': 'std::option::Option', 'name': 'zip' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'option<t>, e -> result<t, e>',
|
||||
'others': [
|
||||
{ 'path': 'std::option::Option', 'name': 'ok_or' },
|
||||
{ 'path': 'std::result::Result', 'name': 'transpose' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'result<option<t>, e> -> option<result<t, e>>',
|
||||
'others': [
|
||||
{ 'path': 'std::result::Result', 'name': 'transpose' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'option<t>, option<t> -> bool',
|
||||
'others': [
|
||||
{ 'path': 'std::option::Option', 'name': 'eq' },
|
||||
],
|
||||
},
|
||||
{
|
||||
'query': 'option<option<t>> -> option<t>',
|
||||
'others': [
|
||||
{ 'path': 'std::option::Option', 'name': 'flatten' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
87
tests/rustdoc-js/type-parameters.js
Normal file
87
tests/rustdoc-js/type-parameters.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
// exact-check
|
||||
// ignore-order
|
||||
|
||||
const EXPECTED = [
|
||||
{
|
||||
query: '-> trait:Some',
|
||||
others: [
|
||||
{ path: 'foo', name: 'alef' },
|
||||
{ path: 'foo', name: 'alpha' },
|
||||
],
|
||||
},
|
||||
{
|
||||
query: '-> generic:T',
|
||||
others: [
|
||||
{ path: 'foo', name: 'bet' },
|
||||
{ path: 'foo', name: 'alef' },
|
||||
{ path: 'foo', name: 'beta' },
|
||||
],
|
||||
},
|
||||
{
|
||||
query: 'A -> B',
|
||||
others: [
|
||||
{ path: 'foo', name: 'bet' },
|
||||
],
|
||||
},
|
||||
{
|
||||
query: 'A -> A',
|
||||
others: [
|
||||
{ path: 'foo', name: 'beta' },
|
||||
],
|
||||
},
|
||||
{
|
||||
query: 'A, A',
|
||||
others: [
|
||||
{ path: 'foo', name: 'alternate' },
|
||||
],
|
||||
},
|
||||
{
|
||||
query: 'A, B',
|
||||
others: [
|
||||
{ path: 'foo', name: 'other' },
|
||||
],
|
||||
},
|
||||
{
|
||||
query: 'Other, Other',
|
||||
others: [
|
||||
{ path: 'foo', name: 'other' },
|
||||
{ path: 'foo', name: 'alternate' },
|
||||
],
|
||||
},
|
||||
{
|
||||
query: 'generic:T',
|
||||
in_args: [
|
||||
{ path: 'foo', name: 'bet' },
|
||||
{ path: 'foo', name: 'beta' },
|
||||
{ path: 'foo', name: 'other' },
|
||||
{ path: 'foo', name: 'alternate' },
|
||||
],
|
||||
},
|
||||
{
|
||||
query: 'generic:Other',
|
||||
in_args: [
|
||||
{ path: 'foo', name: 'bet' },
|
||||
{ path: 'foo', name: 'beta' },
|
||||
{ path: 'foo', name: 'other' },
|
||||
{ path: 'foo', name: 'alternate' },
|
||||
],
|
||||
},
|
||||
{
|
||||
query: 'trait:Other',
|
||||
in_args: [
|
||||
{ path: 'foo', name: 'other' },
|
||||
{ path: 'foo', name: 'alternate' },
|
||||
],
|
||||
},
|
||||
{
|
||||
query: 'Other',
|
||||
in_args: [
|
||||
{ path: 'foo', name: 'other' },
|
||||
{ path: 'foo', name: 'alternate' },
|
||||
],
|
||||
},
|
||||
{
|
||||
query: 'trait:T',
|
||||
in_args: [],
|
||||
},
|
||||
];
|
15
tests/rustdoc-js/type-parameters.rs
Normal file
15
tests/rustdoc-js/type-parameters.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
#![crate_name="foo"]
|
||||
|
||||
pub trait Some {}
|
||||
impl Some for () {}
|
||||
pub trait Other {}
|
||||
impl Other for () {}
|
||||
|
||||
pub fn alef<T: Some>() -> T { loop {} }
|
||||
pub fn alpha() -> impl Some { }
|
||||
|
||||
pub fn bet<T, U>(t: T) -> U { loop {} }
|
||||
pub fn beta<T>(t: T) -> T {}
|
||||
|
||||
pub fn other<T: Other, U: Other>(t: T, u: U) { loop {} }
|
||||
pub fn alternate<T: Other>(t: T, u: T) { loop {} }
|
Loading…
Add table
Add a link
Reference in a new issue