use super::intermediate_public_item::NameableItem;
use crate::{
crate_wrapper::CrateWrapper, intermediate_public_item::IntermediatePublicItem,
public_item::PublicItem, render::RenderingContext, Options, PublicApi,
};
use rustdoc_types::{
Crate, Id, Impl, Import, Item, ItemEnum, Module, Struct, StructKind, VariantKind,
};
use std::{
collections::{HashMap, VecDeque},
vec,
};
#[derive(Debug)]
struct UnprocessedItem<'c> {
parent_path: Vec<NameableItem<'c>>,
id: &'c Id,
}
pub struct ItemProcessor<'c> {
crate_: CrateWrapper<'c>,
options: Options,
work_queue: VecDeque<UnprocessedItem<'c>>,
output: Vec<IntermediatePublicItem<'c>>,
}
impl<'c> ItemProcessor<'c> {
pub fn new(crate_: &'c Crate, options: Options) -> Self {
ItemProcessor {
crate_: CrateWrapper::new(crate_),
options,
work_queue: VecDeque::new(),
output: vec![],
}
}
fn add_to_work_queue(&mut self, parent_path: Vec<NameableItem<'c>>, id: &'c Id) {
self.work_queue
.push_front(UnprocessedItem { parent_path, id });
}
fn run(&mut self) {
while let Some(unprocessed_item) = self.work_queue.pop_front() {
if let Some(item) = self.crate_.get_item(unprocessed_item.id) {
self.process_any_item(item, unprocessed_item);
}
}
}
fn process_any_item(&mut self, item: &'c Item, unprocessed_item: UnprocessedItem<'c>) {
match &item.inner {
ItemEnum::Import(import) => {
if import.glob {
self.process_import_glob_item(import, unprocessed_item, item);
} else {
self.process_import_item(item, import, unprocessed_item);
}
}
ItemEnum::Impl(impl_) => {
self.process_impl_item(unprocessed_item, item, impl_);
}
_ => {
self.process_item(unprocessed_item, item, None);
}
}
}
fn process_import_glob_item(
&mut self,
import: &'c Import,
unprocessed_item: UnprocessedItem<'c>,
item: &'c Item,
) {
if let Some(Item {
inner: ItemEnum::Module(Module { items, .. }),
..
}) = import
.id
.as_ref()
.and_then(|id| self.get_item_if_not_in_path(&unprocessed_item.parent_path, id))
{
for item_id in items {
self.add_to_work_queue(unprocessed_item.parent_path.clone(), item_id);
}
} else {
self.process_item(
unprocessed_item,
item,
Some(format!("<<{}::*>>", import.source)),
);
}
}
fn process_import_item(
&mut self,
item: &'c Item,
import: &'c Import,
unprocessed_item: UnprocessedItem<'c>,
) {
let mut actual_item = item;
if let Some(imported_item) = import
.id
.as_ref()
.and_then(|id| self.get_item_if_not_in_path(&unprocessed_item.parent_path, id))
{
actual_item = imported_item;
}
self.process_item(unprocessed_item, actual_item, Some(import.name.clone()));
}
fn process_impl_item(
&mut self,
unprocessed_item: UnprocessedItem<'c>,
item: &'c Item,
impl_: &'c Impl,
) {
if !ImplKind::from(item, impl_).is_active(self.options) {
return;
}
self.process_item(unprocessed_item, item, None);
}
fn process_item(
&mut self,
unprocessed_item: UnprocessedItem<'c>,
item: &'c Item,
overridden_name: Option<String>,
) {
let finished_item = unprocessed_item.finish(item, overridden_name);
let children = children_for_item(item).into_iter().flatten();
let impls = impls_for_item(item).into_iter().flatten();
for id in children.chain(impls).rev() {
self.add_to_work_queue(finished_item.path().into(), id);
}
self.output.push(finished_item);
}
fn get_item_if_not_in_path(
&mut self,
parent_path: &[NameableItem<'c>],
id: &'c Id,
) -> Option<&'c Item> {
if parent_path.iter().any(|m| m.item.id == *id) {
return None;
}
self.crate_.get_item(id)
}
fn id_to_items(&self) -> HashMap<&Id, Vec<&IntermediatePublicItem>> {
let mut id_to_items: HashMap<&Id, Vec<&IntermediatePublicItem>> = HashMap::new();
for finished_item in &self.output {
id_to_items
.entry(&finished_item.item().id)
.or_default()
.push(finished_item);
}
id_to_items
}
}
impl<'c> UnprocessedItem<'c> {
fn finish(self, item: &'c Item, overridden_name: Option<String>) -> IntermediatePublicItem<'c> {
let mut path = self.parent_path;
path.push(NameableItem {
item,
overridden_name,
sorting_prefix: sorting_prefix(item),
});
IntermediatePublicItem::new(path)
}
}
pub(crate) fn sorting_prefix(item: &Item) -> u8 {
match &item.inner {
ItemEnum::ExternCrate { .. } => 1,
ItemEnum::Import(_) => 2,
ItemEnum::Primitive(_) => 3,
ItemEnum::Module(_) => 4,
ItemEnum::Macro(_) => 5,
ItemEnum::ProcMacro(_) => 6,
ItemEnum::Enum(_) => 7,
ItemEnum::Union(_) => 8,
ItemEnum::Struct(_) => 9,
ItemEnum::StructField(_) => 10,
ItemEnum::Variant(_) => 11,
ItemEnum::Constant(_) => 12,
ItemEnum::Static(_) => 13,
ItemEnum::Trait(_) => 14,
ItemEnum::AssocType { .. } => 15,
ItemEnum::AssocConst { .. } => 16,
ItemEnum::Function(_) => 17,
ItemEnum::Typedef(_) => 19,
ItemEnum::Impl(impl_) => match ImplKind::from(item, impl_) {
ImplKind::Inherent => 20,
ImplKind::Trait => 21,
ImplKind::AutoDerived => 22,
ImplKind::AutoTrait => 23,
ImplKind::Blanket => 24,
},
ItemEnum::ForeignType => 25,
ItemEnum::OpaqueTy(_) => 26,
ItemEnum::TraitAlias(_) => 27,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ImplKind {
Inherent,
Trait,
AutoDerived,
AutoTrait,
Blanket,
}
impl ImplKind {
fn from(impl_item: &Item, impl_: &Impl) -> Self {
let has_blanket_impl = matches!(impl_.blanket_impl, Some(_));
let is_automatically_derived = impl_item
.attrs
.iter()
.any(|a| a == "#[automatically_derived]");
match (impl_.synthetic, has_blanket_impl) {
(true, false) => ImplKind::AutoTrait,
(false, true) => ImplKind::Blanket,
_ if is_automatically_derived => ImplKind::AutoDerived,
_ if impl_.trait_.is_none() => ImplKind::Inherent,
_ => ImplKind::Trait,
}
}
}
impl ImplKind {
fn is_active(&self, options: Options) -> bool {
match self {
ImplKind::Blanket => !options.omit_blanket_impls,
ImplKind::AutoTrait => !options.omit_auto_trait_impls,
ImplKind::AutoDerived => !options.omit_auto_derived_impls,
ImplKind::Inherent | ImplKind::Trait => true,
}
}
}
const fn children_for_item(item: &Item) -> Option<&Vec<Id>> {
match &item.inner {
ItemEnum::Module(m) => Some(&m.items),
ItemEnum::Union(u) => Some(&u.fields),
ItemEnum::Struct(Struct {
kind: StructKind::Plain { fields, .. },
..
})
| ItemEnum::Variant(rustdoc_types::Variant {
kind: VariantKind::Struct { fields, .. },
..
}) => Some(fields),
ItemEnum::Enum(e) => Some(&e.variants),
ItemEnum::Trait(t) => Some(&t.items),
ItemEnum::Impl(i) => Some(&i.items),
_ => None,
}
}
pub fn impls_for_item(item: &Item) -> Option<&[Id]> {
match &item.inner {
ItemEnum::Union(u) => Some(&u.impls),
ItemEnum::Struct(s) => Some(&s.impls),
ItemEnum::Enum(e) => Some(&e.impls),
ItemEnum::Primitive(p) => Some(&p.impls),
ItemEnum::Trait(t) => Some(&t.implementations),
_ => None,
}
}
pub fn public_api_in_crate(crate_: &Crate, options: Options) -> super::PublicApi {
let mut item_processor = ItemProcessor::new(crate_, options);
item_processor.add_to_work_queue(vec![], &crate_.root);
item_processor.run();
let context = RenderingContext {
crate_,
id_to_items: item_processor.id_to_items(),
options,
};
PublicApi {
items: item_processor
.output
.iter()
.map(|item| PublicItem::from_intermediate_public_item(&context, item))
.collect::<Vec<_>>(),
missing_item_ids: item_processor.crate_.missing_item_ids(),
}
}