bevy/crates/bevy_reflect/derive/src/field_attributes.rs
BD103 22305acf66
Rename bevy_reflect_derive folder to derive (#13269)
# Objective

- Some of the "large" crates have sub-crates, usually for things such as
macros.
- For an example, see [`bevy_ecs_macros` at
`bevy_ecs/macros`](4f9f987099/crates/bevy_ecs/macros).
- The one crate that does not follow this convention is
[`bevy_reflect_derive`](4f9f987099/crates/bevy_reflect/bevy_reflect_derive),
which is in the `bevy_reflect/bevy_reflect_derive` folder and not
`bevy_reflect/derive` or `bevy_reflect/macros`.

## Solution

- Rename folder `bevy_reflect_derive` to `derive`.
- I chose to use `derive` instead of `macros` because the crate name
itself ends in `_derive`. (One of only two crates to actually use this
convention, funnily enough.)

## Testing

- Build and test `bevy_reflect` and `bevy_reflect_derive`.
- Apply the following patch to `publish.sh` to run it in `--dry-run`
mode, to test that the path has been successfully updated:
- If you have any security concerns about applying random diffs, feel
free to skip this step. Worst case scenario it fails and Cart has to
manually publish a few crates.

```bash
# Apply patch to make `publish.sh` *not* actually publish anything.
git apply path/to/foo.patch
# Make `publish.sh` executable.
chmod +x tools/publish.sh
# Execute `publish.sh`.
./tools/publish.sh
```

```patch
diff --git a/tools/publish.sh b/tools/publish.sh
index b020bad28..fbcc09281 100644
--- a/tools/publish.sh
+++ b/tools/publish.sh
@@ -49,7 +49,7 @@ crates=(
 
 if [ -n "$(git status --porcelain)" ]; then
     echo "You have local changes!"
-    exit 1
+    # exit 1
 fi
 
 pushd crates
@@ -61,15 +61,15 @@ do
   cp ../LICENSE-APACHE "$crate"
   pushd "$crate"
   git add LICENSE-MIT LICENSE-APACHE
-  cargo publish --no-verify --allow-dirty
+  cargo publish --no-verify --allow-dirty --dry-run
   popd
-  sleep 20
+  # sleep 20
 done
 
 popd
 
 echo "Publishing root crate"
-cargo publish --allow-dirty
+cargo publish --allow-dirty --dry-run
 
 echo "Cleaning local state"
 git reset HEAD --hard
```

---

## Changelog

- Moved `bevy_reflect_derive` from
`crates/bevy_reflect/bevy_reflect_derive` to
`crates/bevy_reflect/derive`.
2024-05-07 07:55:32 +00:00

180 lines
6.1 KiB
Rust

//! Contains code related to field attributes for reflected types.
//!
//! A field attribute is an attribute which applies to particular field or variant
//! as opposed to an entire struct or enum. An example of such an attribute is
//! the derive helper attribute for `Reflect`, which looks like: `#[reflect(ignore)]`.
use crate::utility::terminated_parser;
use crate::REFLECT_ATTRIBUTE_NAME;
use syn::parse::ParseStream;
use syn::{Attribute, LitStr, Meta, Token};
mod kw {
syn::custom_keyword!(ignore);
syn::custom_keyword!(skip_serializing);
syn::custom_keyword!(default);
}
pub(crate) const IGNORE_SERIALIZATION_ATTR: &str = "skip_serializing";
pub(crate) const IGNORE_ALL_ATTR: &str = "ignore";
pub(crate) const DEFAULT_ATTR: &str = "default";
/// Stores data about if the field should be visible via the Reflect and serialization interfaces
///
/// Note the relationship between serialization and reflection is such that a member must be reflected in order to be serialized.
/// In boolean logic this is described as: `is_serialized -> is_reflected`, this means we can reflect something without serializing it but not the other way round.
/// The `is_reflected` predicate is provided as `self.is_active()`
#[derive(Default, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ReflectIgnoreBehavior {
/// Don't ignore, appear to all systems
#[default]
None,
/// Ignore when serializing but not when reflecting
IgnoreSerialization,
/// Ignore both when serializing and reflecting
IgnoreAlways,
}
impl ReflectIgnoreBehavior {
/// Returns `true` if the ignoring behavior implies member is included in the reflection API, and false otherwise.
pub fn is_active(self) -> bool {
match self {
ReflectIgnoreBehavior::None | ReflectIgnoreBehavior::IgnoreSerialization => true,
ReflectIgnoreBehavior::IgnoreAlways => false,
}
}
/// The exact logical opposite of `self.is_active()` returns true iff this member is not part of the reflection API whatsoever (neither serialized nor reflected)
pub fn is_ignored(self) -> bool {
!self.is_active()
}
}
/// Controls how the default value is determined for a field.
#[derive(Default, Clone)]
pub(crate) enum DefaultBehavior {
/// Field is required.
#[default]
Required,
/// Field can be defaulted using `Default::default()`.
Default,
/// Field can be created using the given function name.
///
/// This assumes the function is in scope, is callable with zero arguments,
/// and returns the expected type.
Func(syn::ExprPath),
}
/// A container for attributes defined on a reflected type's field.
#[derive(Default, Clone)]
pub(crate) struct FieldAttributes {
/// Determines how this field should be ignored if at all.
pub ignore: ReflectIgnoreBehavior,
/// Sets the default behavior of this field.
pub default: DefaultBehavior,
}
impl FieldAttributes {
/// Parse all field attributes marked "reflect" (such as `#[reflect(ignore)]`).
pub fn parse_attributes(attrs: &[Attribute]) -> syn::Result<Self> {
let mut args = FieldAttributes::default();
attrs
.iter()
.filter_map(|attr| {
if !attr.path().is_ident(REFLECT_ATTRIBUTE_NAME) {
// Not a reflect attribute -> skip
return None;
}
let Meta::List(meta) = &attr.meta else {
return Some(syn::Error::new_spanned(attr, "expected meta list"));
};
// Parse all attributes inside the list, collecting any errors
meta.parse_args_with(terminated_parser(Token![,], |stream| {
args.parse_field_attribute(stream)
}))
.err()
})
.reduce(|mut acc, err| {
acc.combine(err);
acc
})
.map_or(Ok(args), Err)
}
/// Parses a single field attribute.
fn parse_field_attribute(&mut self, input: ParseStream) -> syn::Result<()> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::ignore) {
self.parse_ignore(input)
} else if lookahead.peek(kw::skip_serializing) {
self.parse_skip_serializing(input)
} else if lookahead.peek(kw::default) {
self.parse_default(input)
} else {
Err(lookahead.error())
}
}
/// Parse `ignore` attribute.
///
/// Examples:
/// - `#[reflect(ignore)]`
fn parse_ignore(&mut self, input: ParseStream) -> syn::Result<()> {
if self.ignore != ReflectIgnoreBehavior::None {
return Err(input.error(format!(
"only one of {:?} is allowed",
[IGNORE_ALL_ATTR, IGNORE_SERIALIZATION_ATTR]
)));
}
input.parse::<kw::ignore>()?;
self.ignore = ReflectIgnoreBehavior::IgnoreAlways;
Ok(())
}
/// Parse `skip_serializing` attribute.
///
/// Examples:
/// - `#[reflect(skip_serializing)]`
fn parse_skip_serializing(&mut self, input: ParseStream) -> syn::Result<()> {
if self.ignore != ReflectIgnoreBehavior::None {
return Err(input.error(format!(
"only one of {:?} is allowed",
[IGNORE_ALL_ATTR, IGNORE_SERIALIZATION_ATTR]
)));
}
input.parse::<kw::skip_serializing>()?;
self.ignore = ReflectIgnoreBehavior::IgnoreSerialization;
Ok(())
}
/// Parse `default` attribute.
///
/// Examples:
/// - `#[reflect(default)]`
/// - `#[reflect(default = "path::to::func")]`
fn parse_default(&mut self, input: ParseStream) -> syn::Result<()> {
if !matches!(self.default, DefaultBehavior::Required) {
return Err(input.error(format!("only one of {:?} is allowed", [DEFAULT_ATTR])));
}
input.parse::<kw::default>()?;
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
let lit = input.parse::<LitStr>()?;
self.default = DefaultBehavior::Func(lit.parse()?);
} else {
self.default = DefaultBehavior::Default;
}
Ok(())
}
}