1
0
Fork 0
mirror of https://codeberg.org/icewind/vbsp.git synced 2026-06-04 02:54:08 +02:00

derive: update to syn2 and structmeta

This commit is contained in:
Robin Appelman 2023-11-11 23:41:18 +01:00
commit 7aacb8a6eb
7 changed files with 332 additions and 86 deletions

View file

@ -1,16 +1,20 @@
use proc_macro2::{Ident, TokenStream};
use quote::{quote, ToTokens, TokenStreamExt};
use crate::parse_attrs;
use merge::Merge;
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
use structmeta::StructMeta;
use syn::spanned::Spanned;
use syn::{
Data, DataEnum, DataStruct, DeriveInput, Field, GenericParam, Path, PathArguments, PathSegment,
Type, TypePath, Variant,
Data, DataEnum, DataStruct, DeriveInput, Error, Field, GenericParam, Ident, LitStr, Path,
PathArguments, PathSegment, Result, Type, TypePath, Variant,
};
use syn_util::{contains_attribute, get_attribute_value};
type Result<T, E = &'static str> = std::result::Result<T, E>;
pub fn derive_entity(input: DeriveInput) -> Result<proc_macro2::TokenStream> {
if input.generics.lifetimes().count() > 1 {
return Err("Can't derive Entity on structs or entities with more than 1 lifetime");
return Err(Error::new(
input.generics.span(),
"Can't derive Entity on structs or entities with more than 1 lifetime",
));
}
let source_lifetime = input
.generics
@ -18,10 +22,19 @@ pub fn derive_entity(input: DeriveInput) -> Result<proc_macro2::TokenStream> {
.iter()
.find(|param| matches!(param, GenericParam::Lifetime(_)));
#[cfg(feature = "__vbsp_as_self")]
let crate_name = "crate";
#[cfg(not(feature = "__vbsp_as_self"))]
let crate_name = "vbsp";
let crate_ident = Ident::new(crate_name, input.span());
match &input.data {
Data::Struct(data) => derive_entity_struct(&input, data, source_lifetime),
Data::Enum(data) => derive_entity_enum(&input, data, source_lifetime),
_ => Err("Can only derive Entity for structs and enums"),
Data::Struct(data) => derive_entity_struct(&input, data, source_lifetime, crate_ident),
Data::Enum(data) => derive_entity_enum(&input, data, source_lifetime, crate_ident),
_ => Err(Error::new(
input.span(),
"Can only derive Entity for structs and enums",
)),
}
}
@ -29,34 +42,40 @@ fn derive_entity_enum(
input: &DeriveInput,
data: &DataEnum,
source_lifetime: Option<&GenericParam>,
crate_ident: Ident,
) -> Result<proc_macro2::TokenStream> {
let variants = data
.variants
.iter()
.filter(|variant| !contains_attribute(&variant.attrs, &["entity", "default"]))
.map(EntityVariant::try_from)
.collect::<Result<Vec<_>, _>>()?;
.collect::<Result<Vec<EntityVariant>>>()?;
let default = &data
.variants
if variants
.iter()
.find(|variant| contains_attribute(&variant.attrs, &["entity", "default"]))
.ok_or("Enum must have one variant with `#[entity(default)]` set")?
.ident;
.filter(|variant| variant.is_default())
.count()
!= 1
{
return Err(Error::new(
data.variants.span(),
"Enums must have exactly one variant with `#[entity(default)]` set",
));
}
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let name = &input.ident;
let lifetime_or_default = LifetimeOrAnonymous(source_lifetime);
Ok(quote! {
impl #impl_generics TryFrom<crate::RawEntity<#lifetime_or_default>> for #name #ty_generics #where_clause {
type Error = crate::error::EntityParseError;
let span = input.span();
fn try_from(raw: crate::RawEntity<#source_lifetime>) -> Result<Self, Self::Error> {
Ok(quote_spanned! {span =>
impl #impl_generics TryFrom<#crate_ident::RawEntity<#lifetime_or_default>> for #name #ty_generics #where_clause {
type Error = #crate_ident::error::EntityParseError;
fn try_from(raw: #crate_ident::RawEntity<#source_lifetime>) -> Result<Self, Self::Error> {
let class = raw.prop("classname")?;
Ok(match class {
#(#variants)*
_ => Self::#default(raw),
})
}
}
@ -67,22 +86,23 @@ fn derive_entity_struct(
input: &DeriveInput,
data: &DataStruct,
source_lifetime: Option<&GenericParam>,
crate_ident: Ident,
) -> Result<proc_macro2::TokenStream> {
let fields = data
.fields
.iter()
.map(EntityField::try_from)
.collect::<Result<Vec<_>, _>>()?;
.collect::<Result<Vec<_>>>()?;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let name = &input.ident;
let lifetime_or_default = LifetimeOrAnonymous(source_lifetime);
Ok(quote! {
impl #impl_generics TryFrom<crate::RawEntity<#lifetime_or_default>> for #name #ty_generics #where_clause {
type Error = crate::error::EntityParseError;
impl #impl_generics TryFrom<#crate_ident::RawEntity<#lifetime_or_default>> for #name #ty_generics #where_clause {
type Error = #crate_ident::error::EntityParseError;
fn try_from(raw: crate::RawEntity<#source_lifetime>) -> Result<Self, Self::Error> {
fn try_from(raw: #crate_ident::RawEntity<#source_lifetime>) -> Result<Self, Self::Error> {
Ok(#name {
#(#fields)*
})
@ -102,6 +122,19 @@ impl ToTokens for LifetimeOrAnonymous<'_> {
}
}
#[derive(Default, StructMeta, Merge)]
struct EntityFieldAttrs {
name: Option<LitStr>,
#[merge(strategy = merge::bool::overwrite_false)]
default: bool,
}
impl EntityFieldAttrs {
pub fn name(&self) -> Option<String> {
self.name.as_ref().map(LitStr::value)
}
}
struct EntityField<'a> {
field: &'a Ident,
name: String,
@ -109,20 +142,19 @@ struct EntityField<'a> {
}
impl<'a> TryFrom<&'a Field> for EntityField<'a> {
type Error = &'static str;
type Error = syn::Error;
fn try_from(field: &'a Field) -> std::result::Result<Self, Self::Error> {
let ident = &field
.ident
.as_ref()
.ok_or("Can't derive Entity on structs with unnamed fields")?;
let name = get_attribute_value(&field.attrs, &["entity", "name"])
.unwrap_or_else(|| ident.to_string());
let default = contains_attribute(&field.attrs, &["entity", "default"]);
fn try_from(field: &'a Field) -> Result<Self> {
let attrs: EntityFieldAttrs = parse_attrs(&field.attrs)?;
let ident = &field.ident.as_ref().ok_or(Error::new(
field.span(),
"Can't derive Entity on structs with unnamed fields",
))?;
let name = attrs.name().unwrap_or_else(|| ident.to_string());
Ok(EntityField {
field: ident,
name,
default,
default: attrs.default,
})
}
}
@ -140,25 +172,46 @@ impl ToTokens for EntityField<'_> {
}
struct EntityVariant<'a> {
name: String,
name: NameOrDefault,
variant: &'a Ident,
ty: &'a Path,
}
impl<'a> TryFrom<&'a Variant> for EntityVariant<'a> {
type Error = &'static str;
impl EntityVariant<'_> {
fn is_default(&self) -> bool {
matches!(self.name, NameOrDefault::Default)
}
}
fn try_from(value: &'a Variant) -> std::result::Result<Self, Self::Error> {
let name = get_attribute_value(&value.attrs, &["entity", "name"]).ok_or(
"All variants must have the `#[entity(name)]` or `#[entity(default)]` attribute set",
)?;
enum NameOrDefault {
Name(String),
Default,
}
impl<'a> TryFrom<&'a Variant> for EntityVariant<'a> {
type Error = syn::Error;
fn try_from(value: &'a Variant) -> Result<Self> {
let attrs: EntityFieldAttrs = parse_attrs(&value.attrs)?;
let name = match attrs.default {
true => NameOrDefault::Default,
false => NameOrDefault::Name(attrs.name().ok_or_else(||Error::new(value.span(), "All variants must have the `#[entity(name)]` or `#[entity(default)]` attribute set"),) ?)
};
if value.fields.len() != 1 {
return Err("All enum variants must have exactly one field");
return Err(Error::new(
value.span(),
"All enum variants must have exactly one field",
));
}
let field = value.fields.iter().next().unwrap();
let path = match &field.ty {
Type::Path(TypePath { path, .. }) => path,
_ => return Err("Variants can only contain plain types"),
_ => {
return Err(Error::new(
field.span(),
"Variants can only contain plain types",
))
}
};
Ok(EntityVariant {
name,
@ -184,6 +237,13 @@ impl ToTokens for EntityVariant<'_> {
})
.collect(),
};
stream.append_all(quote! {#name => Self::#variant(#ty::try_from(raw)?),});
match name {
NameOrDefault::Default => {
stream.append_all(quote! {_ => Self::#variant(#ty::from(raw)),})
}
NameOrDefault::Name(name) => {
stream.append_all(quote! {#name => Self::#variant(#ty::try_from(raw)?),})
}
}
}
}

View file

@ -1,5 +1,6 @@
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
use merge::Merge;
use syn::parse::Parse;
use syn::{parse_macro_input, Attribute, DeriveInput, Result};
mod entity;
@ -8,12 +9,21 @@ pub fn derive_entity(input: proc_macro::TokenStream) -> proc_macro::TokenStream
derive_wrapper(input, entity::derive_entity)
}
fn derive_wrapper<F: Fn(DeriveInput) -> Result<proc_macro2::TokenStream, &'static str>>(
fn derive_wrapper<F: Fn(DeriveInput) -> Result<proc_macro2::TokenStream>>(
input: proc_macro::TokenStream,
derive: F,
) -> proc_macro::TokenStream {
match derive(parse_macro_input!(input as DeriveInput)) {
Ok(tokens) => tokens.into(),
Err(e) => quote!(compile_error!(#e)).into(),
Err(e) => e.into_compile_error().into(),
}
}
fn parse_attrs<T: Parse + Default + Merge>(attrs: &[Attribute]) -> Result<T> {
let mut result = T::default();
for attr in attrs {
let parsed = attr.parse_args()?;
result.merge(parsed);
}
Ok(result)
}