Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Rustdoc] save generic type data to index + basic search #26356

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 65 additions & 6 deletions src/librustdoc/html/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,16 +253,26 @@ struct IndexItem {
/// A type used for the search index.
struct Type {
name: Option<String>,
// true both for Option<T> and T, false otherwise.
generic: bool,
ty_params: Box<Vec<Type>>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Box<Vec<..>>? This is only useful in rare cases, Vec itself is already only three usize large.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I didn't know any better :). It was Box<Type> initially to avoid infinite recursion, and when I changed to Vec I didn't reconsider the Box. Will change, thanks!

}

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<String> = 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")
}
}
}
Expand Down Expand Up @@ -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: Box::new(vec![]),
});
}

inputs.extend(&mut decl.inputs.values.iter().map(|arg| {
Expand All @@ -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: Box::new(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<Type> = 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: Box::new(ty_params),
}
}

fn get_index_type_name(clean_type: &clean::Type) -> Option<String> {
Expand Down
79 changes: 78 additions & 1 deletion src/librustdoc/html/static/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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
Expand Down Expand Up @@ -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")
Expand Down