diff --git a/source/postcard-derive/src/lib.rs b/source/postcard-derive/src/lib.rs index ec5ae0e..7c9c405 100644 --- a/source/postcard-derive/src/lib.rs +++ b/source/postcard-derive/src/lib.rs @@ -8,7 +8,7 @@ pub fn derive_max_size(item: proc_macro::TokenStream) -> proc_macro::TokenStream } /// Derive the `postcard_schema::Schema` trait for a struct or enum. -#[proc_macro_derive(Schema)] +#[proc_macro_derive(Schema, attributes(postcard))] pub fn derive_schema(item: proc_macro::TokenStream) -> proc_macro::TokenStream { schema::do_derive_schema(item) } diff --git a/source/postcard-derive/src/schema.rs b/source/postcard-derive/src/schema.rs index 4deb85c..5b88f62 100644 --- a/source/postcard-derive/src/schema.rs +++ b/source/postcard-derive/src/schema.rs @@ -2,138 +2,183 @@ use proc_macro2::{Span, TokenStream}; use quote::{quote, quote_spanned}; use syn::{ parse_macro_input, parse_quote, spanned::Spanned, Data, DeriveInput, Fields, GenericParam, - Generics, + Generics, Path, }; pub fn do_derive_schema(item: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(item as DeriveInput); let span = input.span(); - let name = input.ident; + let name = &input.ident; + + let generator = match Generator::new(&input) { + Ok(generator) => generator, + Err(err) => return err.into_compile_error().into(), + }; // Add a bound `T: Schema` to every type parameter T. - let generics = add_trait_bounds(input.generics); + let generics = generator.add_trait_bounds(input.generics); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - let ty = generate_type(&input.data, span, name.to_string()) + let ty = generator + .generate_type(&input.data, span, name.to_string()) .unwrap_or_else(syn::Error::into_compile_error); + let postcard_schema = &generator.postcard_schema; let expanded = quote! { - impl #impl_generics ::postcard_schema::Schema for #name #ty_generics #where_clause { - const SCHEMA: &'static ::postcard_schema::schema::NamedType = #ty; + impl #impl_generics #postcard_schema::Schema for #name #ty_generics #where_clause { + const SCHEMA: &'static #postcard_schema::schema::NamedType = #ty; } }; expanded.into() } -fn generate_type(data: &Data, span: Span, name: String) -> Result { - let ty = match data { - Data::Struct(data) => generate_struct(&data.fields), - Data::Enum(data) => { - let name = data.variants.iter().map(|v| v.ident.to_string()); - let ty = data.variants.iter().map(|v| generate_variants(&v.fields)); - - quote! { - &::postcard_schema::schema::DataModelType::Enum(&[ - #( &::postcard_schema::schema::NamedVariant { name: #name, ty: #ty } ),* - ]) +struct Generator { + postcard_schema: Path, +} + +impl Generator { + fn new(input: &DeriveInput) -> syn::Result { + let mut generator = Self { + postcard_schema: parse_quote!(::postcard_schema), + }; + for attr in &input.attrs { + if attr.path().is_ident("postcard") { + attr.parse_nested_meta(|meta| { + // #[postcard(crate = path::to::postcard)] + if meta.path.is_ident("crate") { + generator.postcard_schema = meta.value()?.parse()?; + return Ok(()); + } + + Err(meta.error("unsupported #[postcard] attribute")) + })?; } } - Data::Union(_) => { - return Err(syn::Error::new( - span, - "unions are not supported by `postcard::experimental::schema`", - )) - } - }; + Ok(generator) + } - Ok(quote! { - &::postcard_schema::schema::NamedType { - name: #name, - ty: #ty, - } - }) -} + fn generate_type( + &self, + data: &Data, + span: Span, + name: String, + ) -> Result { + let postcard_schema = &self.postcard_schema; + let ty = match data { + Data::Struct(data) => self.generate_struct(&data.fields), + Data::Enum(data) => { + let name = data.variants.iter().map(|v| v.ident.to_string()); + let ty = data + .variants + .iter() + .map(|v| self.generate_variants(&v.fields)); -fn generate_struct(fields: &Fields) -> TokenStream { - match fields { - syn::Fields::Named(fields) => { - let fields = fields.named.iter().map(|f| { + quote! { + &#postcard_schema::schema::DataModelType::Enum(&[ + #( &#postcard_schema::schema::NamedVariant { name: #name, ty: #ty } ),* + ]) + } + } + Data::Union(_) => { + return Err(syn::Error::new( + span, + "unions are not supported by `postcard::experimental::schema`", + )) + } + }; + + Ok(quote! { + &#postcard_schema::schema::NamedType { + name: #name, + ty: #ty, + } + }) + } + + fn generate_struct(&self, fields: &Fields) -> TokenStream { + let postcard_schema = &self.postcard_schema; + match fields { + syn::Fields::Named(fields) => { + let fields = fields.named.iter().map(|f| { let ty = &f.ty; let name = f.ident.as_ref().unwrap().to_string(); - quote_spanned!(f.span() => &::postcard_schema::schema::NamedValue { name: #name, ty: <#ty as ::postcard_schema::Schema>::SCHEMA }) + quote_spanned!(f.span() => &#postcard_schema::schema::NamedValue { name: #name, ty: <#ty as #postcard_schema::Schema>::SCHEMA }) }); - quote! { &::postcard_schema::schema::DataModelType::Struct(&[ - #( #fields ),* - ]) } - } - syn::Fields::Unnamed(fields) => { - if fields.unnamed.len() == 1 { - let f = fields.unnamed[0].clone(); - let ty = &f.ty; - let qs = quote_spanned!(f.span() => <#ty as ::postcard_schema::Schema>::SCHEMA); - - quote! { &::postcard_schema::schema::DataModelType::NewtypeStruct(#qs) } - } else { - let fields = fields.unnamed.iter().map(|f| { - let ty = &f.ty; - quote_spanned!(f.span() => <#ty as ::postcard_schema::Schema>::SCHEMA) - }); - quote! { &::postcard_schema::schema::DataModelType::TupleStruct(&[ + quote! { &#postcard_schema::schema::DataModelType::Struct(&[ #( #fields ),* ]) } } - } - syn::Fields::Unit => { - quote! { &::postcard_schema::schema::DataModelType::UnitStruct } + syn::Fields::Unnamed(fields) => { + if fields.unnamed.len() == 1 { + let f = fields.unnamed[0].clone(); + let ty = &f.ty; + let qs = quote_spanned!(f.span() => <#ty as #postcard_schema::Schema>::SCHEMA); + + quote! { &#postcard_schema::schema::DataModelType::NewtypeStruct(#qs) } + } else { + let fields = fields.unnamed.iter().map(|f| { + let ty = &f.ty; + quote_spanned!(f.span() => <#ty as #postcard_schema::Schema>::SCHEMA) + }); + quote! { &#postcard_schema::schema::DataModelType::TupleStruct(&[ + #( #fields ),* + ]) } + } + } + syn::Fields::Unit => { + quote! { &#postcard_schema::schema::DataModelType::UnitStruct } + } } } -} -fn generate_variants(fields: &Fields) -> TokenStream { - match fields { - syn::Fields::Named(fields) => { - let fields = fields.named.iter().map(|f| { + fn generate_variants(&self, fields: &Fields) -> TokenStream { + let postcard_schema = &self.postcard_schema; + match fields { + syn::Fields::Named(fields) => { + let fields = fields.named.iter().map(|f| { let ty = &f.ty; let name = f.ident.as_ref().unwrap().to_string(); - quote_spanned!(f.span() => &::postcard_schema::schema::NamedValue { name: #name, ty: <#ty as ::postcard_schema::Schema>::SCHEMA }) + quote_spanned!(f.span() => &#postcard_schema::schema::NamedValue { name: #name, ty: <#ty as #postcard_schema::Schema>::SCHEMA }) }); - quote! { &::postcard_schema::schema::DataModelVariant::StructVariant(&[ - #( #fields ),* - ]) } - } - syn::Fields::Unnamed(fields) => { - if fields.unnamed.len() == 1 { - let f = fields.unnamed[0].clone(); - let ty = &f.ty; - let qs = quote_spanned!(f.span() => <#ty as ::postcard_schema::Schema>::SCHEMA); - - quote! { &::postcard_schema::schema::DataModelVariant::NewtypeVariant(#qs) } - } else { - let fields = fields.unnamed.iter().map(|f| { - let ty = &f.ty; - quote_spanned!(f.span() => <#ty as ::postcard_schema::Schema>::SCHEMA) - }); - quote! { &::postcard_schema::schema::DataModelVariant::TupleVariant(&[ + quote! { &#postcard_schema::schema::DataModelVariant::StructVariant(&[ #( #fields ),* ]) } } - } - syn::Fields::Unit => { - quote! { &::postcard_schema::schema::DataModelVariant::UnitVariant } + syn::Fields::Unnamed(fields) => { + if fields.unnamed.len() == 1 { + let f = fields.unnamed[0].clone(); + let ty = &f.ty; + let qs = quote_spanned!(f.span() => <#ty as #postcard_schema::Schema>::SCHEMA); + + quote! { &#postcard_schema::schema::DataModelVariant::NewtypeVariant(#qs) } + } else { + let fields = fields.unnamed.iter().map(|f| { + let ty = &f.ty; + quote_spanned!(f.span() => <#ty as #postcard_schema::Schema>::SCHEMA) + }); + quote! { &#postcard_schema::schema::DataModelVariant::TupleVariant(&[ + #( #fields ),* + ]) } + } + } + syn::Fields::Unit => { + quote! { &#postcard_schema::schema::DataModelVariant::UnitVariant } + } } } -} -/// Add a bound `T: MaxSize` to every type parameter T. -fn add_trait_bounds(mut generics: Generics) -> Generics { - for param in &mut generics.params { - if let GenericParam::Type(ref mut type_param) = *param { - type_param - .bounds - .push(parse_quote!(::postcard_schema::Schema)); + /// Add a bound `T: MaxSize` to every type parameter T. + fn add_trait_bounds(&self, mut generics: Generics) -> Generics { + let postcard_schema = &self.postcard_schema; + for param in &mut generics.params { + if let GenericParam::Type(ref mut type_param) = *param { + type_param + .bounds + .push(parse_quote!(#postcard_schema::Schema)); + } } + generics } - generics } diff --git a/source/postcard-schema/src/lib.rs b/source/postcard-schema/src/lib.rs index cd7016d..b028958 100644 --- a/source/postcard-schema/src/lib.rs +++ b/source/postcard-schema/src/lib.rs @@ -6,6 +6,39 @@ pub mod impls; pub mod schema; +/// Derive [`Schema`] for a struct or enum +/// +/// # Examples +/// +/// ``` +/// use postcard_schema::Schema; +/// +/// #[derive(Schema)] +/// struct Point { +/// x: i32, +/// y: i32, +/// } +/// ``` +/// +/// # Attributes +/// +/// ## `#[postcard(crate = ...)]` +/// +/// The `#[postcard(crate = ...)]` attribute can be used to specify a path to the `postcard_schema` +/// crate instance to use when referring to [`Schema`] and [schema types](schema) from generated +/// code. This is normally only applicable when invoking re-exported derives from a different crate. +/// +/// ``` +/// # use postcard_schema::Schema; +/// use postcard_schema as reexported_postcard_schema; +/// +/// #[derive(Schema)] +/// #[postcard(crate = reexported_postcard_schema)] +/// struct Point { +/// x: i32, +/// y: i32, +/// } +/// ``` #[cfg(feature = "derive")] pub use postcard_derive::Schema; @@ -15,3 +48,36 @@ pub trait Schema { /// type. const SCHEMA: &'static schema::NamedType; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn crate_path() { + #[allow(unused)] + #[derive(Schema)] + #[postcard(crate = crate)] + struct Point { + x: i32, + y: i32, + } + + assert_eq!( + Point::SCHEMA, + &schema::NamedType { + name: "Point", + ty: &schema::DataModelType::Struct(&[ + &schema::NamedValue { + name: "x", + ty: i32::SCHEMA + }, + &schema::NamedValue { + name: "y", + ty: i32::SCHEMA + }, + ]) + } + ); + } +}