use hashbrown::HashSet; use itertools::Itertools; use orchid_base::error::{OrcErrv, OrcRes, Reporter, mk_errv}; use orchid_base::interner::{Interner, Tok}; use orchid_base::location::Pos; use orchid_base::name::VName; /// Errors produced by absolute_path #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub enum AbsPathError { /// `super` of root requested, for example, `app::main` referenced /// `super::super::super::std` TooManySupers, /// root selected, for example, `app::main` referenced exactly `super::super`. /// The empty path also triggers this. RootPath, } impl AbsPathError { pub async fn err_obj(self, i: &Interner, pos: Pos, path: &str) -> OrcErrv { let (descr, msg) = match self { AbsPathError::RootPath => ( i.i("Path ends on root module").await, format!( "{path} is equal to the empty path. You cannot directly reference the root. \ Use one fewer 'super::' or add more segments to make it valid." ), ), AbsPathError::TooManySupers => ( i.i("Too many 'super::' steps in path").await, format!("{path} is leading outside the root."), ), }; mk_errv(descr, msg, [pos]) } } /// Turn a relative (import) path into an absolute path. /// If the import path is empty, the return value is also empty. /// /// # Errors /// /// if the relative path contains as many or more `super` segments than the /// length of the absolute path. pub async fn absolute_path( mut cwd: &[Tok], mut rel: &[Tok], i: &Interner, ) -> Result { let i_self = i.i("self").await; let i_super = i.i("super").await; let mut relative = false; if let Some((_, tail)) = rel.split_first().filter(|(h, _)| **h == i_self) { rel = tail; relative = true; } else { while let Some((_, tail)) = rel.split_first().filter(|(h, _)| **h == i_super) { cwd = cwd.split_last().ok_or(AbsPathError::TooManySupers)?.1; rel = tail; relative = true; } } if relative { VName::new(cwd.iter().chain(rel).cloned()) } else { VName::new(rel.to_vec()) } .map_err(|_| AbsPathError::RootPath) } pub struct DealiasCtx<'a> { pub i: &'a Interner, pub rep: &'a Reporter, } pub async fn resolv_glob( cwd: &[Tok], root: &Mod, abs_path: &[Tok], pos: Pos, i: &Interner, ctx: &mut Mod::Ctx<'_>, ) -> OrcRes>> { let coprefix_len = cwd.iter().zip(abs_path).take_while(|(a, b)| a == b).count(); let (co_prefix, diff_path) = abs_path.split_at(abs_path.len().min(coprefix_len + 1)); let fst_diff = walk(root, false, co_prefix.iter().cloned(), ctx).await.expect("Invalid step in cwd"); let target_module = match walk(fst_diff, true, diff_path.iter().cloned(), ctx).await { Ok(t) => t, Err(e) => { let path = abs_path[..=coprefix_len + e.pos].iter().join("::"); let (tk, msg) = match e.kind { ChildErrorKind::Constant => ("Invalid import path", format!("{path} is a const")), ChildErrorKind::Missing => ("Invalid import path", format!("{path} not found")), ChildErrorKind::Private => ("Import inaccessible", format!("{path} is private")), }; return Err(mk_errv(i.i(tk).await, msg, [pos])); }, }; Ok(target_module.children(coprefix_len < abs_path.len())) } pub type ChildResult<'a, T> = Result<&'a T, ChildErrorKind>; pub trait Tree { type Ctx<'a>; #[must_use] fn children(&self, public_only: bool) -> HashSet>; #[must_use] fn child( &self, key: Tok, public_only: bool, ctx: &mut Self::Ctx<'_>, ) -> impl Future>; } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub enum ChildErrorKind { Missing, /// Only thrown if public_only is true Private, Constant, } #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct ChildError { pub pos: usize, pub kind: ChildErrorKind, } // Problem: walk should take into account aliases and visibility // // help: since every alias is also its own import, visibility only has to be // checked on the top level // // idea: do a simple stack machine like below with no visibility for aliases and // call it from an access-checking implementation for just the top level // // caveat: we need to check EVERY IMPORT to ensure that all // errors are raised pub async fn walk<'a, T: Tree>( root: &'a T, public_only: bool, path: impl IntoIterator>, ctx: &mut T::Ctx<'_>, ) -> Result<&'a T, ChildError> { let mut cur = root; for (i, item) in path.into_iter().enumerate() { match cur.child(item, public_only, ctx).await { ChildResult::Ok(v) => cur = v, ChildResult::Err(kind) => return Err(ChildError { pos: i, kind }), } } Ok(cur) }