use rustdoc_types::{Id, Impl, Item, ItemEnum, Type};
use trustfall::{
FieldValue,
provider::{
AsVertex, CandidateValue, ContextIterator, ContextOutcomeIterator, ResolveEdgeInfo,
VertexInfo, VertexIterator, resolve_neighbors_with,
},
};
use crate::{
RustdocAdapter,
adapter::{Origin, Vertex},
hashtables::{HashMap, HashSet},
indexed_crate::ImplEntry,
stability::PublicApiStabilityPolicy,
};
pub(crate) fn resolve_impl_methods<'a, V: AsVertex<Vertex<'a>> + 'a>(
adapter: &'a RustdocAdapter<'a>,
contexts: ContextIterator<'a, V>,
resolve_info: &ResolveEdgeInfo,
) -> ContextOutcomeIterator<'a, V, VertexIterator<'a, Vertex<'a>>> {
let neighbor_info = resolve_info.destination();
if let Some(resolver) = neighbor_info.dynamically_required_property("name") {
resolver.resolve_with(&adapter, contexts, move |vertex, candidate| {
resolve_method_from_candidate_value(adapter, vertex, candidate)
})
} else if let Some(candidate) = neighbor_info.statically_required_property("name") {
resolve_neighbors_with(contexts, move |vertex| {
resolve_method_from_candidate_value(adapter, vertex, candidate.clone())
})
} else {
resolve_neighbors_with(contexts, move |vertex| {
let origin = vertex.origin;
let indexed_crate = &adapter.crate_at_origin(origin).own_crate;
let item_index = &indexed_crate.inner.index;
let impl_item = vertex.as_item().expect("not an Impl item");
let impl_vertex = vertex.as_impl().expect("not an Impl vertex");
resolve_methods_slow_path(
impl_item,
impl_vertex,
origin,
item_index,
indexed_crate.stability_policy,
)
})
}
}
fn find_impl_owner_id(impl_vertex: &Impl) -> Option<&Id> {
let mut ty = &impl_vertex.for_;
loop {
match ty {
Type::ResolvedPath(path) => break Some(&path.id),
Type::BorrowedRef {
lifetime: _,
is_mutable: _,
type_,
} => {
ty = type_;
}
Type::RawPointer {
is_mutable: _,
type_,
} => {
ty = type_;
}
_ => {
break None;
}
}
}
}
fn resolve_method_from_candidate_value<'a>(
adapter: &'a RustdocAdapter<'a>,
vertex: &Vertex<'a>,
method_name: CandidateValue<FieldValue>,
) -> VertexIterator<'a, Vertex<'a>> {
let origin = vertex.origin;
let indexed_crate = &adapter.crate_at_origin(origin).own_crate;
let item_index = &indexed_crate.inner.index;
let impl_index = indexed_crate
.impl_method_index
.as_ref()
.expect("no impl index provided");
let impl_id = &vertex.as_item().expect("not an Item vertex").id;
let impl_vertex = vertex.as_impl().expect("not an Impl vertex");
if let Some(impl_owner_id) = find_impl_owner_id(impl_vertex) {
match method_name {
CandidateValue::Impossible => Box::new(std::iter::empty()),
CandidateValue::Single(name) => {
let method_name = name.as_str().expect("method name was not a string");
resolve_impl_method_by_name(
origin,
impl_index,
item_index,
impl_owner_id,
impl_id,
method_name,
)
}
CandidateValue::Multiple(names) => Box::new(names.into_iter().flat_map(move |name| {
let method_name = name.as_str().expect("method name was not a string");
resolve_impl_method_by_name(
origin,
impl_index,
item_index,
impl_owner_id,
impl_id,
method_name,
)
})),
_ => {
let impl_item = vertex.as_item().expect("not an Impl item");
resolve_methods_slow_path(
impl_item,
impl_vertex,
origin,
item_index,
indexed_crate.stability_policy,
)
}
}
} else {
let impl_item = vertex.as_item().expect("not an Impl item");
resolve_methods_slow_path(
impl_item,
impl_vertex,
origin,
item_index,
indexed_crate.stability_policy,
)
}
}
fn resolve_impl_method_by_name<'a>(
origin: Origin,
impl_index: &'a HashMap<ImplEntry<'a>, Vec<(&'a Item, &'a Item)>>,
item_index: &'a HashMap<Id, Item>,
impl_owner_id: &'a Id,
impl_id: &'a Id,
method_name: &str,
) -> VertexIterator<'a, Vertex<'a>> {
if let Some(method_ids) = impl_index.get(&(impl_owner_id, method_name)) {
Box::new(
method_ids
.iter()
.filter(move |(impl_item, _)| &impl_item.id == impl_id)
.map(move |(impl_item, item)| {
let parent = method_parent_for_impl_method(impl_item, item, item_index);
origin.make_method_vertex(item, parent)
}),
)
} else {
Box::new(std::iter::empty())
}
}
fn method_parent_for_impl_method<'a>(
impl_item: &'a Item,
method_item: &'a Item,
item_index: &'a HashMap<Id, Item>,
) -> &'a Item {
let ItemEnum::Impl(impl_) = &impl_item.inner else {
unreachable!("method index returned a non-impl parent item: {impl_item:?}");
};
if impl_.items.contains(&method_item.id) {
return impl_item;
}
let method_name = method_item
.name
.as_deref()
.expect("method item had no name");
debug_assert!(
impl_
.provided_trait_methods
.iter()
.any(|name| name == method_name),
"method item was neither explicitly implemented nor trait-provided: {method_item:?}",
);
let trait_path = impl_
.trait_
.as_ref()
.expect("provided trait method appeared on an inherent impl");
let trait_item = item_index
.get(&trait_path.id)
.expect("provided trait method parent trait was missing from the item index");
let ItemEnum::Trait(trait_) = &trait_item.inner else {
unreachable!("provided method parent was not a trait: {trait_item:?}");
};
debug_assert!(
trait_.items.contains(&method_item.id),
"provided method was not listed on its parent trait: {method_item:?}",
);
trait_item
}
fn resolve_methods_slow_path<'a>(
impl_item: &'a Item,
impl_vertex: &'a Impl,
origin: Origin,
item_index: &'a HashMap<Id, Item>,
stability_policy: PublicApiStabilityPolicy,
) -> VertexIterator<'a, Vertex<'a>> {
let provided_methods: Box<dyn Iterator<Item = &Id>> =
if impl_vertex.provided_trait_methods.is_empty() {
Box::new(std::iter::empty())
} else {
let method_names: HashSet<&str> = impl_vertex
.provided_trait_methods
.iter()
.map(|x| x.as_str())
.collect();
let trait_path = impl_vertex
.trait_
.as_ref()
.expect("no trait but provided_trait_methods was non-empty");
let trait_item = item_index.get(&trait_path.id);
if let Some(trait_item) = trait_item {
if let ItemEnum::Trait(trait_item) = &trait_item.inner {
Box::new(trait_item.items.iter().filter(move |item_id| {
let Some(next_item) = item_index.get(item_id) else {
return false;
};
let rustdoc_types::ItemEnum::Function(function) = &next_item.inner else {
return false;
};
let Some(name) = next_item.name.as_deref() else {
return false;
};
method_names.contains(name)
&& stability_policy.effective_function_has_body(function)
}))
} else {
unreachable!("found a non-trait type {trait_item:?}");
}
} else {
Box::new(std::iter::empty())
}
};
let mut produced_methods: HashSet<&str> = Default::default();
Box::new(
impl_vertex
.items
.iter()
.chain(provided_methods)
.filter_map(move |item_id| {
let next_item = &item_index.get(item_id);
if let Some(next_item) = next_item {
let item_name = next_item.name.as_deref()?;
match &next_item.inner {
rustdoc_types::ItemEnum::Function(..) => {
if produced_methods.insert(item_name) {
let parent =
method_parent_for_impl_method(impl_item, next_item, item_index);
Some(origin.make_method_vertex(next_item, parent))
} else {
None
}
}
_ => None,
}
} else {
None
}
}),
)
}