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")