derive/src/struct
derive/src/struct/classify.rs
//! Warning: this file has been mostly generated by Claude.
//! I checked for its correctness but I'm not familiar with syn API
//! so things might not work as expected at some point. I'll try to add
//! enough test cases to be sure everything is ok
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::{spanned::Spanned, *};
use crate::utils::strip_lifetimes;
use super::OptSpec;
#[derive(Debug)]
pub(crate) enum FieldKind {
Optional(FieldShape),
Required(FieldShape),
}
impl FieldKind {
pub fn sub(&self, opt: &Option<OptSpec>) -> TokenStream {
match self {
Self::Optional(_) => quote!(::opt),
Self::Required(_) if opt.is_some() => quote!(::cond),
_ => quote!(),
}
}
pub fn opt(&self, opt: &Option<OptSpec>, ident_opt: &Ident) -> TokenStream {
match self {
Self::Optional(_) => quote!(, #ident_opt),
Self::Required(_) if opt.is_some() => quote!(, #ident_opt),
_ => quote!(),
}
}
pub fn extra_val(&self, opt: &Option<OptSpec>) -> TokenStream {
match self {
Self::Required(_)
if let Some(OptSpec {
condition: Some(c), ..
}) = opt =>
{
quote!(, #c)
}
_ => quote!(),
}
}
pub fn extra_ref(&self, opt: &Option<OptSpec>) -> TokenStream {
match self {
Self::Required(_)
if let Some(OptSpec {
condition: Some(c), ..
}) = opt =>
{
quote!(, &#c)
}
_ => quote!(),
}
}
}
#[derive(Debug)]
pub(crate) enum FieldShape {
Scalar(Type),
Slice(Type),
Msg(Type),
}
impl FieldShape {
pub fn ty(&self) -> &Type {
match self {
Self::Scalar(t) | Self::Slice(t) | Self::Msg(t) => t,
}
}
pub fn root(&self) -> TokenStream {
match self {
Self::Scalar(_) => quote!(elvwf::scalar),
Self::Slice(_) => quote!(elvwf::slice),
Self::Msg(_) => quote!(elvwf::msg),
}
}
pub fn by_ref(&self) -> bool {
matches!(self, Self::Msg(_))
}
}
impl ToTokens for FieldShape {
fn to_tokens(&self, tokens: &mut TokenStream) {
let (Self::Msg(ty) | Self::Slice(ty) | Self::Scalar(ty)) = self;
ty.to_tokens(tokens);
}
}
impl ToTokens for FieldKind {
fn to_tokens(&self, tokens: &mut TokenStream) {
let (Self::Optional(shape) | Self::Required(shape)) = self;
shape.to_tokens(tokens);
}
}
const SCALAR_IDENTS: &[&str] = &[
"u8", "u16", "u32", "u64", "u128", "usize", "i8", "i16", "i32", "i64", "i128", "isize", "f32",
"f64",
];
fn is_scalar(ty: &Type) -> bool {
let Type::Path(TypePath { qself: None, path }) = ty else {
return false;
};
let Some(ident) = path.get_ident() else {
return false;
};
SCALAR_IDENTS.iter().any(|s| ident == s)
}
fn as_option(ty: &Type) -> Option<&Type> {
let Type::Path(TypePath { qself: None, path }) = ty else {
return None;
};
let seg = path.segments.last()?;
if seg.ident != "Option" {
return None;
}
let PathArguments::AngleBracketed(args) = &seg.arguments else {
return None;
};
if args.args.len() != 1 {
return None;
}
match args.args.first()? {
GenericArgument::Type(inner) => Some(inner),
_ => None,
}
}
fn is_u8(ty: &Type) -> bool {
matches!(ty, Type::Path(TypePath { qself: None, path }) if path.is_ident("u8"))
}
fn classify_shape(ty: &Type) -> Result<(FieldShape, FieldShape)> {
if is_scalar(ty) {
return Ok((
FieldShape::Scalar(ty.clone()),
FieldShape::Scalar(strip_lifetimes(ty)),
));
}
if let Type::Reference(TypeReference { elem, .. }) = ty {
match &**elem {
Type::Path(TypePath { qself: None, path })
if path.is_ident("str") || path.is_ident("CStr") =>
{
return Ok((
FieldShape::Slice(ty.clone()),
FieldShape::Slice(strip_lifetimes(ty)),
));
}
Type::Slice(TypeSlice { elem, .. }) | Type::Array(TypeArray { elem, .. }) => {
if !is_u8(elem) {
return Err(Error::new(
elem.span(),
"only `&[u8]` or `&[u8; N]` slices are supported",
));
}
return Ok((
FieldShape::Slice(ty.clone()),
FieldShape::Slice(strip_lifetimes(ty)),
));
}
_ => {
return Err(Error::new(
ty.span(),
"unsupported reference type: expected `&str`, `&CStr`, `&[u8]` or `&[u8; N]`",
));
}
}
}
Ok((
FieldShape::Msg(ty.clone()),
FieldShape::Msg(strip_lifetimes(ty)),
))
}
pub fn classify(ty: &Type) -> Result<(FieldKind, FieldKind)> {
if let Some(inner) = as_option(ty) {
if as_option(inner).is_some() {
return Err(Error::new(
inner.span(),
"nested `Option<Option<…>>` is not supported",
));
}
Ok(classify_shape(inner)
.map(|(kind, stripped)| (FieldKind::Optional(kind), FieldKind::Optional(stripped)))?)
} else {
Ok(classify_shape(ty)
.map(|(kind, stripped)| (FieldKind::Required(kind), FieldKind::Required(stripped)))?)
}
}