sacp-cbor-derive 0.12.0

Derive macros for sacp-cbor native encode/decode
Documentation
use proc_macro2::TokenStream;
use quote::quote;
use syn::{GenericArgument, Ident, Path, PathArguments, Type};

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum VariantKind {
    Null,
    Bool,
    Integer,
    Float,
    Bytes,
    Text,
    Array,
    Map,
}

impl VariantKind {
    pub(crate) const ORDER: [VariantKind; 8] = [
        VariantKind::Null,
        VariantKind::Bool,
        VariantKind::Integer,
        VariantKind::Float,
        VariantKind::Bytes,
        VariantKind::Text,
        VariantKind::Array,
        VariantKind::Map,
    ];

    pub(crate) fn idx(self) -> usize {
        match self {
            VariantKind::Null => 0,
            VariantKind::Bool => 1,
            VariantKind::Integer => 2,
            VariantKind::Float => 3,
            VariantKind::Bytes => 4,
            VariantKind::Text => 5,
            VariantKind::Array => 6,
            VariantKind::Map => 7,
        }
    }

    pub(crate) fn to_cbor_kind_ts(self) -> TokenStream {
        match self {
            VariantKind::Null => quote!(::sacp_cbor::CborKind::Null),
            VariantKind::Bool => quote!(::sacp_cbor::CborKind::Bool),
            VariantKind::Integer => quote!(::sacp_cbor::CborKind::Integer),
            VariantKind::Float => quote!(::sacp_cbor::CborKind::Float),
            VariantKind::Bytes => quote!(::sacp_cbor::CborKind::Bytes),
            VariantKind::Text => quote!(::sacp_cbor::CborKind::Text),
            VariantKind::Array => quote!(::sacp_cbor::CborKind::Array),
            VariantKind::Map => quote!(::sacp_cbor::CborKind::Map),
        }
    }
}

pub(crate) fn is_option_type(ty: &Type) -> bool {
    let Type::Path(tp) = ty else { return false };
    let Some(seg) = tp.path.segments.last() else {
        return false;
    };
    seg.ident == "Option"
}

pub(crate) fn vec_inner_type(ty: &Type) -> Option<&Type> {
    let Type::Path(tp) = ty else { return None };
    let seg = tp.path.segments.last()?;
    if seg.ident != "Vec" {
        return None;
    }
    let PathArguments::AngleBracketed(args) = &seg.arguments else {
        return None;
    };
    let mut iter = args.args.iter();
    let Some(GenericArgument::Type(inner)) = iter.next() else {
        return None;
    };
    if iter.next().is_some() {
        return None;
    }
    Some(inner)
}

pub(crate) fn type_is_ident(ty: &Type, name: &str) -> bool {
    let Type::Path(tp) = ty else { return false };
    let Some(seg) = tp.path.segments.last() else {
        return false;
    };
    seg.ident == name
}

fn path_might_be_self(path: &Path, self_ident: &Ident) -> bool {
    let Some(last) = path.segments.last() else {
        return false;
    };
    if last.ident != *self_ident {
        return false;
    }
    if path.segments.len() == 1 {
        return true;
    }
    path.segments
        .iter()
        .take(path.segments.len() - 1)
        .all(|seg| matches!(seg.ident.to_string().as_str(), "crate" | "self" | "super"))
}

pub(crate) fn type_mentions_self(ty: &Type, self_ident: &Ident) -> bool {
    match ty {
        Type::Path(tp) => {
            if tp.qself.is_none() && path_might_be_self(&tp.path, self_ident) {
                return true;
            }
            if let Some(q) = &tp.qself {
                if type_mentions_self(&q.ty, self_ident) {
                    return true;
                }
            }
            tp.path.segments.iter().any(|seg| match &seg.arguments {
                PathArguments::AngleBracketed(args) => args.args.iter().any(|arg| match arg {
                    GenericArgument::Type(inner) => type_mentions_self(inner, self_ident),
                    _ => false,
                }),
                _ => false,
            })
        }
        Type::Reference(tr) => type_mentions_self(&tr.elem, self_ident),
        Type::Tuple(tt) => tt.elems.iter().any(|t| type_mentions_self(t, self_ident)),
        Type::Array(ta) => type_mentions_self(&ta.elem, self_ident),
        Type::Slice(ts) => type_mentions_self(&ts.elem, self_ident),
        Type::Group(tg) => type_mentions_self(&tg.elem, self_ident),
        Type::Paren(tp) => type_mentions_self(&tp.elem, self_ident),
        _ => false,
    }
}

pub(crate) fn type_kind(ty: &Type) -> Option<VariantKind> {
    match ty {
        Type::Reference(tr) => type_kind(&tr.elem),
        Type::Group(tg) => type_kind(&tg.elem),
        Type::Paren(tp) => type_kind(&tp.elem),
        Type::Slice(ts) => {
            if type_is_ident(&ts.elem, "u8") {
                Some(VariantKind::Bytes)
            } else {
                None
            }
        }
        Type::Path(tp) => {
            let seg = tp.path.segments.last()?;
            let ident = seg.ident.to_string();
            match ident.as_str() {
                "bool" => Some(VariantKind::Bool),
                "i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64"
                | "u128" | "usize" => Some(VariantKind::Integer),
                "f32" | "f64" => Some(VariantKind::Float),
                "String" | "str" => Some(VariantKind::Text),
                "CanonicalCborRef" => None,
                "MapEntries" => Some(VariantKind::Map),
                "Vec" => {
                    let inner = vec_inner_type(ty)?;
                    if type_is_ident(inner, "u8") {
                        Some(VariantKind::Bytes)
                    } else {
                        Some(VariantKind::Array)
                    }
                }
                "Option" | "CborValueRef" => None,
                _ => None,
            }
        }
        _ => None,
    }
}