delta-pack-derive 0.3.0

Proc-macro derive for delta-pack
Documentation
use syn::{spanned::Spanned, GenericArgument, PathArguments, Type as SynType};

use crate::attrs::FieldAttrs;
use crate::ir::{compute_num_bits, IntInfo, IntStorage, Type};

pub struct TypeCtx<'a> {
    pub self_name: &'a syn::Ident,
}

pub fn map_field(ty: &SynType, attrs: &FieldAttrs, ctx: &TypeCtx) -> syn::Result<Type> {
    map_type(ty, Some(attrs), ctx)
}

fn map_type(ty: &SynType, attrs: Option<&FieldAttrs>, ctx: &TypeCtx) -> syn::Result<Type> {
    let path = match ty {
        SynType::Path(p) if p.qself.is_none() => &p.path,
        _ => return Err(syn::Error::new(ty.span(), "unsupported type")),
    };

    let last = path
        .segments
        .last()
        .ok_or_else(|| syn::Error::new(ty.span(), "empty path"))?;
    let name = last.ident.to_string();

    match name.as_str() {
        "String" => Ok(Type::String),
        "bool" => Ok(Type::Boolean),
        "f32" => {
            let precision = attrs.and_then(|a| a.precision);
            Ok(Type::Float { precision })
        }
        "f64" => Err(syn::Error::new(
            ty.span(),
            "f64 is not supported; use f32 (delta-pack only encodes 4-byte floats)",
        )),
        "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" => {
            let storage = match name.as_str() {
                "i8" => IntStorage::I8,
                "i16" => IntStorage::I16,
                "i32" => IntStorage::I32,
                "i64" => IntStorage::I64,
                "u8" => IntStorage::U8,
                "u16" => IntStorage::U16,
                "u32" => IntStorage::U32,
                "u64" => IntStorage::U64,
                _ => unreachable!(),
            };
            let signed = storage.is_signed();
            let mut min = attrs.and_then(|a| a.min);
            let max = attrs.and_then(|a| a.max);
            if !signed && min.is_none() {
                min = Some(0);
            }
            let num_bits = match (min, max) {
                (Some(a), Some(b)) => compute_num_bits(a, b),
                _ => None,
            };
            Ok(Type::Int(IntInfo {
                min,
                max,
                num_bits,
                storage,
            }))
        }
        "usize" | "isize" | "i128" | "u128" => Err(syn::Error::new(
            ty.span(),
            format!(
                "{} is not supported; use a fixed-width integer (i8..i64, u8..u64)",
                name
            ),
        )),
        "Vec" => {
            let inner = single_generic(last, ty.span())?;
            Ok(Type::Array(Box::new(map_type(inner, None, ctx)?)))
        }
        "Option" => {
            let inner = single_generic(last, ty.span())?;
            Ok(Type::Optional(Box::new(map_type(inner, None, ctx)?)))
        }
        "IndexMap" => {
            let (key, value) = two_generics(last, ty.span())?;
            let key_ir = map_type(key, None, ctx)?;
            let value_ir = map_type(value, None, ctx)?;
            match &key_ir {
                Type::String | Type::Int(_) => {}
                _ => {
                    return Err(syn::Error::new(
                        key.span(),
                        "IndexMap key must be a string or integer type",
                    ))
                }
            }
            Ok(Type::Record {
                key: Box::new(key_ir),
                value: Box::new(value_ir),
            })
        }
        "HashMap" | "BTreeMap" => Err(syn::Error::new(
            ty.span(),
            format!(
                "{} is not supported; use IndexMap (delta-pack requires insertion-order preservation)",
                name
            ),
        )),
        "Box" => {
            let inner = single_generic(last, ty.span())?;
            let inner_path = match inner {
                SynType::Path(p) if p.qself.is_none() => &p.path,
                _ => {
                    return Err(syn::Error::new(
                        inner.span(),
                        "Box<T> where T is not a named type is not supported",
                    ));
                }
            };
            let inner_name = &inner_path
                .segments
                .last()
                .ok_or_else(|| syn::Error::new(inner.span(), "empty path"))?
                .ident;
            if inner_name == "Self" || inner_name == ctx.self_name {
                Ok(Type::SelfReference)
            } else {
                Err(syn::Error::new(
                    ty.span(),
                    "Box<T> is only supported for self-references (Box<Self>); wrap other types without Box",
                ))
            }
        }
        _ if name == "Self" || &last.ident == ctx.self_name => Ok(Type::SelfReference),
        _ => Ok(Type::Reference(path.clone())),
    }
}

fn single_generic(seg: &syn::PathSegment, span: proc_macro2::Span) -> syn::Result<&SynType> {
    match &seg.arguments {
        PathArguments::AngleBracketed(args) => {
            if args.args.len() != 1 {
                return Err(syn::Error::new(span, "expected one type parameter"));
            }
            match &args.args[0] {
                GenericArgument::Type(t) => Ok(t),
                _ => Err(syn::Error::new(span, "expected type parameter")),
            }
        }
        _ => Err(syn::Error::new(span, "expected generic arguments")),
    }
}

fn two_generics(
    seg: &syn::PathSegment,
    span: proc_macro2::Span,
) -> syn::Result<(&SynType, &SynType)> {
    match &seg.arguments {
        PathArguments::AngleBracketed(args) => {
            if args.args.len() != 2 {
                return Err(syn::Error::new(span, "expected two type parameters"));
            }
            let a = match &args.args[0] {
                GenericArgument::Type(t) => t,
                _ => return Err(syn::Error::new(span, "expected type parameter")),
            };
            let b = match &args.args[1] {
                GenericArgument::Type(t) => t,
                _ => return Err(syn::Error::new(span, "expected type parameter")),
            };
            Ok((a, b))
        }
        _ => Err(syn::Error::new(span, "expected generic arguments")),
    }
}