typekin 0.9.0

new-type pattern with concept of Friends borrowed from c++, no-std and fully const
Documentation
use crate::{
    runner,
    runner::MkErr,
};
use proc_macro2::Ident;

use quote::ToTokens;
use std::cmp::Ordering;
use std::{
    collections::BTreeSet,
    fmt::{
        Debug,
        Formatter,
    },
};
use syn::{
    Path,
    parse::{
        Parse,
        ParseStream,
    },
};

#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub(crate) enum FriendshipLevel {
    None,
    Make,
    Rel,
    Bit,
    Math,
    Full,
    XCustom(String),
}

impl FriendshipLevel {
    fn default_level() -> BTreeSet<Self> {
        let mut set = BTreeSet::new();

        for it in [Self::Make, Self::Rel, Self::Bit, Self::Math] {
            set.insert(it);
        }

        return set;
    }

    pub(crate) fn normalize(&self) -> BTreeSet<Self> {
        match self {
            Self::None => vec![],
            Self::Full => vec![Self::Make, Self::Rel, Self::Bit, Self::Math],
            _ => vec![self.clone()],
        }
        .into_iter()
        .collect()
    }
}

impl Parse for FriendshipLevel {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let ident: Ident = input.parse()?;

        let it = match ident.to_string().as_str() {
            "None" => Self::None,
            "Make" => Self::Make,
            "Rel" => Self::Rel,
            "Bit" => Self::Bit,
            "Math" => Self::Math,
            "Full" => Self::Full,
            x_custom => {
                if !x_custom.starts_with("X") {
                    return ident.fail("custom level must start with `X`");
                }
                Self::XCustom(x_custom.to_string())
            }
        };

        return Ok(it);
    }
}

// -------------------------------------

#[derive(Clone)]
pub(crate) struct FriendReq {
    repr: String,
    pub(crate) ty: Path,
    pub(crate) level: BTreeSet<FriendshipLevel>,
    pub(crate) conv: Option<Path>,
}

impl FriendReq {
    pub(crate) fn new(
        ty: Path,
        level: BTreeSet<FriendshipLevel>,
        conv: Option<Path>,
    ) -> Self {
        return Self {
            repr: ty.to_token_stream().to_string(),
            ty,
            conv,
            level,
        };
    }
}

impl Eq for FriendReq {}

impl PartialOrd for FriendReq {
    fn partial_cmp(
        &self,
        other: &Self,
    ) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl PartialEq for FriendReq {
    fn eq(
        &self,
        other: &Self,
    ) -> bool {
        return self.repr == other.repr;
    }
}

impl Ord for FriendReq {
    fn cmp(
        &self,
        other: &Self,
    ) -> Ordering {
        return self.repr.cmp(&other.repr);
    }
}

impl Debug for FriendReq {
    fn fmt(
        &self,
        f: &mut Formatter<'_>,
    ) -> std::fmt::Result {
        write!(f, "FriendReq[{:?}]", self.level)
    }
}

impl Parse for FriendReq {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let ty: Path = input.parse()?;

        let mut level = BTreeSet::<FriendshipLevel>::new();
        let mut conv = None::<Path>;

        runner::parse_optional_attributes(input, |name, stream| {
            match name {
                "level" => level = runner::list(stream)?.collect(),
                "conv" => conv = Some(stream.parse()?),
                _ => return stream.span().fail("unknown arg"),
            }

            return Ok(true);
        })?;

        if level.contains(&FriendshipLevel::Full) {
            level.extend(FriendshipLevel::default_level());
            level.remove(&FriendshipLevel::Full);
        }

        if level.is_empty() {
            level = FriendshipLevel::default_level();
        }

        level.remove(&FriendshipLevel::None);

        let it = FriendReq::new(ty, level, conv);

        return Ok(it);
    }
}