diff --git a/src/librustdoc/html/render.rs b/src/librustdoc/html/render.rs
index 0080b5e5f223f..5533d84750a46 100644
--- a/src/librustdoc/html/render.rs
+++ b/src/librustdoc/html/render.rs
@@ -253,16 +253,26 @@ struct IndexItem {
/// A type used for the search index.
struct Type {
name: Option,
+ // true both for Option and T, false otherwise.
+ generic: bool,
+ ty_params: Vec,
}
impl fmt::Display for Type {
- /// Formats type as {name: $name}.
+ /// Formats type as {json}.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Wrapping struct fmt should never call us when self.name is None,
// but just to be safe we write `null` in that case.
- match self.name {
- Some(ref n) => write!(f, "{{\"name\":\"{}\"}}", n),
- None => write!(f, "null")
+ if let Some(ref n) = self.name {
+ try!(write!(f, "{{\"name\":\"{}\",", n));
+ try!(write!(f, "\"generic\":{},", self.generic));
+ let ty_params: Vec = self.ty_params.iter().map(|ref t| {
+ format!("{}", t)
+ }).collect();
+ // No try, use as return value.
+ write!(f, "\"ty_params\":[{}]}}", ty_params.connect(","))
+ } else {
+ write!(f, "null")
}
}
}
@@ -2566,7 +2576,11 @@ fn get_index_search_type(item: &clean::Item,
// Consider `self` an argument as well.
if let Some(name) = parent {
- inputs.push(Type { name: Some(name.into_ascii_lowercase()) });
+ inputs.push(Type {
+ name: Some(name.into_ascii_lowercase()),
+ generic: false,
+ ty_params: vec![],
+ });
}
inputs.extend(&mut decl.inputs.values.iter().map(|arg| {
@@ -2581,8 +2595,53 @@ fn get_index_search_type(item: &clean::Item,
Some(IndexItemFunctionType { inputs: inputs, output: output })
}
+fn is_clean_type_generic(clean_type: &clean::Type) -> bool {
+ match *clean_type {
+ clean::ResolvedPath { ref path, is_generic, .. } => {
+ let segments = &path.segments;
+ let segment = &segments[segments.len() - 1];
+ let has_ty_params = match segment.params {
+ clean::PathParameters::AngleBracketed { ref types, .. } => !types.is_empty(),
+ _ => false
+ };
+ is_generic || has_ty_params
+ }
+ _ => false
+ }
+}
+
fn get_index_type(clean_type: &clean::Type) -> Type {
- Type { name: get_index_type_name(clean_type).map(|s| s.into_ascii_lowercase()) }
+ if is_clean_type_generic(clean_type) {
+ get_generic_index_type(clean_type)
+ } else {
+ Type {
+ name: get_index_type_name(clean_type).map(|s| s.into_ascii_lowercase()),
+ generic: false,
+ ty_params: vec![],
+ }
+ }
+}
+
+fn get_generic_index_type(clean_type: &clean::Type) -> Type {
+ let path = match *clean_type {
+ clean::ResolvedPath { ref path, .. } => path,
+ _ => unreachable!()
+ };
+ let segments = &path.segments;
+ let segment = &segments[segments.len() - 1];
+
+ let ty_params: Vec = match segment.params {
+ clean::PathParameters::AngleBracketed { ref types, .. } => {
+ types.iter().map(|t| get_index_type(t)).collect()
+ }
+ _ => unreachable!()
+ };
+
+ Type {
+ name: Some(segment.name.clone().into_ascii_lowercase()),
+ generic: true,
+ ty_params: ty_params,
+ }
}
fn get_index_type_name(clean_type: &clean::Type) -> Option {
diff --git a/src/librustdoc/html/static/main.js b/src/librustdoc/html/static/main.js
index 7f8f40ff08a4d..c19305e4be788 100644
--- a/src/librustdoc/html/static/main.js
+++ b/src/librustdoc/html/static/main.js
@@ -224,7 +224,8 @@
for (var i = 0; i < nSearchWords; ++i) {
var type = searchIndex[i].type;
- if (!type) {
+ // skip missing types as well as wrong number of args
+ if (!type || type.inputs.length != inputs.length) {
continue;
}
@@ -239,6 +240,12 @@
output == typeOutput) {
results.push({id: i, index: -1, dontValidate: true});
}
+ // maybe we can find a generic match?
+ else {
+ if (genericTypeMatchesQuery(inputs, output, type)) {
+ results.push({id: i, index: 1, dontValidate: true});
+ }
+ }
}
} else {
// gather matching search results up to a certain maximum
@@ -378,6 +385,76 @@
return results;
}
+ /***
+ * All combinations of keys using values.
+ * @return {[[[key, value]]]} [Array of array of pairs]
+ */
+ function generateCombinations(keys, values) {
+ if (!keys.length) {
+ return [[]];
+ }
+ var combos = [];
+ var key = keys[0];
+ for (var i = 0; i < values.length; ++i) {
+ var value = values[i];
+ var combo = [[key, value]];
+ var next = generateCombinations(keys.slice(1), values.slice(i + 1));
+ for (var j = 0; j < next.length; ++j) {
+ combos.push(combo.concat(next[j]));
+ }
+ }
+ return combos;
+ }
+
+ /**
+ * Decide if the generic type (`type`) can be particularized
+ * to `inputs -> output`.
+ *
+ * @param {[string]} inputs [List of types]
+ * @param {[string]} output [Output type]
+ * @param {[object]} type [Type from search index]
+ * @return {[boolean]} [Whether types match]
+ */
+ function genericTypeMatchesQuery(inputs, output, type) {
+ // Currently only knows about queries such as
+ // `i32 -> i32` matching against `T -> T`.
+ var genericInputs = [];
+ for (var i = 0; i < type.inputs.length; ++i) {
+ if (type.inputs[i].generic && type.inputs[i].ty_params.length === 0) {
+ genericInputs.push(type.inputs[i].name);
+ }
+ }
+
+ var possibleMappings = generateCombinations(genericInputs, inputs);
+ var typeOutput = type.output ? type.output.name : "";
+
+ // For every possible mapping, try to replace the generics
+ // accordingly and see if the types match.
+ for (var i = 0; i < possibleMappings.length; ++i) {
+ var mapping = possibleMappings[i];
+ var newTypeInputs = type.inputs.map(function (input) {
+ return input.name;
+ }).sort();
+ var newTypeOutput = typeOutput;
+ for (var j = 0; j < mapping.length; ++j) {
+ var index = newTypeInputs.indexOf(mapping[j][0]);
+ newTypeInputs[index] = mapping[j][1];
+ if (newTypeOutput === mapping[j][0]) {
+ newTypeOutput = mapping[j][1];
+ }
+ }
+
+ // Does this new, particularized type, match?
+ if (inputs.toString() === newTypeInputs.toString() &&
+ output == newTypeOutput) {
+ return true;
+ }
+ }
+
+ // Couldn't find any match till here, sorry.
+ return false;
+ }
+
/**
* Validate performs the following boolean logic. For example:
* "File::open" will give IF A PARENT EXISTS => ("file" && "open")