Skip to content

Commit

Permalink
Merge pull request #59 from MrGVSV/schematic-attr
Browse files Browse the repository at this point in the history
Add `schematic_attr` for forwarding attributes
  • Loading branch information
MrGVSV authored Oct 4, 2023
2 parents 6814bd5 + 9262271 commit a6e9509
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 59 deletions.
18 changes: 16 additions & 2 deletions bevy_proto_derive/src/asset_schematic/container_attributes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use syn::{Attribute, Error};

use crate::common::input::{parse_input_meta, InputType, OutputType, SchematicIo};
use crate::utils::constants::{ASSET_SCHEMATIC_ATTR, FROM_ATTR, INPUT_ATTR, INTO_ATTR};
use crate::common::input::{
parse_input_meta, ForwardAttributes, InputType, OutputType, SchematicIo,
};
use crate::utils::constants::{
ASSET_SCHEMATIC_ATTR, ASSET_SCHEMATIC_ATTR_ATTR, FROM_ATTR, INPUT_ATTR, INTO_ATTR,
};
use crate::utils::{
define_attribute, parse_bool, parse_nested_meta, AttrArg, AttrArgValue, AttrTarget,
};
Expand All @@ -12,13 +16,19 @@ define_attribute!("no_preload" => NoPreloadArg(bool) for AttrTarget::Asset);
#[derive(Default)]
pub(super) struct ContainerAttributes {
no_preload: NoPreloadArg,
forward_attrs: ForwardAttributes,
}

impl ContainerAttributes {
pub fn new(attrs: &[Attribute], io: &mut SchematicIo) -> Result<Self, Error> {
let mut this = Self::default();

for attr in attrs {
if attr.path().is_ident(ASSET_SCHEMATIC_ATTR_ATTR) {
this.forward_attrs.extend_from_attribute(attr)?;
continue;
}

if !attr.path().is_ident(ASSET_SCHEMATIC_ATTR) {
continue;
}
Expand All @@ -37,4 +47,8 @@ impl ContainerAttributes {
pub fn no_preload(&self) -> bool {
self.no_preload.get().copied().unwrap_or_default()
}

pub fn forward_attrs(&self) -> &ForwardAttributes {
&self.forward_attrs
}
}
1 change: 1 addition & 0 deletions bevy_proto_derive/src/asset_schematic/derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ impl DeriveAssetSchematic {
self.io(),
self.data(),
self.generics(),
self.attrs.forward_attrs(),
self.attrs().no_preload(),
)?;
let load_def = self.load_def();
Expand Down
32 changes: 18 additions & 14 deletions bevy_proto_derive/src/common/fields/schematic_field.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::common::fields::{FieldConfig, FieldKind};
use crate::common::input::ForwardAttributes;
use crate::utils::constants::{CONTEXT_IDENT, DEPENDENCIES_IDENT, INPUT_IDENT, TEMP_IDENT};
use crate::utils::exports::{
AssetServer, EntityAccess, FromReflect, FromSchematicInput, FromSchematicPreloadInput,
Expand All @@ -8,14 +9,12 @@ use crate::utils::NextId;
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use syn::spanned::Spanned;
use syn::{parse_quote, Attribute, Error, Field, Member, Type};
use syn::{parse_quote, Error, Field, Member, Type};

/// The base field information for fields of a `Schematic` or `AssetSchematic`.
pub(crate) struct SchematicField {
/// The Bevy `#[reflect]` attributes applied to the field.
///
/// These are used when generating the input type.
reflect_attrs: Vec<Attribute>,
/// Attributes that should be forwarded to the generated input field.
forward_attrs: ForwardAttributes,
/// The field's configuration.
config: FieldConfig,
/// The member (ident or index) used to access this field.
Expand All @@ -35,16 +34,11 @@ impl SchematicField {
.clone()
.map(Member::Named)
.unwrap_or(Member::Unnamed(field_index.into())),
reflect_attrs: Vec::new(),
forward_attrs: ForwardAttributes::default(),
config: FieldConfig::default(),
}
}

/// Pushes a `#[reflect]` attribute to the field.
pub fn push_reflect_attr(&mut self, attr: Attribute) {
self.reflect_attrs.push(attr);
}

/// Returns a reference to the field's configuration.
pub fn config(&self) -> &FieldConfig {
&self.config
Expand All @@ -60,6 +54,16 @@ impl SchematicField {
&self.member
}

/// The attributes that should be forwarded to the generated input field.
pub fn forward_attrs(&self) -> &ForwardAttributes {
&self.forward_attrs
}

/// A mutable reference to the attributes that should be forwarded to the generated input field.
pub fn forward_attrs_mut(&mut self) -> &mut ForwardAttributes {
&mut self.forward_attrs
}

/// Determines whether or not a generated input type should contain this field.
///
/// For fields that cannot be configured by a prototype file (e.g. `path` attributes),
Expand Down Expand Up @@ -113,18 +117,18 @@ impl SchematicField {

/// Generate this field's definition within a generated input type.
pub fn generate_definition(&self) -> TokenStream {
let reflect_attrs = &self.reflect_attrs;
let forward_attrs = self.forward_attrs();
let ty = match self.input_ty() {
Ok(ty) => ty,
Err(err) => return err.to_compile_error(),
};
match &self.member {
Member::Named(ident) => quote! {
#(#reflect_attrs)*
#forward_attrs
#ident: #ty
},
Member::Unnamed(_) => quote! {
#(#reflect_attrs)*
#forward_attrs
#ty
},
}
Expand Down
41 changes: 31 additions & 10 deletions bevy_proto_derive/src/common/fields/schematic_fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ use crate::common::fields::{
OptionalArg, SchematicField,
};
use crate::common::input::{InputType, SchematicIo};
use crate::utils::constants::{ASSET_ATTR, ENTITY_ATTR, FROM_ATTR};
use crate::utils::constants::{
ASSET_ATTR, ASSET_SCHEMATIC_ATTR, ASSET_SCHEMATIC_ATTR_ATTR, ENTITY_ATTR, FROM_ATTR,
SCHEMATIC_ATTR, SCHEMATIC_ATTR_ATTR,
};
use crate::utils::{parse_bool, parse_nested_meta, AttrArg};
use proc_macro2::Span;
use syn::meta::ParseNestedMeta;
use syn::spanned::Spanned;
use syn::{Error, Field, Fields, Type};
use syn::{Attribute, Error, Field, Fields, Type};

/// The collection of fields for a struct or enum.
pub(crate) enum SchematicFields {
Expand Down Expand Up @@ -71,16 +74,17 @@ struct ProtoFieldBuilder<'a> {
impl<'a> ProtoFieldBuilder<'a> {
fn build(mut self) -> Result<SchematicField, Error> {
for attr in &self.field.attrs {
if attr.path().is_ident("reflect") {
self.proto_field.push_reflect_attr(attr.clone());
}

if !attr.path().is_ident(self.derive_type.attr_name()) {
continue;
}

match self.derive_type {
DeriveType::Schematic => {
if attr.path().is_ident(SCHEMATIC_ATTR_ATTR) {
self.parse_forwarded_attr(attr)?;
continue;
}

if !attr.path().is_ident(SCHEMATIC_ATTR) {
continue;
}

parse_nested_meta!(attr, |meta| {
FROM_ATTR => self.parse_from_meta(meta),
ASSET_ATTR => self.parse_asset_meta(meta),
Expand All @@ -89,9 +93,19 @@ impl<'a> ProtoFieldBuilder<'a> {
})?;
}
DeriveType::AssetSchematic => {
if attr.path().is_ident(ASSET_SCHEMATIC_ATTR_ATTR) {
self.parse_forwarded_attr(attr)?;
continue;
}

if !attr.path().is_ident(ASSET_SCHEMATIC_ATTR) {
continue;
}

parse_nested_meta!(attr, |meta| {
FROM_ATTR => self.parse_from_meta(meta),
ASSET_ATTR => self.parse_asset_meta(meta),
ENTITY_ATTR => self.parse_entity_meta(meta),
OptionalArg::NAME => self.parse_optional_meta(meta),
})?;
}
Expand Down Expand Up @@ -203,6 +217,13 @@ impl<'a> ProtoFieldBuilder<'a> {
.try_set_optional(parse_bool(&meta)?, meta.input.span())
}

/// Parse a `#[schematic_attr]` attribute.
fn parse_forwarded_attr(&mut self, attr: &Attribute) -> Result<(), Error> {
self.proto_field
.forward_attrs_mut()
.extend_from_attribute(attr)
}

/// Method used to require that a `Schematic::Input` type be generated for the attribute
/// (identified by the given [`Span`]).
fn require_input(&mut self, span: Span) -> Result<(), Error> {
Expand Down
43 changes: 40 additions & 3 deletions bevy_proto_derive/src/common/input/attr.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::common::input::{InputType, SchematicIo};
use crate::utils::parse_nested_meta;
use crate::utils::{define_attribute, AttrArg, AttrTarget};
use proc_macro2::Ident;
use quote::ToTokens;
use proc_macro2::{Ident, TokenStream};
use quote::{quote, ToTokens};
use std::fmt::{Debug, Formatter};
use syn::meta::ParseNestedMeta;
use syn::{Error, Visibility};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{Attribute, Error, Meta, Token, Visibility};

define_attribute!("vis" => InputVisArg(Visibility) for AttrTarget::InputVisibility, no_debug);
define_attribute!("name" => InputNameArg(Ident) for AttrTarget::Input);
Expand All @@ -30,3 +32,38 @@ pub(crate) fn parse_input_meta(meta: ParseNestedMeta, io: &mut SchematicIo) -> R
InputNameArg::NAME => io.try_set_input_ty(InputType::Generated(meta.value()?.parse()?), None),
})
}

#[derive(Default)]
pub(crate) struct ForwardAttributes {
attributes: Punctuated<Meta, Token![,]>,
}

impl ForwardAttributes {
pub fn extend_from_attribute(&mut self, attr: &Attribute) -> syn::Result<()> {
let other = attr.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)?;
self.attributes.extend(other);

Ok(())
}
}

impl Parse for ForwardAttributes {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
attributes: input.parse_terminated(Meta::parse, Token![,])?,
})
}
}

impl ToTokens for ForwardAttributes {
fn to_tokens(&self, tokens: &mut TokenStream) {
if self.attributes.is_empty() {
return;
}

let meta = self.attributes.iter();
tokens.extend(quote! {
#(#[ #meta ])*
})
}
}
7 changes: 6 additions & 1 deletion bevy_proto_derive/src/common/input/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use to_phantom::ToPhantom;

use crate::common::data::{SchematicData, SchematicVariant};
use crate::common::fields::{SchematicField, SchematicFields};
use crate::common::input::{InputType, SchematicIo};
use crate::common::input::{ForwardAttributes, InputType, SchematicIo};
use crate::utils::constants::{CONTEXT_IDENT, DEPENDENCIES_IDENT, ID_IDENT, INPUT_IDENT};
use crate::utils::exports::{
DependenciesBuilder, FromSchematicInput, FromSchematicPreloadInput, Reflect, SchematicContext,
Expand All @@ -17,6 +17,7 @@ pub(crate) fn generate_input(
io: &SchematicIo,
data: &SchematicData,
generics: &Generics,
attributes: &ForwardAttributes,
no_preload: bool,
) -> Result<Option<TokenStream>, Error> {
let input_ident = match io.input_ty() {
Expand Down Expand Up @@ -72,6 +73,7 @@ pub(crate) fn generate_input(

Some(quote! {
#[derive(#Reflect)]
#attributes
#vis struct #input_ty_def #where_clause;

#from_impl
Expand Down Expand Up @@ -116,6 +118,7 @@ pub(crate) fn generate_input(

Some(quote! {
#[derive(#Reflect)]
#attributes
#vis struct #input_ty_def (
#(#definitions,)*
#phantom_ty
Expand Down Expand Up @@ -168,6 +171,7 @@ pub(crate) fn generate_input(

Some(quote! {
#[derive(#Reflect)]
#attributes
#vis struct #input_ty_def #where_clause {
#(#definitions,)*
#phantom_ty
Expand Down Expand Up @@ -213,6 +217,7 @@ pub(crate) fn generate_input(

Some(quote! {
#[derive(#Reflect)]
#attributes
#vis enum #input_ty_def #where_clause {
#(#definitions,)*
#phantom_ty
Expand Down
26 changes: 15 additions & 11 deletions bevy_proto_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,6 @@ mod utils;
///
/// # Attributes
///
/// ## Reflection Attributes
///
/// Because this derive macro might generate a custom input type that also relies on reflection,
/// any reflection attributes on a _field_ are included on the generated field.
///
/// For example, adding `#[reflect(default)]` to a field will have it also have the generated
/// input's field be marked with `#[reflect(default)]`.
///
/// This, of course, only applies when an input actually needs to be generated.
///
/// ## Container Attributes
///
/// ### `#[schematic(kind = {"resource"|"bundle"})]`
Expand Down Expand Up @@ -83,6 +73,13 @@ mod utils;
/// - `From<CustomSchematic> for ExternalType`
/// - `FromSchematicInput<CustomSchematic> for ExternalType`
///
/// ### `#[schematic_attr]`
///
/// This attribute is used to forward attributes to the generated input type,
/// if one is generated.
///
/// For example, we can add derives on the input type like `#[schematic_attr(derive(Clone, Debug))]`.
///
/// ## Field Attributes
///
/// ### `#[schematic(asset)]`
Expand Down Expand Up @@ -188,7 +185,14 @@ mod utils;
/// If this fails, this attribute can be used to force the field to be optional.
///
/// It can also be used to opt-out by specifying `#[schematic(optional = false)]`.
#[proc_macro_derive(Schematic, attributes(schematic))]
///
/// ### `#[schematic_attr]`
///
/// This attribute is used to forward attributes to the corresponding field on the generated input type,
/// if one is generated.
///
/// This is useful for passing things like `#[schematic_attr(reflect(ignore))]`.
#[proc_macro_derive(Schematic, attributes(schematic, schematic_attr))]
pub fn derive_schematic(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveSchematic);
input.into_token_stream().into()
Expand Down
Loading

0 comments on commit a6e9509

Please sign in to comment.