use core::any::TypeId;
use effectful::{
bound::SsBound,
effective::{Canonical, Effective},
environment::Environment,
higher_ranked::Rank1,
tri, DynBind, SendSync,
};
use crate::{
any::type_name,
hkt::Marker,
protocol::{walker::hint::HintMeta, DynVisitor},
symbol::Symbol,
walk::{DynWalkerAdapter, DynWalkerError, DynWalkerObjSafe},
Walker,
};
use super::VisitResult;
pub mod tags {
use super::*;
pub type Struct = TagConst<{ Symbol::new("Struct").to_int() }>;
pub type Map = TagConst<{ Symbol::new("Map").to_int() }>;
pub type Variant = TagConst<{ Symbol::new("Variant").to_int() }>;
pub type Key = TagConst<{ Symbol::new("Key").to_int() }>;
pub type Value = TagConst<{ Symbol::new("Value").to_int() }>;
pub type FieldNames = TagConst<{ Symbol::new("Field Names").to_int() }>;
pub type TypeName = TagConst<{ Symbol::new("Type Name").to_int() }>;
pub type TypeId = TagConst<{ Symbol::new("Type ID").to_int() }>;
}
pub trait TagKind<E: SsBound>: Copy + DynBind<E> + 'static {
fn symbol(&self) -> Symbol;
}
pub trait ConstTagKind<E: SsBound>: TagKind<E> {
const NEW: Self;
}
impl<E: SsBound, const SYMBOL: u64> ConstTagKind<E> for TagConst<SYMBOL> {
const NEW: Self = TagConst;
}
#[derive(Copy, Clone, SendSync)]
pub struct TagConst<const SYMBOL: u64>;
#[derive(Copy, Clone, SendSync)]
pub struct TagDyn(pub Symbol);
impl<const SYMBOL: u64, E: SsBound> TagKind<E> for TagConst<SYMBOL> {
fn symbol(&self) -> Symbol {
Symbol::from_int(SYMBOL)
}
}
impl<const SYMBOL: u64> TagConst<SYMBOL> {
pub const VALUE: Symbol = Symbol::from_int(SYMBOL);
}
impl<E: SsBound> TagKind<E> for TagDyn {
fn symbol(&self) -> Symbol {
self.0
}
}
pub trait Tag<'src, K: TagKind<E>, E: Environment>: DynBind<E> {
fn visit<'r>(
&'r mut self,
kind: K,
walker: DynWalkerObjSafe<'r, 'src, E>,
) -> Canonical<'r, VisitResult, E>;
}
impl<'u, 'src, K: TagKind<E>, E> type_name::Lower<'u, 'src, &'u &'src ()> for dyn Tag<'static, K, E>
where
E: Environment,
{
type Lowered = dyn Tag<'src, K, E> + 'u;
}
impl<'u, 'src, K: TagKind<E>, E> type_name::Raise<'u, 'src, &'u &'src ()>
for dyn Tag<'src, K, E> + 'u
where
E: Environment,
{
type Raised = dyn Tag<'static, K, E>;
}
impl<K: TagKind<E>, E: Environment> HintMeta for dyn Tag<'static, K, E> {
type Known = Rank1<TagKnown>;
type Hint = Rank1<TagHint<K>>;
}
impl<K: TagKind<E>, E: Environment> effectful::environment::InEnvironment
for dyn Tag<'static, K, E>
{
type Env = E;
}
#[derive(SendSync)]
pub struct TagKnown {
pub kind_available: Option<bool>,
}
#[derive(SendSync)]
pub struct TagHint<K> {
pub kind: K,
}
#[derive(Debug, PartialEq, Clone, Copy, SendSync)]
pub enum TagErrorKind<E> {
NeverWalked,
/// This can only happen if a panic happens during the walk and is then caught before calling
/// finish.
WalkNeverFinished,
Walker(E),
SkippedWasWalked,
}
#[derive(Debug, PartialEq, Copy, Clone, SendSync)]
#[allow(unused)]
pub struct TagError<E> {
symbol: Symbol,
err: TagErrorKind<E>,
}
impl<E> TagError<E> {
fn new<K: TagKind<Env>, Env: SsBound>(tag: K, err: E) -> Self {
Self {
symbol: tag.symbol(),
err: TagErrorKind::Walker(err),
}
}
fn never_walked<K: TagKind<Env>, Env: SsBound>(tag: K) -> Self {
Self {
symbol: tag.symbol(),
err: TagErrorKind::NeverWalked,
}
}
fn walk_never_finished<K: TagKind<Env>, Env: SsBound>(tag: K) -> Self {
Self {
symbol: tag.symbol(),
err: TagErrorKind::WalkNeverFinished,
}
}
fn was_walked<K: TagKind<Env>, Env: SsBound>(tag: K) -> Self {
Self {
symbol: tag.symbol(),
err: TagErrorKind::SkippedWasWalked,
}
}
}
#[inline(always)]
pub fn visit_tag<'r, 'src, K: TagKind<E>, E: Environment, W: crate::Walker<'src, E> + 'r>(
kind: K,
visitor: DynVisitor<'r, 'src, E>,
walker: W,
) -> Canonical<'r, Result<VisitResult<W>, TagError<W::Error>>, E> {
// Wrap the walker to allow it to be passed to a dyn walker argument.
let walker = DynWalkerAdapter::new(walker);
// Give everything to the effective chain to be used as context.
E::value((kind, visitor, walker))
.update_map((), |_, (kind, visitor, walker)| {
// Try to visit the tag kind as given.
tri!(visitor.upcast_mut::<dyn Tag<'src, K, E>>())
.visit(*kind, walker)
.map((), |_, x| VisitResult::to_flow(x))
.cast()
})
.or_else_update((), |_, (kind, visitor, walker), _| {
// If the tag type is already dynamic we don't need to try visiting again.
if TypeId::of::<K>() == TypeId::of::<TagDyn>() {
return E::value(None).cast();
}
// Visit using the dynamic tag, but with the same symbol.
tri!(visitor.upcast_mut::<dyn Tag<'src, TagDyn, E>>())
.visit(TagDyn(kind.symbol()), walker)
.map((), |_, x| VisitResult::to_flow(x))
.cast()
})
.map((), |_, ((kind, _, walker), result)| match result {
// The visit was performed so return the control flow.
Some(flow) => Ok(VisitResult::Control(flow)),
// The visit was not performed.
// We need to give back the walker as we got it.
None => match walker.into_inner() {
Ok(walker) => Ok(VisitResult::Skipped(walker)),
Err(err) => Err(map_walker_err(kind, err)),
},
})
.cast()
}
fn map_walker_err<'ctx, K: TagKind<E>, W: Walker<'ctx, E>, E: Environment>(
kind: K,
err: DynWalkerError<'ctx, W, E>,
) -> TagError<W::Error> {
match err {
DynWalkerError::Walker(err) => TagError::new(kind, err),
DynWalkerError::NeverWalked(_) => TagError::never_walked(kind),
DynWalkerError::WalkNeverFinished => TagError::walk_never_finished(kind),
DynWalkerError::WasWalked(_) => TagError::was_walked(kind),
}
}