myd 0.1.1

An implementation of the rust module system
Documentation
//! `ModuleInformation` represents the toplevel information produced by myd,
//! namely where modules are and what items they contain.

use ego_tree::{NodeId, NodeMut, NodeRef, Tree};
use proc_macro2::Ident;
use syn::Item;

use crate::{
    syn_helpers::{ident_crate, ident_self, ident_super},
    ItemId, Module, ModuleOrItem, ResolveError,
};

/// A struct representing all [`Module`]s as a [`Tree`].
///
/// To create use [`crate::parse::parse`] which will parse
/// a directory structure into the [`ModuleInformation`]
#[derive(Clone, Debug)]
pub struct ModuleInformation {
    /// The module tree (with `_` as the root of the namespace)
    pub tree: Tree<Module>,
}

impl From<Tree<Module>> for ModuleInformation {
    fn from(them: Tree<Module>) -> Self {
        Self { tree: them }
    }
}

/// A mutable reference to an item
#[derive(Debug)]
pub struct ItemRefMut<'a> {
    /// The `Module` which contains this item
    node: NodeMut<'a, Module>,
    /// THe index of this item within the modules `items`
    id: usize,
}

impl<'a> ItemRefMut<'a> {
    /// Gets a mutable reference to the item
    pub fn value(&'a mut self) -> &'a mut syn::Item {
        self.node.value().items.get_mut(self.id).unwrap()
    }
}

impl ModuleInformation {
    /// Gets a crate if it exists
    pub fn get_crate<T>(&self, crat: T) -> Option<NodeRef<Module>>
    where
        Ident: PartialEq<T>,
    {
        self.tree
            .root()
            .children()
            .find(|x| x.value().ident == crat)
    }

    /// Gets an [`ItemId`]'s [`syn::Item`] from this
    ///
    /// # See Also
    ///
    /// [`Self::get_mut`]
    pub fn get<'a>(&'a self, id: &ItemId) -> Option<&'a Item> {
        self.tree
            .get(id.node)
            .and_then(|x| x.value().items.get(id.item_id))
    }

    /// Gets an [`ItemId`]'s [`syn::Item`] mutably from this
    pub fn get_mut<'a>(&'a mut self, id: &ItemId) -> Option<ItemRefMut<'a>> {
        self.tree.get_mut(id.node).and_then(|mut x| {
            if x.value().items.len() > id.item_id {
                Some(ItemRefMut {
                    node: x,
                    id: id.item_id,
                })
            } else {
                None
            }
        })
    }

    /// Gets an item or a node at a path
    pub fn path(
        &self,
        relative_to: NodeId,
        path: &syn::Path,
    ) -> Result<ModuleOrItem, ResolveError> {
        let mut iterator = path.segments.iter().enumerate();

        // The first element in the iterator is more complicated.
        //
        // Here we check if:
        //
        //     a) This is based of the root and so set it as the
        //        start point,
        //     b) There is an item, in which case we must figure out:
        //           i)  The item is relative to this module
        //           ii) The item is relative to the root module
        let mut module = if path.leading_colon.is_some() {
            self.tree.root()
        } else {
            let mut module = self.tree.get(relative_to).ok_or(ResolveError::NotFound)?;

            // Check the first element to see if it is a child of the module,
            // and if not then assume it is from the root.
            if let Some((_, seg1)) = iterator.next() {
                if seg1.ident == ident_self() {
                } else if seg1.ident == ident_crate() {
                    module = Module::get_crate(module).ok_or(ResolveError::NotFound)?;
                } else if seg1.ident == ident_super() {
                    module = module.parent().ok_or(ResolveError::TooManySupers)?;
                } else if let Some(child) = Module::get_item(module, self, &seg1.ident)? {
                    // Check whether:
                    //
                    //     a) a submodule is returned, which we will use as the
                    //        new root
                    //     b) an item is returned but only one item is meant
                    //        to exist in the path anyway, and so return it
                    //     c) an item is returned but we still have more path,
                    //        in this case we return an error
                    //
                    // TODO: Handle the few cases when (c) is not an error
                    match child {
                        ModuleOrItem::Module(x) => module = self.tree.get(x).unwrap(),
                        ModuleOrItem::Item(_) if path.segments.len() > 1 => {
                            return Err(ResolveError::PrematureItem)
                        }
                        ModuleOrItem::Item(item) => return Ok(item.into()),
                    }
                } else if let Some(child) = Module::get_item(self.tree.root(), self, &seg1.ident)? {
                    // Check if the root has this as a child.
                    // Does not accept items as that is not how rust code works
                    match child {
                        ModuleOrItem::Module(x) => module = self.tree.get(x).unwrap(),
                        ModuleOrItem::Item(_) => return Err(ResolveError::RootItem),
                    }
                } else {
                    // Error if we failed to resolve
                    return Err(ResolveError::NotFound);
                }
            }

            module
        };

        // Traverse down the path
        for (i, segment) in iterator {
            if segment.ident == ident_self() {
            } else if segment.ident == ident_super() {
                module = module.parent().ok_or(ResolveError::TooManySupers)?;
            } else {
                match Module::get_item(module, self, &segment.ident)? {
                    Some(child) => match child {
                        // Check if:
                        //     a) There is a submodule which we reroot based of of,
                        //     b) A premature item,
                        //     c) An item and it is the final element in the list,
                        ModuleOrItem::Module(x) => module = self.tree.get(x).unwrap(),
                        ModuleOrItem::Item(_) if path.segments.len() > i + 1 => {
                            return Err(ResolveError::PrematureItem)
                        }
                        ModuleOrItem::Item(item) => return Ok(item.into()),
                    },
                    None => {
                        return Err(ResolveError::NotFound);
                    }
                }
            }
        }

        // If we still havent found an item above
        // then this module is what has been referenced by the path.
        Ok(module.id().into())
    }
}