//! Generic module tree structure //! //! Used by various stages of the pipeline with different parameters use std::fmt; use hashbrown::HashMap; use itertools::Itertools as _; use never::Never; use substack::Substack; use trait_set::trait_set; use crate::boxed_iter::BoxedIter; use crate::combine::Combine; use crate::intern::{intern, Token}; use crate::join::try_join_maps; use crate::location::CodeOrigin; use crate::name::{VName, VPath}; use crate::proj_error::{ProjectError, ProjectErrorObj}; use crate::sequence::Sequence; /// An umbrella trait for operations you can carry out on any part of the tree /// structure pub trait TreeTransforms: Sized { /// Data held at the leaves of the tree type Item; /// Data associated with modules type XMod; /// Data associated with entries inside modules type XEnt; /// Recursive type to enable [TreeTransforms::map_data] to transform the whole /// tree type SelfType: TreeTransforms; /// Implementation for [TreeTransforms::map_data] fn map_data_rec( self, item: &mut impl FnMut(Substack>, Self::Item) -> T, module: &mut impl FnMut(Substack>, Self::XMod) -> U, entry: &mut impl FnMut(Substack>, Self::XEnt) -> V, path: Substack>, ) -> Self::SelfType; /// Transform all the data in the tree without changing its structure fn map_data( self, mut item: impl FnMut(Substack>, Self::Item) -> T, mut module: impl FnMut(Substack>, Self::XMod) -> U, mut entry: impl FnMut(Substack>, Self::XEnt) -> V, ) -> Self::SelfType { self.map_data_rec(&mut item, &mut module, &mut entry, Substack::Bottom) } /// Visit all elements in the tree. This is like [TreeTransforms::search] but /// without the early exit /// /// * init - can be used for reduce, otherwise pass `()` /// * callback - a callback applied on every module. /// * [`Substack>`] - the walked path /// * [Module] - the current module /// * `T` - data for reduce. fn search_all<'a, T>( &'a self, init: T, mut callback: impl FnMut( Substack>, ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>, T, ) -> T, ) -> T { let res = self.search(init, |stack, member, state| Ok::(callback(stack, member, state))); res.unwrap_or_else(|e| match e {}) } /// Visit elements in the tree depth first with the provided function /// /// * init - can be used for reduce, otherwise pass `()` /// * callback - a callback applied on every module. Can return [Err] to /// short-circuit the walk /// * [`Substack>`] - the walked path /// * [Module] - the current module /// * `T` - data for reduce. fn search<'a, T, E>( &'a self, init: T, mut callback: impl FnMut( Substack>, ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>, T, ) -> Result, ) -> Result { self.search_rec(init, Substack::Bottom, &mut callback) } /// Internal version of [TreeTransforms::search_all] fn search_rec<'a, T, E>( &'a self, state: T, stack: Substack>, callback: &mut impl FnMut( Substack>, ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>, T, ) -> Result, ) -> Result; } /// The member in a [ModEntry] which is associated with a name in a [Module] #[derive(Debug, Clone, PartialEq, Eq)] pub enum ModMember { /// Arbitrary data Item(Item), /// A child module Sub(Module), } impl TreeTransforms for ModMember { type Item = Item; type XEnt = XEnt; type XMod = XMod; type SelfType = ModMember; fn map_data_rec( self, item: &mut impl FnMut(Substack>, Item) -> T, module: &mut impl FnMut(Substack>, XMod) -> U, entry: &mut impl FnMut(Substack>, XEnt) -> V, path: Substack>, ) -> Self::SelfType { match self { Self::Item(it) => ModMember::Item(item(path, it)), Self::Sub(sub) => ModMember::Sub(sub.map_data_rec(item, module, entry, path)), } } fn search_rec<'a, T, E>( &'a self, state: T, stack: Substack>, callback: &mut impl FnMut( Substack>, ModMemberRef<'a, Item, XMod, XEnt>, T, ) -> Result, ) -> Result { match self { Self::Item(it) => callback(stack, ModMemberRef::Item(it), state), Self::Sub(m) => m.search_rec(state, stack, callback), } } } /// Reasons why merging trees might fail pub enum ConflictKind { /// Error during the merging of items Item(Item::Error), /// Error during the merging of module metadata Module(XMod::Error), /// Error during the merging of entry metadata XEnt(XEnt::Error), /// An item appeared in one tree where the other contained a submodule ItemModule, } macro_rules! impl_for_conflict { ($target:ty, ($($deps:tt)*), $for:ty, $body:tt) => { impl $target for $for where Item::Error: $($deps)*, XMod::Error: $($deps)*, XEnt::Error: $($deps)*, $body }; } impl_for_conflict!(Clone, (Clone), ConflictKind, { fn clone(&self) -> Self { match self { ConflictKind::Item(it_e) => ConflictKind::Item(it_e.clone()), ConflictKind::Module(mod_e) => ConflictKind::Module(mod_e.clone()), ConflictKind::XEnt(ent_e) => ConflictKind::XEnt(ent_e.clone()), ConflictKind::ItemModule => ConflictKind::ItemModule, } } }); impl_for_conflict!(fmt::Debug, (fmt::Debug), ConflictKind, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ConflictKind::Item(it_e) => f.debug_tuple("TreeCombineErr::Item").field(it_e).finish(), ConflictKind::Module(mod_e) => f.debug_tuple("TreeCombineErr::Module").field(mod_e).finish(), ConflictKind::XEnt(ent_e) => f.debug_tuple("TreeCombineErr::XEnt").field(ent_e).finish(), ConflictKind::ItemModule => write!(f, "TreeCombineErr::Item2Module"), } } }); /// Error produced when two trees cannot be merged pub struct TreeConflict { /// Which subtree caused the failure pub path: VPath, /// What type of failure occurred pub kind: ConflictKind, } impl TreeConflict { fn new(kind: ConflictKind) -> Self { Self { path: VPath::new([]), kind } } fn push(self, seg: Token) -> Self { Self { path: self.path.prefix([seg]), kind: self.kind } } } impl_for_conflict!(Clone, (Clone), TreeConflict, { fn clone(&self) -> Self { Self { path: self.path.clone(), kind: self.kind.clone() } } }); impl_for_conflict!(fmt::Debug, (fmt::Debug), TreeConflict, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("TreeConflict") .field("path", &self.path) .field("kind", &self.kind) .finish() } }); impl Combine for ModMember { type Error = TreeConflict; fn combine(self, other: Self) -> Result { match (self, other) { (Self::Item(i1), Self::Item(i2)) => match i1.combine(i2) { Ok(i) => Ok(Self::Item(i)), Err(e) => Err(TreeConflict::new(ConflictKind::Item(e))), }, (Self::Sub(m1), Self::Sub(m2)) => m1.combine(m2).map(Self::Sub), (..) => Err(TreeConflict::new(ConflictKind::ItemModule)), } } } /// Data about a name in a [Module] #[derive(Debug, Clone, PartialEq, Eq)] pub struct ModEntry { /// The submodule or item pub member: ModMember, /// Additional fields pub x: XEnt, } impl Combine for ModEntry { type Error = TreeConflict; fn combine(self, other: Self) -> Result { match self.x.combine(other.x) { Err(e) => Err(TreeConflict::new(ConflictKind::XEnt(e))), Ok(x) => Ok(Self { x, member: self.member.combine(other.member)? }), } } } impl ModEntry { /// Returns the item in this entry if it contains one. #[must_use] pub fn item(&self) -> Option<&Item> { match &self.member { ModMember::Item(it) => Some(it), ModMember::Sub(_) => None, } } } impl TreeTransforms for ModEntry { type Item = Item; type XEnt = XEnt; type XMod = XMod; type SelfType = ModEntry; fn map_data_rec( self, item: &mut impl FnMut(Substack>, Item) -> T, module: &mut impl FnMut(Substack>, XMod) -> U, entry: &mut impl FnMut(Substack>, XEnt) -> V, path: Substack>, ) -> Self::SelfType { ModEntry { member: self.member.map_data_rec(item, module, entry, path.clone()), x: entry(path, self.x), } } fn search_rec<'a, T, E>( &'a self, state: T, stack: Substack>, callback: &mut impl FnMut( Substack>, ModMemberRef<'a, Item, XMod, XEnt>, T, ) -> Result, ) -> Result { self.member.search_rec(state, stack, callback) } } impl ModEntry { /// Wrap a member directly with trivial metadata pub fn wrap(member: ModMember) -> Self { Self { member, x: XEnt::default() } } /// Wrap an item directly with trivial metadata pub fn leaf(item: Item) -> Self { Self::wrap(ModMember::Item(item)) } } impl ModEntry { /// Create an empty submodule pub fn empty() -> Self { Self::wrap(ModMember::Sub(Module::wrap([]))) } /// Create a module #[must_use] pub fn tree>(arr: impl IntoIterator) -> Self { Self::wrap(ModMember::Sub(Module::wrap(arr.into_iter().map(|(k, v)| (intern(k.as_ref()), v))))) } /// Create a record in the list passed to [ModEntry#tree] which describes a /// submodule. This mostly exists to deal with strange rustfmt block /// breaking behaviour pub fn tree_ent>(key: K, arr: impl IntoIterator) -> (K, Self) { (key, Self::tree(arr)) } /// Namespace the tree with the list of names /// /// The unarray is used to trick rustfmt into breaking the sub-item /// into a block without breaking anything else. #[must_use] pub fn ns(name: impl AsRef, [mut end]: [Self; 1]) -> Self { let elements = name.as_ref().split("::").collect::>(); for name in elements.into_iter().rev() { end = Self::tree([(name, end)]); } end } fn not_mod_panic() -> T { panic!("Expected module but found leaf") } /// Return the wrapped module. Panic if the entry wraps an item pub fn unwrap_mod(self) -> Module { if let ModMember::Sub(m) = self.member { m } else { Self::not_mod_panic() } } /// Return the wrapped module. Panic if the entry wraps an item pub fn unwrap_mod_ref(&self) -> &Module { if let ModMember::Sub(m) = &self.member { m } else { Self::not_mod_panic() } } } /// A module, containing imports, #[derive(Debug, Clone, PartialEq, Eq)] pub struct Module { /// Submodules and items by name pub entries: HashMap, ModEntry>, /// Additional fields pub x: XMod, } trait_set! { /// A filter applied to a module tree pub trait Filter<'a, Item, XMod, XEnt> = for<'b> Fn(&'b ModEntry) -> bool + Clone + 'a } /// A line in a [Module] pub type Record = (Token, ModEntry); impl Module { /// Returns child names for which the value matches a filter #[must_use] pub fn keys<'a>( &'a self, filter: impl for<'b> Fn(&'b ModEntry) -> bool + 'a, ) -> BoxedIter> { Box::new((self.entries.iter()).filter(move |(_, v)| filter(v)).map(|(k, _)| k.clone())) } /// Return the module at the end of the given path pub fn walk_ref<'a: 'b, 'b>( &'a self, prefix: &'b [Token], path: &'b [Token], filter: impl Filter<'b, Item, XMod, XEnt>, ) -> Result<&'a Self, WalkError<'b>> { let mut module = self; for (pos, step) in path.iter().enumerate() { let kind = match module.entries.get(step) { None => ErrKind::Missing, Some(ent) if !filter(ent) => ErrKind::Filtered, Some(ModEntry { member: ModMember::Item(_), .. }) => ErrKind::NotModule, Some(ModEntry { member: ModMember::Sub(next), .. }) => { module = next; continue; }, }; let options = Sequence::new(move || module.keys(filter.clone())); return Err(WalkError { kind, prefix, path, pos, options }); } Ok(module) } /// Return the member at the end of the given path /// /// # Panics /// /// if path is empty, since the reference cannot be forwarded that way pub fn walk1_ref<'a: 'b, 'b>( &'a self, prefix: &'b [Token], path: &'b [Token], filter: impl Filter<'b, Item, XMod, XEnt>, ) -> Result<(&'a ModEntry, &'a Self), WalkError<'b>> { let (last, parent) = path.split_last().expect("Path cannot be empty"); let pos = path.len() - 1; let module = self.walk_ref(prefix, parent, filter.clone())?; let err_kind = match &module.entries.get(last) { Some(entry) if filter(entry) => return Ok((entry, module)), Some(_) => ErrKind::Filtered, None => ErrKind::Missing, }; let options = Sequence::new(move || module.keys(filter.clone())); Err(WalkError { kind: err_kind, options, prefix, path, pos }) } /// Walk from one node to another in a tree, asserting that the origin can see /// the target. /// /// # Panics /// /// If the target is the root node pub fn inner_walk<'a: 'b, 'b>( &'a self, origin: &[Token], target: &'b [Token], is_exported: impl for<'c> Fn(&'c ModEntry) -> bool + Clone + 'b, ) -> Result<(&'a ModEntry, &'a Self), WalkError<'b>> { let ignore_vis_len = 1 + origin.iter().zip(target).take_while(|(a, b)| a == b).count(); if target.len() <= ignore_vis_len { return self.walk1_ref(&[], target, |_| true); } let (ignore_vis_path, hidden_path) = target.split_at(ignore_vis_len); let first_divergence = self.walk_ref(&[], ignore_vis_path, |_| true)?; first_divergence.walk1_ref(ignore_vis_path, hidden_path, is_exported) } /// Wrap entry table in a module with trivial metadata pub fn wrap(entries: impl IntoIterator>) -> Self where XMod: Default { Self { entries: entries.into_iter().collect(), x: XMod::default() } } } impl TreeTransforms for Module { type Item = Item; type XEnt = XEnt; type XMod = XMod; type SelfType = Module; fn map_data_rec( self, item: &mut impl FnMut(Substack>, Item) -> T, module: &mut impl FnMut(Substack>, XMod) -> U, entry: &mut impl FnMut(Substack>, XEnt) -> V, path: Substack>, ) -> Self::SelfType { Module { x: module(path.clone(), self.x), entries: (self.entries.into_iter()) .map(|(k, e)| (k.clone(), e.map_data_rec(item, module, entry, path.push(k)))) .collect(), } } fn search_rec<'a, T, E>( &'a self, mut state: T, stack: Substack>, callback: &mut impl FnMut( Substack>, ModMemberRef<'a, Item, XMod, XEnt>, T, ) -> Result, ) -> Result { state = callback(stack.clone(), ModMemberRef::Mod(self), state)?; for (key, value) in &self.entries { state = value.search_rec(state, stack.push(key.clone()), callback)?; } Ok(state) } } impl Combine for Module { type Error = TreeConflict; fn combine(self, Self { entries, x }: Self) -> Result { let entries = try_join_maps(self.entries, entries, |k, l, r| l.combine(r).map_err(|e| e.push(k.clone())))?; let x = (self.x.combine(x)).map_err(|e| TreeConflict::new(ConflictKind::Module(e)))?; Ok(Self { x, entries }) } } impl fmt::Display for Module { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "module {{")?; for (name, ModEntry { member, x: extra }) in &self.entries { match member { ModMember::Sub(module) => write!(f, "\n{name} {extra} = {module}"), ModMember::Item(item) => write!(f, "\n{name} {extra} = {item}"), }?; } write!(f, "\n\n{}\n}}", &self.x) } } /// A non-owning version of [ModMember]. Either an item-ref or a module-ref. pub enum ModMemberRef<'a, Item, XMod, XEnt> { /// Leaf Item(&'a Item), /// Node Mod(&'a Module), } /// Possible causes why the path could not be walked #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ErrKind { /// `require_exported` was set to `true` and a module wasn't exported Filtered, /// A module was not found Missing, /// The path leads into a leaf node NotModule, } #[derive(Clone)] /// All details about a failed tree-walk pub struct WalkError<'a> { /// Failure mode kind: ErrKind, /// Path to the module where the walk started prefix: &'a [Token], /// Planned walk path path: &'a [Token], /// Index into walked path where the error occurred pos: usize, /// Alternatives to the failed steps options: Sequence<'a, Token>, } impl<'a> WalkError<'a> { /// Total length of the path represented by this error #[must_use] pub fn depth(&self) -> usize { self.prefix.len() + self.pos + 1 } /// Attach a location to the error and convert into trait object for reporting #[must_use] pub fn at(self, origin: &CodeOrigin) -> ProjectErrorObj { let details = WalkErrorDetails { origin: origin.clone(), path: VName::new((self.prefix.iter()).chain(self.path.iter().take(self.pos + 1)).cloned()) .expect("empty paths don't cause an error"), options: self.options.iter().collect(), }; match self.kind { ErrKind::Filtered => FilteredError(details).pack(), ErrKind::Missing => MissingError(details).pack(), ErrKind::NotModule => NotModuleError(details).pack(), } } /// Construct an error for the very last item in a slice. This is often done /// outside [super::tree] so it gets a function rather than exposing the /// fields of [WalkError] pub fn last( path: &'a [Token], kind: ErrKind, options: Sequence<'a, Token>, ) -> Self { WalkError { kind, path, options, pos: path.len() - 1, prefix: &[] } } } impl<'a> fmt::Debug for WalkError<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WalkError") .field("kind", &self.kind) .field("prefix", &self.prefix) .field("path", &self.path) .field("pos", &self.pos) .finish_non_exhaustive() } } struct WalkErrorDetails { path: VName, options: Vec>, origin: CodeOrigin, } impl WalkErrorDetails { fn print_options(&self) -> String { format!("options are {}", self.options.iter().join(", ")) } } struct FilteredError(WalkErrorDetails); impl ProjectError for FilteredError { const DESCRIPTION: &'static str = "The path leads into a private module"; fn one_position(&self) -> CodeOrigin { self.0.origin.clone() } fn message(&self) -> String { format!("{} is private, {}", self.0.path, self.0.print_options()) } } struct MissingError(WalkErrorDetails); impl ProjectError for MissingError { const DESCRIPTION: &'static str = "Nonexistent path"; fn one_position(&self) -> CodeOrigin { self.0.origin.clone() } fn message(&self) -> String { format!("{} does not exist, {}", self.0.path, self.0.print_options()) } } struct NotModuleError(WalkErrorDetails); impl ProjectError for NotModuleError { const DESCRIPTION: &'static str = "The path leads into a leaf"; fn one_position(&self) -> CodeOrigin { self.0.origin.clone() } fn message(&self) -> String { format!("{} is not a module, {}", self.0.path, self.0.print_options()) } }