forked from Orchid/orchid
150 lines
4.5 KiB
Rust
150 lines
4.5 KiB
Rust
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<String>],
|
|
mut rel: &[Tok<String>],
|
|
i: &Interner,
|
|
) -> Result<VName, AbsPathError> {
|
|
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<Mod: Tree>(
|
|
cwd: &[Tok<String>],
|
|
root: &Mod,
|
|
abs_path: &[Tok<String>],
|
|
pos: Pos,
|
|
i: &Interner,
|
|
ctx: &mut Mod::Ctx<'_>,
|
|
) -> OrcRes<HashSet<Tok<String>>> {
|
|
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<Tok<String>>;
|
|
#[must_use]
|
|
fn child(
|
|
&self,
|
|
key: Tok<String>,
|
|
public_only: bool,
|
|
ctx: &mut Self::Ctx<'_>,
|
|
) -> impl Future<Output = ChildResult<'_, Self>>;
|
|
}
|
|
#[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<Item = Tok<String>>,
|
|
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)
|
|
}
|