diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 102e548d..c60934ce 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -54,15 +54,15 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - - name: Setup Rust with rustfmt + - name: Setup Rust (nightly) with rustfmt uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: stable + toolchain: nightly components: rustfmt override: true - - name: Run rustfmt - run: cargo +stable fmt --all -- --check + - name: Run rustfmt (nightly) + run: cargo +nightly fmt --all -- --check clippy: runs-on: ubuntu-latest diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 00000000..47fa835f --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,84 @@ +# Architecture + +This document explains how the `bevy_auto_plugin` ecosystem is structured and how macro expansion flows through the crates. + +## High-Level Flow + +1. **User-facing macros** are re-exported from `bevy_auto_plugin` (in [`src/lib.rs`](src/lib.rs) under the `prelude`). +2. When a user applies: + - `#[derive(AutoPlugin)]` → expansion handled by + [`bevy_auto_plugin_shared/src/__private/expand/derive/auto_plugin.rs`](crates/bevy_auto_plugin_shared/src/__private/expand/derive/auto_plugin.rs) + - `#[auto_plugin]` (attribute form) → expansion handled by + [`bevy_auto_plugin_shared/src/__private/expand/attr/auto_plugin.rs`](crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_plugin.rs) + - `#[auto_*( ... )]` attributes → routed to + [`bevy_auto_plugin_shared/src/__private/expand/attr/`](crates/bevy_auto_plugin_shared/src/__private/expand/attr/) + +--- + +## Attribute Types + +`auto_*` attributes fall into two categories: + +### Actions + +- Mutate the input token stream **and/or** append code that registers static slices used during plugin initialization. +- Parsing types are defined in: + [`bevy_auto_plugin_shared/src/macro_api/attributes/actions`](crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions) + +Each action attribute is modeled as: `ItemAttribute, R>` + +Where: + +| Generic | Meaning | +|---------|----------| +| `T` | The [`darling`](https://crates.io/crates/darling) parameter struct for the attribute | +| `P` | Plugin context: `WithPlugin` | +| `G` | Generics handling: `WithZeroGenerics`, `WithZeroOrSingleGenerics`, or `WithZeroOrManyGenerics` | +| `R` | Resolver restricting usage: `AllowAny`, `AllowFn`, or `AllowStructOrEnum` | + +notes: +- `Composed` can probably be merged into `ItemAttribute` but it's currently left open if we decide to add macros that aren't attached to the usual items (unlikely). + +Actions can implement traits for one or both of the following wrapper types: + +#### [`AppMutationEmitter`](crates/bevy_auto_plugin_shared/src/macro_api/emitters/app_mutation.rs) + +- Generates tokens for static slice registration and plugin mutation. +- **May also mutate the input tokens** (rare). + Example use case: allowing helper attributes on enum variants. + See: + [`bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_configure_system_set.rs`](crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_configure_system_set.rs) + +#### [`AttrEmitter`](crates/bevy_auto_plugin_shared/src/macro_api/emitters/attr.rs) + +- Responsible for serializing a parsed parameter struct back into attribute tokens (useful for rewrite expansions). + +--- + +### Rewrites + +- Expand into one or more other attributes. +- Only modify the **item’s attributes**, not the item body. +- Uses wrapper type [`AttrExpansionEmitter`](crates/bevy_auto_plugin_shared/src/macro_api/emitters/attr_expansion.rs) + +--- + +## Supporting Infrastructure + +### Custom Parsing + +Located in: +[`bevy_auto_plugin_shared/src/syntax/ast`](crates/bevy_auto_plugin_shared/src/syntax/ast) +Includes custom AST fragments, parsing helpers, and type definitions used across attributes. + +### Codegen Tokens + +All hygienic Bevy paths are centralized in: +[`bevy_auto_plugin_shared/src/codegen/tokens.rs`](crates/bevy_auto_plugin_shared/src/codegen/tokens.rs) + +- Crate alias resolution is handled via [`proc-macro-crate`](https://crates.io/crates/proc-macro-crate). + +### Token Preservation + +Macro expansions are designed to **preserve input tokens on errors**. +This ensures IDEs (like rust-analyzer) maintain valid syntax trees and prevent code “going red” on partially-written macro usage. diff --git a/Cargo.toml b/Cargo.toml index bbcd24d0..01eb7f78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ repository = "https://github.com/StrikeForceZero/bevy_auto_plugin" license = "MIT OR Apache-2.0" description = "Procedural attribute macros for Bevy apps that reduce boilerplate by automatically registering components, resources, events, states, and systems in your plugin's build function." edition = "2024" -rust-version = "1.88" publish = true [features] @@ -79,6 +78,7 @@ log = { workspace = true } wasm-bindgen-test = { workspace = true } internal_test_util = { workspace = true } internal_test_proc_macro = { workspace = true } +meta_merge = "0.1.1" [build-dependencies] rustc_version = "0.4" diff --git a/build.rs b/build.rs index e59de1e8..28a5cf2c 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,7 @@ -use rustc_version::{Channel, version_meta}; +use rustc_version::{ + Channel, + version_meta, +}; fn main() { println!("cargo:rustc-check-cfg=cfg(nightly)"); diff --git a/crates/bevy_auto_plugin_proc_macros/Cargo.toml b/crates/bevy_auto_plugin_proc_macros/Cargo.toml index dcf2559b..b1d2b869 100644 --- a/crates/bevy_auto_plugin_proc_macros/Cargo.toml +++ b/crates/bevy_auto_plugin_proc_macros/Cargo.toml @@ -18,13 +18,4 @@ log_plugin_build = ["bevy_auto_plugin_shared/log_plugin_build"] [dependencies] bevy_auto_plugin_shared = { workspace = true } -syn = { workspace = true } -proc-macro2 = { workspace = true } -quote = { workspace = true } -darling = { workspace = true } - -[dev-dependencies] -# needed for doc-tests -bevy = { workspace = true } -bevy_app = { workspace = true } -bevy_auto_plugin = { path = "../../.", default-features = false } \ No newline at end of file +proc-macro2 = { workspace = true } \ No newline at end of file diff --git a/crates/bevy_auto_plugin_proc_macros/src/lib.rs b/crates/bevy_auto_plugin_proc_macros/src/lib.rs index c508a707..8ee617ca 100644 --- a/crates/bevy_auto_plugin_proc_macros/src/lib.rs +++ b/crates/bevy_auto_plugin_proc_macros/src/lib.rs @@ -2,8 +2,7 @@ use bevy_auto_plugin_shared::__private::expand; use proc_macro::TokenStream as CompilerStream; use proc_macro2::TokenStream as MacroStream; -#[allow(dead_code)] -/// thin adapter converting between the compiler-level and proc_macro2 streams +/// Thin adapter converting between the compiler-level and proc_macro2 streams fn handle_attribute MacroStream>( handler: F, attr: CompilerStream, @@ -12,168 +11,147 @@ fn handle_attribute MacroStream>( handler(attr.into(), input.into()).into() } -/// Derives `AutoPlugin` which generates the initialization function that automatically registering types, events, and resources in the `App`. -#[doc = include_str!("../docs/proc_attributes/derive_auto_plugin.md")] +/// Derives `AutoPlugin`, which generates the initialization function that automatically registers types, events, resources, system, ..., etc. in the `App`. #[proc_macro_derive(AutoPlugin, attributes(auto_plugin))] pub fn derive_auto_plugin(input: CompilerStream) -> CompilerStream { expand::derive::auto_plugin::expand_derive_auto_plugin(input.into()).into() } -/// Attaches to a fn and injects a call to the initialization function that automatically registering types, events, and resources in the `App`. -#[doc = include_str!("../docs/proc_attributes/auto_plugin.md")] +/// Attaches to an `fn` and injects a call to the `AutoPlugin` initialization function that automatically registers types, events, resources, system, ..., etc. in the `App`. #[allow(unused_variables, unused_mut, unreachable_code)] #[proc_macro_attribute] pub fn auto_plugin(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_plugin::expand_auto_plugin, attr, input) } -/// Automatically registers a type with the Bevy `App`. -#[doc = include_str!("../docs/proc_attributes/auto_register_type.md")] +/// Automatically registers item deriving `Reflect` in `TypeRegistry` for the Bevy `App`. #[proc_macro_attribute] pub fn auto_register_type(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_register_type, attr, input) } -/// Automatically adds a message type to the Bevy `App`. -#[doc = include_str!("../docs/proc_attributes/auto_add_message.md")] +/// Automatically adds a `Message` to the Bevy `App`. #[proc_macro_attribute] pub fn auto_add_message(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_add_message, attr, input) } -/// Automatically configures a `SystemSet`. -#[doc = include_str!("../docs/proc_attributes/auto_configure_system_set.md")] +/// Automatically configures a `SystemSet` in the Bevy `App`. #[proc_macro_attribute] pub fn auto_configure_system_set(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_configure_system_set, attr, input) } -/// Automatically inserts a resource in the Bevy `App`. -#[doc = include_str!("../docs/proc_attributes/auto_init_resource.md")] +/// Automatically initializes a `Resource` in the Bevy `App`. #[proc_macro_attribute] pub fn auto_init_resource(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_init_resource, attr, input) } -/// Automatically inserts a resource in the Bevy `App`. -#[doc = include_str!("../docs/proc_attributes/auto_insert_resource.md")] +/// Automatically inserts a `Resource` in the Bevy `App`. #[proc_macro_attribute] pub fn auto_insert_resource(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_insert_resource, attr, input) } -/// Automatically initializes a State in the Bevy `App`. -#[doc = include_str!("../docs/proc_attributes/auto_init_state.md")] +/// Automatically initializes a `State` in the Bevy `App`. #[proc_macro_attribute] pub fn auto_init_state(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_init_state, attr, input) } -/// Automatically initializes a SubState in the Bevy `App`. -#[doc = include_str!("../docs/proc_attributes/auto_init_sub_state.md")] +/// Automatically initializes a `SubState` in the Bevy `App`. #[proc_macro_attribute] pub fn auto_init_sub_state(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_init_sub_state, attr, input) } /// Automatically registers a required component `Name` with a value using the concrete name of the item. -#[doc = include_str!("../docs/proc_attributes/auto_name.md")] #[proc_macro_attribute] pub fn auto_name(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_name, attr, input) } -/// Automatically registers item as States for bevy app. (See below for additional options) -#[doc = include_str!("../docs/proc_attributes/auto_register_state_type.md")] +/// Automatically registers item representing `States` in `TypeRegistry` for the Bevy `App`. #[proc_macro_attribute] pub fn auto_register_state_type(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_register_state_type, attr, input) } -/// Automatically adds the fn as a system for bevy app. (See below for additional options) -#[doc = include_str!("../docs/proc_attributes/auto_add_system.md")] +/// Automatically adds the `fn` as a `System` for the Bevy `App`. #[proc_macro_attribute] pub fn auto_add_system(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_add_system, attr, input) } -/// Automatically adds the fn as a proc_attributes observer to bevy app. (See below for additional options) -#[doc = include_str!("../docs/proc_attributes/auto_add_observer.md")] +/// Automatically adds the `fn` as a `Observer` to the Bevy `App`. #[proc_macro_attribute] pub fn auto_add_observer(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_add_observer, attr, input) } -/// Automatically adds the plugin as a sub-plugin to bevy app. (See below for additional options) -#[doc = include_str!("../docs/proc_attributes/auto_add_system.md")] +/// Automatically adds the `Plugin` as a sub-plugin to the Bevy `App`. #[proc_macro_attribute] pub fn auto_add_plugin(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_add_plugin, attr, input) } -/// Automatically registers item as Component for bevy app. (See below for additional options) -#[doc = include_str!("../docs/proc_attributes/auto_component.md")] +/// Automatically registers item as `Component` for the Bevy `App`. #[proc_macro_attribute] pub fn auto_component(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_component, attr, input) } -/// Automatically registers item as Resource for bevy app. (See below for additional options) -#[doc = include_str!("../docs/proc_attributes/auto_resource.md")] +/// Automatically registers item as `Resource` for the Bevy `App`. #[proc_macro_attribute] pub fn auto_resource(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_resource, attr, input) } -/// Automatically registers item as Event for bevy app. (See below for additional options) -#[doc = include_str!("../docs/proc_attributes/auto_event.md")] +/// Automatically registers item as `Event` for the Bevy `App`. #[proc_macro_attribute] pub fn auto_event(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_event, attr, input) } -/// Automatically registers item as Message for bevy app. (See below for additional options) -#[doc = include_str!("../docs/proc_attributes/auto_message.md")] +/// Automatically registers item as `Message` for the Bevy `App`. #[proc_macro_attribute] pub fn auto_message(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_message, attr, input) } -/// Automatically registers item as States for bevy app. (See below for additional options) -#[doc = include_str!("../docs/proc_attributes/auto_states.md")] +/// Automatically registers item as `States` for the Bevy `App`. #[proc_macro_attribute] pub fn auto_states(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_states, attr, input) } -/// Automatically adds the fn as a system for bevy app. (See below for additional options) -#[doc = include_str!("../docs/proc_attributes/auto_system.md")] +/// Automatically registers item as `SubStates` for the Bevy `App`. +#[proc_macro_attribute] +pub fn auto_sub_states(attr: CompilerStream, input: CompilerStream) -> CompilerStream { + handle_attribute(expand::attr::auto_sub_states, attr, input) +} + +/// Automatically adds the `fn` as a `System` for the Bevy `App`. #[proc_macro_attribute] pub fn auto_system(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_system, attr, input) } -/// Automatically adds proc_attributes observer to bevy app. (See below for additional options) -#[doc = include_str!("../docs/proc_attributes/auto_observer.md")] +/// Automatically adds `Observer` to the Bevy `App`. #[proc_macro_attribute] pub fn auto_observer(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_observer, attr, input) } -/// Automatically binds `plugin = _` to every auto_* attribute below it -#[doc = include_str!("../docs/proc_attributes/auto_run_on_build.md")] +/// Automatically runs the `fn` on build. #[proc_macro_attribute] pub fn auto_run_on_build(attr: CompilerStream, input: CompilerStream) -> CompilerStream { handle_attribute(expand::attr::auto_run_on_build, attr, input) } /// Automatically binds `plugin = _` to every auto_* attribute below it -#[doc = include_str!("../docs/proc_attributes/auto_bind_plugin.md")] #[proc_macro_attribute] pub fn auto_bind_plugin(attr: CompilerStream, input: CompilerStream) -> CompilerStream { - handle_attribute( - expand::attr::auto_bind_plugin::auto_bind_plugin_outer, - attr, - input, - ) + handle_attribute(expand::attr::auto_bind_plugin::auto_bind_plugin_outer, attr, input) } diff --git a/crates/bevy_auto_plugin_shared/src/__private/attribute.rs b/crates/bevy_auto_plugin_shared/src/__private/attribute.rs deleted file mode 100644 index e3aefe17..00000000 --- a/crates/bevy_auto_plugin_shared/src/__private/attribute.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::codegen::ExpandAttrs; -use crate::syntax::validated::non_empty_path::NonEmptyPath; -use proc_macro2::TokenStream as MacroStream; - -pub trait RewriteAttribute { - fn expand_args(&self, plugin: &NonEmptyPath) -> MacroStream; - fn expand_attrs(&self, plugin: &NonEmptyPath) -> ExpandAttrs; -} diff --git a/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs b/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs index 067304e6..6e976da0 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/auto_plugin_registry.rs @@ -1,11 +1,22 @@ // derived from Bevy Butler - MIT/Apache 2.0 https://github.com/TGRCdev/bevy-butler/blob/4eca26421d275134e0adc907e8c851bdcf10823a/bevy-butler/src/__private/plugin.rs -use proc_macro2::{Ident, TokenStream as MacroStream}; +use proc_macro2::{ + Ident, + TokenStream as MacroStream, +}; use quote::quote; -use std::any::{TypeId, type_name}; -use std::collections::HashMap; -use std::sync::LazyLock; -use syn::{ExprClosure, Path}; +use std::{ + any::{ + TypeId, + type_name, + }, + collections::HashMap, + sync::LazyLock, +}; +use syn::{ + ExprClosure, + Path, +}; pub use bevy_app; #[cfg(any(target_arch = "wasm32", feature = "inventory"))] @@ -34,10 +45,7 @@ pub static AUTO_PLUGIN_REGISTRY: LazyLock = LazyLock::new(|| let mut registry: HashMap> = HashMap::new(); for (ix, AutoPluginRegistryEntryFactory(type_factory, sys_factory)) in iter.enumerate() { - registry - .entry(type_factory()) - .or_default() - .push(*sys_factory); + registry.entry(type_factory()).or_default().push(*sys_factory); #[allow(unused_assignments)] { count = ix + 1; @@ -80,12 +88,9 @@ pub trait AutoPlugin: AutoPluginTypeId { } fn static_build(app: &mut bevy_app::App) { let type_id = ::type_id(); - AUTO_PLUGIN_REGISTRY - .get_entries(type_id) - .iter() - .for_each(|build_fn| { - build_fn(app); - }); + AUTO_PLUGIN_REGISTRY.get_entries(type_id).iter().for_each(|build_fn| { + build_fn(app); + }); } } @@ -102,10 +107,7 @@ pub struct AutoPluginRegistry(HashMap>); impl AutoPluginRegistry { pub(crate) fn get_entries(&'static self, marker: TypeId) -> &'static [BevyAppBuildFn] { - self.0 - .get(&marker) - .map(|v| v.as_slice()) - .unwrap_or_default() + self.0.get(&marker).map(|v| v.as_slice()).unwrap_or_default() } } @@ -125,15 +127,17 @@ pub fn _plugin_entry_block(static_ident: &Ident, plugin: &Path, expr: &ExprClosu #[macro_export] #[doc(hidden)] macro_rules! _plugin_entry { - ($static_ident:ident, $entry:expr) => { - #[::bevy_auto_plugin::__private::shared::__private::auto_plugin_registry::linkme::distributed_slice(::bevy_auto_plugin::__private::shared::__private::auto_plugin_registry::AUTO_PLUGINS)] - #[linkme(crate = ::bevy_auto_plugin::__private::shared::__private::auto_plugin_registry::linkme)] - #[allow(non_upper_case_globals)] - static $static_ident: - ::bevy_auto_plugin::__private::shared::__private::auto_plugin_registry::AutoPluginRegistryEntryFactory = - $entry; - }; - } + ($static_ident:ident, $entry:expr) => { + #[::bevy_auto_plugin::__private::shared::__private::auto_plugin_registry::linkme::distributed_slice( + ::bevy_auto_plugin::__private::shared::__private::auto_plugin_registry::AUTO_PLUGINS + )] + #[linkme(crate = ::bevy_auto_plugin::__private::shared::__private::auto_plugin_registry::linkme)] + #[allow(non_upper_case_globals)] + static $static_ident: + ::bevy_auto_plugin::__private::shared::__private::auto_plugin_registry::AutoPluginRegistryEntryFactory = + $entry; + }; +} #[cfg(any(target_arch = "wasm32", feature = "inventory"))] #[macro_export] diff --git a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/action.rs b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/action.rs new file mode 100644 index 00000000..01b48fe5 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/action.rs @@ -0,0 +1,43 @@ +use crate::{ + macro_api::prelude::*, + util::macros::ok_or_emit_with, +}; +use proc_macro2::TokenStream as MacroStream; +use quote::{ + ToTokens, + quote, +}; + +pub fn proc_attribute_outer(attr: MacroStream, input: MacroStream) -> MacroStream +where + T: ItemAttributeArgs + + ItemAttributeParse + + ItemAttributeInput + + ItemAttributeTarget + + ItemAttributeUniqueIdent + + ItemAttributeContext + + ItemAttributePlugin, + AppMutationEmitter: ToTokens + EmitAppMutationTokens, +{ + let args = ok_or_emit_with!( + T::from_attr_input_with_context(attr, input.clone(), Context::default()), + input + ); + let mut app_mut_emitter = AppMutationEmitter::from_args(args); + let processed_item = { + if let Err((tokens, err)) = app_mut_emitter.post_process_inner_item() { + let err = err.to_compile_error(); + return quote! { + #err + #tokens + }; + } + app_mut_emitter.args.input_item().to_token_stream() + }; + let after_item_tokens = + ok_or_emit_with!(app_mut_emitter.wrap_body(|body| quote! { #body }), processed_item); + quote! { + #processed_item + #after_item_tokens + } +} diff --git a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_bind_plugin.rs b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_bind_plugin.rs index 29f2b137..fd32ac81 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_bind_plugin.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_bind_plugin.rs @@ -1,35 +1,129 @@ -use crate::util::macros::compile_error_with; +use crate::{ + macro_api::prelude::*, + syntax::extensions::item::ItemAttrsExt, + util::macros::compile_error_with, +}; use proc_macro2::TokenStream as MacroStream; +use quote::ToTokens; +use syn::spanned::Spanned; -pub fn auto_bind_plugin_inner(attr: MacroStream, input: MacroStream) -> syn::Result { - use crate::__private::expand::attr; - use crate::macro_api::with_plugin::WithPlugin; - use crate::syntax::extensions::item::ItemAttrsExt; - use proc_macro2::Span; - use quote::quote; - use syn::{Item, parse2}; +pub fn auto_bind_plugin_inner( + attr: MacroStream, + input: MacroStream, + context: Context, +) -> syn::Result { + // TODO: need to determine correct flow to maintain input tokens for errors + let mut item_attribute = + ItemAttribute::, AllowAny>::from_attr_input( + attr, + input.clone(), + context, + )?; - let mut item = parse2::(input)?; - let args = parse2::>(attr)?; - let plugin = args.plugin; + let plugin_path = item_attribute.args.plugin(); + let item = item_attribute.input_item.ensure_ast_mut()?; + let mut attrs = item.take_attrs().map_err(|err| syn::Error::new(item.span(), err))?; - let Ok(mut attrs) = item.take_attrs() else { - return Err(syn::Error::new( - Span::call_site(), - "auto_bind_plugin supports only functions, structs, or enums", - )); - }; + attrs_inject_plugin_param(&mut attrs, plugin_path); - attr::inject_plugin_arg_for_attributes(&mut attrs, &plugin); + let Ok(_) = item.put_attrs(attrs) else { unreachable!() }; - let Ok(_) = item.put_attrs(attrs) else { - unreachable!() - }; - - Ok(quote! { #item }) + Ok(item.to_token_stream()) } pub fn auto_bind_plugin_outer(attr: MacroStream, input: MacroStream) -> MacroStream { let og_input = input.clone(); - auto_bind_plugin_inner(attr, input).unwrap_or_else(|err| compile_error_with!(err, og_input)) + auto_bind_plugin_inner(attr, input, Context::default()) + .unwrap_or_else(|err| compile_error_with!(err, og_input)) +} + +#[cfg(test)] +mod tests { + use super::*; + use internal_test_proc_macro::xtest; + use quote::quote; + #[xtest] + fn test_auto_bind_plugin_inner() { + let attr = quote!(plugin = Test); + let input = quote! { + #[derive(Component, Reflect)] + #[reflect(Component)] + #[auto_register_type] + #[some::path::auto_name] + struct FooComponent; + }; + assert_eq!( + auto_bind_plugin_outer(attr, input).to_string(), + quote! { + # [derive (Component , Reflect)] + # [reflect (Component)] + # [auto_register_type (plugin = Test)] + # [some::path::auto_name (plugin = Test)] + struct FooComponent ; + } + .to_string() + ); + } +} + +pub fn attrs_inject_plugin_param(attrs: &mut Vec, plugin: &syn::Path) { + use syn::Meta; + + for attr in attrs { + let last = attr.path().segments.last().map(|s| s.ident.to_string()).unwrap_or_default(); + + if !last.starts_with("auto_") { + continue; + } + + let already_has_plugin = match &attr.meta { + Meta::List(ml) => list_has_key(ml, "plugin"), + Meta::Path(_) => false, + Meta::NameValue(_) => true, + }; + + if already_has_plugin { + continue; + } + + attr_inject_plugin_param(attr, plugin); + } +} + +fn attr_inject_plugin_param(attr: &mut syn::Attribute, plugin: &syn::Path) { + use syn::{ + Meta, + parse_quote, + }; + match &attr.meta { + Meta::Path(path) => *attr = parse_quote!( #[#path(plugin = #plugin)] ), + Meta::List(ml) => { + let path = &ml.path; + let inner = &ml.tokens; + if inner.is_empty() { + *attr = parse_quote!( #[#path(plugin = #plugin)] ) + } else { + *attr = parse_quote!( #[#path(plugin = #plugin, #inner)] ) + } + } + _ => {} + } +} + +fn list_has_key(ml: &syn::MetaList, key: &str) -> bool { + use syn::{ + Meta, + Token, + parse::Parser, + punctuated::Punctuated, + }; + let parser = Punctuated::::parse_terminated; + match parser.parse2(ml.tokens.clone()) { + Ok(list) => list.iter().any(|m| match m { + Meta::NameValue(nv) => nv.path.is_ident(key), + Meta::List(ml2) => ml2.path.is_ident(key), + Meta::Path(p) => p.is_ident(key), + }), + Err(_) => false, + } } diff --git a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_plugin.rs b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_plugin.rs index 03bf43f0..2a0b85af 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_plugin.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/auto_plugin.rs @@ -1,14 +1,23 @@ -use crate::util::macros::{compile_error_with, ok_or_emit_with, parse_macro_input2_or_emit_with}; +use crate::util::macros::{ + compile_error_with, + ok_or_emit_with, + parse_macro_input2_or_emit_with, +}; use proc_macro2::TokenStream as MacroStream; use syn::ItemFn; pub fn expand_auto_plugin(attr: MacroStream, input: MacroStream) -> MacroStream { - use crate::macro_api::attributes::prelude::{AutoPluginFnArgs, resolve_app_param_name}; - use crate::syntax::analysis::fn_param::require_fn_param_mutable_reference; + use crate::{ + macro_api::prelude::*, + syntax::analysis::fn_param::require_fn_param_mutable_reference, + }; use proc_macro2::Ident; use quote::quote; - use syn::spanned::Spanned; - use syn::{FnArg, parse2}; + use syn::{ + FnArg, + parse2, + spanned::Spanned, + }; let og_input = input.clone(); let item = parse_macro_input2_or_emit_with!(input as ItemFn, og_input); let params = ok_or_emit_with!(parse2::(attr), og_input); @@ -28,11 +37,7 @@ pub fn expand_auto_plugin(attr: MacroStream, input: MacroStream) -> MacroStream .collect::>(); let self_arg = self_args.first(); - // TODO: use helper - let app_param_ident = ok_or_emit_with!( - resolve_app_param_name(&item, params.app_param.as_ref()), - og_input - ); + let app_param_ident = ok_or_emit_with!(resolve_app_param_name(&item), og_input); if let Err(err) = require_fn_param_mutable_reference(&item, app_param_ident, "bevy app") { return compile_error_with!(err, og_input); diff --git a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs index a0ea176d..05d79be5 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/mod.rs @@ -1,387 +1,56 @@ -use crate::__private::attribute::RewriteAttribute; -use crate::__private::auto_plugin_registry::_plugin_entry_block; -use crate::codegen::with_target_path::{ToTokensIterItem, WithTargetPath}; -use crate::macro_api::attributes::ItemAttributeArgs; -use crate::macro_api::attributes::prelude::*; -use crate::macro_api::with_plugin::{PluginBound, WithPlugin}; -use crate::syntax::diagnostic::kind::item_kind; -use darling::FromMeta; -use proc_macro2::{Ident, Span, TokenStream as MacroStream}; -use quote::{ToTokens, format_ident, quote}; -use syn::{Item, parse2}; -use thiserror::Error; +use crate::macro_api::prelude::*; +use proc_macro2::TokenStream as MacroStream; +pub mod action; pub mod auto_bind_plugin; pub mod auto_plugin; +pub mod rewrite; -pub enum ArgParser { - SynParse2, - Custom(CustomParser), -} - -impl ArgParser { - pub fn parse(&self, attr: MacroStream, input: &mut MacroStream) -> syn::Result { - match self { - ArgParser::SynParse2 => parse2::(attr), - ArgParser::Custom(custom) => custom.parse(attr, input), - } - } -} - -pub enum CustomParser { - AttrOnly(fn(MacroStream) -> syn::Result), - AttrInput(fn(MacroStream, &mut MacroStream) -> syn::Result), -} - -impl CustomParser { - pub fn parse(&self, attr: MacroStream, input: &mut MacroStream) -> syn::Result { - match self { - CustomParser::AttrOnly(f) => f(attr), - CustomParser::AttrInput(f) => f(attr, input), - } - } -} - -fn body( - body: impl Fn(MacroStream) -> MacroStream, -) -> impl Fn(&Ident, T, &Item) -> syn::Result { - move |ident, params, _item| { - let unique_ident = params.get_unique_ident(ident); - let plugin = params.plugin().clone(); - let with_target_path = WithTargetPath::from((ident.into(), params)); - let output = with_target_path - .to_tokens_iter_items() - .enumerate() - .map( - |( - ix, - ToTokensIterItem { - required_uses, - main_tokens: tokens, - }, - )| { - let body = body(tokens); - let expr: syn::ExprClosure = syn::parse_quote!(|app| { - #(#required_uses)* - #body - }); - // required for generics - let unique_ident = format_ident!("{unique_ident}_{ix}"); - let output = _plugin_entry_block(&unique_ident, &plugin, &expr); - Ok(output) - }, - ) - .collect::>()?; - assert!( - !output.is_empty(), - "No plugin entry points were generated for ident: {ident}" - ); - Ok(output) - } -} - -#[derive(Error, Debug)] -enum ProcAttributeError { - #[error("Failed to parse item: {0}")] - ParseItem(syn::Error), - #[error("Failed to resolve ident: {0}")] - ResolveIdent(syn::Error), - #[error("Failed to parse attr or input: {0}")] - ParseAttrOrInput(syn::Error), - #[error("Failed to generate body: {0}")] - GenerateBody(syn::Error), -} - -impl ProcAttributeError { - fn err(&self) -> &syn::Error { - match self { - ProcAttributeError::ParseItem(err) => err, - ProcAttributeError::ResolveIdent(err) => err, - ProcAttributeError::ParseAttrOrInput(err) => err, - ProcAttributeError::GenerateBody(err) => err, - } - } -} - -impl ToTokens for ProcAttributeError { - fn to_tokens(&self, tokens: &mut MacroStream) { - tokens.extend(self.err().to_compile_error()); - } -} - -fn proc_attribute_core( - attr: impl Into, - // this is the only way we can strip out non-derive based attribute helpers - input: &mut MacroStream, - resolve_ident: fn(&Item) -> syn::Result<&Ident>, - parse_attr_input: ArgParser, - body: F, -) -> Result -where - A: PluginBound, - F: FnOnce(&Ident, A, &Item) -> syn::Result, -{ - let attr = attr.into(); - - // need to clone input so we can pass through input untouched for optimal IDE support - let item: Item = parse2(input.clone()).map_err(ProcAttributeError::ParseItem)?; - - let ident = resolve_ident(&item) - .map_err(|err| { - let message = format!( - "Attribute macro is not allowed on {}: {err}", - item_kind(&item) - ); - // make sure the call_site span is used instead so the user knows what attribute caused the error - syn::Error::new(Span::call_site(), message) - }) - .map_err(ProcAttributeError::ResolveIdent)?; - - let args = parse_attr_input - .parse(attr, input) - .map_err(ProcAttributeError::ParseAttrOrInput)?; - - let output = body(ident, args, &item).map_err(ProcAttributeError::GenerateBody)?; - - Ok(quote! { - #input - #output - }) -} - -fn proc_attribute_inner( - attr: impl Into, - input: impl Into, - resolve_ident: fn(&Item) -> syn::Result<&Ident>, - parse_attr_input: ArgParser, - body: F, -) -> MacroStream -where - A: PluginBound, - F: FnOnce(&Ident, A, &Item) -> syn::Result, -{ - let attr = attr.into(); - let mut input = input.into(); - - match proc_attribute_core(attr, &mut input, resolve_ident, parse_attr_input, body) { - Ok(res) => res, - Err(err) => quote! { - #err - #input - }, - } -} - -/// Maps [`crate::syntax::analysis::item::IdentFromItemResult`] to [`syn::Result<&Ident>`] -fn resolve_item_ident(item: &Item) -> syn::Result<&Ident> { - T::Inner::resolve_item_ident(item).map_err(syn::Error::from) -} - -pub fn proc_attribute_outer( - attr: impl Into, - input: impl Into, -) -> MacroStream -where - T: PluginBound, -{ - proc_attribute_inner( - attr, - input, - resolve_item_ident::, - ArgParser::::SynParse2, - body(|input| quote! { app #input ; }), - ) -} - -pub fn proc_attribute_with_parser_outer( - attr: impl Into, - input: impl Into, - parser: ArgParser, -) -> MacroStream -where - T: PluginBound, -{ - proc_attribute_inner( - attr, - input, - resolve_item_ident::, - parser, - body(|input| quote! { app #input ; }), - ) -} - -pub fn proc_attribute_outer_call_fn( - attr: impl Into, - input: impl Into, -) -> MacroStream -where - T: PluginBound, -{ - proc_attribute_inner( - attr, - input, - resolve_item_ident::, - ArgParser::::SynParse2, - body(|input| quote! { #input(app) ; }), - ) -} - -fn proc_attribute_rewrite_inner( - attr: MacroStream, - input: MacroStream, -) -> syn::Result { - use crate::macro_api::with_plugin::WithPlugin; - let args = parse2::>(attr)?; - let args_ts = args.inner.expand_attrs(&args.plugin()); - Ok(quote! { - #args_ts - #input - }) -} - -pub fn proc_attribute_rewrite_outer( - attr: MacroStream, - input: MacroStream, -) -> MacroStream { - proc_attribute_rewrite_inner::(attr, input).unwrap_or_else(|err| err.to_compile_error()) -} - -pub fn inject_plugin_arg_for_attributes(attrs: &mut Vec, plugin: &syn::Path) { - use syn::Meta; - - for attr in attrs { - let last = attr - .path() - .segments - .last() - .map(|s| s.ident.to_string()) - .unwrap_or_default(); - - if !last.starts_with("auto_") { - continue; - } - - let already_has_plugin = match &attr.meta { - Meta::List(ml) => list_has_key(ml, "plugin"), - Meta::Path(_) => false, - Meta::NameValue(_) => true, - }; - - if already_has_plugin { - continue; - } - - inject_plugin_arg(attr, plugin); - } -} - -fn inject_plugin_arg(attr: &mut syn::Attribute, plugin: &syn::Path) { - use syn::Meta; - use syn::parse_quote; - match &attr.meta { - Meta::Path(path) => *attr = parse_quote!( #[#path(plugin = #plugin)] ), - Meta::List(ml) => { - let path = &ml.path; - let inner = &ml.tokens; - if inner.is_empty() { - *attr = parse_quote!( #[#path(plugin = #plugin)] ) - } else { - *attr = parse_quote!( #[#path(plugin = #plugin, #inner)] ) - } - } - _ => {} - } -} - -fn list_has_key(ml: &syn::MetaList, key: &str) -> bool { - use syn::Meta; - use syn::Token; - use syn::parse::Parser; - use syn::punctuated::Punctuated; - let parser = Punctuated::::parse_terminated; - match parser.parse2(ml.tokens.clone()) { - Ok(list) => list.iter().any(|m| match m { - Meta::NameValue(nv) => nv.path.is_ident(key), - Meta::List(ml2) => ml2.path.is_ident(key), - Meta::Path(p) => p.is_ident(key), - }), - Err(_) => false, - } -} - -macro_rules! gen_auto_attribute_outer_call_fns { +macro_rules! gen_action_outers { ( $( $fn:ident => $args:ty ),+ $(,)? ) => { - $( + $( #[inline] pub fn $fn(attr: MacroStream, input: MacroStream) -> MacroStream { - proc_attribute_outer_call_fn::>(attr, input) + action::proc_attribute_outer::<$args>(attr, input) } - )+ - }; -} - -macro_rules! gen_auto_attribute_outers { - // Each item: fn_name => ArgsTy [using ] - ( $( $fn:ident => $args:ty $(: parser = $parser:expr)? ),+ $(,)? ) => { - $( - gen_auto_attribute_outers!(@one $fn, $args $(, $parser)?); - )+ - }; - - // No parser - (@one $fn:ident, $args:ty) => { - #[inline] - pub fn $fn(attr: MacroStream, input: MacroStream) -> MacroStream { - proc_attribute_outer::>(attr, input) - } - }; - - // With parser - (@one $fn:ident, $args:ty, $parser:expr) => { - #[inline] - pub fn $fn(attr: MacroStream, input: MacroStream) -> MacroStream { - proc_attribute_with_parser_outer::>(attr, input, $parser) - } + )+ }; } -macro_rules! gen_auto_outers { +macro_rules! gen_rewrite_outers { ( $( $fn:ident => $args:ty ),+ $(,)? ) => { $( #[inline] pub fn $fn(attr: MacroStream, input: MacroStream) -> MacroStream { - proc_attribute_rewrite_outer::<$args>(attr, input) + rewrite::proc_attribute_rewrite_outer::<$args>(attr, input) } )+ }; } -gen_auto_attribute_outer_call_fns! { - auto_run_on_build => RunOnBuildArgs, -} - -gen_auto_attribute_outers! { - auto_register_type => RegisterTypeArgs, - auto_add_message => AddMessageArgs, - auto_init_resource => InitResourceArgs, - auto_insert_resource => InsertResourceArgs, - auto_init_state => InitStateArgs, - auto_init_sub_state => InitSubStateArgs, - auto_name => NameArgs, - auto_register_state_type => RegisterStateTypeArgs, - auto_add_system => AddSystemArgs, - auto_add_observer => AddObserverArgs, - auto_add_plugin => AddPluginArgs, - auto_configure_system_set => ConfigureSystemSetArgs: - parser = ArgParser::Custom(CustomParser::AttrInput(configure_system_set_args_from_attr_input)), -} - -gen_auto_outers! { - auto_component => ComponentArgs, - auto_resource => ResourceArgs, - auto_system => SystemArgs, - auto_event => EventArgs, - auto_message => MessageArgs, - auto_observer => ObserverArgs, - auto_states => StatesArgs, +gen_action_outers! { + auto_run_on_build => IaRunOnBuild, + auto_register_type => IaRegisterType, + auto_add_message => IaAddMessage, + auto_init_resource => IaInitResource, + auto_insert_resource => IaInsertResource, + auto_init_state => IaInitState, + auto_init_sub_state => IaInitSubState, + auto_name => IaName, + auto_register_state_type => IaRegisterStateType, + auto_add_system => IaAddSystem, + auto_add_observer => IaAddObserver, + auto_add_plugin => IaAddPlugin, + auto_configure_system_set => IaConfigureSystemSet, +} + +gen_rewrite_outers! { + auto_component => IaComponent, + auto_resource => IaResource, + auto_system => IaSystem, + auto_event => IaEvent, + auto_message => IaMessage, + auto_observer => IaObserver, + auto_states => IaState, + auto_sub_states => IaSubState, } diff --git a/crates/bevy_auto_plugin_shared/src/__private/expand/attr/rewrite.rs b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/rewrite.rs new file mode 100644 index 00000000..32e61183 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/__private/expand/attr/rewrite.rs @@ -0,0 +1,18 @@ +use crate::{ + macro_api::prelude::*, + util::macros::ok_or_emit_with, +}; +use proc_macro2::TokenStream as MacroStream; +use quote::ToTokens; + +pub fn proc_attribute_rewrite_outer(attr: MacroStream, input: MacroStream) -> MacroStream +where + AttrExpansionEmitter: ToTokens, + T: ItemAttributeArgs + ItemAttributeParse + ItemAttributeInput + ItemAttributeContext, +{ + let args = ok_or_emit_with!( + T::from_attr_input_with_context(attr, input.clone(), Context::default()), + input + ); + AttrExpansionEmitter::from_item_attribute(args).to_token_stream() +} diff --git a/crates/bevy_auto_plugin_shared/src/__private/expand/derive/auto_plugin.rs b/crates/bevy_auto_plugin_shared/src/__private/expand/derive/auto_plugin.rs index 3a703048..427c3b8c 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/expand/derive/auto_plugin.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/expand/derive/auto_plugin.rs @@ -3,8 +3,10 @@ use proc_macro2::TokenStream as MacroStream; use syn::spanned::Spanned; pub fn expand_derive_auto_plugin(input: MacroStream) -> MacroStream { - use crate::macro_api::derives::auto_plugin::AutoPluginDeriveArgs; - use crate::syntax::extensions::generics; + use crate::{ + macro_api::prelude::*, + syntax::extensions::generics, + }; use darling::FromDeriveInput; use quote::quote; use syn::DeriveInput; @@ -22,11 +24,7 @@ pub fn expand_derive_auto_plugin(input: MacroStream) -> MacroStream { let mut compile_warnings = quote! {}; #[allow(deprecated)] - if params - .auto_plugin - .impl_generic_auto_plugin_trait - .is_present() - { + if params.auto_plugin.impl_generic_auto_plugin_trait.is_present() { compile_warnings.extend( syn::Error::new( params.auto_plugin.impl_generic_auto_plugin_trait.span(), diff --git a/crates/bevy_auto_plugin_shared/src/__private/mod.rs b/crates/bevy_auto_plugin_shared/src/__private/mod.rs index 51af8759..6dbb24cc 100644 --- a/crates/bevy_auto_plugin_shared/src/__private/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/__private/mod.rs @@ -1,4 +1,3 @@ -pub mod attribute; pub mod auto_plugin_registry; pub mod expand; diff --git a/crates/bevy_auto_plugin_shared/src/codegen/emit.rs b/crates/bevy_auto_plugin_shared/src/codegen/emit.rs new file mode 100644 index 00000000..d7167210 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/codegen/emit.rs @@ -0,0 +1,208 @@ +#![allow(dead_code)] + +use proc_macro2::TokenStream; +use quote::ToTokens; + +pub type EmitResult = Result<(T, V), (T, E)>; +pub type EmitOkOnlyResult = Result<(T, V), E>; +pub type EmitErrOnlyResult = Result; + +pub struct Ctx(pub T, pub V); + +impl Ctx { + pub fn and_then(self, f: impl FnOnce(&T, V) -> Result) -> EmitResult { + let Ctx(ts, v) = self; + match f(&ts, v) { + Ok(u) => Ok((ts, u)), + Err(e) => Err((ts, e)), + } + } + pub fn and_then_mut( + self, + f: impl FnOnce(&mut T, V) -> Result, + ) -> EmitResult { + let Ctx(mut ts, v) = self; + match f(&mut ts, v) { + Ok(u) => Ok((ts, u)), + Err(e) => Err((ts, e)), + } + } +} + +impl Ctx { + pub fn start(ts: T) -> Ctx { + Ctx(ts, ()) + } +} + +pub trait WithTs { + type Output; + fn with_ts(self, ts: T) -> Self::Output; +} + +impl WithTs for Result { + type Output = EmitResult; + #[inline] + fn with_ts(self, ts: T) -> Self::Output { + match self { + Ok(v) => Ok((ts, v)), + Err(e) => Err((ts, e)), + } + } +} + +pub trait WithTsError { + type Output; + fn with_ts_on_err(self, ts: TokenStream) -> Self::Output; +} + +impl WithTsError for Result { + type Output = Result; + #[inline] + fn with_ts_on_err(self, ts: TokenStream) -> Self::Output { + self.map_err(|e| (ts.clone(), e)) + } +} + +pub trait EmitResultExt { + fn split(self) -> (T, Result); + fn join((ts, res): (T, Result)) -> EmitResult { + res.with_ts(ts) + } + fn into_tokens(self) -> TokenStream + where + T: ToTokens; + fn to_tokens(&self) -> TokenStream + where + T: ToTokens; + fn map_inner(self, f: impl FnOnce(V) -> U) -> EmitResult; + fn map_inner_err(self, f: impl FnOnce(E) -> U) -> EmitResult; + fn map_err_context(self, f: impl FnOnce(T) -> U) -> Result<(T, V), (U, E)>; + fn strip_err_context(self) -> EmitOkOnlyResult; + fn strip_ok_context(self) -> EmitErrOnlyResult; + fn and_then_ctx(self, f: impl FnOnce(&T, V) -> Result) -> EmitResult; + fn and_then_ctx_mut(self, f: impl FnOnce(&mut T, V) -> Result) -> EmitResult; +} + +impl EmitResultExt for EmitResult { + #[inline] + fn split(self) -> (T, Result) { + match self { + Ok((ts, v)) => (ts, Ok(v)), + Err((ts, e)) => (ts, Err(e)), + } + } + + #[inline] + fn into_tokens(self) -> TokenStream + where + T: ToTokens, + { + match self { + Ok((ts, _)) => ts.into_token_stream(), + Err((ts, _)) => ts.into_token_stream(), + } + } + + #[inline] + fn to_tokens(&self) -> TokenStream + where + T: ToTokens, + { + match self { + Ok((ts, _)) => ts.to_token_stream(), + Err((ts, _)) => ts.to_token_stream(), + } + } + + #[inline] + fn map_inner(self, f: impl FnOnce(V) -> U) -> EmitResult { + self.and_then(|(ts, v)| Ok(f(v)).with_ts(ts)) + } + + #[inline] + fn map_inner_err(self, f: impl FnOnce(E) -> U) -> EmitResult { + self.or_else(|(ts, e)| Err(f(e)).with_ts(ts)) + } + + #[inline] + fn map_err_context(self, f: impl FnOnce(T) -> U) -> Result<(T, V), (U, E)> { + self.map_err(|(ts, e)| (f(ts), e)) + } + + #[inline] + fn strip_err_context(self) -> EmitOkOnlyResult { + self.map_err(|(_, e)| e) + } + + #[inline] + fn strip_ok_context(self) -> EmitErrOnlyResult { + self.map(|(_, v)| v) + } + + #[inline] + fn and_then_ctx(self, f: impl FnOnce(&T, V) -> Result) -> EmitResult { + match self { + Ok((ts, v)) => Ctx(ts, v).and_then(f), + Err((ts, e)) => Err((ts, e)), + } + } + + #[inline] + fn and_then_ctx_mut(self, f: impl FnOnce(&mut T, V) -> Result) -> EmitResult { + match self { + Ok((ts, v)) => Ctx(ts, v).and_then_mut(f), + Err((ts, e)) => Err((ts, e)), + } + } +} + +pub type CtxOnly = Ctx; +impl From> for CtxOnly +where + T: ToTokens, +{ + fn from(value: Result<(T, V), (T, E)>) -> Self { + match value { + Ok((ts, _)) => Ctx(ts, ()), + Err((ts, _)) => Ctx(ts, ()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use internal_test_proc_macro::xtest; + use internal_test_util::assert_ts_eq; + use quote::quote; + + #[xtest] + fn result_split_and_map() { + // split ok + { + let ok: EmitResult = Ok((quote! { T }, 5)); + let (ts, res) = ok.split(); + assert_ts_eq!(&ts, quote! { T }); + assert_eq!(res.unwrap(), 5); + } + + // map + { + let ok: EmitResult = Ok((quote! { U }, "abc")); + let mapped = ok.map(|(ts, s)| (ts, s.len())); + let (ts, res) = mapped.split(); + assert_ts_eq!(ts, quote! { U }); + assert_eq!(res.unwrap(), 3); + } + + // split err + { + let err: EmitResult = + Err((quote! { E }, syn::Error::new(proc_macro2::Span::call_site(), "nope"))); + let (ts, res) = err.split(); + assert_ts_eq!(&ts, quote! { E }); + assert!(res.is_err()); + } + } +} diff --git a/crates/bevy_auto_plugin_shared/src/codegen/mod.rs b/crates/bevy_auto_plugin_shared/src/codegen/mod.rs index c11edc45..55fa70e7 100644 --- a/crates/bevy_auto_plugin_shared/src/codegen/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/codegen/mod.rs @@ -1,8 +1,11 @@ +pub mod emit; pub mod tokens; -pub mod with_target_path; use proc_macro2::TokenStream as MacroStream; -use quote::{ToTokens, quote}; +use quote::{ + ToTokens, + quote, +}; #[derive(Debug, Default, Clone)] pub struct ExpandAttrs { @@ -17,22 +20,6 @@ impl PartialEq for ExpandAttrs { } impl ExpandAttrs { - pub fn to_use_attr_ts_tuple(&self) -> (MacroStream, MacroStream) { - let use_items = &self.use_items; - let attrs = &self.attrs; - ( - quote! { - #(#use_items)* - }, - quote! { - #(#attrs)* - }, - ) - } - pub fn with(mut self, other: Self) -> Self { - self.append(other); - self - } pub fn append(&mut self, other: Self) { self.attrs.extend(other.attrs); self.use_items.extend(other.use_items); diff --git a/crates/bevy_auto_plugin_shared/src/codegen/tokens.rs b/crates/bevy_auto_plugin_shared/src/codegen/tokens.rs index 2bbae76c..372ce43a 100644 --- a/crates/bevy_auto_plugin_shared/src/codegen/tokens.rs +++ b/crates/bevy_auto_plugin_shared/src/codegen/tokens.rs @@ -1,60 +1,18 @@ -use crate::codegen::ExpandAttrs; -use crate::macro_api::attributes::AttributeIdent; -use crate::macro_api::attributes::prelude::*; -use crate::syntax::validated::non_empty_path::NonEmptyPath; -use proc_macro2::{Ident, TokenStream}; -use quote::{ToTokens, quote}; +use crate::{ + codegen::ExpandAttrs, + macro_api::prelude::*, + syntax::validated::non_empty_path::NonEmptyPath, +}; +use proc_macro2::{ + Ident, + TokenStream, +}; +use quote::{ + ToTokens, + quote, +}; use syn::parse_quote; -#[derive(Debug, Clone)] -pub struct ArgsWithPlugin { - pub plugin: NonEmptyPath, - pub args: T, -} - -impl ArgsWithPlugin -where - T: ArgsBackToTokens, -{ - pub fn new(plugin: NonEmptyPath, args: T) -> Self { - Self { plugin, args } - } - fn back_to_tokens(&self, tokens: &mut TokenStream) { - let macro_path = T::full_attribute_path(); - let inner_args = self.args.back_to_inner_arg_token_stream(); - let args = { - let plugin = &self.plugin; - let mut plugin_args = quote! { plugin = #plugin }; - if !inner_args.is_empty() { - plugin_args.extend(quote! { , #inner_args }); - } - plugin_args - }; - tokens.extend(quote! { - #[#macro_path(#args)] - }); - } -} - -// TODO: break this out so theres one for attributes and generic one for just args in general -pub trait ArgsBackToTokens: AttributeIdent { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream); - fn back_to_inner_arg_token_stream(&self) -> TokenStream { - let mut tokens = TokenStream::new(); - self.back_to_inner_arg_tokens(&mut tokens); - tokens - } -} - -impl ToTokens for ArgsWithPlugin -where - T: ArgsBackToTokens, -{ - fn to_tokens(&self, tokens: &mut TokenStream) { - self.back_to_tokens(tokens); - } -} - pub fn reflect<'a>(idents: impl IntoIterator) -> ExpandAttrs { let idents = idents.into_iter().collect::>(); let use_items = idents @@ -63,9 +21,6 @@ pub fn reflect<'a>(idents: impl IntoIterator) -> ExpandAttrs { .filter_map(|ident| { Some(match ident.to_string().as_str() { // Make the helper available for #[reflect(Component)] - // TODO: we could eliminate the need for globs if we pass the ident in - // then we can do `ReflectComponent as ReflectComponent$ident` - // #[reflect(Component$ident)] "Component" => crate::__private::paths::reflect::reflect_component_use_tokens(), // Make the helper available for #[reflect(Resource)] "Resource" => crate::__private::paths::reflect::reflect_resource_use_tokens(), @@ -127,6 +82,11 @@ pub fn derive_states_path() -> NonEmptyPath { parse_quote!(#states::state::States) } +pub fn derive_sub_states_path() -> NonEmptyPath { + let states = crate::__private::paths::state::root_path(); + parse_quote!(#states::state::SubStates) +} + pub fn derive_component<'a>( extra_items: impl IntoIterator, ) -> TokenStream { @@ -196,34 +156,64 @@ pub fn derive_states<'a>(extra_items: impl IntoIterator )], } } +pub fn derive_sub_states<'a>( + extra_items: impl IntoIterator, +) -> ExpandAttrs { + ExpandAttrs { + use_items: vec![crate::__private::paths::state::derive_use_tokens()], + attrs: vec![derive_from( + [ + vec![ + &derive_sub_states_path(), + &parse_quote!(Debug), + &parse_quote!(Default), + &parse_quote!(Clone), + &parse_quote!(PartialEq), + &parse_quote!(Eq), + &parse_quote!(Hash), + ], + extra_items.into_iter().collect::>(), + ] + .concat(), + )], + } +} pub fn derive_reflect() -> TokenStream { let derive_reflect_path = derive_reflect_path(); quote! { #[derive(#derive_reflect_path)] } } +// TODO: we forgot to add this back in, but tests are still passing? +#[allow(dead_code)] pub fn use_bevy_state_app_ext_states() -> syn::ItemUse { let root = crate::__private::paths::state::root_path(); parse_quote! { use #root::app::AppExtStates as _; } } -pub fn auto_register_type(plugin: NonEmptyPath, args: RegisterTypeArgs) -> TokenStream { - ArgsWithPlugin::new(plugin, args).to_token_stream() +pub fn auto_register_type(args: RegisterTypeAttrEmitter) -> TokenStream { + args.to_token_stream() +} +pub fn auto_register_state_type(args: RegisterStateTypeAttrEmitter) -> TokenStream { + args.to_token_stream() +} +pub fn auto_name(args: NameAttrEmitter) -> TokenStream { + args.to_token_stream() } -pub fn auto_name(plugin: NonEmptyPath, args: NameArgs) -> TokenStream { - ArgsWithPlugin::new(plugin, args).to_token_stream() +pub fn auto_init_resource(args: InitResourceAttrEmitter) -> TokenStream { + args.to_token_stream() } -pub fn auto_init_resource(plugin: NonEmptyPath, args: InitResourceArgs) -> TokenStream { - ArgsWithPlugin::new(plugin, args).to_token_stream() +pub fn auto_init_states(args: InitStateAttrEmitter) -> TokenStream { + args.to_token_stream() } -pub fn auto_init_states(plugin: NonEmptyPath, args: InitStateArgs) -> TokenStream { - ArgsWithPlugin::new(plugin, args).to_token_stream() +pub fn auto_init_sub_states(args: InitSubStateAttrEmitter) -> TokenStream { + args.to_token_stream() } -pub fn auto_add_systems(plugin: NonEmptyPath, args: AddSystemArgs) -> TokenStream { - ArgsWithPlugin::new(plugin, args).to_token_stream() +pub fn auto_add_systems(args: AddSystemAttrEmitter) -> TokenStream { + args.to_token_stream() } -pub fn auto_add_observer(plugin: NonEmptyPath, args: AddObserverArgs) -> TokenStream { - ArgsWithPlugin::new(plugin, args).to_token_stream() +pub fn auto_add_observer(args: AddObserverAttrEmitter) -> TokenStream { + args.to_token_stream() } -pub fn auto_add_message(plugin: NonEmptyPath, args: AddMessageArgs) -> TokenStream { - ArgsWithPlugin::new(plugin, args).to_token_stream() +pub fn auto_add_message(args: AddMessageAttrEmitter) -> TokenStream { + args.to_token_stream() } diff --git a/crates/bevy_auto_plugin_shared/src/codegen/with_target_path.rs b/crates/bevy_auto_plugin_shared/src/codegen/with_target_path.rs deleted file mode 100644 index 4251e1e3..00000000 --- a/crates/bevy_auto_plugin_shared/src/codegen/with_target_path.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::syntax::validated::concrete_path::{ - ConcreteTargetPath, ConcreteTargetPathWithGenericsCollection, -}; -use crate::syntax::validated::path_without_generics::{ - PathWithoutGenerics, TryFromPathWithoutGenericsError, -}; -use proc_macro2::TokenStream; -use syn::Path; - -pub trait ToTokensWithConcreteTargetPath: GenericsArgs { - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut TokenStream, - target: &ConcreteTargetPath, - ); - fn to_token_stream_with_concrete_target_path( - &self, - target: &ConcreteTargetPath, - ) -> TokenStream { - let mut tokens = TokenStream::new(); - self.to_tokens_with_concrete_target_path(&mut tokens, target); - tokens - } - fn required_use_statements(&self) -> Vec { - vec![] - } -} - -#[derive(Debug, Clone)] -pub struct ToTokensIterItem { - pub required_uses: Vec, - pub main_tokens: TokenStream, -} - -impl ToTokensIterItem { - #[cfg(test)] - pub fn into_main_tokens(self) -> TokenStream { - self.main_tokens - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct WithTargetPath { - target: PathWithoutGenerics, - pub inner: T, -} - -impl WithTargetPath { - pub fn target(&self) -> &PathWithoutGenerics { - &self.target - } - pub fn inner(&self) -> &T { - &self.inner - } -} - -impl WithTargetPath { - pub fn concrete_target_paths(&self) -> ConcreteTargetPathWithGenericsCollection { - self.clone().into() - } -} - -impl WithTargetPath { - /// used to reconstruct `|app| { #(#required_uses)* #main_tokens }` - pub fn to_tokens_iter_items(&self) -> impl Iterator { - self.concrete_target_paths() - .into_iter() - .map(|concrete_target_path| ToTokensIterItem { - required_uses: self.inner.required_use_statements(), - main_tokens: self - .inner - .to_token_stream_with_concrete_target_path(&concrete_target_path), - }) - } - #[cfg(test)] - /// used in tests checking the output of `ToTokensIterItem::main_tokens` - pub fn to_tokens_iter(&self) -> impl Iterator { - self.to_tokens_iter_items() - .map(ToTokensIterItem::into_main_tokens) - } -} - -impl From<(PathWithoutGenerics, T)> for WithTargetPath { - fn from(value: (PathWithoutGenerics, T)) -> Self { - let (target, inner) = value; - Self { target, inner } - } -} - -impl From> for (PathWithoutGenerics, T) { - fn from(value: WithTargetPath) -> (PathWithoutGenerics, T) { - (value.target, value.inner) - } -} - -impl TryFrom<(Path, T)> for WithTargetPath { - type Error = TryFromPathWithoutGenericsError; - fn try_from(value: (Path, T)) -> Result { - let (path, inner) = value; - let target = PathWithoutGenerics::try_from(path)?; - Ok(Self { target, inner }) - } -} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_message.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_message.rs index b2c518d5..2bccfc79 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_message.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_message.rs @@ -1,122 +1,40 @@ -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::{AttributeIdent, ItemAttributeArgs}; -use crate::syntax::analysis::item::{IdentFromItemResult, resolve_ident_from_struct_or_enum}; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::concrete_path::ConcreteTargetPath; +use crate::macro_api::prelude::*; use darling::FromMeta; use proc_macro2::TokenStream; -use quote::quote; -use syn::Item; +use quote::{ + ToTokens, + quote, +}; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] -pub struct AddMessageArgs { - #[darling(multiple)] - pub generics: Vec, -} +pub struct AddMessageArgs {} impl AttributeIdent for AddMessageArgs { const IDENT: &'static str = "auto_add_message"; } -impl ItemAttributeArgs for AddMessageArgs { - fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_> { - resolve_ident_from_struct_or_enum(item) - } -} - -impl GenericsArgs for AddMessageArgs { - fn type_lists(&self) -> &[TypeList] { - &self.generics - } -} - -impl ToTokensWithConcreteTargetPath for AddMessageArgs { - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut TokenStream, - target: &ConcreteTargetPath, - ) { - tokens.extend(quote! { - .add_message::< #target >() - }) - } -} - -impl ArgsBackToTokens for AddMessageArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - let mut args = vec![]; - if !self.generics().is_empty() { - args.extend(self.generics().to_attribute_arg_vec_tokens()); +pub type IaAddMessage = + ItemAttribute, AllowStructOrEnum>; +pub type AddMessageAppMutEmitter = AppMutationEmitter; +pub type AddMessageAttrEmitter = AttrEmitter; + +impl EmitAppMutationTokens for AddMessageAppMutEmitter { + fn to_app_mutation_tokens(&self, tokens: &mut TokenStream, app_param: &syn::Ident) { + for concrete_path in self.args.concrete_paths() { + tokens.extend(quote! { + #app_param.add_message::<#concrete_path>(); + }); } - tokens.extend(quote! { #(#args),* }); } } -#[cfg(test)] -mod tests { - use super::*; - use crate::codegen::with_target_path::WithTargetPath; - use internal_test_proc_macro::xtest; - use syn::{Path, parse_quote, parse2}; - - #[xtest] - fn test_to_tokens_no_generics() -> syn::Result<()> { - let args = parse2::(quote!())?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .add_message :: < FooTarget > () - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_single() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .add_message :: < FooTarget > () - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_multiple() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool), generics(bool, bool)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .add_message :: < FooTarget > () - } - .to_string() - ); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .add_message :: < FooTarget > () - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) +impl ToTokens for AddMessageAttrEmitter { + fn to_tokens(&self, tokens: &mut TokenStream) { + let args = self.args.args.extra_args(); + tokens.extend(quote! { + #(#args),* + }); + *tokens = self.wrap_as_attr(tokens); } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_observer.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_observer.rs index 1266c89e..5cda7dd4 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_observer.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_observer.rs @@ -1,124 +1,40 @@ -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::{AttributeIdent, ItemAttributeArgs}; -use crate::syntax::analysis::item::{IdentFromItemResult, resolve_ident_from_fn}; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::concrete_path::ConcreteTargetPath; +use crate::macro_api::prelude::*; use darling::FromMeta; use proc_macro2::TokenStream; -use quote::quote; -use syn::Item; +use quote::{ + ToTokens, + quote, +}; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] -pub struct AddObserverArgs { - #[darling(multiple)] - pub generics: Vec, -} +pub struct AddObserverArgs {} impl AttributeIdent for AddObserverArgs { const IDENT: &'static str = "auto_add_observer"; } -impl ItemAttributeArgs for AddObserverArgs { - fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_> { - resolve_ident_from_fn(item) - } -} - -impl GenericsArgs for AddObserverArgs { - const TURBOFISH: bool = true; - fn type_lists(&self) -> &[TypeList] { - &self.generics - } -} - -impl ToTokensWithConcreteTargetPath for AddObserverArgs { - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut TokenStream, - target: &ConcreteTargetPath, - ) { - tokens.extend(quote! { - .add_observer(#target) - }) - } -} - -impl ArgsBackToTokens for AddObserverArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - let mut args = vec![]; - if !self.generics().is_empty() { - args.extend(self.generics().to_attribute_arg_vec_tokens()); +pub type IaAddObserver = + ItemAttribute, AllowFn>; +pub type AddObserverAppMutEmitter = AppMutationEmitter; +pub type AddObserverAttrEmitter = AttrEmitter; + +impl EmitAppMutationTokens for AddObserverAppMutEmitter { + fn to_app_mutation_tokens(&self, tokens: &mut TokenStream, app_param: &syn::Ident) { + for concrete_path in self.args.concrete_paths() { + tokens.extend(quote! { + #app_param.add_observer( #concrete_path ); + }); } - tokens.extend(quote! { #(#args),* }); } } -#[cfg(test)] -mod tests { - use super::*; - use crate::codegen::with_target_path::WithTargetPath; - use internal_test_proc_macro::xtest; - use syn::{Path, parse_quote, parse2}; - - #[xtest] - fn test_to_tokens_no_generics() -> syn::Result<()> { - let args = parse2::(quote!())?; - let path: Path = parse_quote!(foo_target); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - - .add_observer(foo_target) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_single() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool)))?; - let path: Path = parse_quote!(foo_target); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .add_observer(foo_target::) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_multiple() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool), generics(bool, bool)))?; - let path: Path = parse_quote!(foo_target); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .add_observer(foo_target::) - } - .to_string() - ); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .add_observer(foo_target::) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) +impl ToTokens for AddObserverAttrEmitter { + fn to_tokens(&self, tokens: &mut TokenStream) { + let args = self.args.args.extra_args(); + tokens.extend(quote! { + #(#args),* + }); + *tokens = self.wrap_as_attr(tokens); } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_plugin.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_plugin.rs index c2fae94c..de881017 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_plugin.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_plugin.rs @@ -1,20 +1,17 @@ -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::{AttributeIdent, ItemAttributeArgs}; -use crate::syntax::analysis::item::{IdentFromItemResult, resolve_ident_from_struct_or_enum}; -use crate::syntax::ast::flag_or_expr::FlagOrExpr; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::concrete_path::ConcreteTargetPath; +use crate::{ + macro_api::prelude::*, + syntax::ast::flag_or_expr::FlagOrExpr, +}; use darling::FromMeta; use proc_macro2::TokenStream; -use quote::quote; -use syn::Item; +use quote::{ + ToTokens, + quote, +}; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] pub struct AddPluginArgs { - #[darling(default)] - pub generics: Option, #[darling(default)] pub init: FlagOrExpr, } @@ -23,158 +20,37 @@ impl AttributeIdent for AddPluginArgs { const IDENT: &'static str = "auto_add_plugin"; } -impl ItemAttributeArgs for AddPluginArgs { - fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_> { - resolve_ident_from_struct_or_enum(item) - } -} - -impl GenericsArgs for AddPluginArgs { - const TURBOFISH: bool = true; - fn type_lists(&self) -> &[TypeList] { - self.generics.as_slice() - } -} - -impl ToTokensWithConcreteTargetPath for AddPluginArgs { - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut TokenStream, - target: &ConcreteTargetPath, - ) { - if let Some(expr) = &self.init.expr { - tokens.extend(quote! { - .add_plugins({ let plugin: #target = #expr; plugin }) - }) - } else if self.init.present { - tokens.extend(quote! { - .add_plugins(#target::default()) - }) - } else { - tokens.extend(quote! { - .add_plugins(#target) - }) +pub type IaAddPlugin = + ItemAttribute, AllowStructOrEnum>; +pub type AddPluginAppMutEmitter = AppMutationEmitter; +pub type AddPluginAttrEmitter = AttrEmitter; + +impl EmitAppMutationTokens for AddPluginAppMutEmitter { + fn to_app_mutation_tokens(&self, tokens: &mut TokenStream, app_param: &syn::Ident) { + for concrete_path in self.args.concrete_paths() { + if let Some(expr) = &self.args.args.base.init.expr { + tokens.extend(quote! { + #app_param.add_plugins({ let plugin: #concrete_path = #expr; plugin }); + }); + } else if self.args.args.base.init.present { + tokens.extend(quote! { + #app_param.add_plugins(#concrete_path::default()); + }); + } else { + tokens.extend(quote! { + #app_param.add_plugins(#concrete_path); + }); + } } } } -#[cfg(test)] -mod tests { - use super::*; - use crate::codegen::with_target_path::WithTargetPath; - use internal_test_proc_macro::xtest; - use syn::{Path, parse_quote, parse2}; - - #[xtest] - fn test_to_tokens_no_generics_no_init() -> syn::Result<()> { - let args = parse2::(quote!())?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .add_plugins(FooTarget) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_no_generics_init_present() -> syn::Result<()> { - let args = parse2::(quote!(init))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .add_plugins(FooTarget :: default ()) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_no_generics_init() -> syn::Result<()> { - let args = parse2::(quote!(init(FooTarget(1, true))))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .add_plugins({ let plugin: FooTarget = FooTarget(1, true); plugin }) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_no_init() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .add_plugins(FooTarget :: < u8 , bool >) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_init_present() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool), init))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .add_plugins(FooTarget :: < u8 , bool > :: default ()) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_single() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool), init(FooTarget(1, true))))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .add_plugins({ let plugin: FooTarget :: < u8 , bool > = FooTarget(1, true); plugin }) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - #[should_panic(expected = "Duplicate field `generics`")] - fn test_to_tokens_multiple() { - parse2::(quote!( - generics(u8, bool), - generics(bool, bool), - plugin(FooTarget(1, true)) - )) - .unwrap(); +impl ToTokens for AddPluginAttrEmitter { + fn to_tokens(&self, tokens: &mut TokenStream) { + let args = self.args.args.extra_args(); + tokens.extend(quote! { + #(#args),* + }); + *tokens = self.wrap_as_attr(tokens); } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_system.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_system.rs index 6ac706ee..f1d2c053 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_system.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_add_system.rs @@ -1,21 +1,17 @@ -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::{AttributeIdent, ItemAttributeArgs}; -use crate::macro_api::schedule_config::ScheduleWithScheduleConfigArgs; -use crate::syntax::analysis::item::{IdentFromItemResult, resolve_ident_from_fn}; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::concrete_path::ConcreteTargetPath; +use crate::macro_api::{ + prelude::*, + schedule_config::ScheduleWithScheduleConfigArgs, +}; use darling::FromMeta; use proc_macro2::TokenStream; -use quote::{ToTokens, quote}; -use syn::Item; +use quote::{ + ToTokens, + quote, +}; #[derive(FromMeta, Debug, Clone, PartialEq, Hash)] #[darling(derive_syn_parse)] pub struct AddSystemArgs { - #[darling(multiple)] - pub generics: Vec, #[darling(flatten)] pub schedule_config: ScheduleWithScheduleConfigArgs, } @@ -24,40 +20,31 @@ impl AttributeIdent for AddSystemArgs { const IDENT: &'static str = "auto_add_system"; } -impl ItemAttributeArgs for AddSystemArgs { - fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_> { - resolve_ident_from_fn(item) - } -} +pub type IaAddSystem = + ItemAttribute, AllowFn>; +pub type AddSystemAppMutEmitter = AppMutationEmitter; +pub type AddSystemAttrEmitter = AttrEmitter; -impl GenericsArgs for AddSystemArgs { - const TURBOFISH: bool = true; - fn type_lists(&self) -> &[TypeList] { - &self.generics +impl EmitAppMutationTokens for AddSystemAppMutEmitter { + fn to_app_mutation_tokens(&self, tokens: &mut TokenStream, app_param: &syn::Ident) { + let schedule = &self.args.args.base.schedule_config.schedule; + let config_tokens = self.args.args.base.schedule_config.config.to_token_stream(); + for concrete_path in self.args.concrete_paths() { + tokens.extend(quote! { + #app_param . add_systems(#schedule, #concrete_path #config_tokens); + }); + } } } -impl ToTokensWithConcreteTargetPath for AddSystemArgs { - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut TokenStream, - target: &ConcreteTargetPath, - ) { - let schedule = &self.schedule_config.schedule; - let config_tokens = self.schedule_config.config.to_token_stream(); +impl ToTokens for AddSystemAttrEmitter { + fn to_tokens(&self, tokens: &mut TokenStream) { + let mut args = self.args.args.extra_args(); + // TODO: cleanup + args.extend(self.args.args.base.schedule_config.to_inner_arg_tokens_vec()); tokens.extend(quote! { - .add_systems(#schedule, #target #config_tokens) - }) - } -} - -impl ArgsBackToTokens for AddSystemArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - let mut args = vec![]; - if !self.generics().is_empty() { - args.extend(self.generics().to_attribute_arg_vec_tokens()); - } - args.extend(self.schedule_config.to_inner_arg_tokens_vec()); - tokens.extend(quote! { #(#args),* }); + #(#args),* + }); + *tokens = self.wrap_as_attr(tokens); } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_configure_system_set.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_configure_system_set.rs deleted file mode 100644 index cb440b99..00000000 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_configure_system_set.rs +++ /dev/null @@ -1,687 +0,0 @@ -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::{AttributeIdent, ItemAttributeArgs}; -use crate::macro_api::schedule_config::{ScheduleConfigArgs, ScheduleWithScheduleConfigArgs}; -use crate::macro_api::with_plugin::WithPlugin; -use crate::syntax::analysis::item::{IdentFromItemResult, resolve_ident_from_struct_or_enum}; -use crate::syntax::ast::flag::Flag; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::parse::item::ts_item_has_attr; -use crate::syntax::parse::scrub_helpers::{AttrSite, scrub_helpers_and_ident_with_filter}; -use crate::syntax::validated::concrete_path::ConcreteTargetPath; -use darling::FromMeta; -use proc_macro2::{Ident, TokenStream}; -use quote::{ToTokens, quote}; -use syn::spanned::Spanned; -use syn::{Attribute, Item, parse_quote, parse2}; - -const CONFIG_ATTR_NAME: &str = "auto_configure_system_set_config"; -const CHAIN_CONFLICT_ERR: &str = "`chain` and `chain_ignore_deferred` are mutually exclusive"; - -fn is_config_helper(attr: &Attribute) -> bool { - attr.path().is_ident(CONFIG_ATTR_NAME) -} - -#[derive(FromMeta, Default, Debug, Clone, PartialEq, Hash)] -#[darling(derive_syn_parse, and_then = Self::validate)] -pub struct ConfigureSystemSetArgsInnerEntry { - /// allows per schedule entry/variants to be configured - pub group: Option, - /// order in [`ConfigureSystemSetArgsInner::entries`] - pub order: Option, - /// .chain() - pub chain: Flag, - /// .chain_ignore_deferred() - pub chain_ignore_deferred: Flag, - #[darling(default)] - pub config: ScheduleConfigArgs, -} - -impl ConfigureSystemSetArgsInnerEntry { - fn validate(self) -> darling::Result { - if self.chain.is_present() && self.chain_ignore_deferred.is_present() { - let err = darling::Error::custom( - "`chain` and `chain_ignore_deferred` are mutually exclusive", - ) - .with_span(&self.chain_ignore_deferred.span()); - Err(err) - } else { - Ok(self) - } - } -} - -pub type ConfigureSystemSetArgsInnerEntries = Vec<(Ident, ConfigureSystemSetArgsInnerEntry)>; - -#[derive(FromMeta, Debug, Clone, PartialEq, Hash)] -#[darling(derive_syn_parse)] -/// for enums only -pub struct ConfigureSystemSetArgsInner { - #[darling(skip)] - pub entries: Vec<(Ident, ConfigureSystemSetArgsInnerEntry)>, -} - -#[derive(FromMeta, Debug, Clone, PartialEq, Hash)] -#[darling(derive_syn_parse, and_then = Self::validate)] -pub struct ConfigureSystemSetArgs { - #[darling(multiple, default)] - pub generics: Vec, - /// allows per schedule entry/variants to be configured - pub group: Option, - #[darling(flatten)] - pub schedule_config: ScheduleWithScheduleConfigArgs, - /// .chain() - pub chain: Flag, - /// .chain_ignore_deferred() - pub chain_ignore_deferred: Flag, - #[darling(skip)] - /// Some when enum, None when struct - pub inner: Option, - #[darling(skip, default)] - /// internal - used to track if this is the last attribute in the item to strip helpers - pub _strip_helpers: bool, -} - -impl ConfigureSystemSetArgs { - fn validate(self) -> darling::Result { - if self.chain.is_present() && self.chain_ignore_deferred.is_present() { - let err = darling::Error::custom(CHAIN_CONFLICT_ERR) - .with_span(&self.chain_ignore_deferred.span()); - Err(err) - } else { - Ok(self) - } - } -} - -impl AttributeIdent for ConfigureSystemSetArgs { - const IDENT: &'static str = "auto_configure_system_set"; -} - -impl ItemAttributeArgs for ConfigureSystemSetArgs { - fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_> { - resolve_ident_from_struct_or_enum(item) - } -} - -impl GenericsArgs for ConfigureSystemSetArgs { - const TURBOFISH: bool = true; - fn type_lists(&self) -> &[TypeList] { - &self.generics - } -} - -impl ToTokensWithConcreteTargetPath for ConfigureSystemSetArgs { - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut TokenStream, - target: &ConcreteTargetPath, - ) { - let schedule = &self.schedule_config.schedule; - let config_tokens = self.schedule_config.config.to_token_stream(); - if let Some(inner) = &self.inner { - // enum - let chained = if self.chain.is_present() { - quote! { .chain() } - } else if self.chain_ignore_deferred.is_present() { - quote! { .chain_ignore_deferred() } - } else { - quote! {} - }; - let mut entries = vec![]; - for (ident, entry) in inner.entries.iter() { - let chained = if entry.chain.is_present() { - quote! { .chain() } - } else if entry.chain_ignore_deferred.is_present() { - quote! { .chain_ignore_deferred() } - } else { - quote! {} - }; - let config_tokens = entry.config.to_token_stream(); - entries.push(quote! { - #target :: #ident #chained #config_tokens - }); - } - if !entries.is_empty() { - tokens.extend(quote! { - .configure_sets(#schedule, (#(#entries),*) #chained #config_tokens) - }); - } - } else { - // struct - if target.generics.is_empty() { - tokens.extend(quote! { - .configure_sets(#schedule, #target #config_tokens) - }); - } else { - // TODO: generics are kind of silly here - // but if someone does use them we'll assume its just a marker type - // that can be initialized via `Default::default()` - tokens.extend(quote! { - .configure_sets(#schedule, #target::default() #config_tokens) - }); - } - } - } -} - -impl ArgsBackToTokens for ConfigureSystemSetArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - let mut args = vec![]; - if !self.generics().is_empty() { - args.extend(self.generics().to_attribute_arg_vec_tokens()); - } - args.extend(self.schedule_config.to_inner_arg_tokens_vec()); - tokens.extend(quote! { #(#args),* }); - } -} - -/// HACK - if the item doesn't have anymore `auto_configure_system_set` - set a flag to strip out the helper attributes -fn check_strip_helpers(input: TokenStream, args: &mut ConfigureSystemSetArgs) -> syn::Result<()> { - if !ts_item_has_attr(input, &parse_quote!(auto_configure_system_set))? { - args._strip_helpers = true; - } - Ok(()) -} - -#[cfg(test)] -pub fn args_from_attr_input( - attr: TokenStream, - // this is the only way we can strip out non-derive based attribute helpers - input: &mut TokenStream, -) -> syn::Result { - let mut args = parse2::(attr)?; - check_strip_helpers(input.clone(), &mut args)?; - args_with_plugin_from_args_input(&mut args, input)?; - Ok(args) -} - -pub fn with_plugin_args_from_attr_input( - attr: TokenStream, - // this is the only way we can strip out non-derive based attribute helpers - input: &mut TokenStream, -) -> syn::Result> { - let mut args = parse2::>(attr)?; - check_strip_helpers(input.clone(), &mut args.inner)?; - args_with_plugin_from_args_input(&mut args.inner, input)?; - Ok(args) -} - -pub fn args_with_plugin_from_args_input( - args: &mut ConfigureSystemSetArgs, - // this is the only way we can strip out non-derive based attribute helpers - input: &mut TokenStream, -) -> syn::Result<()> { - fn resolve_ident(item: &Item) -> syn::Result<&Ident> { - resolve_ident_from_struct_or_enum(item) - .map_err(|err| syn::Error::new(item.span(), format!("failed to resolve ident: {err}"))) - } - - fn is_allowed_helper(site: &AttrSite, attr: &Attribute) -> bool { - is_config_helper(attr) && matches!(site, AttrSite::Variant { .. }) - } - - // 2) Scrub helpers from the item and resolve ident - let scrub = scrub_helpers_and_ident_with_filter( - input.clone(), - is_allowed_helper, - is_config_helper, - resolve_ident, - )?; - - // 3) - if args._strip_helpers { - // Always write back the scrubbed item to *input* so helpers never re-trigger and IDE has something to work with - scrub.write_back(input)?; - } else { - // Check if we have errors to print and if so, strip helpers from the item - // Otherwise, maintain helpers for the next attribute to process - scrub.write_if_errors_with_scrubbed_item(input)?; - } - - // 4) If it's a struct, there are no entries to compute - let data_enum = match scrub.item { - Item::Struct(_) => { - return Ok(()); - } - Item::Enum(ref en) => en, - _ => unreachable!("resolve_ident_from_struct_or_enum guarantees struct|enum"), - }; - - // 5) Collect per-variant configs: - // - per-group configs: HashMap - // - default (no-group) config: Option - use std::collections::HashMap; - - #[derive(Default)] - struct PerVariant { - /// group == None - default: Option, - /// group == Some(g) - per_group: HashMap, - } - - let mut variants_cfg: HashMap = HashMap::new(); - - // Require an observed order per variant, based on site enumeration position. - // Track the FIRST observed index we see for that variant. - let mut observed_order_by_variant: HashMap = HashMap::new(); - - // Parse all removed helper attrs (last-one-wins per key), but error on duplicates for the SAME key. - // Treat a second helper on the same (variant, group or None) as a hard error. - for (observed_index, site) in scrub.all_with_removed_attrs().into_iter().enumerate() { - if let AttrSite::Variant { variant } = &site.site { - observed_order_by_variant - .entry(variant.clone()) - .or_insert(observed_index); - - for attr in &site.attrs { - // Only care about our helper - if !is_config_helper(attr) { - continue; - } - - let mut entry = ConfigureSystemSetArgsInnerEntry::from_meta(&attr.meta)?; - - // If order wasn't provided on the helper, set it to the first observed index for this variant - if entry.order.is_none() { - entry.order = Some( - *observed_order_by_variant - .get(variant) - .unwrap_or(&observed_index), - ); - } - - let bucket = variants_cfg.entry(variant.clone()).or_default(); - match &entry.group { - Some(g) => { - if bucket.per_group.contains_key(g) { - return Err(syn::Error::new( - attr.span(), - format!("duplicate helper for variant `{variant}` and group `{g}`",), - )); - } - bucket.per_group.insert(g.clone(), entry); - } - None => { - if bucket.default.is_some() { - return Err(syn::Error::new( - attr.span(), - format!( - "duplicate default (no-group) helper for variant `{variant}`", - ), - )); - } - bucket.default = Some(entry); - } - } - } - } - } - - // 6) Walk the enum variants and assemble entries using fallback rules: - // chosen = per_group[outer_group] - // || (default with group overwritten to outer_group) - // || synthesized default (group = outer_group, order = observed) - let outer_group = args.group.clone(); - let mut entries: ConfigureSystemSetArgsInnerEntries = - Vec::with_capacity(data_enum.variants.len()); - - for v in &data_enum.variants { - let v_ident = v.ident.clone(); - - let prev_observed_len = observed_order_by_variant.len(); - // Find observed order for this variant (if we never saw the site, use sequential fallback) - let observed = *observed_order_by_variant - .entry(v_ident.clone()) - .or_insert_with(|| prev_observed_len); - - let chosen_entry = (|| { - let bucket = variants_cfg.get(&v_ident); - - // prefer explicit group match - if let (Some(g), Some(b)) = (&outer_group, bucket) - && let Some(found) = b.per_group.get(g) - { - return Some(found.clone()); - } - - // else use the default helper but override its group to the outer group - if let Some(b) = bucket - && let Some(mut def) = b.default.clone() - { - def.group = outer_group.clone(); - if def.order.is_none() { - def.order = Some(observed); - } - return Some(def); - } - - // else synthesize a default entry - Some(ConfigureSystemSetArgsInnerEntry { - group: outer_group.clone(), - order: Some(observed), - ..Default::default() - }) - })() - .expect("infallible"); - - entries.push((v_ident, chosen_entry)); - } - - // 7) Sort & filter - entries.sort_by_key(|(_, e)| e.order.unwrap_or_default()); - entries.retain(|(_, e)| { - // same group as outer group - match (&e.group, &outer_group) { - (Some(g), Some(og)) => g == og, - // If either side is None, keep it (acts as "applies to any") - _ => true, - } - }); - - // 8) Store into args and return - args.inner = Some(ConfigureSystemSetArgsInner { entries }); - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::codegen::with_target_path::WithTargetPath; - use internal_test_proc_macro::xtest; - use syn::{Path, parse_quote, parse2}; - - fn ident_and_args_from_attr_input( - attr: TokenStream, - mut input: TokenStream, - ) -> Result<(Ident, ConfigureSystemSetArgs), syn::Error> { - let item = parse2::(input.clone())?; - let ident = resolve_ident_from_struct_or_enum(&item).map_err(|err| { - syn::Error::new(item.span(), format!("failed to resolve ident: {err}")) - })?; - args_from_attr_input(attr, &mut input).and_then(|args| Ok((ident.clone(), args))) - } - - fn ident_and_args_from_attr_mut_input( - attr: TokenStream, - input: &mut TokenStream, - ) -> Result<(Ident, ConfigureSystemSetArgs), syn::Error> { - let item = parse2::(input.clone())?; - let ident = resolve_ident_from_struct_or_enum(&item).map_err(|err| { - syn::Error::new(item.span(), format!("failed to resolve ident: {err}")) - })?; - args_from_attr_input(attr, input).and_then(|args| Ok((ident.clone(), args))) - } - - mod test_struct { - use super::*; - #[xtest] - fn test_to_tokens_no_generics() -> syn::Result<()> { - let args = parse2::(quote!(schedule = Update))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - . configure_sets (Update , FooTarget) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_single() -> syn::Result<()> { - let args = - parse2::(quote!(schedule = Update, generics(u8, bool)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - . configure_sets (Update , FooTarget :: ::default() ) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_multiple() -> syn::Result<()> { - let args = parse2::(quote!( - schedule = Update, - generics(u8, bool), - generics(bool, bool) - ))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - . configure_sets (Update , FooTarget :: ::default() ) - } - .to_string() - ); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - . configure_sets (Update , FooTarget :: ::default() ) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - } - - mod test_enum { - use super::*; - use crate::syntax::validated::path_without_generics::PathWithoutGenerics; - - #[xtest] - fn test_to_tokens_no_generics() -> syn::Result<()> { - let (ident, args) = ident_and_args_from_attr_input( - quote!(schedule = Update), - quote! { - enum Foo { - A, - B, - } - }, - )?; - let args_with_target = - WithTargetPath::try_from((PathWithoutGenerics::from(ident), args)).unwrap(); // infallible - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - . configure_sets (Update , ( Foo::A , Foo::B )) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_single() -> syn::Result<()> { - let (ident, args) = ident_and_args_from_attr_input( - quote!(schedule = Update), - quote! { - enum Foo { - A, - B, - } - }, - )?; - let args_with_target = - WithTargetPath::try_from((PathWithoutGenerics::from(ident), args)).unwrap(); // infallible - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - . configure_sets (Update , ( Foo::A , Foo::B )) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_multiple() -> syn::Result<()> { - let (ident, args) = ident_and_args_from_attr_input( - quote!(schedule = Update), - quote! { - enum Foo { - #[auto_configure_system_set_config(group = A)] - A, - #[auto_configure_system_set_config(group = B)] - B, - } - }, - )?; - let args_with_target = - WithTargetPath::try_from((PathWithoutGenerics::from(ident), args)).unwrap(); // infallible - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - . configure_sets (Update , ( Foo::A , Foo::B )) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - // TODO: more tests - - #[xtest] - fn test_helper() -> syn::Result<()> { - let (_ident, args) = ident_and_args_from_attr_input( - quote! { - group = A, - schedule = Update, - }, - quote! { - enum Foo { - #[auto_configure_system_set_config(group = A)] - A, - #[auto_configure_system_set_config(group = A)] - B, - } - }, - )?; - assert_eq!( - args, - ConfigureSystemSetArgs { - schedule_config: ScheduleWithScheduleConfigArgs { - schedule: parse_quote!(Update), - config: ScheduleConfigArgs::default(), - }, - inner: Some(ConfigureSystemSetArgsInner { - entries: vec![ - ( - parse_quote!(A), - ConfigureSystemSetArgsInnerEntry { - group: parse_quote!(A), - order: Some(1), - ..Default::default() - } - ), - ( - parse_quote!(B), - ConfigureSystemSetArgsInnerEntry { - group: parse_quote!(A), - order: Some(2), - ..Default::default() - } - ) - ] - }), - group: Some(parse_quote!(A)), - generics: vec![], - chain: Flag::from(false), - chain_ignore_deferred: Flag::from(false), - _strip_helpers: true, - } - ); - Ok(()) - } - - #[xtest] - fn test_helper_removed_from_ts() { - let mut input = quote! { - enum Foo { - #[auto_configure_system_set_config(group = A)] - A, - #[auto_configure_system_set_config(group = A)] - B, - } - }; - let _ = ident_and_args_from_attr_mut_input( - quote! { - group = A, - schedule = Update, - }, - &mut input, - ); - assert_eq!( - input.to_string(), - quote! { - enum Foo { - A, - B, - } - } - .to_string() - ); - } - - #[xtest] - fn test_conflict_outer() { - let mut input = quote! { - enum Foo { - A, - } - }; - let res = ident_and_args_from_attr_mut_input( - quote! { - schedule = Update, - chain, chain_ignore_deferred - }, - &mut input, - ) - .map_err(|e| e.to_string()); - - assert_eq!(res, Err(CHAIN_CONFLICT_ERR.into())); - } - - #[xtest] - fn test_conflict_entries() { - let mut input = quote! { - enum Foo { - #[auto_configure_system_set_config(chain, chain_ignore_deferred)] - A, - } - }; - let res = ident_and_args_from_attr_mut_input( - quote! { - schedule = Update, - }, - &mut input, - ) - .map_err(|e| e.to_string()); - - assert_eq!(res, Err(CHAIN_CONFLICT_ERR.into())); - } - } -} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_configure_system_set/inflate.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_configure_system_set/inflate.rs new file mode 100644 index 00000000..e38937a8 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_configure_system_set/inflate.rs @@ -0,0 +1,376 @@ +use super::CONFIG_ATTR_NAME; +use crate::{ + codegen::emit::{ + Ctx, + EmitResult, + EmitResultExt, + }, + macro_api::{ + attributes::actions::auto_configure_system_set, + input_item::InputItem, + prelude::{ + ConfigureSystemSetArgs, + ConfigureSystemSetArgsInner, + ConfigureSystemSetArgsInnerEntries, + ConfigureSystemSetArgsInnerEntry, + }, + }, + syntax::{ + analysis::item::item_has_attr, + parse::scrub_helpers::{ + AttrSite, + ScrubOutcome, + scrub_helpers_with_filter, + }, + }, +}; +use darling::FromMeta; +use proc_macro2::{ + Ident, + Span, + TokenStream, +}; +use quote::{ + ToTokens, + quote, +}; +use std::collections::HashMap; +use syn::{ + Attribute, + Item, + Path, + parse_quote, + spanned::Spanned, +}; + +pub fn output( + args: &ConfigureSystemSetArgs, + app_param: &Ident, + concrete_path: &Path, + has_generics: bool, +) -> TokenStream { + let mut tokens = TokenStream::new(); + let schedule = &args.schedule_config.schedule; + let config_tokens = args.schedule_config.config.to_token_stream(); + if let Some(inner) = &args.inner { + // enum + let chained = if args.chain.is_present() { + quote! { .chain() } + } else if args.chain_ignore_deferred.is_present() { + quote! { .chain_ignore_deferred() } + } else { + quote! {} + }; + let mut entries = vec![]; + for (ident, entry) in inner.entries.iter() { + let chained = if entry.chain.is_present() { + quote! { .chain() } + } else if entry.chain_ignore_deferred.is_present() { + quote! { .chain_ignore_deferred() } + } else { + quote! {} + }; + let config_tokens = entry.config.to_token_stream(); + entries.push(quote! { + #concrete_path :: #ident #chained #config_tokens + }); + } + if !entries.is_empty() { + tokens.extend(quote! { + #app_param.configure_sets(#schedule, (#(#entries),*) #chained #config_tokens); + }); + } + } else { + // struct + if has_generics { + // TODO: generics are kind of silly here + // but if someone does use them we'll assume its just a marker type + // that can be initialized via `Default::default()` + tokens.extend(quote! { + #app_param.configure_sets(#schedule, #concrete_path::default() #config_tokens); + }); + } else { + tokens.extend(quote! { + #app_param.configure_sets(#schedule, #concrete_path #config_tokens); + }); + } + } + tokens +} + +pub fn check_strip_helpers(item: &Item) -> bool { + !item_has_attr(item, &parse_quote!(auto_configure_system_set)) +} + +#[cfg(test)] +pub fn args_from_attr_input( + attr: TokenStream, + input: TokenStream, +) -> EmitResult { + let input_item = + InputItem::from_ts_validated(input.clone()).map_err(|e| (InputItem::Tokens(input), e))?; + let (input_item, args) = + Ctx::start(input_item).and_then(|_, _| syn::parse2::(attr))?; + inflate_args_from_input_item(args, &input_item) +} + +/// Type alias for the per-variant configuration data structure +type VariantConfigMap = HashMap; +/// Type alias for tracking observed order of variants +type ObservedOrderMap = HashMap; + +/// Holds configuration data for a specific enum variant +#[derive(Default)] +struct PerVariant { + /// Default configuration (when group == None) + default: Option, + /// Group-specific configurations (when group == Some(g)) + per_group: HashMap, +} + +/// Processes an input TokenStream and constructs ConfigureSystemSetArgs +pub fn inflate_args_from_input_item( + mut args: ConfigureSystemSetArgs, + input_item: &InputItem, +) -> EmitResult { + Ctx::start(input_item.clone()) + .and_then(|input_item, _| { + // Process the input helper attributes + process_helper_attributes(input_item) + }) + .and_then_ctx_mut(|input_item, scrubbed_outcome| { + let should_strip_helpers = check_strip_helpers(&scrubbed_outcome.original_item); + let maybe_scrubbed_input_item_tokens = if should_strip_helpers { + scrubbed_outcome.to_scrubbed_item_tokens() + } else { + scrubbed_outcome.to_original_item_tokens() + }; + *input_item = InputItem::Tokens(maybe_scrubbed_input_item_tokens); + // TODO: so this is kind of ugly. we check if we need to re-emit the scrubbed item. + // but in doing so we are required to include any errors which breaks syn parsing. + // and our context required `InputItem` instead of just a `TokenStream`. + // so we check if the scrubbed item has errors and if so break out early. + match input_item.has_compiler_errors() { + Ok(has_compiler_errors) => { + if has_compiler_errors { + return Err( + // TODO: we need a ui test to make sure the other errors are still emitted with their spans + syn::Error::new(Span::call_site(), format!("invalid {CONFIG_ATTR_NAME}s:")), + ); + } + } + Err(err) => { + return Err( + syn::Error::new( + Span::call_site(), + format!("bevy_auto_plugin bug - please open an issue with a reproduction case: {err:?}"), + ), + ); + } + } + Ok(scrubbed_outcome) + }) + .and_then_ctx(|_maybe_scrubbed_input_item, scrub_outcome| { + // Handle based on item type + match &scrub_outcome.original_item { + Item::Enum(item_enum) => { + // Process enum variants for the specified group to populate args + let inner = process_enum_variants_for_group( + args.group.as_ref(), + item_enum, + &scrub_outcome, + )?; + args.inner = inner; + Ok(args) + } + Item::Struct(_) => Ok(args), + _ => { + let err = syn::Error::new(Span::call_site(), "Only struct or enum supported"); + Err(err) + } + } + }) +} + +/// Scrubs helper attributes from input and prepares for processing +fn process_helper_attributes( + input_item: impl AsRef, +) -> Result { + fn is_allowed_helper(site: &AttrSite, attr: &Attribute) -> bool { + auto_configure_system_set::is_config_helper(attr) + && matches!(site, AttrSite::Variant { .. }) + } + + scrub_helpers_with_filter( + input_item, + is_allowed_helper, + auto_configure_system_set::is_config_helper, + ) +} + +/// Processes enum variants to extract and organize configuration entries +fn process_enum_variants_for_group( + group: Option<&Ident>, + item_enum: &syn::ItemEnum, + scrubbed_item: &ScrubOutcome, +) -> Result, syn::Error> { + // Parse and collect configuration data from variant attributes + let (variant_configs, observed_order) = collect_variant_configs(scrubbed_item)?; + + // Create entries based on variant configs and apply fallback rules + let entries = create_variant_entries(item_enum, &variant_configs, &observed_order, group); + + Ok(Some(ConfigureSystemSetArgsInner { entries })) +} + +/// Collects configuration data from variant attributes +fn collect_variant_configs( + scrubbed_item: &ScrubOutcome, +) -> Result<(VariantConfigMap, ObservedOrderMap), syn::Error> { + let mut variants_cfg: VariantConfigMap = HashMap::new(); + let mut observed_order_by_variant: ObservedOrderMap = HashMap::new(); + + for (observed_index, site) in scrubbed_item.all_with_removed_attrs().into_iter().enumerate() { + if let AttrSite::Variant { variant } = &site.site { + observed_order_by_variant.entry(variant.clone()).or_insert(observed_index); + process_variant_attributes(variant, &site.attrs, observed_index, &mut variants_cfg)?; + } + } + + Ok((variants_cfg, observed_order_by_variant)) +} + +/// Processes attributes for a specific variant +fn process_variant_attributes( + variant: &Ident, + attrs: &[Attribute], + observed_index: usize, + variants_cfg: &mut VariantConfigMap, +) -> syn::Result<()> { + for attr in attrs { + // Skip non-config helpers + if !auto_configure_system_set::is_config_helper(attr) { + continue; + } + + // Parse entry from attribute metadata + let mut entry = ConfigureSystemSetArgsInnerEntry::from_meta(&attr.meta)?; + + // Set default order if not specified + if entry.order.is_none() { + entry.order = Some(observed_index); + } + + let bucket = variants_cfg.entry(variant.clone()).or_default(); + + // Store entry based on group + match &entry.group { + Some(g) => { + if bucket.per_group.contains_key(g) { + return Err(syn::Error::new( + attr.span(), + format!("duplicate helper for variant `{variant}` and group `{g}`"), + )); + } + bucket.per_group.insert(g.clone(), entry); + } + None => { + if bucket.default.is_some() { + return Err(syn::Error::new( + attr.span(), + format!("duplicate default (no-group) helper for variant `{variant}`"), + )); + } + bucket.default = Some(entry); + } + } + } + + Ok(()) +} + +/// Creates entries for each variant based on configs and fallback rules +fn create_variant_entries( + item_enum: &syn::ItemEnum, + variants_cfg: &VariantConfigMap, + observed_order: &ObservedOrderMap, + outer_group: Option<&Ident>, +) -> ConfigureSystemSetArgsInnerEntries { + let mut entries = Vec::with_capacity(item_enum.variants.len()); + let mut next_fallback_index = observed_order.len(); + + for variant in &item_enum.variants { + let variant_ident = variant.ident.clone(); + + // Find or create observed order for this variant + let observed_index = observed_order.get(&variant_ident).copied().unwrap_or_else(|| { + let idx = next_fallback_index; + next_fallback_index += 1; + idx + }); + + // Apply fallback rules to select entry + let entry = + select_entry_with_fallback(&variant_ident, variants_cfg, observed_index, outer_group); + + entries.push((variant_ident, entry)); + } + + // Sort by order and filter by group + sort_and_filter_entries(entries, outer_group) +} + +/// Selects the appropriate entry for a variant based on fallback rules +fn select_entry_with_fallback( + variant_ident: &Ident, + variants_cfg: &VariantConfigMap, + observed_index: usize, + outer_group: Option<&Ident>, +) -> ConfigureSystemSetArgsInnerEntry { + let bucket = variants_cfg.get(variant_ident); + + // First try: explicit group match + if let (Some(g), Some(b)) = (outer_group, bucket) + && let Some(found) = b.per_group.get(g) + { + return found.clone(); + } + + // Second try: default entry with group override + if let Some(b) = bucket + && let Some(mut default_entry) = b.default.clone() + { + default_entry.group = outer_group.cloned(); + if default_entry.order.is_none() { + default_entry.order = Some(observed_index); + } + return default_entry; + } + + // Fallback: synthesize default entry + ConfigureSystemSetArgsInnerEntry { + group: outer_group.cloned(), + order: Some(observed_index), + ..Default::default() + } +} + +/// Sorts entries by order and filters by group +fn sort_and_filter_entries( + mut entries: ConfigureSystemSetArgsInnerEntries, + outer_group: Option<&Ident>, +) -> ConfigureSystemSetArgsInnerEntries { + // Sort by order + entries.sort_by_key(|(_, entry)| entry.order.unwrap_or_default()); + + // Filter by group + entries.retain(|(_, entry)| { + match (&entry.group, outer_group) { + (Some(g), Some(og)) => g == og, + // If either side is None, keep it (acts as "applies to any") + _ => true, + } + }); + + entries +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_configure_system_set/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_configure_system_set/mod.rs new file mode 100644 index 00000000..45fc40cd --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_configure_system_set/mod.rs @@ -0,0 +1,469 @@ +mod inflate; + +use crate::{ + macro_api::{ + prelude::*, + schedule_config::{ + ScheduleConfigArgs, + ScheduleWithScheduleConfigArgs, + }, + }, + syntax::ast::flag::Flag, +}; +use darling::FromMeta; +use proc_macro2::{ + Ident, + TokenStream, +}; +use quote::{ + ToTokens, + quote, +}; +use syn::Attribute; + +const CONFIG_ATTR_NAME: &str = "auto_configure_system_set_config"; +const CHAIN_CONFLICT_ERR: &str = "`chain` and `chain_ignore_deferred` are mutually exclusive"; + +fn is_config_helper(attr: &Attribute) -> bool { + attr.path().is_ident(CONFIG_ATTR_NAME) +} + +#[derive(FromMeta, Default, Debug, Clone, PartialEq, Hash)] +#[darling(derive_syn_parse, and_then = Self::validate)] +pub struct ConfigureSystemSetArgsInnerEntry { + /// allows per schedule entry/variants to be configured + pub group: Option, + /// order in [`ConfigureSystemSetArgsInner::entries`] + pub order: Option, + /// .chain() + pub chain: Flag, + /// .chain_ignore_deferred() + pub chain_ignore_deferred: Flag, + #[darling(default)] + pub config: ScheduleConfigArgs, +} + +impl ConfigureSystemSetArgsInnerEntry { + fn validate(self) -> darling::Result { + if self.chain.is_present() && self.chain_ignore_deferred.is_present() { + let err = darling::Error::custom( + "`chain` and `chain_ignore_deferred` are mutually exclusive", + ) + .with_span(&self.chain_ignore_deferred.span()); + Err(err) + } else { + Ok(self) + } + } +} + +pub type ConfigureSystemSetArgsInnerEntries = Vec<(Ident, ConfigureSystemSetArgsInnerEntry)>; + +#[derive(FromMeta, Debug, Clone, PartialEq, Hash)] +#[darling(derive_syn_parse)] +/// for enums only +pub struct ConfigureSystemSetArgsInner { + #[darling(skip)] + pub entries: Vec<(Ident, ConfigureSystemSetArgsInnerEntry)>, +} + +#[derive(FromMeta, Debug, Clone, PartialEq, Hash)] +#[darling(derive_syn_parse, and_then = Self::validate)] +pub struct ConfigureSystemSetArgs { + /// allows per schedule entry/variants to be configured + pub group: Option, + #[darling(flatten)] + pub schedule_config: ScheduleWithScheduleConfigArgs, + /// .chain() + pub chain: Flag, + /// .chain_ignore_deferred() + pub chain_ignore_deferred: Flag, + #[darling(skip)] + /// Some when enum, None when struct + pub inner: Option, +} + +impl ConfigureSystemSetArgs { + fn validate(self) -> darling::Result { + if self.chain.is_present() && self.chain_ignore_deferred.is_present() { + let err = darling::Error::custom(CHAIN_CONFLICT_ERR) + .with_span(&self.chain_ignore_deferred.span()); + Err(err) + } else { + Ok(self) + } + } +} + +impl AttributeIdent for ConfigureSystemSetArgs { + const IDENT: &'static str = "auto_configure_system_set"; +} + +pub type IaConfigureSystemSet = ItemAttribute< + Composed, + AllowStructOrEnum, +>; +pub type ConfigureSystemSetAppMutEmitter = AppMutationEmitter; +pub type ConfigureSystemSetAttrEmitter = AttrEmitter; + +impl EmitAppMutationTokens for ConfigureSystemSetAppMutEmitter { + fn post_process_inner_item(&mut self) -> Result<(), (InputItem, syn::Error)> { + let input_item = &mut self.args.input_item; + let args = &mut self.args.args.base; + if args.inner.is_none() { + let (maybe_scrubbed_input_item, inflated_args) = + inflate::inflate_args_from_input_item(args.clone(), input_item)?; + *args = inflated_args; + *input_item = maybe_scrubbed_input_item; + } + Ok(()) + } + fn to_app_mutation_tokens(&self, tokens: &mut TokenStream, app_param: &syn::Ident) { + let args = self.args.args.base.clone(); + // checks if we need to inflate args + let inflated_args = if args.inner.is_none() { + let args = args.clone(); + let input_item = &self.args.input_item; + match inflate::inflate_args_from_input_item(args, input_item) { + Ok((_, args)) => args, + Err((_, err)) => { + tokens.extend(err.to_compile_error()); + return; + } + } + } else { + args + }; + let generics = self.args.args.generics(); + for concrete_path in self.args.concrete_paths() { + tokens.extend(inflate::output( + &inflated_args, + app_param, + &concrete_path, + !generics.is_empty(), + )); + } + } +} + +impl ToTokens for ConfigureSystemSetAttrEmitter { + fn to_tokens(&self, tokens: &mut TokenStream) { + let mut args = self.args.args.extra_args(); + args.extend(self.args.args.base.schedule_config.to_inner_arg_tokens_vec()); + tokens.extend(quote! { + #(#args),* + }); + *tokens = self.wrap_as_attr(tokens); + todo!("not implemented"); + // TODO: would need to modify item to inject helper attributes + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + codegen::emit::EmitResultExt, + macro_api::attributes::actions::auto_configure_system_set::inflate::args_from_attr_input, + }; + use internal_test_proc_macro::xtest; + use syn::{ + Path, + parse_quote, + parse2, + }; + + fn ident_and_args_from_attr_input( + attr: TokenStream, + input: TokenStream, + ) -> Result<(Ident, ConfigureSystemSetArgs), syn::Error> { + let mut input_item = InputItem::Tokens(input.clone()); + let ident = input_item.ident()?.clone(); + let (_, inflated_args) = args_from_attr_input(attr, input.clone()).strip_err_context()?; + Ok((ident, inflated_args)) + } + + mod test_struct { + use super::*; + use crate::macro_api::attributes::actions::auto_configure_system_set::inflate::output; + use quote::quote; + + #[xtest] + fn test_to_tokens_no_generics() -> syn::Result<()> { + let args = parse2::(quote!(schedule = Update))?; + let path: Path = parse_quote!(FooTarget); + let app_param = parse_quote!(app); + let tokens = output(&args, &app_param, &path, false); + assert_eq!( + tokens.to_string(), + quote! { + #app_param . configure_sets (Update , FooTarget); + } + .to_string() + ); + Ok(()) + } + + #[xtest] + fn test_to_tokens_single() -> syn::Result<()> { + let args = parse2::(quote!(schedule = Update))?; + let app_param = parse_quote!(app); + let tokens = output(&args, &app_param, &parse_quote!(FooTarget::), true); + assert_eq!( + tokens.to_string(), + quote! { + #app_param . configure_sets (Update , FooTarget :: ::default() ); + } + .to_string() + ); + Ok(()) + } + + #[xtest] + fn test_to_tokens_multiple() -> syn::Result<()> { + let args = parse2::(quote!(schedule = Update))?; + let app_param = parse_quote!(app); + let tokens = output(&args, &app_param, &parse_quote!(FooTarget::), true); + assert_eq!( + tokens.to_string(), + quote! { + #app_param . configure_sets (Update , FooTarget :: ::default() ); + } + .to_string() + ); + let tokens = output(&args, &app_param, &parse_quote!(FooTarget::), true); + assert_eq!( + tokens.to_string(), + quote! { + #app_param . configure_sets (Update , FooTarget :: ::default() ); + } + .to_string() + ); + Ok(()) + } + } + + mod test_enum { + use super::*; + use crate::macro_api::attributes::actions::auto_configure_system_set::inflate::{ + check_strip_helpers, + output, + }; + use internal_test_util::{ + assert_ts_eq, + token_stream::token_string, + }; + use quote::quote; + + #[xtest] + fn test_to_tokens_no_generics() -> syn::Result<()> { + let (ident, args) = ident_and_args_from_attr_input( + quote!(schedule = Update), + quote! { + enum Foo { + A, + B, + } + }, + )?; + let app_param = parse_quote!(app); + let output = output(&args, &app_param, &(ident.into()), false); + assert_eq!( + output.to_string(), + quote! { + #app_param . configure_sets (Update , ( Foo::A , Foo::B )); + } + .to_string() + ); + Ok(()) + } + + #[xtest] + fn test_to_tokens_single() -> syn::Result<()> { + let (ident, args) = ident_and_args_from_attr_input( + quote!(schedule = Update), + quote! { + enum Foo { + A, + B, + } + }, + )?; + let app_param = parse_quote!(app); + let tokens = output(&args, &app_param, &(ident.into()), false); + assert_eq!( + tokens.to_string(), + quote! { + #app_param . configure_sets (Update , ( Foo::A , Foo::B )); + } + .to_string() + ); + Ok(()) + } + + #[xtest] + fn test_to_tokens_multiple() -> syn::Result<()> { + let (ident, args) = ident_and_args_from_attr_input( + quote!(schedule = Update), + quote! { + enum Foo { + #[auto_configure_system_set_config(group = A)] + A, + #[auto_configure_system_set_config(group = B)] + B, + } + }, + )?; + let app_param = parse_quote!(app); + let tokens = output(&args, &app_param, &(ident.into()), false); + assert_eq!( + tokens.to_string(), + quote! { + #app_param . configure_sets (Update , ( Foo::A , Foo::B )); + } + .to_string() + ); + Ok(()) + } + + // TODO: more tests + + #[xtest] + fn test_helper() -> syn::Result<()> { + let (_, inflated_args) = args_from_attr_input( + quote! { + group = A, + schedule = Update, + }, + quote! { + enum Foo { + #[auto_configure_system_set_config(group = A)] + A, + #[auto_configure_system_set_config(group = A)] + B, + } + }, + ) + .strip_err_context()?; + assert_eq!( + inflated_args, + ConfigureSystemSetArgs { + schedule_config: ScheduleWithScheduleConfigArgs { + schedule: parse_quote!(Update), + config: ScheduleConfigArgs::default(), + }, + inner: Some(ConfigureSystemSetArgsInner { + entries: vec![ + ( + parse_quote!(A), + ConfigureSystemSetArgsInnerEntry { + group: parse_quote!(A), + order: Some(1), + ..Default::default() + } + ), + ( + parse_quote!(B), + ConfigureSystemSetArgsInnerEntry { + group: parse_quote!(A), + order: Some(2), + ..Default::default() + } + ) + ] + }), + group: Some(parse_quote!(A)), + chain: Flag::from(false), + chain_ignore_deferred: Flag::from(false), + } + ); + Ok(()) + } + + #[xtest] + fn test_helper_removed_from_ts() -> syn::Result<()> { + let attr = quote! { + group = A, + schedule = Update, + }; + let input = quote! { + enum Foo { + #[auto_configure_system_set_config(group = A)] + A, + #[auto_configure_system_set_config(group = A)] + B, + } + }; + let (scrubbed_input, _) = args_from_attr_input(attr, input).strip_err_context()?; + assert_ts_eq!( + scrubbed_input, + quote! { + enum Foo { + A, + B, + } + } + ); + Ok(()) + } + + #[xtest] + fn test_conflict_outer() { + let res = ident_and_args_from_attr_input( + quote! { + schedule = Update, + chain, chain_ignore_deferred + }, + quote! { + enum Foo { + A, + } + }, + ) + .map_err(|e| e.to_string()); + + assert_eq!(res, Err(CHAIN_CONFLICT_ERR.into())); + } + + #[xtest] + fn test_conflict_entries() { + let res = ident_and_args_from_attr_input( + quote! { + schedule = Update, + }, + quote! { + enum Foo { + #[auto_configure_system_set_config(chain, chain_ignore_deferred)] + A, + } + }, + ) + .map_err(|e| e.to_string()); + + assert_eq!(res, Err(CHAIN_CONFLICT_ERR.into())); + } + + #[xtest] + fn test_dont_strip_helpers_early() -> Result<(), (String, syn::Error)> { + let attr = quote! { group = A, schedule = Update }; + let input = quote! { + #[auto_configure_system_set(group = B, schedule = FixedUpdate)] + enum Foo { + #[auto_configure_system_set_config(group = A, config(run_if = always))] + #[auto_configure_system_set_config(group = B, config(run_if = never))] + A, + } + }; + let mut input_item = + InputItem::from_ts_validated(input.clone()).expect("should be valid item"); + let item = input_item.ensure_ast().expect("should be valid item ast"); + assert!(!check_strip_helpers(item)); + let (tokens, _) = + args_from_attr_input(attr, input.clone()).map_err_context(token_string)?; + + assert_ts_eq!(tokens, input); + + Ok(()) + } + } +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_resource.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_resource.rs index 6046865f..1b7fde56 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_resource.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_resource.rs @@ -1,118 +1,48 @@ -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::{AttributeIdent, ItemAttributeArgs}; -use crate::syntax::analysis::item::{IdentFromItemResult, resolve_ident_from_struct_or_enum}; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::concrete_path::ConcreteTargetPath; +use crate::macro_api::{ + emitters::app_mutation::{ + AppMutationEmitter, + EmitAppMutationTokens, + }, + prelude::*, +}; use darling::FromMeta; use proc_macro2::TokenStream; -use quote::quote; -use syn::Item; +use quote::{ + ToTokens, + quote, +}; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] -pub struct InitResourceArgs { - #[darling(multiple)] - pub generics: Vec, -} +pub struct InitResourceArgs {} impl AttributeIdent for InitResourceArgs { const IDENT: &'static str = "auto_init_resource"; } -impl ItemAttributeArgs for InitResourceArgs { - fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_> { - resolve_ident_from_struct_or_enum(item) - } -} - -impl GenericsArgs for InitResourceArgs { - fn type_lists(&self) -> &[TypeList] { - &self.generics +pub type IaInitResource = ItemAttribute< + Composed, + AllowStructOrEnum, +>; +pub type InitResourceAppMutEmitter = AppMutationEmitter; +pub type InitResourceAttrEmitter = AttrEmitter; + +impl EmitAppMutationTokens for InitResourceAppMutEmitter { + fn to_app_mutation_tokens(&self, tokens: &mut TokenStream, app_param: &syn::Ident) { + for concrete_path in self.args.concrete_paths() { + tokens.extend(quote! { + #app_param.init_resource::<#concrete_path>(); + }); + } } } -impl ToTokensWithConcreteTargetPath for InitResourceArgs { - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut TokenStream, - target: &ConcreteTargetPath, - ) { +impl ToTokens for InitResourceAttrEmitter { + fn to_tokens(&self, tokens: &mut TokenStream) { + let args = self.args.args.extra_args(); tokens.extend(quote! { - .init_resource::< #target >() - }) - } -} - -impl ArgsBackToTokens for InitResourceArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(self.generics().to_attribute_arg_tokens()); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::codegen::with_target_path::WithTargetPath; - use internal_test_proc_macro::xtest; - use syn::{Path, parse_quote, parse2}; - - #[xtest] - fn test_to_tokens_no_generics() -> syn::Result<()> { - let args = parse2::(quote!())?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .init_resource :: < FooTarget > () - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_single() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .init_resource :: < FooTarget > () - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_multiple() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool), generics(bool, bool)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .init_resource :: < FooTarget > () - } - .to_string() - ); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .init_resource :: < FooTarget > () - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) + #(#args),* + }); + *tokens = self.wrap_as_attr(tokens); } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_state.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_state.rs index e8c8b995..f9a4aa3b 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_state.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_state.rs @@ -1,15 +1,10 @@ -use crate::codegen::tokens; -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::{AttributeIdent, ItemAttributeArgs}; -use crate::syntax::analysis::item::{IdentFromItemResult, resolve_ident_from_struct_or_enum}; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::concrete_path::ConcreteTargetPath; +use crate::macro_api::prelude::*; use darling::FromMeta; use proc_macro2::TokenStream; -use quote::quote; -use syn::Item; +use quote::{ + ToTokens, + quote, +}; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] @@ -19,60 +14,26 @@ impl AttributeIdent for InitStateArgs { const IDENT: &'static str = "auto_init_state"; } -impl ItemAttributeArgs for InitStateArgs { - fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_> { - resolve_ident_from_struct_or_enum(item) - } -} - -impl GenericsArgs for InitStateArgs { - fn type_lists(&self) -> &[TypeList] { - &[] - } -} +pub type IaInitState = + ItemAttribute, AllowStructOrEnum>; +pub type InitStateAppMutEmitter = AppMutationEmitter; +pub type InitStateAttrEmitter = AttrEmitter; -impl ToTokensWithConcreteTargetPath for InitStateArgs { - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut TokenStream, - target: &ConcreteTargetPath, - ) { +impl EmitAppMutationTokens for InitStateAppMutEmitter { + fn to_app_mutation_tokens(&self, tokens: &mut TokenStream, app_param: &syn::Ident) { + let target = &self.args.target; tokens.extend(quote! { - .init_state::< #target >() - }) - } - fn required_use_statements(&self) -> Vec { - vec![tokens::use_bevy_state_app_ext_states()] + #app_param.init_state::<#target>(); + }); } } -impl ArgsBackToTokens for InitStateArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(self.generics().to_attribute_arg_tokens()); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::codegen::with_target_path::WithTargetPath; - use internal_test_proc_macro::xtest; - use syn::{Path, parse_quote, parse2}; - - #[xtest] - fn test_to_tokens_no_generics() -> syn::Result<()> { - let args = parse2::(quote!())?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .init_state :: < FooTarget > () - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) +impl ToTokens for InitStateAttrEmitter { + fn to_tokens(&self, tokens: &mut TokenStream) { + let args = self.args.args.extra_args(); + tokens.extend(quote! { + #(#args),* + }); + *tokens = self.wrap_as_attr(tokens); } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_sub_state.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_sub_state.rs index a6be47be..6446ec04 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_sub_state.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_init_sub_state.rs @@ -1,15 +1,10 @@ -use crate::codegen::tokens; -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::{AttributeIdent, ItemAttributeArgs}; -use crate::syntax::analysis::item::{IdentFromItemResult, resolve_ident_from_struct_or_enum}; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::concrete_path::ConcreteTargetPath; +use crate::macro_api::prelude::*; use darling::FromMeta; use proc_macro2::TokenStream; -use quote::quote; -use syn::Item; +use quote::{ + ToTokens, + quote, +}; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] @@ -19,60 +14,26 @@ impl AttributeIdent for InitSubStateArgs { const IDENT: &'static str = "auto_init_sub_state"; } -impl ItemAttributeArgs for InitSubStateArgs { - fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_> { - resolve_ident_from_struct_or_enum(item) - } -} - -impl GenericsArgs for InitSubStateArgs { - fn type_lists(&self) -> &[TypeList] { - &[] - } -} +pub type IaInitSubState = + ItemAttribute, AllowStructOrEnum>; +pub type InitSubStateAppMutEmitter = AppMutationEmitter; +pub type InitSubStateAttrEmitter = AttrEmitter; -impl ToTokensWithConcreteTargetPath for InitSubStateArgs { - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut TokenStream, - target: &ConcreteTargetPath, - ) { +impl EmitAppMutationTokens for InitSubStateAppMutEmitter { + fn to_app_mutation_tokens(&self, tokens: &mut TokenStream, app_param: &syn::Ident) { + let target = &self.args.target; tokens.extend(quote! { - .add_sub_state::< #target >() - }) - } - fn required_use_statements(&self) -> Vec { - vec![tokens::use_bevy_state_app_ext_states()] + #app_param.add_sub_state::<#target>(); + }); } } -impl ArgsBackToTokens for InitSubStateArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(self.generics().to_attribute_arg_tokens()); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::codegen::with_target_path::WithTargetPath; - use internal_test_proc_macro::xtest; - use syn::{Path, parse_quote, parse2}; - - #[xtest] - fn test_to_tokens_no_generics() -> syn::Result<()> { - let args = parse2::(quote!())?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .add_sub_state :: < FooTarget > () - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) +impl ToTokens for InitSubStateAttrEmitter { + fn to_tokens(&self, tokens: &mut TokenStream) { + let args = self.args.args.extra_args(); + tokens.extend(quote! { + #(#args),* + }); + *tokens = self.wrap_as_attr(tokens); } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_insert_resource.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_insert_resource.rs index e500a7b9..910485ef 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_insert_resource.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_insert_resource.rs @@ -1,102 +1,83 @@ -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::{AttributeIdent, ItemAttributeArgs}; -use crate::syntax::analysis::item::{IdentFromItemResult, resolve_ident_from_struct_or_enum}; -use crate::syntax::ast::any_expr::AnyExprCallClosureMacroPath; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::concrete_path::ConcreteTargetPath; +use crate::{ + macro_api::prelude::*, + syntax::ast::any_expr::AnyExprCallClosureMacroPath, +}; use darling::FromMeta; use proc_macro2::TokenStream; -use quote::quote; -use syn::Item; +use quote::{ + ToTokens, + quote, +}; +use syn::spanned::Spanned; #[derive(FromMeta, Debug, Clone, PartialEq, Hash)] -#[darling(derive_syn_parse)] +#[darling(derive_syn_parse, and_then = Self::validate)] pub struct InsertResourceArgs { - #[darling(default)] - pub generics: Option, - pub resource: AnyExprCallClosureMacroPath, + // TODO: after removing resource, remove _resolved, make init required + pub resource: Option, + pub init: Option, + #[darling(skip)] + _resolved: Option, } -impl AttributeIdent for InsertResourceArgs { - const IDENT: &'static str = "auto_insert_resource"; -} - -impl ItemAttributeArgs for InsertResourceArgs { - fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_> { - resolve_ident_from_struct_or_enum(item) +impl InsertResourceArgs { + fn validate(self) -> darling::Result { + Ok(Self { _resolved: Some(Self::resolve_resource(&self)?.clone()), ..self }) } -} - -impl GenericsArgs for InsertResourceArgs { - fn type_lists(&self) -> &[TypeList] { - self.generics.as_slice() + fn resolve_resource(&self) -> darling::Result<&AnyExprCallClosureMacroPath> { + if let Some(resolved) = self._resolved.as_ref() { + Ok(resolved) + } else { + let deprecated = |msg| darling::Error::custom(msg).with_span(&self.resource.span()); + match (self.resource.as_ref(), self.init.as_ref()) { + (Some(_), Some(_)) => { + Err(deprecated("resource and init are mutually exclusive, use init instead")) + } + (None, None) => Err(darling::Error::missing_field("init")), + (Some(_), None) => Err(deprecated("resource is deprecated, use init instead")), + (None, Some(res)) => Ok(res), + } + } } } -impl ToTokensWithConcreteTargetPath for InsertResourceArgs { - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut TokenStream, - target: &ConcreteTargetPath, - ) { - let resource = &self.resource; - tokens.extend(quote! { - .insert_resource::< #target >(#resource) - }) - } +impl AttributeIdent for InsertResourceArgs { + const IDENT: &'static str = "auto_insert_resource"; } -#[cfg(test)] -mod tests { - use super::*; - use crate::codegen::with_target_path::WithTargetPath; - use internal_test_proc_macro::xtest; - use syn::{Path, parse_quote, parse2}; - - #[xtest] - fn test_to_tokens_no_generics() -> syn::Result<()> { - let args = parse2::(quote!(resource(FooTarget)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .insert_resource :: < FooTarget > (FooTarget) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } +pub type IaInsertResource = ItemAttribute< + Composed, + AllowStructOrEnum, +>; +pub type InsertResourceAppMutEmitter = AppMutationEmitter; +pub type InsertResourceAttrEmitter = AttrEmitter; - #[xtest] - fn test_to_tokens_single() -> syn::Result<()> { - let args = - parse2::(quote!(generics(u8, bool), resource(FooTarget(1, true))))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .insert_resource :: < FooTarget > (FooTarget(1, true)) +impl EmitAppMutationTokens for InsertResourceAppMutEmitter { + fn to_app_mutation_tokens(&self, tokens: &mut TokenStream, app_param: &syn::Ident) { + let resource = match self.args.args.base.resolve_resource() { + Ok(resource) => resource, + Err(err) => { + let err = syn::Error::from(err); + tokens.extend(err.to_compile_error()); + return; } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) + }; + for concrete_path in self.args.concrete_paths() { + tokens.extend(quote! { + #app_param.insert_resource({ let resource: #concrete_path = #resource; resource}); + }); + } } +} - #[xtest] - #[should_panic(expected = "Duplicate field `generics`")] - fn test_to_tokens_multiple() { - parse2::(quote!( - generics(u8, bool), - generics(bool, bool), - resource(FooTarget(1, true)) - )) - .unwrap(); +impl ToTokens for InsertResourceAttrEmitter { + fn to_tokens(&self, tokens: &mut TokenStream) { + let mut args = self.args.args.extra_args(); + let resource = &self.args.args.base.resource; + args.push(quote! { resource = #resource }); + tokens.extend(quote! { + #(#args),* + }); + *tokens = self.wrap_as_attr(tokens); } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_name.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_name.rs index 962148ca..0922b4c3 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_name.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_name.rs @@ -1,21 +1,17 @@ -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::{AttributeIdent, ItemAttributeArgs}; -use crate::syntax::analysis::item::{IdentFromItemResult, resolve_ident_from_struct_or_enum}; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::extensions::lit::LitExt; -use crate::syntax::validated::concrete_path::ConcreteTargetPath; +use crate::{ + macro_api::prelude::*, + syntax::extensions::lit::LitExt, +}; use darling::FromMeta; use proc_macro2::TokenStream; -use quote::quote; -use syn::Item; +use quote::{ + ToTokens, + quote, +}; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] pub struct NameArgs { - #[darling(multiple)] - pub generics: Vec, pub name: Option, } @@ -23,134 +19,43 @@ impl AttributeIdent for NameArgs { const IDENT: &'static str = "auto_name"; } -impl ItemAttributeArgs for NameArgs { - fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_> { - resolve_ident_from_struct_or_enum(item) - } -} - -impl GenericsArgs for NameArgs { - fn type_lists(&self) -> &[TypeList] { - &self.generics - } -} +pub type IaName = + ItemAttribute, AllowStructOrEnum>; +pub type NameAppMutEmitter = AppMutationEmitter; +pub type NameAttrEmitter = AttrEmitter; -impl ToTokensWithConcreteTargetPath for NameArgs { - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut TokenStream, - target: &ConcreteTargetPath, - ) { - let name = self - .name - .as_ref() - .map(|name| name.unquoted_string()) - .unwrap_or_else(|| { +impl EmitAppMutationTokens for NameAppMutEmitter { + fn to_app_mutation_tokens(&self, tokens: &mut TokenStream, app_param: &syn::Ident) { + let args = &self.args.args.base; + for concrete_path in self.args.concrete_paths() { + let name = args.name.as_ref().map(|name| name.unquoted_string()).unwrap_or_else(|| { // TODO: move to util fn - quote!(#target) + quote!(#concrete_path) .to_string() + .replace(" :: < ", "<") .replace(" < ", "<") .replace(" >", ">") .replace(" ,", ",") // TODO: offer option to only remove all spaces? // .replace(" ", "") }); - let bevy_ecs = crate::__private::paths::ecs::ecs_root_path(); - tokens.extend(quote! { - .register_required_components_with::<#target, #bevy_ecs::prelude::Name>(|| #bevy_ecs::prelude::Name::new(#name)) - }) - } -} - -impl ArgsBackToTokens for NameArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(self.generics().to_attribute_arg_tokens()); + let bevy_ecs = crate::__private::paths::ecs::ecs_root_path(); + tokens.extend(quote! { + #app_param.register_required_components_with::<#concrete_path, #bevy_ecs::prelude::Name>(|| #bevy_ecs::prelude::Name::new(#name)); + }); + } } } -#[cfg(test)] -mod tests { - use super::*; - use crate::codegen::with_target_path::WithTargetPath; - use internal_test_proc_macro::xtest; - use syn::{Path, parse_quote, parse2}; - - #[xtest] - fn test_to_tokens_no_generics() -> syn::Result<()> { - let args = parse2::(quote!())?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let bevy_ecs = crate::__private::paths::ecs::ecs_root_path(); - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .register_required_components_with::(|| #bevy_ecs::prelude::Name::new("FooTarget")) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_with_custom_name_no_generics() -> syn::Result<()> { - let args = parse2::(quote!(name = "bar"))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let bevy_ecs = crate::__private::paths::ecs::ecs_root_path(); - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .register_required_components_with::(|| #bevy_ecs::prelude::Name::new("bar")) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_single() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let bevy_ecs = crate::__private::paths::ecs::ecs_root_path(); - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .register_required_components_with::, #bevy_ecs::prelude::Name>(|| #bevy_ecs::prelude::Name::new("FooTarget")) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_multiple() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool), generics(bool, bool)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let bevy_ecs = crate::__private::paths::ecs::ecs_root_path(); - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .register_required_components_with::, #bevy_ecs::prelude::Name>(|| #bevy_ecs::prelude::Name::new("FooTarget")) - } - .to_string() - ); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .register_required_components_with::, #bevy_ecs::prelude::Name>(|| #bevy_ecs::prelude::Name::new("FooTarget")) - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) +impl ToTokens for NameAttrEmitter { + fn to_tokens(&self, tokens: &mut TokenStream) { + let mut args = self.args.args.extra_args(); + if let Some(name) = &self.args.args.base.name { + args.push(quote! { name = #name }); + } + tokens.extend(quote! { + #(#args),* + }); + *tokens = self.wrap_as_attr(tokens); } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_register_state_type.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_register_state_type.rs index 79bf2d3c..2fcfb582 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_register_state_type.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_register_state_type.rs @@ -1,121 +1,44 @@ -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::{AttributeIdent, ItemAttributeArgs}; -use crate::syntax::analysis::item::{IdentFromItemResult, resolve_ident_from_struct_or_enum}; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::concrete_path::ConcreteTargetPath; +use crate::macro_api::prelude::*; use darling::FromMeta; use proc_macro2::TokenStream; -use quote::quote; -use syn::Item; +use quote::{ + ToTokens, + quote, +}; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] -pub struct RegisterStateTypeArgs { - #[darling(multiple)] - pub generics: Vec, -} +pub struct RegisterStateTypeArgs {} impl AttributeIdent for RegisterStateTypeArgs { const IDENT: &'static str = "auto_register_state_type"; } -impl ItemAttributeArgs for RegisterStateTypeArgs { - fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_> { - resolve_ident_from_struct_or_enum(item) - } -} - -impl GenericsArgs for RegisterStateTypeArgs { - fn type_lists(&self) -> &[TypeList] { - &self.generics +pub type IaRegisterStateType = ItemAttribute< + Composed, + AllowStructOrEnum, +>; +pub type RegisterStateTypeAppMutEmitter = AppMutationEmitter; +pub type RegisterStateTypeAttrEmitter = AttrEmitter; + +impl EmitAppMutationTokens for RegisterStateTypeAppMutEmitter { + fn to_app_mutation_tokens(&self, tokens: &mut TokenStream, app_param: &syn::Ident) { + for concrete_path in self.args.concrete_paths() { + let bevy_state = crate::__private::paths::state::root_path(); + tokens.extend(quote! { + #app_param.register_type :: < #bevy_state::prelude::State< #concrete_path > >(); + #app_param.register_type :: < #bevy_state::prelude::NextState< #concrete_path > >(); + }); + } } } -impl ToTokensWithConcreteTargetPath for RegisterStateTypeArgs { - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut TokenStream, - target: &ConcreteTargetPath, - ) { - let bevy_state = crate::__private::paths::state::root_path(); +impl ToTokens for RegisterStateTypeAttrEmitter { + fn to_tokens(&self, tokens: &mut TokenStream) { + let args = self.args.args.extra_args(); tokens.extend(quote! { - .register_type :: < #bevy_state::prelude::State< #target > >() - .register_type :: < #bevy_state::prelude::NextState< #target > >() - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::codegen::with_target_path::WithTargetPath; - use internal_test_proc_macro::xtest; - use syn::{Path, parse_quote, parse2}; - - #[xtest] - fn test_to_tokens_no_generics() -> syn::Result<()> { - let args = parse2::(quote!())?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let bevy_state = crate::__private::paths::state::root_path(); - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .register_type :: < #bevy_state::prelude::State< FooTarget > >() - .register_type :: < #bevy_state::prelude::NextState< FooTarget > >() - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_single() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let bevy_state = crate::__private::paths::state::root_path(); - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .register_type :: < #bevy_state::prelude::State< FooTarget > >() - .register_type :: < #bevy_state::prelude::NextState< FooTarget > >() - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_multiple() -> syn::Result<()> { - let args = - parse2::(quote!(generics(u8, bool), generics(bool, bool)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let bevy_state = crate::__private::paths::state::root_path(); - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .register_type :: < #bevy_state::prelude::State< FooTarget > >() - .register_type :: < #bevy_state::prelude::NextState< FooTarget > >() - } - .to_string() - ); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .register_type :: < #bevy_state::prelude::State< FooTarget > >() - .register_type :: < #bevy_state::prelude::NextState< FooTarget > >() - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) + #(#args),* + }); + *tokens = self.wrap_as_attr(tokens); } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_register_type.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_register_type.rs index 702fa221..3ce3a864 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_register_type.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_register_type.rs @@ -1,118 +1,41 @@ -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::{AttributeIdent, ItemAttributeArgs}; -use crate::syntax::analysis::item::{IdentFromItemResult, resolve_ident_from_struct_or_enum}; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::concrete_path::ConcreteTargetPath; +use crate::macro_api::prelude::*; use darling::FromMeta; use proc_macro2::TokenStream; -use quote::quote; -use syn::Item; +use quote::{ + ToTokens, + quote, +}; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] -pub struct RegisterTypeArgs { - #[darling(multiple)] - pub generics: Vec, -} +pub struct RegisterTypeArgs {} impl AttributeIdent for RegisterTypeArgs { const IDENT: &'static str = "auto_register_type"; } -impl ItemAttributeArgs for RegisterTypeArgs { - fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_> { - resolve_ident_from_struct_or_enum(item) - } -} - -impl GenericsArgs for RegisterTypeArgs { - fn type_lists(&self) -> &[TypeList] { - &self.generics +pub type IaRegisterType = ItemAttribute< + Composed, + AllowStructOrEnum, +>; +pub type RegisterTypeAppMutEmitter = AppMutationEmitter; +pub type RegisterTypeAttrEmitter = AttrEmitter; + +impl EmitAppMutationTokens for RegisterTypeAppMutEmitter { + fn to_app_mutation_tokens(&self, tokens: &mut TokenStream, app_param: &syn::Ident) { + for concrete_path in self.args.concrete_paths() { + tokens.extend(quote! { + #app_param.register_type::<#concrete_path>(); + }); + } } } - -impl ToTokensWithConcreteTargetPath for RegisterTypeArgs { - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut TokenStream, - target: &ConcreteTargetPath, - ) { +impl ToTokens for RegisterTypeAttrEmitter { + fn to_tokens(&self, tokens: &mut TokenStream) { + let args = self.args.args.extra_args(); tokens.extend(quote! { - .register_type :: < #target >() - }) - } -} - -impl ArgsBackToTokens for RegisterTypeArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(self.generics().to_attribute_arg_tokens()); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::codegen::with_target_path::WithTargetPath; - use internal_test_proc_macro::xtest; - use syn::{Path, parse_quote, parse2}; - - #[xtest] - fn test_to_tokens_no_generics() -> syn::Result<()> { - let args = parse2::(quote!())?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .register_type :: < FooTarget >() - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_single() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .register_type :: < FooTarget >() - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[xtest] - fn test_to_tokens_multiple() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool), generics(bool, bool)))?; - let path: Path = parse_quote!(FooTarget); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .register_type :: < FooTarget >() - } - .to_string() - ); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - .register_type :: < FooTarget >() - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) + #(#args),* + }); + *tokens = self.wrap_as_attr(tokens); } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_run_on_build.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_run_on_build.rs index 49e1a28a..caf0dd37 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_run_on_build.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/auto_run_on_build.rs @@ -1,123 +1,39 @@ -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::{AttributeIdent, ItemAttributeArgs}; -use crate::syntax::analysis::item::{IdentFromItemResult, resolve_ident_from_fn}; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::concrete_path::ConcreteTargetPath; +use crate::macro_api::prelude::*; use darling::FromMeta; use proc_macro2::TokenStream; -use quote::quote; -use syn::Item; +use quote::{ + ToTokens, + quote, +}; #[derive(FromMeta, Debug, Clone, PartialEq, Hash)] #[darling(derive_syn_parse)] -pub struct RunOnBuildArgs { - #[darling(multiple)] - pub generics: Vec, -} +pub struct RunOnBuildArgs {} impl AttributeIdent for RunOnBuildArgs { const IDENT: &'static str = "auto_run_on_build"; } -impl ItemAttributeArgs for RunOnBuildArgs { - fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_> { - resolve_ident_from_fn(item) - } -} - -impl GenericsArgs for RunOnBuildArgs { - const TURBOFISH: bool = true; - fn type_lists(&self) -> &[TypeList] { - &self.generics - } -} - -impl ToTokensWithConcreteTargetPath for RunOnBuildArgs { - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut TokenStream, - target: &ConcreteTargetPath, - ) { - tokens.extend(quote! { - #target - }) - } -} - -impl ArgsBackToTokens for RunOnBuildArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - let mut args = vec![]; - if !self.generics().is_empty() { - args.extend(self.generics().to_attribute_arg_vec_tokens()); +pub type IaRunOnBuild = + ItemAttribute, AllowFn>; +pub type RunOnBuildAppMutEmitter = AppMutationEmitter; +pub type RunOnBuildAttrEmitter = AttrEmitter; + +impl EmitAppMutationTokens for RunOnBuildAppMutEmitter { + fn to_app_mutation_tokens(&self, tokens: &mut TokenStream, app_param: &syn::Ident) { + for concrete_path in self.args.concrete_paths() { + tokens.extend(quote! { + #concrete_path(#app_param); + }); } - tokens.extend(quote! { #(#args),* }); } } -#[cfg(test)] -mod tests { - use super::*; - use crate::codegen::with_target_path::WithTargetPath; - use syn::{Path, parse_quote, parse2}; - - #[internal_test_proc_macro::xtest] - fn test_to_tokens_no_generics() -> syn::Result<()> { - let args = parse2::(quote!())?; - let path: Path = parse_quote!(foo_target); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - - foo_target - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[internal_test_proc_macro::xtest] - fn test_to_tokens_single() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool)))?; - let path: Path = parse_quote!(foo_target); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - foo_target:: - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) - } - - #[internal_test_proc_macro::xtest] - fn test_to_tokens_multiple() -> syn::Result<()> { - let args = parse2::(quote!(generics(u8, bool), generics(bool, bool)))?; - let path: Path = parse_quote!(foo_target); - let args_with_target = WithTargetPath::try_from((path, args))?; - let mut token_iter = args_with_target.to_tokens_iter(); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - foo_target:: - } - .to_string() - ); - assert_eq!( - token_iter.next().expect("token_iter").to_string(), - quote! { - foo_target:: - } - .to_string() - ); - assert!(token_iter.next().is_none()); - Ok(()) +impl ToTokens for RunOnBuildAttrEmitter { + fn to_tokens(&self, tokens: &mut TokenStream) { + let args = self.args.args.extra_args(); + tokens.extend(quote! { + #(#args),* + }); } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs index d8b72748..137d11e9 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/actions/mod.rs @@ -1,13 +1,30 @@ -pub mod auto_add_message; -pub mod auto_add_observer; -pub mod auto_add_plugin; -pub mod auto_add_system; -pub mod auto_configure_system_set; -pub mod auto_init_resource; -pub mod auto_init_state; -pub mod auto_init_sub_state; -pub mod auto_insert_resource; -pub mod auto_name; -pub mod auto_register_state_type; -pub mod auto_register_type; -pub mod auto_run_on_build; +mod auto_add_message; +mod auto_add_observer; +mod auto_add_plugin; +mod auto_add_system; +mod auto_configure_system_set; +mod auto_init_resource; +mod auto_init_state; +mod auto_init_sub_state; +mod auto_insert_resource; +mod auto_name; +mod auto_register_state_type; +mod auto_register_type; +mod auto_run_on_build; + +pub mod prelude { + use super::*; + pub use auto_add_message::*; + pub use auto_add_observer::*; + pub use auto_add_plugin::*; + pub use auto_add_system::*; + pub use auto_configure_system_set::*; + pub use auto_init_resource::*; + pub use auto_init_state::*; + pub use auto_init_sub_state::*; + pub use auto_insert_resource::*; + pub use auto_name::*; + pub use auto_register_state_type::*; + pub use auto_register_type::*; + pub use auto_run_on_build::*; +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/auto_plugin.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/auto_plugin.rs index dbc8175d..f587461c 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/auto_plugin.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/auto_plugin.rs @@ -1,11 +1,19 @@ -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::syntax::analysis::fn_param::require_fn_param_mutable_reference; -use crate::syntax::ast::type_list::TypeList; -use darling::FromMeta; -use darling::util::Flag; +use crate::syntax::{ + analysis::fn_param::require_fn_param_mutable_reference, + ast::type_list::TypeList, +}; +use darling::{ + FromMeta, + util::Flag, +}; use proc_macro2::Ident; -use syn::spanned::Spanned; -use syn::{FnArg, ItemFn, Pat, Path}; +use syn::{ + FnArg, + ItemFn, + Pat, + Path, + spanned::Spanned, +}; #[derive(FromMeta, Debug, Default, Clone)] #[darling(derive_syn_parse, default)] @@ -26,42 +34,28 @@ pub struct AutoPluginStructOrEnumArgs { pub impl_generic_plugin_trait: Flag, } -// TODO: remove? -impl GenericsArgs for AutoPluginStructOrEnumArgs { - fn type_lists(&self) -> &[TypeList] { - #[allow(deprecated)] - &self.generics - } -} - #[derive(FromMeta, Debug, Default, Clone, PartialEq)] #[darling(derive_syn_parse, default)] pub struct AutoPluginFnArgs { #[darling(multiple)] pub generics: Vec, pub plugin: Option, + #[darling(and_then = app_param_check)] pub app_param: Option, } -// TODO: remove? -impl GenericsArgs for AutoPluginFnArgs { - const TURBOFISH: bool = true; - fn type_lists(&self) -> &[TypeList] { - &self.generics +fn app_param_check(app_param: Option) -> darling::Result> { + if let Some(app_param) = app_param { + Err(darling::error::Error::custom( + "manually specifying `app_param` is no longer needed - remove `app_param = ...`", + ) + .with_span(&app_param.span())) + } else { + Ok(app_param) } } -pub fn resolve_app_param_name<'a>( - input: &'a ItemFn, - app_param_name: Option<&'a Ident>, -) -> syn::Result<&'a Ident> { - // Helper: pick a useful Span for errors - let err_span = || { - app_param_name - .map(Ident::span) - .unwrap_or_else(|| input.sig.span()) - }; - +pub fn resolve_app_param_name(input: &ItemFn) -> syn::Result<&Ident> { // Helper: try to get &Ident from a typed arg fn ident_from_typed_arg(arg: &FnArg) -> Option<&Ident> { match arg { @@ -73,44 +67,22 @@ pub fn resolve_app_param_name<'a>( } } - let has_self = input - .sig - .inputs - .iter() - .any(|a| matches!(a, FnArg::Receiver(_))); + let has_self = input.sig.inputs.iter().any(|a| matches!(a, FnArg::Receiver(_))); // collect all named params - let named = input - .sig - .inputs - .iter() - .filter_map(ident_from_typed_arg) - .collect::>(); - - // If user explicitly provided a name, validate it exists and return it - if let Some(given) = app_param_name.as_ref() { - if let Some(found) = named.iter().copied().find(|id| id == given) { - return Ok(found); - } - return Err(syn::Error::new( - err_span(), - format!( - "auto_plugin provided app_param: `{given}` but it was not found in the function signature" - ), - )); - } + let named = input.sig.inputs.iter().filter_map(ident_from_typed_arg).collect::>(); // Otherwise infer. We need exactly one named param after any receiver(s). match named.as_slice() { [] => { if has_self { Err(syn::Error::new( - err_span(), + input.sig.inputs.span(), "auto_plugin requires a method taking at least one parameter in addition to `&self`", )) } else { Err(syn::Error::new( - err_span(), + input.sig.inputs.span(), "auto_plugin requires a function taking at least one named parameter", )) } @@ -121,7 +93,7 @@ pub fn resolve_app_param_name<'a>( Ok(*only) } _more => Err(syn::Error::new( - err_span(), + input.sig.inputs.span(), "auto_plugin requires specifying the name of the `&mut bevy::app::App` parameter \ when there’s more than one parameter, e.g.: #[auto_plugin(app_param=my_app)] @@ -134,35 +106,13 @@ pub fn resolve_app_param_name<'a>( mod tests { use crate::macro_api::attributes::auto_plugin::resolve_app_param_name; use internal_test_proc_macro::xtest; - use proc_macro2::{Ident, Span}; - - #[xtest] - #[should_panic = "auto_plugin provided app_param: `bar` but it was not found in the function signature"] - fn test_resolve_app_param_name_wrong_specified() { - let item = syn::parse_quote! { - fn foo(&mut self, foo: &mut App) {} - }; - let target_ident = Ident::new("bar", Span::call_site()); - let ident = resolve_app_param_name(&item, Some(&target_ident)).unwrap(); - assert_eq!(ident, "foo"); - } - - #[xtest] - fn test_resolve_app_param_name_specified() { - let item = syn::parse_quote! { - fn foo(&mut self, foo1: &mut App, foo2: &mut App, foo3: &mut App) {} - }; - let target_ident = Ident::new("foo2", Span::call_site()); - let ident = resolve_app_param_name(&item, Some(&target_ident)).unwrap(); - assert_eq!(ident, "foo2"); - } #[xtest] fn test_resolve_app_param_name_default() { let item = syn::parse_quote! { fn foo(&mut self, foo: &mut App) {} }; - let ident = resolve_app_param_name(&item, None).unwrap(); + let ident = resolve_app_param_name(&item).unwrap(); assert_eq!(ident, "foo"); } @@ -172,7 +122,7 @@ mod tests { let item = syn::parse_quote! { fn foo() {} }; - match resolve_app_param_name(&item, None) { + match resolve_app_param_name(&item) { Ok(_) => {} Err(err) => panic!("{err:?}"), } @@ -184,7 +134,7 @@ mod tests { let item = syn::parse_quote! { fn foo(&mut self) {} }; - match resolve_app_param_name(&item, None) { + match resolve_app_param_name(&item) { Ok(_) => {} Err(err) => panic!("{err:?}"), } @@ -196,7 +146,7 @@ mod tests { let item = syn::parse_quote! { fn foo(&mut self, foo: &Bar) {} }; - match resolve_app_param_name(&item, None) { + match resolve_app_param_name(&item) { Ok(_) => {} Err(err) => panic!("{err:?}"), } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/mod.rs index b95700ec..96b7784d 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/mod.rs @@ -1,61 +1,323 @@ -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::syntax::analysis::item::IdentFromItemResult; -use crate::syntax::validated::non_empty_path::NonEmptyPath; -use darling::FromMeta; -use proc_macro2::Ident; +use crate::{ + macro_api::prelude::*, + syntax::validated::non_empty_path::NonEmptyPath, + util::macros::impl_from_default, +}; +use darling::{ + FromMeta, + ast::NestedMeta, +}; +use proc_macro2::{ + Ident, + Span, + TokenStream, +}; use quote::format_ident; -use std::hash::Hash; -use syn::parse::Parse; -use syn::{Item, parse_quote}; +use std::{ + hash::Hash, + marker::PhantomData, +}; +use syn::{ + Item, + Path, + Token, + parse::Parser, + parse_quote, + punctuated::Punctuated, +}; mod actions; mod auto_plugin; mod rewrites; -mod traits; pub mod prelude { - pub use super::auto_plugin::{ - AutoPluginFnArgs, AutoPluginStructOrEnumArgs, resolve_app_param_name, + pub use super::{ + AllowAny, + AllowFn, + AllowStructOrEnum, + AttributeIdent, + GenericsCap, + ItemAttribute, + ItemAttributeArgs, + ItemAttributeContext, + ItemAttributeInput, + ItemAttributeParse, + ItemAttributePlugin, + ItemAttributeTarget, + ItemAttributeUniqueIdent, + auto_plugin::{ + AutoPluginFnArgs, + AutoPluginStructOrEnumArgs, + resolve_app_param_name, + }, }; - pub use crate::macro_api::attributes::actions::auto_add_message::AddMessageArgs; - pub use crate::macro_api::attributes::actions::auto_add_observer::AddObserverArgs; - pub use crate::macro_api::attributes::actions::auto_add_plugin::AddPluginArgs; - pub use crate::macro_api::attributes::actions::auto_add_system::AddSystemArgs; - pub use crate::macro_api::attributes::actions::auto_configure_system_set::{ - ConfigureSystemSetArgs, - with_plugin_args_from_attr_input as configure_system_set_args_from_attr_input, + pub use crate::macro_api::attributes::{ + actions::prelude::*, + rewrites::prelude::*, }; - pub use crate::macro_api::attributes::actions::auto_init_resource::InitResourceArgs; - pub use crate::macro_api::attributes::actions::auto_init_state::InitStateArgs; - pub use crate::macro_api::attributes::actions::auto_init_sub_state::InitSubStateArgs; - pub use crate::macro_api::attributes::actions::auto_insert_resource::InsertResourceArgs; - pub use crate::macro_api::attributes::actions::auto_name::NameArgs; - pub use crate::macro_api::attributes::actions::auto_register_state_type::RegisterStateTypeArgs; - pub use crate::macro_api::attributes::actions::auto_register_type::RegisterTypeArgs; - pub use crate::macro_api::attributes::actions::auto_run_on_build::RunOnBuildArgs; - pub use crate::macro_api::attributes::rewrites::auto_component::ComponentArgs; - pub use crate::macro_api::attributes::rewrites::auto_event::EventArgs; - pub use crate::macro_api::attributes::rewrites::auto_message::MessageArgs; - pub use crate::macro_api::attributes::rewrites::auto_observer::ObserverArgs; - pub use crate::macro_api::attributes::rewrites::auto_resource::ResourceArgs; - pub use crate::macro_api::attributes::rewrites::auto_states::StatesArgs; - pub use crate::macro_api::attributes::rewrites::auto_system::SystemArgs; - pub use crate::macro_api::attributes::traits::prelude::*; } pub trait AttributeIdent { const IDENT: &'static str; + #[allow(dead_code)] + // TODO: should we use this over the context macro_paths? + // context macro paths would allow us to resolve aliased versions of this crate fn full_attribute_path() -> NonEmptyPath { let ident = format_ident!("{}", Self::IDENT); parse_quote!( ::bevy_auto_plugin::prelude::#ident ) } } -pub trait ItemAttributeArgs: - AttributeIdent + FromMeta + Parse + ToTokensWithConcreteTargetPath + Hash + Clone -{ +pub trait ItemAttributeArgs: AttributeIdent + Clone { fn global_build_prefix() -> Ident { format_ident!("_auto_plugin_{}_", Self::IDENT) } - fn resolve_item_ident(item: &Item) -> IdentFromItemResult<'_>; +} + +impl AttributeIdent for ItemAttribute +where + T: AttributeIdent + Clone, +{ + const IDENT: &'static str = T::IDENT; +} +impl ItemAttributeArgs for ItemAttribute, R> +where + T: AttributeIdent + Clone, + P: Clone, + G: Clone, + R: Clone, +{ +} + +pub trait IdentPathResolver { + const NOT_ALLOWED_MESSAGE: &'static str = "Unable to resolve ident path"; + fn resolve_ident_path(item: &Item) -> Option; +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +pub struct AllowStructOrEnum; +impl IdentPathResolver for AllowStructOrEnum { + const NOT_ALLOWED_MESSAGE: &'static str = "Only allowed on Struct Or Enum items"; + fn resolve_ident_path(item: &Item) -> Option { + Some(match item { + Item::Struct(item) => item.ident.clone().into(), + Item::Enum(item) => item.ident.clone().into(), + _ => return None, + }) + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +pub struct AllowFn; +impl IdentPathResolver for AllowFn { + const NOT_ALLOWED_MESSAGE: &'static str = "Only allowed on Fn items"; + fn resolve_ident_path(item: &Item) -> Option { + Some(match item { + Item::Fn(item) => item.sig.ident.clone().into(), + _ => return None, + }) + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] +pub struct AllowAny; +impl IdentPathResolver for AllowAny { + fn resolve_ident_path(item: &Item) -> Option { + Some(match item { + Item::Const(item) => item.ident.clone().into(), + Item::Enum(item) => item.ident.clone().into(), + Item::ExternCrate(item) => item.ident.clone().into(), + Item::Fn(item) => item.sig.ident.clone().into(), + Item::ForeignMod(_) => return None, + Item::Impl(_) => return None, + Item::Macro(item) => return item.ident.clone().map(Into::into), + Item::Mod(item) => item.ident.clone().into(), + Item::Static(item) => item.ident.clone().into(), + Item::Struct(item) => item.ident.clone().into(), + Item::Trait(item) => item.ident.clone().into(), + Item::TraitAlias(item) => item.ident.clone().into(), + Item::Type(item) => item.ident.clone().into(), + Item::Union(item) => item.ident.clone().into(), + // TODO: implement + Item::Use(_) => return None, + Item::Verbatim(_) => return None, + _ => return None, + }) + } +} +impl_from_default!(AllowAny => (AllowStructOrEnum, AllowFn)); + +pub trait GenericsCap { + fn concrete_paths(&self) -> Vec; +} + +#[derive(Debug, Clone, PartialEq)] +pub struct ItemAttribute { + pub args: T, + pub context: Context, + pub input_item: InputItem, + pub target: syn::Path, + pub _resolver: PhantomData, +} + +// TODO: where should this live? +impl ItemAttribute { + pub fn _concat_ident_hash(&self, ident: &Ident) -> String + where + T: Hash, + { + use std::hash::{ + Hash, + Hasher, + }; + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + ident.hash(&mut hasher); + self.args.hash(&mut hasher); + format!("{:x}", hasher.finish()) + } + + pub fn _get_unique_ident(&self, prefix: Ident, ident: &Ident) -> Ident + where + T: Hash, + { + let hash = self._concat_ident_hash(ident); + format_ident!("{prefix}_{hash}") + } +} + +pub trait ItemAttributePlugin { + fn plugin(&self) -> &syn::Path; +} + +impl ItemAttributePlugin for ItemAttribute, Resolver> { + fn plugin(&self) -> &Path { + self.args.plugin() + } +} + +pub trait ItemAttributeContext { + fn context(&self) -> &Context; +} + +impl ItemAttributeContext for ItemAttribute { + fn context(&self) -> &Context { + &self.context + } +} + +pub trait ItemAttributeTarget { + fn target(&self) -> &syn::Path; +} + +impl ItemAttributeTarget for ItemAttribute +where + T: AttributeIdent + Hash + Clone, + Resolver: Clone, +{ + fn target(&self) -> &syn::Path { + &self.target + } +} + +pub trait ItemAttributeUniqueIdent: ItemAttributeTarget + ItemAttributeArgs { + fn get_unique_ident(&self) -> Ident; +} + +impl ItemAttributeUniqueIdent for ItemAttribute +where + ItemAttribute: ItemAttributeArgs, + T: AttributeIdent + Hash + Clone, + Resolver: Clone, +{ + fn get_unique_ident(&self) -> Ident { + self._get_unique_ident( + ItemAttribute::::global_build_prefix(), + self.target.get_ident().unwrap(), // infallible + ) + } +} + +pub trait ItemAttributeInput { + fn input_item(&self) -> &InputItem; + fn input_item_mut(&mut self) -> &mut InputItem; +} + +impl ItemAttributeInput for ItemAttribute { + fn input_item(&self) -> &InputItem { + &self.input_item + } + fn input_item_mut(&mut self) -> &mut InputItem { + &mut self.input_item + } +} + +pub trait ItemAttributeParse { + fn from_attr_input_with_context( + attr: TokenStream, + input: TokenStream, + context: Context, + ) -> syn::Result + where + Self: Sized + ItemAttributeInput + ItemAttributeContext + ItemAttributeArgs; +} + +impl ItemAttributeParse for ItemAttribute +where + T: FromMeta, + Resolver: IdentPathResolver, +{ + fn from_attr_input_with_context( + attr: TokenStream, + input: TokenStream, + context: Context, + ) -> syn::Result { + Self::from_attr_input(attr, input, context) + } +} + +impl ItemAttribute +where + T: FromMeta, + Resolver: IdentPathResolver, +{ + pub fn from_attr_input( + attr: TokenStream, + input: TokenStream, + context: Context, + ) -> syn::Result { + let mut input_item = InputItem::Tokens(input); + let item = input_item.ensure_ast()?; + let Some(target) = Resolver::resolve_ident_path(item) else { + // call-site because we want to highlight the attribute + return Err(syn::Error::new(Span::call_site(), Resolver::NOT_ALLOWED_MESSAGE)); + }; + // Parse the attribute’s inner tokens as: Meta, Meta, ... + let metas: Punctuated = + Punctuated::::parse_terminated.parse2(attr)?; + + // darling expects a slice of `Meta` + let metas_vec: Vec = metas.into_iter().collect(); + + Ok(Self { + args: T::from_list(&metas_vec)?, + context, + input_item, + target, + _resolver: PhantomData, + }) + } +} + +impl GenericsCap for ItemAttribute, R> +where + M2: HasGenerics, +{ + fn concrete_paths(&self) -> Vec { + let target = &self.target; + if self.args.generics.generics().is_empty() { + vec![target.clone()] + } else { + self.args.generics.generics().iter().map(|g| syn::parse_quote!(#target::<#g>)).collect() + } + } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_component.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_component.rs index 1fb9c180..c41d02ac 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_component.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_component.rs @@ -1,219 +1,72 @@ -use crate::__private::attribute::RewriteAttribute; -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::{ExpandAttrs, tokens}; -use crate::macro_api::attributes::AttributeIdent; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::prelude::*; -use crate::syntax::ast::flag_or_list::FlagOrList; -use crate::syntax::ast::flag_or_lit::FlagOrLit; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::non_empty_path::NonEmptyPath; +use crate::{ + codegen::{ + ExpandAttrs, + tokens, + }, + macro_api::prelude::*, + syntax::{ + ast::{ + flag_or_list::FlagOrList, + flag_or_lit::FlagOrLit, + }, + validated::non_empty_path::NonEmptyPath, + }, + util::macros::impl_from_default, +}; use darling::FromMeta; -use proc_macro2::{Ident, TokenStream as MacroStream, TokenStream}; -use quote::quote; +use proc_macro2::Ident; use syn::parse_quote; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] pub struct ComponentArgs { - #[darling(multiple)] - pub generics: Vec, pub derive: FlagOrList, pub reflect: FlagOrList, pub register: bool, pub auto_name: FlagOrLit, } -impl GenericsArgs for ComponentArgs { - fn type_lists(&self) -> &[TypeList] { - &self.generics - } -} - impl AttributeIdent for ComponentArgs { const IDENT: &'static str = "auto_component"; } -impl<'a> From<&'a ComponentArgs> for RegisterTypeArgs { - fn from(value: &'a ComponentArgs) -> Self { - Self { - generics: value.generics.clone(), - } +impl From<&ComponentArgs> for RegisterTypeArgs { + fn from(_: &ComponentArgs) -> Self { + Self {} } } impl<'a> From<&'a ComponentArgs> for NameArgs { fn from(value: &'a ComponentArgs) -> Self { - Self { - generics: value.generics.clone(), - name: value.auto_name.lit.clone(), - } - } -} - -impl ArgsBackToTokens for ComponentArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - let mut items = vec![]; - items.extend(self.generics().to_attribute_arg_vec_tokens()); - if self.derive.present { - items.push(self.derive.to_outer_tokens("derive")); - } - if self.reflect.present { - items.push(self.reflect.to_outer_tokens("reflect")); - } - if self.register { - items.push(quote!(register)); - } - if self.auto_name.present { - items.push(self.auto_name.to_outer_tokens("auto_name")); - } - tokens.extend(quote! { #(#items),* }); + Self { name: value.auto_name.lit.clone() } } } -impl RewriteAttribute for ComponentArgs { - fn expand_args(&self, plugin: &NonEmptyPath) -> MacroStream { - let mut args = Vec::new(); - args.push(quote! { plugin = #plugin }); - if !self.generics().is_empty() { - args.extend(self.generics().to_attribute_arg_vec_tokens()); - } - quote! { #(#args),* } - } +pub type IaComponent = + ItemAttribute, AllowStructOrEnum>; +pub type ComponentAttrExpandEmitter = AttrExpansionEmitter; - fn expand_attrs(&self, plugin: &NonEmptyPath) -> ExpandAttrs { - let mut expanded_attrs = ExpandAttrs::default(); - - if self.derive.present { - expanded_attrs - .attrs - .push(tokens::derive_component(&self.derive.items)); +impl AttrExpansionEmitterToExpandAttr for ComponentAttrExpandEmitter { + fn to_expand_attrs(&self, expand_attrs: &mut ExpandAttrs) { + if self.args.args.base.derive.present { + expand_attrs.attrs.push(tokens::derive_component(&self.args.args.base.derive.items)); } - if self.reflect.present { - if self.derive.present { - expanded_attrs.attrs.push(tokens::derive_reflect()); + if self.args.args.base.reflect.present { + if self.args.args.base.derive.present { + expand_attrs.attrs.push(tokens::derive_reflect()); } let component_ident: Ident = parse_quote!(Component); - let items = std::iter::once(&component_ident).chain(self.reflect.items.iter()); - expanded_attrs.append(tokens::reflect(items)) + let items = + std::iter::once(&component_ident).chain(self.args.args.base.reflect.items.iter()); + expand_attrs.append(tokens::reflect(items)) } - if self.register { - expanded_attrs - .attrs - .push(tokens::auto_register_type(plugin.clone(), self.into())); + if self.args.args.base.register { + expand_attrs.attrs.push(tokens::auto_register_type(self.into())); } - if self.auto_name.present { - expanded_attrs - .attrs - .push(tokens::auto_name(plugin.clone(), self.into())); + if self.args.args.base.auto_name.present { + expand_attrs.attrs.push(tokens::auto_name(self.into())); } - expanded_attrs } } -#[cfg(test)] -mod tests { - use super::*; - use crate::macro_api::with_plugin::WithPlugin; - use crate::test_util::combo::combos_one_per_group_or_skip; - use crate::test_util::macros::*; - use darling::ast::NestedMeta; - use internal_test_proc_macro::xtest; - use internal_test_util::{extract_punctuated_paths, vec_spread}; - use quote::ToTokens; - use syn::parse_quote; - - #[xtest] - fn test_expand_back_into_args() -> syn::Result<()> { - for args in combos_one_per_group_or_skip(&[ - vec![quote!(derive), quote!(derive(Debug, Default))], - vec![quote!(reflect), quote!(reflect(Debug, Default))], - vec![quote!(register)], - vec![quote!(auto_name), quote!(auto_name = "foobar")], - ]) { - println!("checking args: {}", quote! { #(#args),*}); - assert_vec_args_expand!(plugin!(parse_quote!(Test)), ComponentArgs, args); - } - Ok(()) - } - - #[xtest] - fn test_expand_attrs_global() -> syn::Result<()> { - let extras = extract_punctuated_paths(parse_quote!(Debug, Default)) - .into_iter() - .map(NonEmptyPath::try_from) - .collect::>>()?; - let args: NestedMeta = parse_quote! {_( - plugin = Test, - derive(#(#extras),*), - reflect(#(#extras),*), - register, - auto_name, - )}; - let args = WithPlugin::::from_nested_meta(&args)?; - let derive_args = vec_spread![tokens::derive_component_path(), ..extras.clone(),]; - let derive_reflect_path = tokens::derive_reflect_path(); - let reflect_args = vec_spread![parse_quote!(Component), ..extras,]; - let reflect_attr = tokens::reflect(reflect_args.iter().map(NonEmptyPath::last_ident)); - assert_eq!( - args.inner - .expand_attrs(&args.plugin()) - .to_token_stream() - .to_string(), - ExpandAttrs { - use_items: reflect_attr.use_items, - attrs: vec![ - quote! { #[derive(#(#derive_args),*)] }, - // TODO: merge these derives - quote! { #[derive(#derive_reflect_path)] }, - quote! { #[reflect(#(#reflect_args),*)] }, - tokens::auto_register_type(args.plugin(), (&args.inner).into()), - tokens::auto_name(args.plugin(), (&args.inner).into()), - ] - } - .to_token_stream() - .to_string() - ); - Ok(()) - } - - #[xtest] - fn test_expand_attrs_global_with_custom_name() -> syn::Result<()> { - let extras = extract_punctuated_paths(parse_quote!(Debug, Default)) - .into_iter() - .map(NonEmptyPath::try_from) - .collect::>>()?; - let args: NestedMeta = parse_quote! {_( - plugin = Test, - derive(#(#extras),*), - reflect(#(#extras),*), - register, - auto_name = "foobar", - )}; - let args = WithPlugin::::from_nested_meta(&args)?; - let derive_args = vec_spread![tokens::derive_component_path(), ..extras.clone(),]; - let derive_reflect_path = tokens::derive_reflect_path(); - let reflect_args = vec_spread![parse_quote!(Component), ..extras,]; - let reflect_attr = tokens::reflect(reflect_args.iter().map(NonEmptyPath::last_ident)); - assert_eq!( - args.inner - .expand_attrs(&args.plugin()) - .to_token_stream() - .to_string(), - ExpandAttrs { - use_items: reflect_attr.use_items, - attrs: vec![ - quote! { #[derive(#(#derive_args),*)] }, - // TODO: merge these derives - quote! { #[derive(#derive_reflect_path)] }, - quote! { #[reflect(#(#reflect_args),*)] }, - tokens::auto_register_type(args.plugin(), (&args.inner).into()), - tokens::auto_name(args.plugin(), (&args.inner).into()), - ] - } - .to_token_stream() - .to_string() - ); - Ok(()) - } -} +impl_from_default!(ComponentArgs => (RegisterTypeArgs, NameArgs)); diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_event.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_event.rs index 8c29c89f..c601e7fa 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_event.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_event.rs @@ -1,15 +1,24 @@ -use crate::__private::attribute::RewriteAttribute; -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::{ExpandAttrs, tokens}; -use crate::macro_api::attributes::AttributeIdent; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::prelude::*; -use crate::syntax::ast::flag_or_list::FlagOrList; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::non_empty_path::NonEmptyPath; +use crate::{ + codegen::{ + ExpandAttrs, + tokens, + }, + macro_api::prelude::*, + syntax::{ + ast::flag_or_list::FlagOrList, + validated::non_empty_path::NonEmptyPath, + }, + util::macros::impl_from_default, +}; use darling::FromMeta; -use proc_macro2::{Ident, TokenStream as MacroStream, TokenStream}; -use quote::{ToTokens, quote}; +use proc_macro2::{ + Ident, + TokenStream, +}; +use quote::{ + ToTokens, + quote, +}; #[derive(FromMeta, Default, Debug, Copy, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] @@ -37,322 +46,47 @@ impl ToTokens for EventTarget { #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] pub struct EventArgs { - #[darling(multiple)] - pub generics: Vec, pub derive: FlagOrList, pub reflect: FlagOrList, pub register: bool, pub target: EventTarget, } -impl GenericsArgs for EventArgs { - fn type_lists(&self) -> &[TypeList] { - &self.generics - } -} - impl AttributeIdent for EventArgs { const IDENT: &'static str = "auto_event"; } impl<'a> From<&'a EventArgs> for RegisterTypeArgs { - fn from(value: &'a EventArgs) -> Self { - Self { - generics: value.generics.clone(), - } + fn from(_: &'a EventArgs) -> Self { + Self {} } } -impl ArgsBackToTokens for EventArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - let mut items = vec![]; - let target = self.target; - items.push(quote! { target(#target) }); - items.extend(self.generics().to_attribute_arg_vec_tokens()); - if self.derive.present { - items.push(self.derive.to_outer_tokens("derive")); - } - if self.reflect.present { - items.push(self.reflect.to_outer_tokens("reflect")); - } - if self.register { - items.push(quote!(register)); - } - tokens.extend(quote! { #(#items),* }); - } -} - -impl RewriteAttribute for EventArgs { - fn expand_args(&self, plugin: &NonEmptyPath) -> MacroStream { - let mut args = Vec::new(); - args.push(quote! { plugin = #plugin }); - if !self.generics().is_empty() { - args.extend(self.generics().to_attribute_arg_vec_tokens()); - } - quote! { #(#args),* } - } - - fn expand_attrs(&self, plugin: &NonEmptyPath) -> ExpandAttrs { - let mut expanded_attrs = ExpandAttrs::default(); - - if self.derive.present { - if matches!(self.target, EventTarget::Global) { - expanded_attrs - .attrs - .push(tokens::derive_event(&self.derive.items)); +pub type IaEvent = + ItemAttribute, AllowStructOrEnum>; +pub type EventAttrExpandEmitter = AttrExpansionEmitter; +impl AttrExpansionEmitterToExpandAttr for EventAttrExpandEmitter { + fn to_expand_attrs(&self, expand_attrs: &mut ExpandAttrs) { + if self.args.args.base.derive.present { + if matches!(self.args.args.base.target, EventTarget::Global) { + expand_attrs.attrs.push(tokens::derive_event(&self.args.args.base.derive.items)); } - if matches!(self.target, EventTarget::Entity) { - expanded_attrs + if matches!(self.args.args.base.target, EventTarget::Entity) { + expand_attrs .attrs - .push(tokens::derive_entity_event(&self.derive.items)); + .push(tokens::derive_entity_event(&self.args.args.base.derive.items)); } } - if self.reflect.present { - if self.derive.present { - expanded_attrs.attrs.push(tokens::derive_reflect()); + if self.args.args.base.reflect.present { + if self.args.args.base.derive.present { + expand_attrs.attrs.push(tokens::derive_reflect()); } - expanded_attrs.append(tokens::reflect(&self.reflect.items)) + expand_attrs.append(tokens::reflect(&self.args.args.base.reflect.items)) } - if self.register { - expanded_attrs - .attrs - .push(tokens::auto_register_type(plugin.clone(), self.into())); + if self.args.args.base.register { + expand_attrs.attrs.push(tokens::auto_register_type(self.into())); } - expanded_attrs } } -#[cfg(test)] -mod tests { - use super::*; - use crate::test_util::combo::combos_one_per_group_or_skip_with; - use crate::test_util::macros::*; - use crate::test_util::test_params::{_inject_derive, Side, TestParams as _TestParams}; - use internal_test_proc_macro::xtest; - use internal_test_util::extract_punctuated_paths; - use syn::parse_quote; - - type TestParams = _TestParams; - - pub trait TestParamsExt { - fn with_global(self, derive: bool) -> Self; - fn with_entity_event(self, derive: bool) -> Self; - } - - impl TestParamsExt for TestParams { - /// calling order matters - fn with_global(mut self, derive: bool) -> Self { - if derive { - _inject_derive( - &mut self.expected_derives.attrs, - &[tokens::derive_event_path()], - Side::Left, - ); - } - self - } - - /// calling order matters - fn with_entity_event(mut self, derive: bool) -> Self { - if derive { - _inject_derive( - &mut self.expected_derives.attrs, - &[tokens::derive_entity_event_path()], - Side::Left, - ); - } - self - } - } - - #[xtest] - fn test_expand_back_into_args() -> syn::Result<()> { - for args in combos_one_per_group_or_skip_with( - &[ - vec![quote!(derive), quote!(derive(Debug, Default))], - vec![quote!(reflect), quote!(reflect(Debug, Default))], - vec![quote!(register)], - ], - // TODO: target(global) is always emitted when no target is provided - quote!(target(global)), - ) { - println!("checking args: {}", quote! { #(#args),*}); - assert_vec_args_expand!(plugin!(parse_quote!(Test)), EventArgs, args); - } - Ok(()) - } - - fn extras() -> Vec { - extract_punctuated_paths(parse_quote!(Debug, Default)) - .into_iter() - .map(NonEmptyPath::try_from) - .collect::>>() - .expect("failed to extract punctuated paths") - } - - #[xtest] - fn test_expand_attrs_default() -> anyhow::Result<()> { - TestParams::from_args(quote! { - plugin = Test, - })? - .test()?; - Ok(()) - } - - #[xtest] - fn test_expand_attrs_no_global_or_entity_flags() -> anyhow::Result<()> { - let extras = extras(); - TestParams::from_args(quote! { - plugin = Test, - derive(#(#extras),*), - reflect(#(#extras),*), - register, - })? - .with_derive(extras.clone()) - .with_global(true) - .with_reflect(extras.clone(), true) - .with_register() - .test()?; - Ok(()) - } - - #[xtest] - fn test_expand_attrs_global_event() -> anyhow::Result<()> { - let extras = extras(); - TestParams::from_args(quote! { - plugin = Test, - target(global), - derive(#(#extras),*), - reflect(#(#extras),*), - register, - })? - .with_derive(extras.clone()) - .with_global(true) - .with_reflect(extras.clone(), true) - .with_register() - .test()?; - Ok(()) - } - - #[xtest] - fn test_expand_attrs_global_event_no_derive() -> anyhow::Result<()> { - let extras = extras(); - TestParams::from_args(quote! { - plugin = Test, - target(global), - reflect(#(#extras),*), - register, - })? - .with_global(false) - .with_reflect(extras.clone(), false) - .with_register() - .test()?; - Ok(()) - } - - #[xtest] - fn test_expand_attrs_entity_event() -> anyhow::Result<()> { - let extras = extras(); - TestParams::from_args(quote! { - plugin = Test, - target(entity), - derive(#(#extras),*), - reflect(#(#extras),*), - register, - })? - .with_derive(extras.clone()) - .with_entity_event(true) - .with_reflect(extras.clone(), true) - .with_register() - .test()?; - Ok(()) - } - - #[xtest] - fn test_expand_attrs_entity_event_no_derive() -> anyhow::Result<()> { - let extras = extras(); - TestParams::from_args(quote! { - plugin = Test, - target(entity), - reflect(#(#extras),*), - register, - })? - .with_entity_event(false) - .with_reflect(extras.clone(), false) - .with_register() - .test()?; - Ok(()) - } - - #[xtest] - fn test_expand_attrs_entity_event_propagate() -> anyhow::Result<()> { - let extras = extras(); - TestParams::from_args(quote! { - plugin = Test, - target(entity), - derive(#(#extras),*), - reflect(#(#extras),*), - register, - })? - .with_derive(extras.clone()) - .with_entity_event(true) - .with_reflect(extras.clone(), true) - .with_register() - .test()?; - Ok(()) - } - - #[xtest] - fn test_expand_attrs_entity_event_propagate_custom() -> anyhow::Result<()> { - let extras = extras(); - TestParams::from_args(quote! { - plugin = Test, - target(entity), - derive(#(#extras),*), - reflect(#(#extras),*), - register, - })? - .with_derive(extras.clone()) - .with_entity_event(true) - .with_reflect(extras.clone(), true) - .with_register() - .test()?; - Ok(()) - } - - #[xtest] - fn test_expand_attrs_entity_event_propagate_custom_and_auto_propagate() -> anyhow::Result<()> { - let extras = extras(); - TestParams::from_args(quote! { - plugin = Test, - target(entity), - derive(#(#extras),*), - reflect(#(#extras),*), - register, - })? - .with_derive(extras.clone()) - .with_entity_event(true) - .with_reflect(extras.clone(), true) - .with_register() - .test()?; - Ok(()) - } - - #[xtest] - fn test_expand_attrs_entity_event_auto_propagate() -> anyhow::Result<()> { - let extras = extras(); - TestParams::from_args(quote! { - plugin = Test, - target(entity), - derive(#(#extras),*), - reflect(#(#extras),*), - register, - })? - .with_derive(extras.clone()) - .with_entity_event(true) - .with_reflect(extras.clone(), true) - .with_register() - .test()?; - Ok(()) - } -} +impl_from_default!(EventArgs => (RegisterTypeArgs)); diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_message.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_message.rs index 669b3e8e..c5a01444 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_message.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_message.rs @@ -1,170 +1,64 @@ -use crate::__private::attribute::RewriteAttribute; -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::{ExpandAttrs, tokens}; -use crate::macro_api::attributes::AttributeIdent; -use crate::macro_api::attributes::actions::auto_add_message::AddMessageArgs; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::prelude::*; -use crate::syntax::ast::flag_or_list::FlagOrList; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::non_empty_path::NonEmptyPath; +use crate::{ + codegen::{ + ExpandAttrs, + tokens, + }, + macro_api::prelude::*, + syntax::{ + ast::flag_or_list::FlagOrList, + validated::non_empty_path::NonEmptyPath, + }, + util::macros::impl_from_default, +}; use darling::FromMeta; -use proc_macro2::{Ident, TokenStream as MacroStream, TokenStream}; -use quote::quote; +use proc_macro2::Ident; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] pub struct MessageArgs { - #[darling(multiple)] - pub generics: Vec, pub derive: FlagOrList, pub reflect: FlagOrList, pub register: bool, } -impl GenericsArgs for MessageArgs { - fn type_lists(&self) -> &[TypeList] { - &self.generics - } -} - impl AttributeIdent for MessageArgs { const IDENT: &'static str = "auto_message"; } impl<'a> From<&'a MessageArgs> for RegisterTypeArgs { - fn from(value: &'a MessageArgs) -> Self { - Self { - generics: value.generics.clone(), - } + fn from(_: &'a MessageArgs) -> Self { + Self {} } } impl<'a> From<&'a MessageArgs> for AddMessageArgs { - fn from(value: &'a MessageArgs) -> Self { - Self { - generics: value.generics.clone(), - } - } -} - -impl ArgsBackToTokens for MessageArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - let mut items = vec![]; - items.extend(self.generics().to_attribute_arg_vec_tokens()); - if self.derive.present { - items.push(self.derive.to_outer_tokens("derive")); - } - if self.reflect.present { - items.push(self.reflect.to_outer_tokens("reflect")); - } - if self.register { - items.push(quote!(register)); - } - tokens.extend(quote! { #(#items),* }); + fn from(_: &'a MessageArgs) -> Self { + Self {} } } -impl RewriteAttribute for MessageArgs { - fn expand_args(&self, plugin: &NonEmptyPath) -> MacroStream { - let mut args = Vec::new(); - args.push(quote! { plugin = #plugin }); - if !self.generics().is_empty() { - args.extend(self.generics().to_attribute_arg_vec_tokens()); - } - quote! { #(#args),* } - } - - fn expand_attrs(&self, plugin: &NonEmptyPath) -> ExpandAttrs { - let mut expanded_attrs = ExpandAttrs::default(); +pub type IaMessage = + ItemAttribute, AllowStructOrEnum>; +pub type MessageAttrExpandEmitter = AttrExpansionEmitter; - if self.derive.present { - expanded_attrs - .attrs - .push(tokens::derive_message(&self.derive.items)); +impl AttrExpansionEmitterToExpandAttr for MessageAttrExpandEmitter { + fn to_expand_attrs(&self, expand_attrs: &mut ExpandAttrs) { + if self.args.args.base.derive.present { + expand_attrs.attrs.push(tokens::derive_message(&self.args.args.base.derive.items)); } - if self.reflect.present { - if self.derive.present { - expanded_attrs.attrs.push(tokens::derive_reflect()); + if self.args.args.base.reflect.present { + if self.args.args.base.derive.present { + expand_attrs.attrs.push(tokens::derive_reflect()); } - expanded_attrs.append(tokens::reflect(&self.reflect.items)) + expand_attrs.append(tokens::reflect(&self.args.args.base.reflect.items)) } - if self.register { - expanded_attrs - .attrs - .push(tokens::auto_register_type(plugin.clone(), self.into())); + if self.args.args.base.register { + expand_attrs.attrs.push(tokens::auto_register_type(self.into())); } // TODO: should this be gated behind a flag? - expanded_attrs - .attrs - .push(tokens::auto_add_message(plugin.clone(), self.into())); - - expanded_attrs + expand_attrs.attrs.push(tokens::auto_add_message(self.into())); } } -#[cfg(test)] -mod tests { - use super::*; - use crate::macro_api::with_plugin::WithPlugin; - use crate::test_util::combo::combos_one_per_group_or_skip; - use crate::test_util::macros::*; - use darling::ast::NestedMeta; - use internal_test_proc_macro::xtest; - use internal_test_util::{extract_punctuated_paths, vec_spread}; - use quote::ToTokens; - use syn::parse_quote; - - #[xtest] - fn test_expand_back_into_args() -> syn::Result<()> { - for args in combos_one_per_group_or_skip(&[ - vec![quote!(derive), quote!(derive(Debug, Default))], - vec![quote!(reflect), quote!(reflect(Debug, Default))], - vec![quote!(register)], - ]) { - println!("checking args: {}", quote! { #(#args),*}); - assert_vec_args_expand!(plugin!(parse_quote!(Test)), MessageArgs, args); - } - Ok(()) - } - - #[xtest] - fn test_expand_attrs_global() -> syn::Result<()> { - let extras = extract_punctuated_paths(parse_quote!(Debug, Default)) - .into_iter() - .map(NonEmptyPath::try_from) - .collect::>>()?; - let args: NestedMeta = parse_quote! {_( - plugin = Test, - derive(#(#extras),*), - reflect(#(#extras),*), - register, - )}; - let args = WithPlugin::::from_nested_meta(&args)?; - let derive_args = vec_spread![tokens::derive_message_path(), ..extras.clone(),]; - let derive_reflect_path = tokens::derive_reflect_path(); - let reflect_args = vec_spread![..extras,]; - let reflect_attr = tokens::reflect(reflect_args.iter().map(NonEmptyPath::last_ident)); - assert_eq!( - args.inner - .expand_attrs(&args.plugin()) - .to_token_stream() - .to_string(), - ExpandAttrs { - use_items: reflect_attr.use_items, - attrs: vec![ - quote! { #[derive(#(#derive_args),*)] }, - // TODO: merge these derives - quote! { #[derive(#derive_reflect_path)] }, - quote! { #[reflect(#(#reflect_args),*)] }, - tokens::auto_register_type(args.plugin(), (&args.inner).into()), - tokens::auto_add_message(args.plugin(), (&args.inner).into()), - ] - } - .to_token_stream() - .to_string() - ); - Ok(()) - } -} +impl_from_default!(MessageArgs => (RegisterTypeArgs, AddMessageArgs)); diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_observer.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_observer.rs index d007a489..27e273e3 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_observer.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_observer.rs @@ -1,117 +1,41 @@ -use crate::__private::attribute::RewriteAttribute; -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::{ExpandAttrs, tokens}; -use crate::macro_api::attributes::AttributeIdent; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::prelude::*; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::non_empty_path::NonEmptyPath; +use crate::{ + codegen::{ + ExpandAttrs, + tokens, + }, + macro_api::prelude::*, + util::macros::impl_from_default, +}; use darling::FromMeta; -use proc_macro2::{TokenStream as MacroStream, TokenStream}; -use quote::quote; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] -pub struct ObserverArgs { - #[darling(multiple)] - pub generics: Vec, -} - -impl GenericsArgs for ObserverArgs { - fn type_lists(&self) -> &[TypeList] { - &self.generics - } -} +pub struct ObserverArgs {} impl AttributeIdent for ObserverArgs { const IDENT: &'static str = "auto_observer"; } impl<'a> From<&'a ObserverArgs> for RegisterTypeArgs { - fn from(value: &'a ObserverArgs) -> Self { - Self { - generics: value.generics.clone(), - } + fn from(_: &'a ObserverArgs) -> Self { + Self {} } } impl<'a> From<&'a ObserverArgs> for AddObserverArgs { - fn from(value: &'a ObserverArgs) -> Self { - AddObserverArgs { - generics: value.generics.clone(), - } + fn from(_: &'a ObserverArgs) -> Self { + AddObserverArgs {} } } -impl ArgsBackToTokens for ObserverArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - AddObserverArgs::from(self).back_to_inner_arg_tokens(tokens); - } -} +pub type IaObserver = + ItemAttribute, AllowFn>; +pub type ObserverAttrExpandEmitter = AttrExpansionEmitter; -impl RewriteAttribute for ObserverArgs { - fn expand_args(&self, plugin: &NonEmptyPath) -> MacroStream { - let mut args = Vec::new(); - args.push(quote! { plugin = #plugin }); - if !self.generics().is_empty() { - args.extend(self.generics().to_attribute_arg_vec_tokens()); - } - quote! { #(#args),* } - } - - fn expand_attrs(&self, plugin: &NonEmptyPath) -> ExpandAttrs { - let mut expanded_attrs = ExpandAttrs::default(); - expanded_attrs - .attrs - .push(tokens::auto_add_observer(plugin.clone(), self.into())); - expanded_attrs +impl AttrExpansionEmitterToExpandAttr for ObserverAttrExpandEmitter { + fn to_expand_attrs(&self, expand_attrs: &mut ExpandAttrs) { + expand_attrs.attrs.push(tokens::auto_add_observer(self.into())); } } -#[cfg(test)] -mod tests { - use super::*; - use crate::macro_api::with_plugin::WithPlugin; - use crate::test_util::macros::*; - use darling::ast::NestedMeta; - use internal_test_proc_macro::xtest; - use internal_test_util::vec_spread; - use quote::ToTokens; - use syn::parse_quote; - - #[xtest] - fn test_expand_back_into_args() -> syn::Result<()> { - let args = vec![quote! {}]; - println!("checking args: {}", quote! { #(#args),*}); - assert_vec_args_expand!(plugin!(parse_quote!(Test)), ObserverArgs, args); - Ok(()) - } - - #[xtest] - fn test_expand_attrs_global() -> syn::Result<()> { - let args: NestedMeta = parse_quote! {_( - plugin = Test, - )}; - let args = WithPlugin::::from_nested_meta(&args)?; - println!( - "{}", - args.inner.expand_attrs(&args.plugin()).to_token_stream() - ); - assert_eq!( - args.inner - .expand_attrs(&args.plugin()) - .to_token_stream() - .to_string(), - ExpandAttrs { - use_items: vec![], - attrs: vec_spread![tokens::auto_add_observer( - args.plugin(), - (&args.inner).into() - ),] - } - .to_token_stream() - .to_string() - ); - Ok(()) - } -} +impl_from_default!(ObserverArgs => (AddObserverArgs)); diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_resource.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_resource.rs index 01210807..f993e17e 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_resource.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_resource.rs @@ -1,43 +1,35 @@ -use crate::__private::attribute::RewriteAttribute; -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::{ExpandAttrs, tokens}; -use crate::macro_api::attributes::AttributeIdent; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::prelude::*; -use crate::syntax::ast::flag_or_list::FlagOrList; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::non_empty_path::NonEmptyPath; +use crate::{ + codegen::{ + ExpandAttrs, + tokens, + }, + macro_api::prelude::*, + syntax::{ + ast::flag_or_list::FlagOrList, + validated::non_empty_path::NonEmptyPath, + }, + util::macros::impl_from_default, +}; use darling::FromMeta; -use proc_macro2::{Ident, TokenStream as MacroStream, TokenStream}; -use quote::quote; +use proc_macro2::Ident; use syn::parse_quote; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] pub struct ResourceArgs { - #[darling(multiple)] - pub generics: Vec, pub derive: FlagOrList, pub reflect: FlagOrList, pub register: bool, pub init: bool, } -impl GenericsArgs for ResourceArgs { - fn type_lists(&self) -> &[TypeList] { - &self.generics - } -} - impl AttributeIdent for ResourceArgs { const IDENT: &'static str = "auto_resource"; } impl<'a> From<&'a ResourceArgs> for RegisterTypeArgs { - fn from(value: &'a ResourceArgs) -> Self { - Self { - generics: value.generics.clone(), - } + fn from(_: &'a ResourceArgs) -> Self { + Self {} } } @@ -47,129 +39,30 @@ impl<'a> From<&'a ResourceArgs> for InitResourceArgs { } } -impl ArgsBackToTokens for ResourceArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - let mut items = vec![]; - items.extend(self.generics().to_attribute_arg_vec_tokens()); - if self.derive.present { - items.push(self.derive.to_outer_tokens("derive")); - } - if self.reflect.present { - items.push(self.reflect.to_outer_tokens("reflect")); - } - if self.register { - items.push(quote!(register)); - } - if self.init { - items.push(quote!(init)); - } - tokens.extend(quote! { #(#items),* }); - } -} - -impl RewriteAttribute for ResourceArgs { - fn expand_args(&self, plugin: &NonEmptyPath) -> MacroStream { - let mut args = Vec::new(); - args.push(quote! { plugin = #plugin }); - if !self.generics().is_empty() { - args.extend(self.generics().to_attribute_arg_vec_tokens()); +pub type IaResource = + ItemAttribute, AllowStructOrEnum>; +pub type ResourceAttrExpandEmitter = AttrExpansionEmitter; +impl AttrExpansionEmitterToExpandAttr for ResourceAttrExpandEmitter { + fn to_expand_attrs(&self, expand_attrs: &mut ExpandAttrs) { + if self.args.args.base.derive.present { + expand_attrs.attrs.push(tokens::derive_resource(&self.args.args.base.derive.items)); } - quote! { #(#args),* } - } - - fn expand_attrs(&self, plugin: &NonEmptyPath) -> ExpandAttrs { - let mut expanded_attrs = ExpandAttrs::default(); - - if self.derive.present { - expanded_attrs - .attrs - .push(tokens::derive_resource(&self.derive.items)); - } - if self.reflect.present { - if self.derive.present { - expanded_attrs.attrs.push(tokens::derive_reflect()); + if self.args.args.base.reflect.present { + if self.args.args.base.derive.present { + expand_attrs.attrs.push(tokens::derive_reflect()); } let component_ident: Ident = parse_quote!(Resource); - let items = std::iter::once(&component_ident).chain(self.reflect.items.iter()); - expanded_attrs.append(tokens::reflect(items)) + let items = + std::iter::once(&component_ident).chain(self.args.args.base.reflect.items.iter()); + expand_attrs.append(tokens::reflect(items)) } - if self.register { - expanded_attrs - .attrs - .push(tokens::auto_register_type(plugin.clone(), self.into())); + if self.args.args.base.register { + expand_attrs.attrs.push(tokens::auto_register_type(self.into())); } - if self.init { - expanded_attrs - .attrs - .push(tokens::auto_init_resource(plugin.clone(), self.into())); + if self.args.args.base.init { + expand_attrs.attrs.push(tokens::auto_init_resource(self.into())); } - expanded_attrs } } -#[cfg(test)] -mod tests { - use super::*; - use crate::macro_api::with_plugin::WithPlugin; - use crate::test_util::combo::combos_one_per_group_or_skip; - use crate::test_util::macros::*; - use darling::ast::NestedMeta; - use internal_test_proc_macro::xtest; - use internal_test_util::{extract_punctuated_paths, vec_spread}; - use quote::ToTokens; - use syn::parse_quote; - - #[xtest] - fn test_expand_back_into_args() -> syn::Result<()> { - for args in combos_one_per_group_or_skip(&[ - vec![quote!(derive), quote!(derive(Debug, Default))], - vec![quote!(reflect), quote!(reflect(Debug, Default))], - vec![quote!(register)], - vec![quote!(init)], - ]) { - println!("checking args: {}", quote! { #(#args),*}); - assert_vec_args_expand!(plugin!(parse_quote!(Test)), ResourceArgs, args); - } - Ok(()) - } - - #[xtest] - fn test_expand_attrs_global() -> syn::Result<()> { - let extras = extract_punctuated_paths(parse_quote!(Debug, Default)) - .into_iter() - .map(NonEmptyPath::try_from) - .collect::>>()?; - let args: NestedMeta = parse_quote! {_( - plugin = Test, - derive(#(#extras),*), - reflect(#(#extras),*), - register, - init, - )}; - let args = WithPlugin::::from_nested_meta(&args)?; - let derive_args = vec_spread![tokens::derive_resource_path(), ..extras.clone(),]; - let derive_reflect_path = tokens::derive_reflect_path(); - let reflect_args = vec_spread![parse_quote!(Resource), ..extras,]; - let reflect_attr = tokens::reflect(reflect_args.iter().map(NonEmptyPath::last_ident)); - assert_eq!( - args.inner - .expand_attrs(&args.plugin()) - .to_token_stream() - .to_string(), - ExpandAttrs { - use_items: reflect_attr.use_items, - attrs: vec![ - quote! { #[derive(#(#derive_args),*)] }, - // TODO: merge these derives - quote! { #[derive(#derive_reflect_path)] }, - quote! { #[reflect(#(#reflect_args),*)] }, - tokens::auto_register_type(args.plugin(), (&args.inner).into()), - tokens::auto_init_resource(args.plugin(), (&args.inner).into()), - ] - } - .to_token_stream() - .to_string() - ); - Ok(()) - } -} +impl_from_default!(ResourceArgs => (RegisterTypeArgs, InitResourceArgs)); diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_states.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_states.rs index 999c9853..0cc673e8 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_states.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_states.rs @@ -1,15 +1,17 @@ -use crate::__private::attribute::RewriteAttribute; -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::{ExpandAttrs, tokens}; -use crate::macro_api::attributes::AttributeIdent; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::prelude::*; -use crate::syntax::ast::flag_or_list::FlagOrList; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::non_empty_path::NonEmptyPath; +use crate::{ + codegen::{ + ExpandAttrs, + tokens, + }, + macro_api::prelude::*, + syntax::{ + ast::flag_or_list::FlagOrList, + validated::non_empty_path::NonEmptyPath, + }, + util::macros::impl_from_default, +}; use darling::FromMeta; -use proc_macro2::{Ident, TokenStream as MacroStream, TokenStream}; -use quote::quote; +use proc_macro2::Ident; #[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] #[darling(derive_syn_parse, default)] @@ -20,12 +22,6 @@ pub struct StatesArgs { pub init: bool, } -impl GenericsArgs for StatesArgs { - fn type_lists(&self) -> &[TypeList] { - &[] - } -} - impl AttributeIdent for StatesArgs { const IDENT: &'static str = "auto_states"; } @@ -36,135 +32,39 @@ impl<'a> From<&'a StatesArgs> for RegisterTypeArgs { } } -impl<'a> From<&'a StatesArgs> for InitStateArgs { +impl<'a> From<&'a StatesArgs> for RegisterStateTypeArgs { fn from(_value: &'a StatesArgs) -> Self { Self::default() } } -impl ArgsBackToTokens for StatesArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - let mut items = vec![]; - items.extend(self.generics().to_attribute_arg_vec_tokens()); - if self.derive.present { - items.push(self.derive.to_outer_tokens("derive")); - } - if self.reflect.present { - items.push(self.reflect.to_outer_tokens("reflect")); - } - if self.register { - items.push(quote!(register)); - } - if self.init { - items.push(quote!(init)); - } - tokens.extend(quote! { #(#items),* }); +impl<'a> From<&'a StatesArgs> for InitStateArgs { + fn from(_value: &'a StatesArgs) -> Self { + Self::default() } } - -impl RewriteAttribute for StatesArgs { - fn expand_args(&self, plugin: &NonEmptyPath) -> MacroStream { - let mut args = Vec::new(); - args.push(quote! { plugin = #plugin }); - if !self.generics().is_empty() { - args.extend(self.generics().to_attribute_arg_vec_tokens()); +pub type IaState = + ItemAttribute, AllowStructOrEnum>; +pub type StateAttrExpandEmitter = AttrExpansionEmitter; +impl AttrExpansionEmitterToExpandAttr for StateAttrExpandEmitter { + fn to_expand_attrs(&self, expand_attrs: &mut ExpandAttrs) { + if self.args.args.base.derive.present { + expand_attrs.append(tokens::derive_states(&self.args.args.base.derive.items)); } - quote! { #(#args),* } - } - - fn expand_attrs(&self, plugin: &NonEmptyPath) -> ExpandAttrs { - let mut expanded_attrs = ExpandAttrs::default(); - - if self.derive.present { - expanded_attrs.append(tokens::derive_states(&self.derive.items)); - } - if self.reflect.present { - if self.derive.present { - expanded_attrs.attrs.push(tokens::derive_reflect()); + if self.args.args.base.reflect.present { + if self.args.args.base.derive.present { + expand_attrs.attrs.push(tokens::derive_reflect()); } - expanded_attrs.append(tokens::reflect(&self.reflect.items)) + expand_attrs.append(tokens::reflect(&self.args.args.base.reflect.items)) } - if self.register { - expanded_attrs - .attrs - .push(tokens::auto_register_type(plugin.clone(), self.into())); + if self.args.args.base.register { + expand_attrs.attrs.push(tokens::auto_register_type(self.into())); + expand_attrs.attrs.push(tokens::auto_register_state_type(self.into())); } - if self.init { - expanded_attrs - .attrs - .push(tokens::auto_init_states(plugin.clone(), self.into())); + if self.args.args.base.init { + expand_attrs.attrs.push(tokens::auto_init_states(self.into())); } - expanded_attrs } } -#[cfg(test)] -mod tests { - use super::*; - use crate::macro_api::with_plugin::WithPlugin; - use crate::test_util::combo::combos_one_per_group_or_skip; - use crate::test_util::macros::*; - use darling::ast::NestedMeta; - use internal_test_proc_macro::xtest; - use internal_test_util::{extract_punctuated_paths, vec_spread}; - use quote::ToTokens; - use syn::parse_quote; - - #[xtest] - fn test_expand_back_into_args() -> syn::Result<()> { - for args in combos_one_per_group_or_skip(&[ - vec![quote!(derive), quote!(derive(Debug, Default))], - vec![quote!(reflect), quote!(reflect(Debug, Default))], - vec![quote!(register)], - vec![quote!(init)], - ]) { - println!("checking args: {}", quote! { #(#args),*}); - assert_vec_args_expand!(plugin!(parse_quote!(Test)), StatesArgs, args); - } - Ok(()) - } - - #[xtest] - fn test_expand_attrs_global() -> syn::Result<()> { - let extras = extract_punctuated_paths(parse_quote!(A, B)) - .into_iter() - .map(NonEmptyPath::try_from) - .collect::>>()?; - let args: NestedMeta = parse_quote! {_( - plugin = Test, - derive(#(#extras),*), - reflect(#(#extras),*), - register, - init, - )}; - let args = WithPlugin::::from_nested_meta(&args)?; - let derive_attr = tokens::derive_states(&extras); - let derive_reflect_path = tokens::derive_reflect_path(); - let reflect_args = vec_spread![..extras,]; - let reflect_attr = tokens::reflect(reflect_args.iter().map(NonEmptyPath::last_ident)); - println!( - "{}", - args.inner.expand_attrs(&args.plugin()).to_token_stream() - ); - assert_eq!( - args.inner - .expand_attrs(&args.plugin()) - .to_token_stream() - .to_string(), - ExpandAttrs { - use_items: [derive_attr.use_items, reflect_attr.use_items].concat(), - attrs: vec_spread![ - ..derive_attr.attrs, - // TODO: merge these derives - quote! { #[derive(#derive_reflect_path)] }, - quote! { #[reflect(#(#reflect_args),*)] }, - tokens::auto_register_type(args.plugin(), (&args.inner).into()), - tokens::auto_init_states(args.plugin(), (&args.inner).into()), - ] - } - .to_token_stream() - .to_string() - ); - Ok(()) - } -} +impl_from_default!(StatesArgs => (RegisterTypeArgs, RegisterStateTypeArgs, InitStateArgs)); diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_sub_states.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_sub_states.rs new file mode 100644 index 00000000..f88a8b0b --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_sub_states.rs @@ -0,0 +1,73 @@ +use crate::{ + codegen::{ + ExpandAttrs, + tokens, + }, + macro_api::prelude::*, + syntax::{ + ast::flag_or_list::FlagOrList, + validated::non_empty_path::NonEmptyPath, + }, + util::macros::impl_from_default, +}; +use darling::FromMeta; +use proc_macro2::Ident; + +#[derive(FromMeta, Debug, Default, Clone, PartialEq, Hash)] +#[darling(derive_syn_parse, default)] +pub struct SubStatesArgs { + pub derive: FlagOrList, + pub reflect: FlagOrList, + pub register: bool, + pub init: bool, +} + +impl AttributeIdent for SubStatesArgs { + const IDENT: &'static str = "auto_sub_states"; +} + +impl<'a> From<&'a SubStatesArgs> for RegisterTypeArgs { + fn from(_value: &'a SubStatesArgs) -> Self { + Self::default() + } +} + +impl<'a> From<&'a SubStatesArgs> for RegisterStateTypeArgs { + fn from(_value: &'a SubStatesArgs) -> Self { + Self::default() + } +} + +impl<'a> From<&'a SubStatesArgs> for InitSubStateArgs { + fn from(_value: &'a SubStatesArgs) -> Self { + Self::default() + } +} + +pub type IaSubState = + ItemAttribute, AllowStructOrEnum>; + +pub type SubStateAttrExpandEmitter = AttrExpansionEmitter; + +impl AttrExpansionEmitterToExpandAttr for SubStateAttrExpandEmitter { + fn to_expand_attrs(&self, expand_attrs: &mut ExpandAttrs) { + if self.args.args.base.derive.present { + expand_attrs.append(tokens::derive_sub_states(&self.args.args.base.derive.items)); + } + if self.args.args.base.reflect.present { + if self.args.args.base.derive.present { + expand_attrs.attrs.push(tokens::derive_reflect()); + } + expand_attrs.append(tokens::reflect(&self.args.args.base.reflect.items)) + } + if self.args.args.base.register { + expand_attrs.attrs.push(tokens::auto_register_type(self.into())); + expand_attrs.attrs.push(tokens::auto_register_state_type(self.into())); + } + if self.args.args.base.init { + expand_attrs.attrs.push(tokens::auto_init_sub_states(self.into())); + } + } +} + +impl_from_default!(SubStatesArgs => (RegisterTypeArgs, RegisterStateTypeArgs, InitSubStateArgs)); diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_system.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_system.rs index 2040b991..2fd6ae4f 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_system.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/auto_system.rs @@ -1,125 +1,50 @@ -use crate::__private::attribute::RewriteAttribute; -use crate::codegen::tokens::ArgsBackToTokens; -use crate::codegen::{ExpandAttrs, tokens}; -use crate::macro_api::attributes::AttributeIdent; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::macro_api::attributes::prelude::*; -use crate::macro_api::schedule_config::ScheduleWithScheduleConfigArgs; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::non_empty_path::NonEmptyPath; +use crate::{ + codegen::{ + ExpandAttrs, + tokens, + }, + macro_api::{ + prelude::*, + schedule_config::ScheduleWithScheduleConfigArgs, + }, +}; use darling::FromMeta; -use proc_macro2::{TokenStream as MacroStream, TokenStream}; -use quote::quote; #[derive(FromMeta, Debug, Clone, PartialEq, Hash)] #[darling(derive_syn_parse)] pub struct SystemArgs { - #[darling(multiple)] - pub generics: Vec, #[darling(flatten)] pub schedule_config: ScheduleWithScheduleConfigArgs, } -impl GenericsArgs for SystemArgs { - fn type_lists(&self) -> &[TypeList] { - &self.generics - } -} - impl AttributeIdent for SystemArgs { const IDENT: &'static str = "auto_system"; } impl<'a> From<&'a SystemArgs> for RegisterTypeArgs { - fn from(value: &'a SystemArgs) -> Self { - Self { - generics: value.generics.clone(), - } + fn from(_: &'a SystemArgs) -> Self { + Self {} } } impl<'a> From<&'a SystemArgs> for AddSystemArgs { fn from(value: &'a SystemArgs) -> Self { - AddSystemArgs { - generics: value.generics.clone(), - schedule_config: value.schedule_config.clone(), - } + AddSystemArgs { schedule_config: value.schedule_config.clone() } } } -impl ArgsBackToTokens for SystemArgs { - fn back_to_inner_arg_tokens(&self, tokens: &mut TokenStream) { - let mut items = vec![]; - items.extend(self.generics().to_attribute_arg_vec_tokens()); - items.extend(self.schedule_config.to_inner_arg_tokens_vec()); - tokens.extend(quote! { #(#items),* }); - } -} +pub type IaSystem = + ItemAttribute, AllowFn>; +pub type SystemAttrExpandEmitter = AttrExpansionEmitter; -impl RewriteAttribute for SystemArgs { - fn expand_args(&self, plugin: &NonEmptyPath) -> MacroStream { - let mut args = Vec::new(); - args.push(quote! { plugin = #plugin }); - if !self.generics().is_empty() { - args.extend(self.generics().to_attribute_arg_vec_tokens()); - } - quote! { #(#args),* } - } - - fn expand_attrs(&self, plugin: &NonEmptyPath) -> ExpandAttrs { - let mut expanded_attrs = ExpandAttrs::default(); - expanded_attrs - .attrs - .push(tokens::auto_add_systems(plugin.clone(), self.into())); - expanded_attrs +impl AttrExpansionEmitterToExpandAttr for SystemAttrExpandEmitter { + fn to_expand_attrs(&self, expand_attrs: &mut ExpandAttrs) { + expand_attrs.attrs.push(tokens::auto_add_systems(self.into())); } } -#[cfg(test)] -mod tests { - use super::*; - use crate::macro_api::with_plugin::WithPlugin; - use crate::test_util::macros::*; - use darling::ast::NestedMeta; - use internal_test_proc_macro::xtest; - use internal_test_util::vec_spread; - use quote::ToTokens; - use syn::parse_quote; - - #[xtest] - fn test_expand_back_into_args() -> syn::Result<()> { - let args = vec![quote! { schedule = Update }]; - println!("checking args: {}", quote! { #(#args),*}); - assert_vec_args_expand!(plugin!(parse_quote!(Test)), SystemArgs, args); - Ok(()) - } - - #[xtest] - fn test_expand_attrs_global() -> syn::Result<()> { - let args: NestedMeta = parse_quote! {_( - plugin = Test, - schedule = Update, - )}; - let args = WithPlugin::::from_nested_meta(&args)?; - println!( - "{}", - args.inner.expand_attrs(&args.plugin()).to_token_stream() - ); - assert_eq!( - args.inner - .expand_attrs(&args.plugin()) - .to_token_stream() - .to_string(), - ExpandAttrs { - use_items: vec![], - attrs: vec_spread![tokens::auto_add_systems( - args.plugin(), - (&args.inner).into() - ),] - } - .to_token_stream() - .to_string() - ); - Ok(()) +impl From for AddSystemArgs { + fn from(value: SystemArgs) -> Self { + Self { schedule_config: value.schedule_config } } } diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/mod.rs index a379c909..ca9210b1 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/rewrites/mod.rs @@ -1,7 +1,20 @@ -pub mod auto_component; -pub mod auto_event; -pub mod auto_message; -pub mod auto_observer; -pub mod auto_resource; -pub mod auto_states; -pub mod auto_system; +mod auto_component; +mod auto_event; +mod auto_message; +mod auto_observer; +mod auto_resource; +mod auto_states; +mod auto_sub_states; +mod auto_system; + +pub mod prelude { + pub use super::*; + pub use auto_component::*; + pub use auto_event::*; + pub use auto_message::*; + pub use auto_observer::*; + pub use auto_resource::*; + pub use auto_states::*; + pub use auto_sub_states::*; + pub use auto_system::*; +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/traits/generics.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/traits/generics.rs deleted file mode 100644 index 0fc0da44..00000000 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/traits/generics.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::generics::GenericsCollection; - -pub trait GenericsArgs { - // TODO: see impl ToTokens for Generics - const TURBOFISH: bool = false; - fn type_lists(&self) -> &[TypeList]; - fn generics(&self) -> GenericsCollection { - GenericsCollection(self.type_lists().to_vec()) - } -} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/traits/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/attributes/traits/mod.rs deleted file mode 100644 index db49589d..00000000 --- a/crates/bevy_auto_plugin_shared/src/macro_api/attributes/traits/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod generics; - -pub mod prelude { - pub use crate::macro_api::attributes::traits::generics::GenericsArgs; -} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/composed.rs b/crates/bevy_auto_plugin_shared/src/macro_api/composed.rs new file mode 100644 index 00000000..1339ba19 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/composed.rs @@ -0,0 +1,181 @@ +use crate::{ + macro_api::prelude::*, + syntax::ast::type_list::TypeList, +}; +use darling::{ + FromMeta, + ast::NestedMeta, +}; +use proc_macro2::TokenStream; +use quote::ToTokens; +use std::{ + collections::HashSet, + hash::{ + Hash, + Hasher, + }, +}; +use syn::{ + parse::{ + Parse, + ParseStream, + }, + parse_quote, + punctuated::Punctuated, +}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Composed { + pub base: CBase, + pub plugin: MPlugin, + pub generics: MGenerics, +} + +impl Hash for Composed +where + C: Hash, + P: Hash, + G: Hash, +{ + fn hash(&self, state: &mut H) { + self.base.hash(state); + self.plugin.hash(state); + self.generics.hash(state); + } +} + +impl FromMeta for Composed +where + CBase: FromMeta, + MPlugin: Mixin, + MGenerics: Mixin, +{ + fn from_list(items: &[NestedMeta]) -> darling::Result { + let plugin_keys: HashSet<&str> = MPlugin::keys().iter().copied().collect(); + let generics_keys: HashSet<&str> = MGenerics::keys().iter().copied().collect(); + + let mut plugin_bucket = Vec::::new(); + let mut generics_bucket = Vec::::new(); + let mut base_bucket = Vec::::new(); + + for nm in items { + let key_opt = match &nm { + NestedMeta::Meta(syn::Meta::Path(p)) => { + p.segments.last().map(|s| s.ident.to_string()) + } + NestedMeta::Meta(syn::Meta::NameValue(nv)) => { + nv.path.segments.last().map(|s| s.ident.to_string()) + } + NestedMeta::Meta(syn::Meta::List(ml)) => { + ml.path.segments.last().map(|s| s.ident.to_string()) + } + NestedMeta::Lit(_) => None, + }; + + let routed = if let Some(ref k) = key_opt { + if plugin_keys.contains(k.as_str()) { + plugin_bucket.push(nm.clone()); + true + } else if generics_keys.contains(k.as_str()) { + generics_bucket.push(nm.clone()); + true + } else { + false + } + } else { + false + }; + + if !routed { + base_bucket.push(nm.clone()); + } + } + + // Parse each bucket + let base = CBase::from_list(&base_bucket)?; + let plugin = MPlugin::from_list(&plugin_bucket)?; + let generics = MGenerics::from_list(&generics_bucket)?; + + Ok(Self { base, plugin, generics }) + } +} + +impl Parse for Composed +where + CBase: FromMeta, + MPlugin: Mixin, + MGenerics: Mixin, +{ + fn parse(input: ParseStream) -> syn::Result { + // Parse `#[attr()]`'s inner as: key = value, key(...), literal, ... + let list: Punctuated = Punctuated::parse_terminated(input)?; + let items: Vec = list.into_iter().collect(); + Composed::::from_list(&items) + .map_err(|e| syn::Error::new(e.span(), e.to_string())) + } +} + +impl AttributeIdent for Composed +where + T: AttributeIdent, +{ + const IDENT: &'static str = T::IDENT; +} + +impl ItemAttributeArgs for Composed +where + T: ItemAttributeArgs, + P: Clone, + G: Clone, +{ +} + +impl Composed { + pub fn plugin(&self) -> &syn::Path { + &self.plugin.plugin + } +} + +impl Composed +where + MGenerics: HasGenerics, +{ + pub fn generics(&self) -> &[TypeList] { + self.generics.generics() + } + #[allow(dead_code)] + // TODO: which one to use? + pub fn concrete_paths(&self, target: &syn::Path) -> Vec { + if self.generics.generics().is_empty() { + vec![target.clone()] + } else { + self.generics.generics().iter().map(|g| parse_quote!(#target :: < #g >)).collect() + } + } +} + +impl Composed +where + MPlugin: ToTokens, + MGenerics: ToTokens, +{ + pub fn extra_args(&self) -> Vec { + let plugin_tokens = self.plugin.to_token_stream(); + let generics_tokens = self.generics.to_token_stream(); + match (plugin_tokens.is_empty(), generics_tokens.is_empty()) { + (true, true) => vec![], + (false, true) => vec![plugin_tokens], + (true, false) => vec![generics_tokens], + (false, false) => vec![plugin_tokens, generics_tokens], + } + } +} + +impl MacroPathProvider for Composed +where + CBase: MacroPathProvider, +{ + fn macro_path(context: &Context) -> &syn::Path { + CBase::macro_path(context) + } +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/context/macro_paths.rs b/crates/bevy_auto_plugin_shared/src/macro_api/context/macro_paths.rs new file mode 100644 index 00000000..a2e7150a --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/context/macro_paths.rs @@ -0,0 +1,138 @@ +use crate::macro_api::{ + attributes::prelude::*, + context::Context, +}; +use syn::parse_quote; + +#[derive(Debug, Clone, PartialEq, Hash)] +pub struct MacroPaths { + /// resolved absolute path to `auto_add_system` + pub emit_add_system_macro: syn::Path, + /// resolved absolute path to `auto_add_message` + pub emit_add_message_macro: syn::Path, + /// resolved absolute path to `auto_add_observer` + pub emit_add_observer_macro: syn::Path, + /// resolved absolute path to `auto_add_plugin` + pub emit_add_plugin_macro: syn::Path, + /// resolved absolute path to `auto_init_resource` + pub emit_init_resource_macro: syn::Path, + /// resolved absolute path to `auto_init_state` + pub emit_init_state_macro: syn::Path, + /// resolved absolute path to `auto_init_sub_state` + pub emit_init_sub_state_macro: syn::Path, + /// resolved absolute path to `auto_insert_resource` + pub emit_insert_resource_macro: syn::Path, + /// resolved absolute path to `auto_register_state_type` + pub emit_register_state_type_macro: syn::Path, + /// resolved absolute path to `auto_register_type` + pub emit_register_type_macro: syn::Path, + /// resolved absolute path to `auto_run_on_build` + pub emit_run_on_build_macro: syn::Path, + /// resolved absolute path to `auto_name` + pub emit_auto_name_macro: syn::Path, + /// resolved absolute path to `auto_configure_system_set` + pub emit_configure_system_set_macro: syn::Path, +} + +impl Default for MacroPaths { + #[rustfmt::skip] + fn default() -> Self { + Self { + emit_add_system_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_add_system ), + emit_add_message_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_add_message ), + emit_add_observer_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_add_observer ), + emit_add_plugin_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_add_plugin ), + emit_init_resource_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_init_resource ), + emit_init_state_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_init_state ), + emit_init_sub_state_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_init_sub_state ), + emit_insert_resource_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_insert_resource ), + emit_register_state_type_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_register_state_type ), + emit_register_type_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_register_type ), + emit_run_on_build_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_run_on_build ), + emit_auto_name_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_name ), + emit_configure_system_set_macro: parse_quote!( ::bevy_auto_plugin::prelude::auto_configure_system_set ), + } + } +} + +pub trait MacroPathProvider { + fn macro_path(context: &Context) -> &syn::Path; +} + +impl MacroPathProvider for AddSystemArgs { + fn macro_path(context: &Context) -> &syn::Path { + &context.macros.emit_add_system_macro + } +} + +impl MacroPathProvider for AddMessageArgs { + fn macro_path(context: &Context) -> &syn::Path { + &context.macros.emit_add_message_macro + } +} + +impl MacroPathProvider for AddObserverArgs { + fn macro_path(context: &Context) -> &syn::Path { + &context.macros.emit_add_observer_macro + } +} + +impl MacroPathProvider for AddPluginArgs { + fn macro_path(context: &Context) -> &syn::Path { + &context.macros.emit_add_plugin_macro + } +} + +impl MacroPathProvider for InitResourceArgs { + fn macro_path(context: &Context) -> &syn::Path { + &context.macros.emit_init_resource_macro + } +} + +impl MacroPathProvider for InitStateArgs { + fn macro_path(context: &Context) -> &syn::Path { + &context.macros.emit_init_state_macro + } +} + +impl MacroPathProvider for InitSubStateArgs { + fn macro_path(context: &Context) -> &syn::Path { + &context.macros.emit_init_sub_state_macro + } +} + +impl MacroPathProvider for InsertResourceArgs { + fn macro_path(context: &Context) -> &syn::Path { + &context.macros.emit_insert_resource_macro + } +} + +impl MacroPathProvider for RegisterStateTypeArgs { + fn macro_path(context: &Context) -> &syn::Path { + &context.macros.emit_register_state_type_macro + } +} + +impl MacroPathProvider for RegisterTypeArgs { + fn macro_path(context: &Context) -> &syn::Path { + &context.macros.emit_register_type_macro + } +} + +impl MacroPathProvider for RunOnBuildArgs { + fn macro_path(context: &Context) -> &syn::Path { + &context.macros.emit_run_on_build_macro + } +} + +impl MacroPathProvider for NameArgs { + fn macro_path(context: &Context) -> &syn::Path { + &context.macros.emit_auto_name_macro + } +} + +impl MacroPathProvider for ConfigureSystemSetArgs { + fn macro_path(context: &Context) -> &syn::Path { + &context.macros.emit_configure_system_set_macro + } +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/context/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/context/mod.rs new file mode 100644 index 00000000..49da3a8a --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/context/mod.rs @@ -0,0 +1,16 @@ +use macro_paths::MacroPaths; + +mod macro_paths; + +// TODO: pass from proc macros and resolve any aliases - instead of defaulting +#[derive(Debug, Clone, Default, PartialEq, Hash)] +pub struct Context { + pub macros: MacroPaths, +} + +pub mod prelude { + pub use super::{ + Context, + macro_paths::*, + }; +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/derives/auto_plugin.rs b/crates/bevy_auto_plugin_shared/src/macro_api/derives/auto_plugin.rs index 6bbc14e5..3915a71b 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/derives/auto_plugin.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/derives/auto_plugin.rs @@ -1,8 +1,17 @@ -use crate::macro_api::attributes::prelude::*; -use crate::macro_api::derives::{FieldData, VariantData}; +use crate::macro_api::{ + attributes::prelude::*, + derives::{ + FieldData, + VariantData, + }, +}; use darling::FromDeriveInput; use proc_macro2::Ident; -use syn::{Attribute, Generics, Visibility}; +use syn::{ + Attribute, + Generics, + Visibility, +}; #[derive(FromDeriveInput, Debug)] #[darling(attributes(auto_plugin), forward_attrs, supports(struct_any, enum_any))] diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/derives/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/derives/mod.rs index 9d9eb9d6..9213e066 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/derives/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/derives/mod.rs @@ -1,8 +1,11 @@ -use darling::{FromField, FromVariant}; +use darling::{ + FromField, + FromVariant, +}; use proc_macro2::Ident; use syn::Type; -pub mod auto_plugin; +mod auto_plugin; #[allow(dead_code)] #[derive(Debug, FromField)] @@ -17,3 +20,8 @@ pub struct VariantData { pub ident: Ident, pub fields: darling::ast::Fields, } + +pub mod prelude { + + pub use super::auto_plugin::*; +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/emitters/app_mutation.rs b/crates/bevy_auto_plugin_shared/src/macro_api/emitters/app_mutation.rs new file mode 100644 index 00000000..cdfb7b06 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/emitters/app_mutation.rs @@ -0,0 +1,74 @@ +use crate::{ + __private::auto_plugin_registry::_plugin_entry_block, + macro_api::prelude::*, +}; +use proc_macro2::TokenStream; +use quote::{ + ToTokens, + format_ident, +}; + +/// for codegen attaching to bevy app +#[derive(Debug, Clone)] +pub struct AppMutationEmitter { + pub(crate) args: T, + // TODO: maybe app params should just be part of another wrapper struct? + pub(crate) app_param: syn::Ident, +} + +impl AppMutationEmitter { + pub fn from_args(args: T) -> AppMutationEmitter { + AppMutationEmitter:: { args, app_param: format_ident!("app") } + } + pub fn wrap_body( + &mut self, + body: impl Fn(TokenStream) -> TokenStream, + ) -> syn::Result + where + T: ItemAttributeArgs + + ItemAttributeParse + + ItemAttributeInput + + ItemAttributeTarget + + ItemAttributeContext + + ItemAttributeUniqueIdent + + ItemAttributePlugin, + AppMutationEmitter: ToTokens, + { + let ident = self.args.target().to_token_stream(); + let app_param = &self.app_param; + let unique_ident = self.args.get_unique_ident(); + let plugin = self.args.plugin().clone(); + let body = body(self.to_token_stream()); + let expr: syn::ExprClosure = syn::parse_quote!(|#app_param| { + #body + }); + // required for generics + let unique_ident = format_ident!("{unique_ident}"); + let output = _plugin_entry_block(&unique_ident, &plugin, &expr); + assert!(!output.is_empty(), "No plugin entry points were generated for ident: {ident}"); + Ok(output) + } +} + +pub trait EmitAppMutationTokens { + // TODO: this should be its own thing but it might overcomplicate things + fn post_process_inner_item(&mut self) -> Result<(), (InputItem, syn::Error)> { + Ok(()) + } + fn to_app_mutation_token_stream(&self, app_param: &syn::Ident) -> TokenStream { + let mut tokens = TokenStream::new(); + self.to_app_mutation_tokens(&mut tokens, app_param); + tokens + } + fn to_app_mutation_tokens(&self, out: &mut TokenStream, app_param: &syn::Ident); +} + +impl ToTokens for AppMutationEmitter +where + Self: EmitAppMutationTokens, + T: ItemAttributeInput, +{ + fn to_tokens(&self, tokens: &mut TokenStream) { + EmitAppMutationTokens::to_app_mutation_tokens(self, tokens, &self.app_param) + } +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/emitters/attr.rs b/crates/bevy_auto_plugin_shared/src/macro_api/emitters/attr.rs new file mode 100644 index 00000000..98580ac2 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/emitters/attr.rs @@ -0,0 +1,75 @@ +use crate::{ + codegen::ExpandAttrs, + macro_api::prelude::*, + syntax::extensions::item::ItemAttrsExt, +}; +use proc_macro2::TokenStream; +use quote::{ + ToTokens, + quote, +}; +use syn::{ + parse_quote, + spanned::Spanned, +}; + +/// for codegen re-emitting macro args +#[derive(Debug, Clone)] +pub(crate) struct AttrEmitter { + pub(crate) args: T, +} + +impl AttrEmitter { + pub(crate) fn from_args(args: T) -> AttrEmitter { + AttrEmitter:: { args } + } +} + +impl AttrEmitter, R>> +where + T: MacroPathProvider, +{ + pub(crate) fn wrap_as_attr(&self, args: &TokenStream) -> TokenStream { + let macro_path = T::macro_path(self.args.context()); + if args.is_empty() { + quote! { #[#macro_path] } + } else { + quote! { #[#macro_path( #args )] } + } + } +} + +pub trait AttrEmitterToExpandAttr { + fn to_expand_attr(&self, expand_attrs: &mut ExpandAttrs); +} + +impl ToTokens for AttrEmitter +where + Self: AttrEmitterToExpandAttr, +{ + fn to_tokens(&self, tokens: &mut TokenStream) { + let mut expand_attr = ExpandAttrs::default(); + AttrEmitterToExpandAttr::to_expand_attr(self, &mut expand_attr); + expand_attr.to_tokens(tokens); + } +} + +impl AttrEmitter +where + T: MacroPathProvider + ItemAttributeInput + ItemAttributeContext, + Self: ToTokens, +{ + // TODO: replace the one ins expand/attr + #[allow(dead_code)] + pub fn inject_attribute_macro(&mut self) -> syn::Result<()> { + let args = self.to_token_stream(); + let macro_path = T::macro_path(self.args.context()).clone(); + self.args.input_item_mut().map_ast(|item| { + // insert attribute tokens + let mut attrs = item.take_attrs().map_err(|err| syn::Error::new(item.span(), err))?; + attrs.insert(0, parse_quote!(#[#macro_path(#args)])); + item.put_attrs(attrs).unwrap(); // infallible + Ok(()) + }) + } +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/emitters/attr_expansion.rs b/crates/bevy_auto_plugin_shared/src/macro_api/emitters/attr_expansion.rs new file mode 100644 index 00000000..28ab8584 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/emitters/attr_expansion.rs @@ -0,0 +1,84 @@ +use crate::{ + codegen::ExpandAttrs, + macro_api::prelude::*, +}; +use proc_macro2::TokenStream; +use quote::ToTokens; +use std::marker::PhantomData; + +/// for codegen rewriting attrs +#[derive(Debug, Clone)] +pub struct AttrExpansionEmitter { + pub(crate) args: T, +} + +impl AttrExpansionEmitter +where + T: ItemAttributeParse, +{ + pub fn from_item_attribute(item_attribute: T) -> AttrExpansionEmitter { + AttrExpansionEmitter:: { args: item_attribute } + } +} + +pub trait AttrExpansionEmitterToExpandAttr { + fn to_expand_attrs(&self, expand_attrs: &mut ExpandAttrs); +} + +impl ToTokens for AttrExpansionEmitter +where + Self: AttrExpansionEmitterToExpandAttr, + T: ItemAttributeInput, +{ + fn to_tokens(&self, tokens: &mut TokenStream) { + let mut expand_attr = ExpandAttrs::default(); + AttrExpansionEmitterToExpandAttr::to_expand_attrs(self, &mut expand_attr); + tokens.extend(expand_attr.to_token_stream()); + tokens.extend(self.args.input_item().to_token_stream()); + } +} + +impl + From, RFrom>>> + for AttrEmitter, RTo>> +where + TTo: From, + GTo: From, + RTo: From, +{ + fn from(value: AttrExpansionEmitter, RFrom>>) -> Self { + let ItemAttribute { args, context, input_item, target, _resolver } = value.args; + + let mapped = Composed { + base: args.base.into(), // TTo: From + plugin: args.plugin, // same P + generics: args.generics.into(), // GTo: From + }; + + let args = ItemAttribute::, RTo> { + args: mapped, + context, // RTo: From + input_item, + target, + _resolver: PhantomData, + }; + + Self::from_args(args) + } +} + +impl + From<&AttrExpansionEmitter, RFrom>>> + for AttrEmitter, RTo>> +where + TTo: From, + GTo: From, + RTo: From, + AttrExpansionEmitter, RFrom>>: Clone, +{ + fn from(value: &AttrExpansionEmitter, RFrom>>) -> Self { + , RFrom>>>>::from( + value.clone(), + ) + } +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/emitters/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/emitters/mod.rs new file mode 100644 index 00000000..c0ea9956 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/emitters/mod.rs @@ -0,0 +1,11 @@ +pub mod app_mutation; +pub mod attr; +pub mod attr_expansion; + +pub mod prelude { + pub use super::{ + app_mutation::*, + attr::*, + attr_expansion::*, + }; +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/input_item.rs b/crates/bevy_auto_plugin_shared/src/macro_api/input_item.rs new file mode 100644 index 00000000..fe420e54 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/input_item.rs @@ -0,0 +1,134 @@ +use crate::syntax::{ + extensions::item::ItemAttrsExt, + parse::item::{ + SingleItemWithErrorsCheckError, + expect_single_item_any_compile_errors, + }, +}; +use proc_macro2::TokenStream; +use quote::{ + ToTokens, + quote, +}; +use syn::{ + parse2, + spanned::Spanned, +}; + +#[derive(Debug, Clone)] +pub enum InputItem { + Tokens(TokenStream), + Item(Box), +} + +impl AsRef for InputItem { + fn as_ref(&self) -> &Self { + self + } +} + +impl From> for InputItem { + fn from(boxed_item: Box) -> Self { + Self::Item(boxed_item) + } +} + +impl From<&syn::Item> for InputItem { + fn from(item: &syn::Item) -> Self { + Self::Item(Box::new(item.clone())) + } +} + +impl From for InputItem { + fn from(item: syn::Item) -> Self { + Self::Item(Box::new(item)) + } +} + +impl TryFrom for InputItem { + type Error = syn::Error; + fn try_from(tokens: TokenStream) -> Result { + Self::from_ts_validated(tokens) + } +} + +impl TryFrom<&TokenStream> for InputItem { + type Error = syn::Error; + fn try_from(tokens: &TokenStream) -> Result { + Self::from_ts_validated(tokens.clone()) + } +} + +impl PartialEq for InputItem { + fn eq(&self, other: &Self) -> bool { + let self_tokens = self.to_token_stream(); + let other_tokens = other.to_token_stream(); + self_tokens.to_string() == other_tokens.to_string() + } +} + +impl InputItem { + pub fn from_ts_validated(tokens: TokenStream) -> syn::Result { + let mut input_item = Self::Tokens(tokens); + input_item.ensure_ast()?; + Ok(input_item) + } + fn _upgrade(&mut self) -> syn::Result<()> { + if let Self::Tokens(tokens) = self { + *self = Self::Item(parse2(tokens.clone())?); + } + Ok(()) + } + pub fn has_compiler_errors(&self) -> Result { + match self { + InputItem::Tokens(tokens) => { + match expect_single_item_any_compile_errors(tokens.clone()) { + Ok((_, compiler_errors)) => Ok(!compiler_errors.is_empty()), + Err(e) => Err(e), + } + } + InputItem::Item(_) => Ok(false), + } + } + pub fn ensure_ast(&mut self) -> syn::Result<&syn::Item> { + self._upgrade()?; + Ok(match &*self { + Self::Item(item) => item.as_ref(), + _ => unreachable!(), + }) + } + pub fn ensure_ast_mut(&mut self) -> syn::Result<&mut syn::Item> { + self._upgrade()?; + Ok(match &mut *self { + Self::Item(item) => item.as_mut(), + _ => unreachable!(), + }) + } + // TODO: use instead of analysis/item helpers + pub fn get_ident(&mut self) -> syn::Result> { + let item = self.ensure_ast()?; + Ok(item.get_ident()) + } + pub fn ident(&mut self) -> syn::Result<&syn::Ident> { + self.ensure_ast().and_then(|item| { + item.get_ident() + .ok_or_else(|| syn::Error::new(item.span(), "expected item to have an ident")) + }) + } + pub fn map_ast(&mut self, f: F) -> syn::Result<()> + where + F: FnOnce(&mut syn::Item) -> syn::Result<()>, + { + let item = self.ensure_ast_mut()?; + f(item) + } +} + +impl ToTokens for InputItem { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(match self { + InputItem::Tokens(ts) => ts.clone(), + InputItem::Item(item) => quote! { #item }, + }) + } +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/mixins/generics/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/mixins/generics/mod.rs new file mode 100644 index 00000000..5328a49f --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/mixins/generics/mod.rs @@ -0,0 +1,17 @@ +use crate::syntax::ast::type_list::TypeList; + +pub mod none; +pub mod with_many; +pub mod with_single; + +pub trait HasGenerics { + fn generics(&self) -> &[TypeList]; +} + +pub mod prelude { + pub use super::HasGenerics; + use super::*; + pub use none::*; + pub use with_many::*; + pub use with_single::*; +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/mixins/generics/none.rs b/crates/bevy_auto_plugin_shared/src/macro_api/mixins/generics/none.rs new file mode 100644 index 00000000..e1ec928b --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/mixins/generics/none.rs @@ -0,0 +1,67 @@ +use crate::{ + macro_api::{ + mixins::HasKeys, + prelude::{ + WithZeroOrManyGenerics, + WithZeroOrOneGenerics, + }, + }, + util::macros::impl_from_default, +}; +use darling::{ + FromMeta, + ast::NestedMeta, +}; +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{ + parse::{ + Parse, + ParseStream, + }, + spanned::Spanned, +}; + +#[derive(Debug, Clone, Default, PartialEq, Hash)] +pub struct WithNoGenerics {} + +impl WithNoGenerics { + pub const KEYS: &'static [&'static str] = &["generics"]; +} + +impl HasKeys for WithNoGenerics { + fn keys() -> &'static [&'static str] { + WithNoGenerics::KEYS + } +} + +impl ToTokens for WithNoGenerics { + fn to_tokens(&self, _tokens: &mut TokenStream) {} +} + +impl FromMeta for WithNoGenerics { + fn from_list(items: &[NestedMeta]) -> darling::Result { + if !items.is_empty() { + let errors = items + .iter() + .map(|item| { + darling::Error::unsupported_shape("generics are not supported") + .with_span(&item.span()) + }) + .collect(); + return Err(darling::Error::multiple(errors)); + } + Ok(Self::default()) + } +} + +impl Parse for WithNoGenerics { + fn parse(input: ParseStream) -> syn::Result { + if !input.is_empty() { + return Err(input.error("generics are not supported")); + } + Ok(Self::default()) + } +} + +impl_from_default!(WithNoGenerics => (WithZeroOrOneGenerics, WithZeroOrManyGenerics)); diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/mixins/generics/with_many.rs b/crates/bevy_auto_plugin_shared/src/macro_api/mixins/generics/with_many.rs new file mode 100644 index 00000000..0c904185 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/mixins/generics/with_many.rs @@ -0,0 +1,87 @@ +use crate::{ + macro_api::mixins::{ + HasKeys, + generics::HasGenerics, + }, + syntax::ast::type_list::TypeList, +}; +use darling::FromMeta; +use proc_macro2::TokenStream; +use quote::{ + ToTokens, + quote, +}; + +#[derive(Debug, Clone, Default, FromMeta, PartialEq, Hash)] +#[darling(derive_syn_parse)] +pub struct WithZeroOrManyGenerics { + #[darling(multiple, default, rename = "generics")] + pub generics: Vec, +} + +impl WithZeroOrManyGenerics { + pub const KEYS: &'static [&'static str] = &["generics"]; +} + +impl HasKeys for WithZeroOrManyGenerics { + fn keys() -> &'static [&'static str] { + WithZeroOrManyGenerics::KEYS + } +} + +impl HasGenerics for WithZeroOrManyGenerics { + fn generics(&self) -> &[TypeList] { + &self.generics + } +} + +impl ToTokens for WithZeroOrManyGenerics { + fn to_tokens(&self, tokens: &mut TokenStream) { + let sets = self.generics.iter().map(|g| quote! { generics(#g) }); + tokens.extend(quote! { + #(#sets),* + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use internal_test_proc_macro::xtest; + use syn::parse_quote; + + #[xtest] + fn test_to_tokens_zero() { + assert_eq!( + WithZeroOrManyGenerics { generics: vec![] }.to_token_stream().to_string(), + quote!().to_string() + ); + } + + #[xtest] + fn test_to_tokens_single() { + assert_eq!( + WithZeroOrManyGenerics { + generics: vec![TypeList(vec![parse_quote!(bool), parse_quote!(u32)])] + } + .to_token_stream() + .to_string(), + quote!(generics(bool, u32)).to_string() + ); + } + + #[xtest] + fn test_to_tokens_multiple() { + assert_eq!( + WithZeroOrManyGenerics { + generics: vec![ + TypeList(vec![parse_quote!(bool), parse_quote!(u32)]), + TypeList(vec![parse_quote!(usize)]), + ], + } + .to_token_stream() + .to_string(), + quote!(generics(bool, u32), generics(usize)).to_string() + ); + } +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/mixins/generics/with_single.rs b/crates/bevy_auto_plugin_shared/src/macro_api/mixins/generics/with_single.rs new file mode 100644 index 00000000..7c331bc7 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/mixins/generics/with_single.rs @@ -0,0 +1,81 @@ +use crate::{ + macro_api::{ + mixins::{ + HasKeys, + generics::HasGenerics, + }, + prelude::WithZeroOrManyGenerics, + }, + syntax::ast::type_list::TypeList, +}; +use darling::FromMeta; +use proc_macro2::TokenStream; +use quote::{ + ToTokens, + quote, +}; + +#[derive(Debug, Clone, Default, FromMeta, PartialEq, Hash)] +#[darling(derive_syn_parse)] +pub struct WithZeroOrOneGenerics { + #[darling(default, rename = "generics")] + pub generics: Option, +} + +impl WithZeroOrOneGenerics { + pub const KEYS: &'static [&'static str] = &["generics"]; +} + +impl HasKeys for WithZeroOrOneGenerics { + fn keys() -> &'static [&'static str] { + WithZeroOrOneGenerics::KEYS + } +} + +impl HasGenerics for WithZeroOrOneGenerics { + fn generics(&self) -> &[TypeList] { + self.generics.as_slice() + } +} + +impl ToTokens for WithZeroOrOneGenerics { + fn to_tokens(&self, tokens: &mut TokenStream) { + let sets = self.generics.iter().map(|g| quote! { generics(#g) }); + tokens.extend(quote! { + #(#sets),* + }); + } +} + +impl From for WithZeroOrManyGenerics { + fn from(value: WithZeroOrOneGenerics) -> Self { + Self { generics: value.generics.as_slice().to_vec() } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use internal_test_proc_macro::xtest; + use syn::parse_quote; + + #[xtest] + fn test_to_tokens_zero() { + assert_eq!( + WithZeroOrOneGenerics { generics: None }.to_token_stream().to_string(), + quote!().to_string(), + ); + } + + #[xtest] + fn test_to_tokens_single() { + assert_eq!( + WithZeroOrOneGenerics { + generics: Some(TypeList(vec![parse_quote!(bool), parse_quote!(u32)])) + } + .to_token_stream() + .to_string(), + quote!(generics(bool, u32)).to_string(), + ); + } +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/mixins/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/mixins/mod.rs new file mode 100644 index 00000000..77c227ba --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/mixins/mod.rs @@ -0,0 +1,36 @@ +use darling::ast::NestedMeta; + +pub mod generics; +pub mod nothing; +pub mod with_plugin; + +pub trait HasKeys { + fn keys() -> &'static [&'static str]; +} + +pub trait Mixin: Sized { + /// Keys this mixin recognizes (top-level names). + fn keys() -> &'static [&'static str]; + + /// Parse from just the metas that were routed to this mixin. + fn from_list(items: &[NestedMeta]) -> darling::Result; +} + +impl Mixin for T +where + T: darling::FromMeta + HasKeys, +{ + fn keys() -> &'static [&'static str] { + ::keys() + } + fn from_list(items: &[NestedMeta]) -> darling::Result { + ::from_list(items) + } +} + +pub mod prelude { + pub use super::*; + pub use generics::prelude::*; + pub use nothing::*; + pub use with_plugin::*; +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/mixins/nothing.rs b/crates/bevy_auto_plugin_shared/src/macro_api/mixins/nothing.rs new file mode 100644 index 00000000..b7da6c9a --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/mixins/nothing.rs @@ -0,0 +1,24 @@ +use crate::{ + macro_api::mixins::{ + HasKeys, + generics::HasGenerics, + }, + syntax::ast::type_list::TypeList, +}; +use darling::FromMeta; + +#[derive(Debug, Clone, FromMeta)] +#[darling(derive_syn_parse)] +pub struct Nothing {} + +impl HasGenerics for Nothing { + fn generics(&self) -> &[TypeList] { + &[] + } +} + +impl HasKeys for Nothing { + fn keys() -> &'static [&'static str] { + &[] + } +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/mixins/with_plugin.rs b/crates/bevy_auto_plugin_shared/src/macro_api/mixins/with_plugin.rs new file mode 100644 index 00000000..0a4c96f9 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/macro_api/mixins/with_plugin.rs @@ -0,0 +1,31 @@ +use crate::macro_api::mixins::HasKeys; +use darling::FromMeta; +use proc_macro2::TokenStream; +use quote::{ + ToTokens, + quote, +}; + +#[derive(Debug, Clone, FromMeta, PartialEq, Hash)] +#[darling(derive_syn_parse)] +pub struct WithPlugin { + #[darling(rename = "plugin")] + pub plugin: syn::Path, +} + +impl WithPlugin { + pub const KEYS: &'static [&'static str] = &["plugin"]; +} + +impl ToTokens for WithPlugin { + fn to_tokens(&self, tokens: &mut TokenStream) { + let plugin = &self.plugin; + tokens.extend(quote! { plugin = #plugin }); + } +} + +impl HasKeys for WithPlugin { + fn keys() -> &'static [&'static str] { + WithPlugin::KEYS + } +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/mod.rs b/crates/bevy_auto_plugin_shared/src/macro_api/mod.rs index f9acb7b7..374e115b 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/mod.rs @@ -1,4 +1,19 @@ -pub(crate) mod attributes; -pub mod derives; -pub mod schedule_config; -pub mod with_plugin; +mod attributes; +mod composed; +mod context; +mod derives; +mod emitters; +mod input_item; +mod mixins; +mod schedule_config; + +pub(crate) mod prelude { + use super::*; + pub use attributes::prelude::*; + pub use composed::*; + pub use context::prelude::*; + pub use derives::prelude::*; + pub use emitters::prelude::*; + pub use input_item::*; + pub use mixins::prelude::*; +} diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/schedule_config.rs b/crates/bevy_auto_plugin_shared/src/macro_api/schedule_config.rs index bf2c4f57..e638ce9d 100644 --- a/crates/bevy_auto_plugin_shared/src/macro_api/schedule_config.rs +++ b/crates/bevy_auto_plugin_shared/src/macro_api/schedule_config.rs @@ -1,7 +1,13 @@ -use crate::syntax::ast::any_expr::{AnyExprCallClosureMacroPath, AnyExprCallMacroPath}; +use crate::syntax::ast::any_expr::{ + AnyExprCallClosureMacroPath, + AnyExprCallMacroPath, +}; use darling::FromMeta; use proc_macro2::TokenStream as MacroStream; -use quote::{ToTokens, quote}; +use quote::{ + ToTokens, + quote, +}; #[derive(FromMeta, Clone, Debug, PartialEq, Hash)] #[darling(derive_syn_parse)] diff --git a/crates/bevy_auto_plugin_shared/src/macro_api/with_plugin.rs b/crates/bevy_auto_plugin_shared/src/macro_api/with_plugin.rs deleted file mode 100644 index 87a2df4c..00000000 --- a/crates/bevy_auto_plugin_shared/src/macro_api/with_plugin.rs +++ /dev/null @@ -1,97 +0,0 @@ -use crate::codegen::tokens::{ArgsBackToTokens, ArgsWithPlugin}; -use crate::codegen::with_target_path::ToTokensWithConcreteTargetPath; -use crate::macro_api::attributes::ItemAttributeArgs; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::concrete_path::ConcreteTargetPath; -use crate::syntax::validated::non_empty_path::NonEmptyPath; -use darling::FromMeta; -use proc_macro2::{Ident, TokenStream as MacroStream}; -use quote::format_ident; -use std::hash::Hash; -use syn::Path; -use syn::parse::Parse; - -pub trait PluginBound: FromMeta + Parse + ToTokensWithConcreteTargetPath + Hash + Clone { - type Inner: ItemAttributeArgs; - fn inner(&self) -> &Self::Inner; - fn plugin(&self) -> &Path; - - fn _concat_ident_hash(&self, ident: &Ident) -> String { - use std::hash::{Hash, Hasher}; - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - ident.hash(&mut hasher); - self.hash(&mut hasher); - format!("{:x}", hasher.finish()) - } - - fn _get_unique_ident(&self, prefix: Ident, ident: &Ident) -> Ident { - let hash = self._concat_ident_hash(ident); - format_ident!("{prefix}_{hash}") - } - - fn get_unique_ident(&self, ident: &Ident) -> Ident { - self._get_unique_ident(Self::Inner::global_build_prefix(), ident) - } -} - -#[derive(FromMeta, Debug, Clone, PartialEq, Hash)] -#[darling(derive_syn_parse)] -pub struct WithPlugin { - pub plugin: Path, - #[darling(flatten)] - pub inner: T, -} - -impl WithPlugin { - pub fn plugin(&self) -> NonEmptyPath { - NonEmptyPath::new(self.plugin.clone()).expect("expected plugin to be a valid path") - } -} - -impl From> for ArgsWithPlugin { - fn from(value: WithPlugin) -> Self { - ArgsWithPlugin::new(value.plugin(), value.inner) - } -} - -impl GenericsArgs for WithPlugin -where - T: GenericsArgs, -{ - const TURBOFISH: bool = T::TURBOFISH; - fn type_lists(&self) -> &[TypeList] { - self.inner.type_lists() - } -} - -impl ToTokensWithConcreteTargetPath for WithPlugin -where - T: ItemAttributeArgs, -{ - fn to_tokens_with_concrete_target_path( - &self, - tokens: &mut MacroStream, - target: &ConcreteTargetPath, - ) { - self.inner - .to_tokens_with_concrete_target_path(tokens, target) - } - - fn required_use_statements(&self) -> Vec { - self.inner.required_use_statements() - } -} - -impl PluginBound for WithPlugin -where - T: ItemAttributeArgs, -{ - type Inner = T; - fn inner(&self) -> &Self::Inner { - &self.inner - } - fn plugin(&self) -> &Path { - &self.plugin - } -} diff --git a/crates/bevy_auto_plugin_shared/src/syntax/analysis/fn_param.rs b/crates/bevy_auto_plugin_shared/src/syntax/analysis/fn_param.rs index e66c37ff..1d466536 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/analysis/fn_param.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/analysis/fn_param.rs @@ -12,8 +12,12 @@ pub fn is_fn_param_mutable_reference( messages: FnParamMutabilityCheckErrMessages, ) -> syn::Result<()> { use crate::syntax::analysis::ty_classify; - use syn::spanned::Spanned; - use syn::{Error, FnArg, Pat}; + use syn::{ + Error, + FnArg, + Pat, + spanned::Spanned, + }; for arg in &item.sig.inputs { if let FnArg::Typed(pat_type) = arg { let Pat::Ident(pat_ident) = &*pat_type.pat else { @@ -28,10 +32,7 @@ pub fn is_fn_param_mutable_reference( return Ok(()); } } - Err(Error::new( - item.sig.inputs.span(), - messages.not_found_message, - )) + Err(Error::new(item.sig.inputs.span(), messages.not_found_message)) } pub fn require_fn_param_mutable_reference( diff --git a/crates/bevy_auto_plugin_shared/src/syntax/analysis/item.rs b/crates/bevy_auto_plugin_shared/src/syntax/analysis/item.rs index 62ffa822..f6bccd56 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/analysis/item.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/analysis/item.rs @@ -1,44 +1,58 @@ -use proc_macro2::Ident; -use syn::Item; -use syn::spanned::Spanned; -use thiserror::Error; +use crate::syntax::extensions::{ + item::ItemAttrsExt, + path::PathExt, +}; +use syn::{ + Attribute, + Item, +}; -#[derive(Error, Debug, PartialEq, Copy, Clone)] -pub enum ResolveIdentFromItemError<'a> { - #[error("Expected function")] - NotFn(&'a Item), - #[error("Expected Struct or Enum")] - NotStructOrEnum(&'a Item), +pub fn item_has_attr(item: &Item, path: &syn::Path) -> bool { + has_attr(item.attrs().unwrap_or_default(), path) } -impl ResolveIdentFromItemError<'_> { - pub fn span(&self) -> proc_macro2::Span { - match self { - Self::NotFn(item) => item.span(), - Self::NotStructOrEnum(item) => item.span(), - } - } +pub fn has_attr(attrs: &[Attribute], path: &syn::Path) -> bool { + attrs.iter().any(|attr| attr.path().is_similar_path_or_ident(path)) } -impl From> for syn::Error { - fn from(value: ResolveIdentFromItemError) -> Self { - Self::new(value.span(), value.to_string()) +#[cfg(test)] +mod tests { + use crate::syntax::analysis::item::has_attr; + use internal_test_proc_macro::xtest; + use syn::parse_quote; + + #[xtest] + fn test_negative_has_attr_empty() { + let target_path = parse_quote!(a); + assert!(!has_attr(&[], &target_path)); } -} -pub type IdentFromItemResult<'a> = Result<&'a Ident, ResolveIdentFromItemError<'a>>; + #[xtest] + fn test_negative_has_attr() { + let target_path = parse_quote!(a); + let input: Vec<_> = parse_quote! { + #[foo] + }; + assert!(!has_attr(&input, &target_path)); + } -pub fn resolve_ident_from_fn(item: &Item) -> IdentFromItemResult<'_> { - match item { - Item::Fn(f) => Ok(&f.sig.ident), - _ => Err(ResolveIdentFromItemError::NotFn(item)), + #[xtest] + fn test_positive_has_attr_single() { + let target_path = parse_quote!(a); + let input: Vec<_> = parse_quote! { + #[#target_path] + }; + assert!(has_attr(&input, &target_path)); } -} -pub fn resolve_ident_from_struct_or_enum(item: &Item) -> IdentFromItemResult<'_> { - match item { - Item::Struct(s) => Ok(&s.ident), - Item::Enum(e) => Ok(&e.ident), - _ => Err(ResolveIdentFromItemError::NotStructOrEnum(item)), + #[xtest] + fn test_positive_has_attr_multiple() { + let target_path = parse_quote!(a); + let input: Vec<_> = parse_quote! { + #[A] + #[#target_path] + #[B] + }; + assert!(has_attr(&input, &target_path)); } } diff --git a/crates/bevy_auto_plugin_shared/src/syntax/analysis/mod.rs b/crates/bevy_auto_plugin_shared/src/syntax/analysis/mod.rs index e91df11d..2294853b 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/analysis/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/analysis/mod.rs @@ -1,3 +1,4 @@ pub mod fn_param; pub mod item; +pub mod path; pub mod ty_classify; diff --git a/crates/bevy_auto_plugin_shared/src/syntax/analysis/path.rs b/crates/bevy_auto_plugin_shared/src/syntax/analysis/path.rs new file mode 100644 index 00000000..05479240 --- /dev/null +++ b/crates/bevy_auto_plugin_shared/src/syntax/analysis/path.rs @@ -0,0 +1,37 @@ +use crate::syntax::ast::type_list::TypeList; +use quote::ToTokens; +use syn::{ + Path, + PathArguments, + parse2, +}; + +pub fn generics_from_path(path: &Path) -> syn::Result { + let mut generics = TypeList::empty(); + for segment in &path.segments { + if let PathArguments::AngleBracketed(angle_bracketed) = &segment.arguments { + let type_list = parse2::(angle_bracketed.args.to_token_stream())?; + generics.0.extend(type_list.0); + } + } + Ok(generics) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::syntax::analysis::path::generics_from_path; + use internal_test_proc_macro::xtest; + use quote::quote; + + #[xtest] + fn test_generics_from_path() -> Result<(), syn::Error> { + let item = parse2::(quote! { + foo::bar:: + })?; + let generics = generics_from_path(&item).expect("no generics"); + let generics = quote! { #generics }; + assert_eq!("u32 , i32", generics.to_string()); + Ok(()) + } +} diff --git a/crates/bevy_auto_plugin_shared/src/syntax/analysis/ty_classify.rs b/crates/bevy_auto_plugin_shared/src/syntax/analysis/ty_classify.rs index 66b06c99..fc02570b 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/analysis/ty_classify.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/analysis/ty_classify.rs @@ -1,12 +1,9 @@ -use syn::{Type, TypeReference}; +use syn::{ + Type, + TypeReference, +}; /// Check if the type is `&mut _` pub fn is_mutable_reference(ty: &Type) -> bool { - matches!( - ty, - Type::Reference(TypeReference { - mutability: Some(_), - .. - }) - ) + matches!(ty, Type::Reference(TypeReference { mutability: Some(_), .. })) } diff --git a/crates/bevy_auto_plugin_shared/src/syntax/ast/any_expr.rs b/crates/bevy_auto_plugin_shared/src/syntax/ast/any_expr.rs index f2f57c3d..1dd05352 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/ast/any_expr.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/ast/any_expr.rs @@ -193,7 +193,10 @@ mod tests { use internal_test_proc_macro::xtest; use proc_macro2::TokenStream; use quote::quote; - use syn::{parse_quote, parse2}; + use syn::{ + parse_quote, + parse2, + }; fn map_err_to_string(r: Result) -> Result { r.map_err(|e| &*format!("{e}").leak()) diff --git a/crates/bevy_auto_plugin_shared/src/syntax/ast/expr_path_or_call.rs b/crates/bevy_auto_plugin_shared/src/syntax/ast/expr_path_or_call.rs index e7b2e004..3101f534 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/ast/expr_path_or_call.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/ast/expr_path_or_call.rs @@ -1,9 +1,13 @@ use darling::FromMeta; use proc_macro2::TokenStream; use quote::ToTokens; -use syn::parse::Parser; -use syn::spanned::Spanned; -use syn::{Expr, Token, punctuated::Punctuated}; +use syn::{ + Expr, + Token, + parse::Parser, + punctuated::Punctuated, + spanned::Spanned, +}; #[derive(Debug, Clone, PartialEq, Hash)] pub enum ExprPathOrCall { @@ -56,9 +60,7 @@ impl FromMeta for ExprPathOrCall { syn::Meta::List(list) => { // Parse exactly one Expr from the list's tokens. let parser = Punctuated::::parse_terminated; - let elems = parser - .parse2(list.tokens.clone()) - .map_err(darling::Error::from)?; + let elems = parser.parse2(list.tokens.clone()).map_err(darling::Error::from)?; let mut it = elems.into_iter(); let expr = it.next().ok_or_else(|| { darling::Error::too_few_items(1).with_span(&list.tokens.span()) @@ -76,10 +78,10 @@ impl FromMeta for ExprPathOrCall { } // Bare flag like `item` is not accepted. - syn::Meta::Path(_) => Err(darling::Error::custom( - "expected `item = ` or `item()`", - ) - .with_span(&meta.span())), + syn::Meta::Path(_) => { + Err(darling::Error::custom("expected `item = ` or `item()`") + .with_span(&meta.span())) + } } } } @@ -89,14 +91,10 @@ impl syn::parse::Parse for ExprPathOrCall { let elems = Punctuated::::parse_terminated(input)?; let mut elems = elems.into_iter(); let Some(elem) = elems.next() else { - return Err(darling::Error::too_few_items(1) - .with_span(&input.span()) - .into()); + return Err(darling::Error::too_few_items(1).with_span(&input.span()).into()); }; if let Some(elem) = elems.next() { - return Err(darling::Error::too_many_items(1) - .with_span(&elem.span()) - .into()); + return Err(darling::Error::too_many_items(1).with_span(&elem.span()).into()); } Ok(match elem { Expr::Call(call) => ExprPathOrCall::Call(call), @@ -112,7 +110,10 @@ impl syn::parse::Parse for ExprPathOrCall { mod tests { use super::*; use internal_test_proc_macro::xtest; - use syn::{Meta, parse_quote}; + use syn::{ + Meta, + parse_quote, + }; #[derive(Debug)] #[allow(dead_code)] diff --git a/crates/bevy_auto_plugin_shared/src/syntax/ast/expr_value.rs b/crates/bevy_auto_plugin_shared/src/syntax/ast/expr_value.rs index 39dfe77f..9cf97d53 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/ast/expr_value.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/ast/expr_value.rs @@ -1,9 +1,17 @@ -use darling::{Error, FromMeta}; +use darling::{ + Error, + FromMeta, +}; use proc_macro2::TokenStream; use quote::ToTokens; -use syn::parse::Parser; -use syn::spanned::Spanned; -use syn::{Expr, Meta, Token, punctuated::Punctuated}; +use syn::{ + Expr, + Meta, + Token, + parse::Parser, + punctuated::Punctuated, + spanned::Spanned, +}; #[derive(Debug, Clone, PartialEq, Hash)] pub struct ExprValue(pub Expr); @@ -43,9 +51,8 @@ impl FromMeta for ExprValue { let list = meta.require_list()?; // Parse its tokens as `T, T, ...` where each `T` is a syn::Type let parser = Punctuated::::parse_terminated; - let elems = parser - .parse2(list.tokens.clone()) - .map_err(|e| failed_err(e, &list.tokens.span()))?; + let elems = + parser.parse2(list.tokens.clone()).map_err(|e| failed_err(e, &list.tokens.span()))?; let mut elems = elems.into_iter(); let Some(elem) = elems.next() else { return Err(Error::too_few_items(1).with_span(&meta.span())); @@ -76,7 +83,10 @@ mod tests { use super::*; use internal_test_proc_macro::xtest; use quote::quote; - use syn::{Meta, parse_quote}; + use syn::{ + Meta, + parse_quote, + }; #[derive(Debug)] #[allow(dead_code)] diff --git a/crates/bevy_auto_plugin_shared/src/syntax/ast/flag.rs b/crates/bevy_auto_plugin_shared/src/syntax/ast/flag.rs index da6000fb..b538d029 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/ast/flag.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/ast/flag.rs @@ -1,9 +1,17 @@ -use darling::{FromMeta, Result}; +use darling::{ + FromMeta, + Result, +}; use proc_macro2::Span; use smart_default::SmartDefault; -use std::hash::{Hash, Hasher}; -use syn::Meta; -use syn::spanned::Spanned; +use std::hash::{ + Hash, + Hasher, +}; +use syn::{ + Meta, + spanned::Spanned, +}; /// Wrapper type for darling::util::Flag that implements PartialEq and Hash /// @@ -41,11 +49,7 @@ impl FromMeta for Flag { fn from_meta(meta: &Meta) -> Result { Ok(match meta { Meta::NameValue(nv) => { - if let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Bool(b), - .. - }) = &nv.value - { + if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Bool(b), .. }) = &nv.value { Self::from_bool(b.value)? } else { return Err(darling::Error::unknown_value("expected boolean literal") @@ -71,11 +75,7 @@ impl From for bool { impl From for Flag { fn from(v: bool) -> Self { - if v { - Self::present() - } else { - Self(darling::util::Flag::from(false)) - } + if v { Self::present() } else { Self(darling::util::Flag::from(false)) } } } @@ -93,27 +93,17 @@ mod tests { #[xtest] #[should_panic = "Unexpected type `lit`"] fn test_from_meta_flag_list_single() { - Flag::from_meta(&parse_quote!(this_flag("foo"))) - .map_err(|e| e.to_string()) - .unwrap(); + Flag::from_meta(&parse_quote!(this_flag("foo"))).map_err(|e| e.to_string()).unwrap(); } #[xtest] #[should_panic = "Multiple errors: (Unexpected type `lit`, Unexpected type `lit`)"] fn test_from_meta_flag_list_multiple() { - Flag::from_meta(&parse_quote!(this_flag("foo", "bar"))) - .map_err(|e| e.to_string()) - .unwrap(); + Flag::from_meta(&parse_quote!(this_flag("foo", "bar"))).map_err(|e| e.to_string()).unwrap(); } #[xtest] fn test_from_meta_flag_nv() -> syn::Result<()> { - assert_eq!( - Flag::from_meta(&parse_quote!(this_flag = true))?, - Flag::present() - ); - assert_eq!( - Flag::from_meta(&parse_quote!(this_flag = false))?, - Flag::from(false) - ); + assert_eq!(Flag::from_meta(&parse_quote!(this_flag = true))?, Flag::present()); + assert_eq!(Flag::from_meta(&parse_quote!(this_flag = false))?, Flag::from(false)); Ok(()) } } diff --git a/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_expr.rs b/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_expr.rs index 04e61f55..4abfb7e4 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_expr.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_expr.rs @@ -1,6 +1,13 @@ -use darling::{Error, FromMeta, Result}; +use darling::{ + Error, + FromMeta, + Result, +}; use smart_default::SmartDefault; -use syn::{Expr, Meta}; +use syn::{ + Expr, + Meta, +}; #[derive(Debug, SmartDefault, Clone, PartialEq, Hash)] pub struct FlagOrExpr { @@ -14,27 +21,18 @@ impl FromMeta for FlagOrExpr { fn from_meta(meta: &Meta) -> Result { match meta { // `#[this_flag]` - Meta::Path(_) => Ok(FlagOrExpr { - present: true, - expr: None, - }), + Meta::Path(_) => Ok(FlagOrExpr { present: true, expr: None }), // `#[this_flag(...)]` Meta::List(list) => { let parsed: Expr = list.parse_args().map_err(|_| { Error::unsupported_format("list with multiple parameters").with_span(list) })?; - Ok(FlagOrExpr { - present: true, - expr: Some(parsed), - }) + Ok(FlagOrExpr { present: true, expr: Some(parsed) }) } // `#[this_flag = ...]` - Meta::NameValue(nv) => Ok(FlagOrExpr { - present: true, - expr: Some(nv.value.clone()), - }), + Meta::NameValue(nv) => Ok(FlagOrExpr { present: true, expr: Some(nv.value.clone()) }), } } } @@ -49,10 +47,7 @@ mod tests { fn test_from_meta_flag_present() -> syn::Result<()> { assert_eq!( FlagOrExpr::from_meta(&parse_quote!(this_flag))?, - FlagOrExpr { - present: true, - expr: None, - } + FlagOrExpr { present: true, expr: None } ); Ok(()) } @@ -60,10 +55,7 @@ mod tests { fn test_from_meta_flag_list_single() -> syn::Result<()> { assert_eq!( FlagOrExpr::from_meta(&parse_quote!(this_flag("foo")))?, - FlagOrExpr { - present: true, - expr: Some(parse_quote!("foo")), - } + FlagOrExpr { present: true, expr: Some(parse_quote!("foo")) } ); Ok(()) } @@ -76,10 +68,7 @@ mod tests { fn test_from_meta_flag_nv() -> syn::Result<()> { assert_eq!( FlagOrExpr::from_meta(&parse_quote!(this_flag = "foo"))?, - FlagOrExpr { - present: true, - expr: Some(parse_quote!("foo")), - } + FlagOrExpr { present: true, expr: Some(parse_quote!("foo")) } ); Ok(()) } diff --git a/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_list.rs b/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_list.rs index 43f40786..9ef8aacd 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_list.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_list.rs @@ -1,10 +1,16 @@ -use darling::{Error, FromMeta, Result}; -use proc_macro2::{Ident, TokenStream}; -use quote::{ToTokens, quote}; +use darling::{ + Error, + FromMeta, + Result, +}; +use quote::ToTokens; use smart_default::SmartDefault; -use syn::parse::Parse; -use syn::punctuated::Punctuated; -use syn::{Meta, Token}; +use syn::{ + Meta, + Token, + parse::Parse, + punctuated::Punctuated, +}; #[derive(Debug, SmartDefault, Clone, PartialEq, Hash)] pub struct FlagOrList @@ -21,9 +27,11 @@ impl FlagOrList where T: ToTokens + Parse, { - pub fn to_outer_tokens(&self, flag_name: &str) -> TokenStream { + #[cfg(test)] + pub fn to_outer_tokens(&self, flag_name: &str) -> proc_macro2::TokenStream { + use quote::quote; use syn::spanned::Spanned; - let flag_ident = Ident::new(flag_name, self.present.span()); + let flag_ident = proc_macro2::Ident::new(flag_name, self.present.span()); if self.present { let items = &self.items; if !items.is_empty() { @@ -44,20 +52,14 @@ where fn from_meta(meta: &Meta) -> Result { match meta { // `#[this_flag]` - Meta::Path(_) => Ok(FlagOrList { - present: true, - items: vec![], - }), + Meta::Path(_) => Ok(FlagOrList { present: true, items: vec![] }), // `#[this_flag(A, B)]` Meta::List(list) => { let parsed: Punctuated = list .parse_args_with(Punctuated::parse_terminated) .map_err(|e| Error::custom(e).with_span(list))?; - Ok(FlagOrList { - present: true, - items: parsed.into_iter().collect(), - }) + Ok(FlagOrList { present: true, items: parsed.into_iter().collect() }) } // Not supported: `#[this_flag = ...]` @@ -70,14 +72,14 @@ where mod tests { use super::*; use internal_test_proc_macro::xtest; + use proc_macro2::Ident; + use quote::quote; use syn::parse_quote; #[xtest] fn test_flag_or_list_to_outer_tokens_not_present() { assert_eq!( - FlagOrList::::default() - .to_outer_tokens("this_flag") - .to_string(), + FlagOrList::::default().to_outer_tokens("this_flag").to_string(), quote! {}.to_string() ) } @@ -85,12 +87,9 @@ mod tests { #[xtest] fn test_flag_or_list_to_outer_tokens_empty() { assert_eq!( - FlagOrList:: { - present: true, - items: vec![] - } - .to_outer_tokens("this_flag") - .to_string(), + FlagOrList:: { present: true, items: vec![] } + .to_outer_tokens("this_flag") + .to_string(), quote! { this_flag }.to_string() ) } @@ -98,12 +97,9 @@ mod tests { #[xtest] fn test_flag_or_list_to_outer_tokens_single_item() { assert_eq!( - FlagOrList:: { - present: true, - items: vec![parse_quote!(A)] - } - .to_outer_tokens("this_flag") - .to_string(), + FlagOrList:: { present: true, items: vec![parse_quote!(A)] } + .to_outer_tokens("this_flag") + .to_string(), quote! { this_flag(A) }.to_string() ) } @@ -111,12 +107,9 @@ mod tests { #[xtest] fn test_flag_or_list_to_outer_tokens_multiple_item() { assert_eq!( - FlagOrList:: { - present: true, - items: vec![parse_quote!(A), parse_quote!(B)] - } - .to_outer_tokens("this_flag") - .to_string(), + FlagOrList:: { present: true, items: vec![parse_quote!(A), parse_quote!(B)] } + .to_outer_tokens("this_flag") + .to_string(), quote! { this_flag(A, B) }.to_string() ) } diff --git a/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_lit.rs b/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_lit.rs index 7c8ae1ae..14c15298 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_lit.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_lit.rs @@ -1,9 +1,15 @@ -use darling::{Error, FromMeta, Result}; -use proc_macro2::{Ident, TokenStream}; -use quote::quote; +use darling::{ + Error, + FromMeta, + Result, +}; use smart_default::SmartDefault; -use syn::spanned::Spanned; -use syn::{Expr, Lit, Meta}; +use syn::{ + Expr, + Lit, + Meta, + spanned::Spanned, +}; #[derive(Debug, SmartDefault, Clone, PartialEq, Hash)] pub struct FlagOrLit { @@ -14,9 +20,11 @@ pub struct FlagOrLit { } impl FlagOrLit { - pub fn to_outer_tokens(&self, flag_name: &str) -> TokenStream { + #[cfg(test)] + pub fn to_outer_tokens(&self, flag_name: &str) -> proc_macro2::TokenStream { + use quote::quote; use syn::spanned::Spanned; - let flag_ident = Ident::new(flag_name, self.present.span()); + let flag_ident = proc_macro2::Ident::new(flag_name, self.present.span()); if self.present { let items = &self.lit; if let Some(lit) = items { @@ -34,20 +42,14 @@ impl FromMeta for FlagOrLit { fn from_meta(meta: &Meta) -> Result { match meta { // `#[this_flag]` - Meta::Path(_) => Ok(FlagOrLit { - present: true, - lit: None, - }), + Meta::Path(_) => Ok(FlagOrLit { present: true, lit: None }), // `#[this_flag(A, B)]` Meta::List(list) => Err(Error::unsupported_format("list").with_span(list)), // Not supported: `#[this_flag = ...]` Meta::NameValue(nv) => match &nv.value { - Expr::Lit(lit) => Ok(FlagOrLit { - present: true, - lit: Some(lit.lit.clone()), - }), + Expr::Lit(lit) => Ok(FlagOrLit { present: true, lit: Some(lit.lit.clone()) }), other => Err(Error::unexpected_expr_type(other).with_span(&other.span())), }, } @@ -58,16 +60,14 @@ impl FromMeta for FlagOrLit { mod tests { use super::*; use internal_test_proc_macro::xtest; + use quote::quote; use syn::parse_quote; #[xtest] fn test_from_meta_flag_present() -> syn::Result<()> { assert_eq!( FlagOrLit::from_meta(&parse_quote!(this_flag))?, - FlagOrLit { - present: true, - lit: None, - } + FlagOrLit { present: true, lit: None } ); Ok(()) } @@ -75,43 +75,30 @@ mod tests { fn test_from_meta_flag_set() -> syn::Result<()> { assert_eq!( FlagOrLit::from_meta(&parse_quote!(this_flag = "foo"))?, - FlagOrLit { - present: true, - lit: Some(parse_quote!("foo")), - } + FlagOrLit { present: true, lit: Some(parse_quote!("foo")) } ); Ok(()) } #[xtest] fn test_flag_or_lit_to_outer_tokens_not_present() { assert_eq!( - FlagOrLit::default() - .to_outer_tokens("this_flag") - .to_string(), + FlagOrLit::default().to_outer_tokens("this_flag").to_string(), quote! {}.to_string() ) } #[xtest] fn test_flag_or_lit_to_outer_tokens_present() { assert_eq!( - FlagOrLit { - present: true, - lit: None, - } - .to_outer_tokens("this_flag") - .to_string(), + FlagOrLit { present: true, lit: None }.to_outer_tokens("this_flag").to_string(), quote! { this_flag }.to_string() ) } #[xtest] fn test_flag_or_lit_to_outer_tokens_set() { assert_eq!( - FlagOrLit { - present: true, - lit: Some(parse_quote!("foo")) - } - .to_outer_tokens("this_flag") - .to_string(), + FlagOrLit { present: true, lit: Some(parse_quote!("foo")) } + .to_outer_tokens("this_flag") + .to_string(), quote! { this_flag = "foo" }.to_string() ) } diff --git a/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_meta.rs b/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_meta.rs index 813c2437..5fe56826 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_meta.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/ast/flag_or_meta.rs @@ -1,9 +1,21 @@ -use darling::{Error, FromMeta, Result}; -use proc_macro2::{Ident, TokenStream}; -use quote::{ToTokens, quote}; +use darling::{ + Error, + FromMeta, + Result, +}; +use proc_macro2::{ + Ident, + TokenStream, +}; +use quote::{ + ToTokens, + quote, +}; use smart_default::SmartDefault; -use syn::Meta; -use syn::parse::Parse; +use syn::{ + Meta, + parse::Parse, +}; #[derive(Debug, SmartDefault, Clone, PartialEq, Hash)] pub struct FlagOrMeta @@ -43,19 +55,13 @@ where fn from_meta(meta: &Meta) -> Result { match meta { // `#[this_flag]` - Meta::Path(_) => Ok(FlagOrMeta { - present: true, - inner_meta: None, - }), + Meta::Path(_) => Ok(FlagOrMeta { present: true, inner_meta: None }), // `#[this_flag(A, B)]` Meta::List(_) => { // (T::from_meta sees Meta::List and can do `list.tokens` parsing inside) let t = T::from_meta(meta)?; - Ok(Self { - present: true, - inner_meta: Some(t), - }) + Ok(Self { present: true, inner_meta: Some(t) }) } // Not supported: `#[this_flag = ...]` @@ -71,10 +77,7 @@ where fn parse(input: syn::parse::ParseStream) -> syn::Result { // #[flag] -> no tokens after the key if input.is_empty() { - return Ok(Self { - present: true, - inner_meta: None, - }); + return Ok(Self { present: true, inner_meta: None }); } // #[flag(...)] -> parenthesized payload parsed as T @@ -86,10 +89,7 @@ where if !input.is_empty() { return Err(input.error("unexpected tokens after parenthesized payload")); } - return Ok(Self { - present: true, - inner_meta: Some(inner), - }); + return Ok(Self { present: true, inner_meta: Some(inner) }); } Err(input.error("expected nothing or a parenthesized payload")) @@ -124,9 +124,7 @@ mod tests { #[xtest] fn test_flag_or_list_to_outer_tokens_not_present() { assert_eq!( - FlagOrMeta::::default() - .to_outer_tokens("this_flag") - .to_string(), + FlagOrMeta::::default().to_outer_tokens("this_flag").to_string(), quote! {}.to_string() ) } @@ -134,12 +132,9 @@ mod tests { #[xtest] fn test_flag_or_list_to_outer_tokens_empty() { assert_eq!( - FlagOrMeta:: { - present: true, - inner_meta: None, - } - .to_outer_tokens("this_flag") - .to_string(), + FlagOrMeta:: { present: true, inner_meta: None } + .to_outer_tokens("this_flag") + .to_string(), quote! { this_flag }.to_string() ) } @@ -147,15 +142,9 @@ mod tests { #[xtest] fn test_flag_or_list_to_outer_tokens_single_item() { assert_eq!( - FlagOrMeta:: { - present: true, - inner_meta: Some(Test { - a: Some(1), - b: None, - }), - } - .to_outer_tokens("this_flag") - .to_string(), + FlagOrMeta:: { present: true, inner_meta: Some(Test { a: Some(1), b: None }) } + .to_outer_tokens("this_flag") + .to_string(), quote! { this_flag(a = 1u32) }.to_string() ) } @@ -163,15 +152,9 @@ mod tests { #[xtest] fn test_flag_or_list_to_outer_tokens_multiple_item() { assert_eq!( - FlagOrMeta:: { - present: true, - inner_meta: Some(Test { - a: Some(1), - b: Some(2), - }), - } - .to_outer_tokens("this_flag") - .to_string(), + FlagOrMeta:: { present: true, inner_meta: Some(Test { a: Some(1), b: Some(2) }) } + .to_outer_tokens("this_flag") + .to_string(), quote! { this_flag(a = 1u32, b = 2u32) }.to_string() ) } @@ -182,10 +165,7 @@ mod tests { let item = syn::parse2::>(input).map_err(|e| e.to_string()); let expected = Ok(FlagOrMeta:: { present: true, - inner_meta: Some(Test { - a: Some(1), - b: Some(2), - }), + inner_meta: Some(Test { a: Some(1), b: Some(2) }), }); assert_eq!(item, expected); } diff --git a/crates/bevy_auto_plugin_shared/src/syntax/ast/type_list.rs b/crates/bevy_auto_plugin_shared/src/syntax/ast/type_list.rs index 2fa1331d..6aeb71ad 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/ast/type_list.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/ast/type_list.rs @@ -1,9 +1,23 @@ -use darling::{Error, FromMeta}; -use proc_macro2::TokenStream; -use quote::{ToTokens, quote}; -use syn::parse::Parser; -use syn::spanned::Spanned; -use syn::{Meta, Token, Type, punctuated::Punctuated}; +use darling::{ + Error, + FromMeta, +}; +use proc_macro2::{ + TokenStream, + TokenTree, +}; +use quote::{ + ToTokens, + quote, +}; +use syn::{ + Meta, + Token, + Type, + parse::Parser, + punctuated::Punctuated, + spanned::Spanned, +}; #[derive(Debug, Clone, Default, PartialEq, Hash)] pub struct TypeList(pub Vec); @@ -44,30 +58,69 @@ impl From for TokenStream { } } +fn failed_err_literal(e: syn::Error, span: &proc_macro2::Span) -> Error { + Error::multiple(vec![ + Error::custom( + "Failed to parse TypeList: expected a list of *types*, but found literal value(s). \ + Use types like `Foo`, `Vec`, `Option`; not `1`, `true`, or string literals." + ).with_span(span), + Error::from(e), + ]) +} + fn failed_err(e: syn::Error, span: &proc_macro2::Span) -> Error { Error::multiple(vec![ - Error::custom("failed to parse TypeList").with_span(span), + Error::custom("Failed to parse TypeList").with_span(span), Error::from(e), ]) } +/// Inspect `TokenStream` for `Literal` +fn contains_literal(ts: &TokenStream) -> bool { + ts.clone().into_iter().any(|tt| matches!(tt, TokenTree::Literal(_))) +} + +/// Inspect `ParseStream` for `Literal` +fn fork_has_literal(input: syn::parse::ParseStream) -> bool { + let f = input.fork(); + let ts: TokenStream = match f.parse() { + Ok(ts) => ts, + Err(_) => return false, + }; + ts.into_iter().any(|tt| matches!(tt, TokenTree::Literal(_))) +} + impl FromMeta for TypeList { fn from_meta(meta: &Meta) -> Result { let list = meta.require_list()?; // Parse its tokens as `T, T, ...` where each `T` is a syn::Type let parser = Punctuated::::parse_terminated; - let elems = parser - .parse2(list.tokens.clone()) - .map_err(|e| failed_err(e, &list.tokens.to_token_stream().span()))?; + let elems = parser.parse2(list.tokens.clone()).map_err(|e| { + if contains_literal(&list.tokens) { + failed_err_literal(e, &list.tokens.span()) + } else { + failed_err(e, &list.tokens.span()) + } + })?; Ok(TypeList(elems.into_iter().collect())) } } impl syn::parse::Parse for TypeList { fn parse(input: syn::parse::ParseStream) -> syn::Result { - use syn::{Token, Type, punctuated::Punctuated}; + use syn::{ + Token, + Type, + punctuated::Punctuated, + }; let elems = Punctuated::::parse_terminated(input) - .map_err(|e| failed_err(e, &input.span()))? + .map_err(|e| { + if fork_has_literal(input) { + failed_err_literal(e, &input.span()) + } else { + failed_err(e, &input.span()) + } + })? .into_iter() .collect(); Ok(TypeList(elems)) @@ -78,15 +131,44 @@ impl syn::parse::Parse for TypeList { mod tests { use super::*; use internal_test_proc_macro::xtest; - use syn::{Meta, Type, parse_quote}; + use syn::{ + Meta, + Type, + parse_quote, + parse2, + }; #[derive(Debug, FromMeta)] + #[darling(derive_syn_parse)] pub struct FooAttr { pub types: TypeList, } #[xtest] fn parse_types() { + let types = quote! { u32, i32, FooBar, [u8; 4] }; + let meta: Meta = parse_quote!(types(#types)); + let attr: FooAttr = parse2(meta.to_token_stream()).unwrap(); + + assert_eq!(attr.types.0.len(), 4); + + // The third element should be `Foo` with generics preserved. + match &attr.types.0[2] { + Type::Path(tp) => { + let seg = tp.path.segments.last().unwrap(); + assert_eq!(seg.ident, "FooBar"); + assert!(matches!(seg.arguments, syn::PathArguments::AngleBracketed(_))); + } + _ => panic!("expected Type::Path for element 2"), + } + + let type_list = &attr.types; + let tokens = quote! { #type_list }; + assert_eq!(tokens.to_string(), types.to_string()); + } + + #[xtest] + fn from_meta_types() { let types = quote! { u32, i32, FooBar, [u8; 4] }; let meta: Meta = parse_quote!(foo(types(#types))); let attr: FooAttr = FooAttr::from_meta(&meta).unwrap(); @@ -98,10 +180,7 @@ mod tests { Type::Path(tp) => { let seg = tp.path.segments.last().unwrap(); assert_eq!(seg.ident, "FooBar"); - assert!(matches!( - seg.arguments, - syn::PathArguments::AngleBracketed(_) - )); + assert!(matches!(seg.arguments, syn::PathArguments::AngleBracketed(_))); } _ => panic!("expected Type::Path for element 2"), } diff --git a/crates/bevy_auto_plugin_shared/src/syntax/diagnostic/kind.rs b/crates/bevy_auto_plugin_shared/src/syntax/diagnostic/kind.rs index 129f6ce2..44e140e1 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/diagnostic/kind.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/diagnostic/kind.rs @@ -1,4 +1,8 @@ -use syn::{Item, Pat, Type}; +use syn::{ + Item, + Pat, + Type, +}; #[allow(dead_code)] pub fn pat_kind(pat: &Pat) -> &'static str { diff --git a/crates/bevy_auto_plugin_shared/src/syntax/extensions/generics.rs b/crates/bevy_auto_plugin_shared/src/syntax/extensions/generics.rs index 22161d8a..a4d7e8a0 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/extensions/generics.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/extensions/generics.rs @@ -1,5 +1,12 @@ use crate::syntax::extensions::path::PathExt; -use syn::{Lifetime, Path, TraitBound, TraitBoundModifier, TypeParamBound, parse_quote}; +use syn::{ + Lifetime, + Path, + TraitBound, + TraitBoundModifier, + TypeParamBound, + parse_quote, +}; /// Injects `Send + Sync + 'static` constraints to any generics that don't have them pub fn inject_send_sync_static(generics: &mut syn::Generics) { @@ -61,7 +68,10 @@ pub fn inject_send_sync_static(generics: &mut syn::Generics) { mod tests { use super::*; use internal_test_proc_macro::xtest; - use quote::{ToTokens, quote}; + use quote::{ + ToTokens, + quote, + }; use syn::parse_quote; #[xtest] diff --git a/crates/bevy_auto_plugin_shared/src/syntax/extensions/item.rs b/crates/bevy_auto_plugin_shared/src/syntax/extensions/item.rs index 1fe3b06e..2651f330 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/extensions/item.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/extensions/item.rs @@ -1,6 +1,10 @@ #![allow(dead_code)] -use syn::{Attribute, Item}; +use quote::ToTokens; +use syn::{ + Attribute, + Item, +}; use thiserror::Error; #[derive(Error, Debug)] @@ -10,6 +14,13 @@ pub enum TakeAndPutAttrsError { } pub trait ItemAttrsExt { + fn get_ident(&self) -> Option<&syn::Ident>; + fn ident(&self) -> syn::Result<&syn::Ident> + where + Self: ToTokens, + { + self.get_ident().ok_or_else(|| syn::Error::new_spanned(self, "Item does not have ident")) + } fn clone_attrs(&self) -> Option>; fn attrs(&self) -> Option<&[Attribute]>; fn attrs_mut(&mut self) -> Result<&mut Vec, TakeAndPutAttrsError>; @@ -18,6 +29,28 @@ pub trait ItemAttrsExt { } impl ItemAttrsExt for Item { + fn get_ident(&self) -> Option<&syn::Ident> { + match self { + Item::Const(item) => Some(&item.ident), + Item::Enum(item) => Some(&item.ident), + Item::ExternCrate(item) => Some(&item.ident), + Item::Fn(item) => Some(&item.sig.ident), + Item::ForeignMod(_) => None, + Item::Impl(_) => None, + Item::Macro(item) => item.ident.as_ref(), + Item::Mod(item) => Some(&item.ident), + Item::Static(item) => Some(&item.ident), + Item::Struct(item) => Some(&item.ident), + Item::Trait(item) => Some(&item.ident), + Item::TraitAlias(item) => Some(&item.ident), + Item::Type(item) => Some(&item.ident), + Item::Union(item) => Some(&item.ident), + // TODO: implement + Item::Use(_) => None, + Item::Verbatim(_) => None, + _ => None, + } + } fn clone_attrs(&self) -> Option> { Some(match self { Item::Const(i) => i.attrs.clone(), diff --git a/crates/bevy_auto_plugin_shared/src/syntax/extensions/path.rs b/crates/bevy_auto_plugin_shared/src/syntax/extensions/path.rs index f680335f..e31d9d68 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/extensions/path.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/extensions/path.rs @@ -1,5 +1,7 @@ -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::concrete_path; +use crate::syntax::{ + analysis::path, + ast::type_list::TypeList, +}; use syn::Path; pub trait PathExt { @@ -19,7 +21,7 @@ impl PathExt for Path { } fn generics(&self) -> syn::Result { - concrete_path::generics_from_path(self) + path::generics_from_path(self) } fn is_similar_path_or_ident(&self, other: &Self) -> bool { diff --git a/crates/bevy_auto_plugin_shared/src/syntax/parse/from_meta.rs b/crates/bevy_auto_plugin_shared/src/syntax/parse/from_meta.rs deleted file mode 100644 index 71bdec7e..00000000 --- a/crates/bevy_auto_plugin_shared/src/syntax/parse/from_meta.rs +++ /dev/null @@ -1,94 +0,0 @@ -use darling::FromMeta; -use syn::Meta; - -#[allow(dead_code)] -pub trait FromMetaExt: FromMeta { - fn from_meta_ext(meta: &Meta) -> darling::Result; -} - -impl FromMetaExt for T { - /// allows us to call from_meta on items that are just a path (no args) - fn from_meta_ext(meta: &Meta) -> darling::Result { - match meta { - Meta::Path(_) => T::from_list(&[]), - _ => T::from_meta(meta), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use internal_test_proc_macro::xtest; - use syn::{Attribute, parse_quote}; - - #[derive(FromMeta, Debug, Default, PartialEq)] - #[darling(default)] - struct TestList { - a: Option, - b: Option, - } - - #[derive(FromMeta, Debug, Default, PartialEq)] - #[darling(default)] - struct TestSingle(Option); - - #[xtest] - #[should_panic(expected = "Unexpected meta-item format `word`")] - fn test_from_meta_word_panic() { - let attr: Attribute = parse_quote!(#[foo]); - match TestList::from_meta(&attr.meta) { - Ok(_) => {} - Err(e) => panic!("{e}"), - } - } - - #[xtest] - fn test_from_meta_ext_word() { - let attr: Attribute = parse_quote!(#[foo]); - assert_eq!( - TestList::from_meta_ext(&attr.meta).ok(), - Some(TestList::default()) - ); - } - - #[xtest] - fn test_from_meta_ext_list() { - let attr: Attribute = parse_quote!(#[foo(a = "bar")]); - assert_eq!( - TestList::from_meta_ext(&attr.meta).ok(), - Some(TestList { - a: Some("bar".to_string()), - ..Default::default() - }) - ); - let attr: Attribute = parse_quote!(#[foo(a = "bar", b = "baz")]); - assert_eq!( - TestList::from_meta_ext(&attr.meta).ok(), - Some(TestList { - a: Some("bar".to_string()), - b: Some("baz".to_string()), - }) - ); - } - - #[xtest] - // Meta::Path on tuple struct unsupported - #[should_panic(expected = "Unexpected meta-item format `word`")] - fn test_from_meta_ext_name_value_empty() { - let attr: Attribute = parse_quote!(#[foo]); - match TestList::from_meta(&attr.meta) { - Ok(_) => {} - Err(e) => panic!("{e}"), - } - } - - #[xtest] - fn test_from_meta_ext_name_value() { - let attr: Attribute = parse_quote!(#[foo = "bar"]); - assert_eq!( - TestSingle::from_meta_ext(&attr.meta).ok(), - Some(TestSingle(Some("bar".to_string()))) - ); - } -} diff --git a/crates/bevy_auto_plugin_shared/src/syntax/parse/item.rs b/crates/bevy_auto_plugin_shared/src/syntax/parse/item.rs index 09e3554a..d00565c2 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/parse/item.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/parse/item.rs @@ -1,61 +1,65 @@ -use crate::syntax::extensions::item::ItemAttrsExt; -use crate::syntax::extensions::path::PathExt; use proc_macro2::TokenStream; -use syn::{Attribute, Item, parse2}; +use syn::{ + File, + Item, + ItemMacro, + Macro, +}; +use thiserror::Error; -pub fn ts_item_has_attr(input: TokenStream, path: &syn::Path) -> syn::Result { - let item = parse2::(input)?; - item_has_attr(item, path) +#[derive(Debug, Error)] +pub enum SingleItemWithErrorsCheckError { + #[error("failed to parse token stream as a single item: {0}")] + ParseFailed(#[from] syn::Error), + #[error("token stream did not contain an item")] + NoRealItem, + #[error("token stream contained more than one item")] + MultipleRealItems, } -pub fn item_has_attr(item: Item, path: &syn::Path) -> syn::Result { - Ok(has_attr(item.attrs().unwrap_or_default(), path)) -} +/// Returns the single Item (struct/enum/etc) if the token stream is: +/// `Item` [+ zero or more `compile_error!` items] +/// Fails otherwise. +pub fn expect_single_item_any_compile_errors( + ts: TokenStream, +) -> Result<(Item, Vec), SingleItemWithErrorsCheckError> { + let file: File = syn::parse2(ts).map_err(SingleItemWithErrorsCheckError::ParseFailed)?; -pub fn has_attr(attrs: &[Attribute], path: &syn::Path) -> bool { - attrs - .iter() - .any(|attr| attr.path().is_similar_path_or_ident(path)) -} + let mut compile_errors = vec![]; + let mut found_item: Option = None; -#[cfg(test)] -mod tests { - use super::*; - use internal_test_proc_macro::xtest; - use syn::parse_quote; + for item in file.items.into_iter() { + if let Some(compiler_error) = as_compile_error_item(&item) { + compile_errors.push(compiler_error.clone()); + continue; + } - #[xtest] - fn test_negative_has_attr_empty() { - let target_path = parse_quote!(a); - assert!(!has_attr(&[], &target_path)); + // It's a "real" item + match &found_item { + None => { + // first real item we've seen — keep it + found_item = Some(item); + } + Some(_) => { + // second real item → reject + return Err(SingleItemWithErrorsCheckError::MultipleRealItems); + } + } } - #[xtest] - fn test_negative_has_attr() { - let target_path = parse_quote!(a); - let input: Vec<_> = parse_quote! { - #[foo] - }; - assert!(!has_attr(&input, &target_path)); - } - - #[xtest] - fn test_positive_has_attr_single() { - let target_path = parse_quote!(a); - let input: Vec<_> = parse_quote! { - #[#target_path] - }; - assert!(has_attr(&input, &target_path)); + match found_item { + Some(item) => Ok((item, compile_errors)), + None => Err(SingleItemWithErrorsCheckError::NoRealItem), } +} - #[xtest] - fn test_positive_has_attr_multiple() { - let target_path = parse_quote!(a); - let input: Vec<_> = parse_quote! { - #[A] - #[#target_path] - #[B] - }; - assert!(has_attr(&input, &target_path)); +/// returns `Some(ItemMacro)` if `compile_error!(...)` ? +fn as_compile_error_item(item: &Item) -> Option<&ItemMacro> { + match item { + Item::Macro(item_macro) => { + let ItemMacro { mac: Macro { path, .. }, .. } = item_macro; + if path.is_ident("compile_error") { Some(item_macro) } else { None } + } + _ => None, } } diff --git a/crates/bevy_auto_plugin_shared/src/syntax/parse/mod.rs b/crates/bevy_auto_plugin_shared/src/syntax/parse/mod.rs index 8134199e..0ac05958 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/parse/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/parse/mod.rs @@ -1,3 +1,2 @@ -pub mod from_meta; pub mod item; pub mod scrub_helpers; diff --git a/crates/bevy_auto_plugin_shared/src/syntax/parse/scrub_helpers.rs b/crates/bevy_auto_plugin_shared/src/syntax/parse/scrub_helpers.rs index b4aea89f..b2d8a56b 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/parse/scrub_helpers.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/parse/scrub_helpers.rs @@ -1,11 +1,23 @@ #![allow(dead_code)] -use crate::syntax::extensions::item::ItemAttrsExt; +use crate::{ + codegen::emit::EmitErrOnlyResult, + macro_api::prelude::InputItem, + syntax::extensions::item::ItemAttrsExt, +}; use darling::FromMeta; use proc_macro2::TokenStream; -use quote::{ToTokens, quote}; -use syn::spanned::Spanned; -use syn::visit_mut::VisitMut; -use syn::{Attribute, Ident, Item, Meta}; +use quote::{ + ToTokens, + quote, +}; +use syn::{ + Attribute, + Ident, + Item, + Meta, + spanned::Spanned, + visit_mut::VisitMut, +}; /// Where an attribute was attached. #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -119,8 +131,10 @@ impl SiteAttrsVec { } pub struct ScrubOutcome { + /// original item (with helpers intact) + pub original_item: Item, /// scrubbed item (no helpers remain) - pub item: Item, + pub scrubbed_item: Item, /// struct/enum ident pub ident: Ident, /// attrs kept per site (non-helpers or empty) @@ -132,82 +146,73 @@ pub struct ScrubOutcome { } impl ScrubOutcome { + /// Returns all observed attrs and call sites regardless if they were removed or not pub fn all_with_removed_attrs(&self) -> Vec { let mut out = Vec::with_capacity(self.observed.len()); for group in self.observed.iter() { let mut attrs = vec![]; // linear search is fine here, since we expect the number of groups to be small - if let Some(removed_attrs) = self - .removed - .iter() - .find(|g| g.site == group.site) - .map(|g| g.attrs.clone()) + if let Some(removed_attrs) = + self.removed.iter().find(|g| g.site == group.site).map(|g| g.attrs.clone()) { attrs.extend(removed_attrs); } - out.push(SiteAttrs { - site: group.site.clone(), - attrs, - }); + out.push(SiteAttrs { site: group.site.clone(), attrs }); } out } - // TODO: Better name? - /// If there are errors, it will write them above the scrubbed item. - /// Otherwise, will NOT write the scrubbed item back into the TokenStream - pub fn write_if_errors_with_scrubbed_item( - &self, - token_stream: &mut TokenStream, - ) -> syn::Result<()> { - write_back( - token_stream, - self.item.span(), - self.errors.clone(), - "failed to scrub helpers", - ) + pub fn to_original_item_tokens(&self) -> TokenStream { + ts_item_errors(&self.original_item, self.errors.clone()) } - pub fn write_back(&self, token_stream: &mut TokenStream) -> syn::Result<()> { - write_back_item( - token_stream, - &self.item, - self.errors.clone(), - "failed to scrub helpers", - ) + pub fn to_scrubbed_item_tokens(&self) -> TokenStream { + ts_item_errors(&self.scrubbed_item, self.errors.clone()) } } -pub fn write_back_item( - token_stream: &mut TokenStream, - item: &Item, - errors: Vec, - message: &str, -) -> syn::Result<()> { - *token_stream = quote! { - #item - }; +impl ToTokens for ScrubOutcome { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.scrubbed_item.to_tokens(tokens); + } +} - write_back(token_stream, item.span(), errors, message) +impl From for EmitErrOnlyResult { + fn from(value: ScrubOutcome) -> Self { + if value.errors.is_empty() { + Ok(value) + } else { + Err(( + value.original_item.to_token_stream(), + value.errors.into_iter().fold( + syn::Error::new(value.original_item.span(), "Failed to scrub helpers"), + |mut acc, err| { + acc.combine(err); + acc + }, + ), + )) + } + } } -pub fn write_back( - token_stream: &mut TokenStream, - span: proc_macro2::Span, - errors: Vec, - message: &str, -) -> syn::Result<()> { - // inject any errors as compile_error! right here. - if !errors.is_empty() { +/// Returns `TokenStream` with errors (if any) +pub fn ts_errors(errors: Vec) -> TokenStream { + if errors.is_empty() { + TokenStream::new() + } else { let err_ts = errors.iter().map(syn::Error::to_compile_error); - *token_stream = quote! { + quote! { #( #err_ts )* - #token_stream - }; - let mut err = syn::Error::new(span, message); - err.extend(errors.clone()); - return Err(err); + } } +} - Ok(()) +/// Returns `TokenStream` with errors (if any) and the given `item` +pub fn ts_item_errors(item: &Item, errors: Vec) -> TokenStream { + let errors = ts_errors(errors); + quote! { + #errors + #item + } } #[derive(Debug, Default)] @@ -246,10 +251,8 @@ impl Scrubber { let mut attrs = match it.take_attrs() { Ok(attrs) => attrs, Err(err) => { - self.errors.push(syn::Error::new( - it.span(), - format!("Failed to parse attrs: {err}"), - )); + self.errors + .push(syn::Error::new(it.span(), format!("Failed to parse attrs: {err}"))); return Vec::new(); } }; @@ -278,12 +281,9 @@ impl VisitMut for Scrubber { let field_ident = field.ident.clone().expect("named struct field"); let KeepSplit { keep, removed } = self.drain_split(&mut field.attrs); field.attrs = keep.clone(); - self.out.observed.push( - AttrSite::StructFieldNamed { - field: field_ident.clone(), - }, - keep, - ); + self.out + .observed + .push(AttrSite::StructFieldNamed { field: field_ident.clone() }, keep); if !removed.is_empty() { self.out .removed @@ -295,13 +295,9 @@ impl VisitMut for Scrubber { for (index, field) in fields_unnamed.unnamed.iter_mut().enumerate() { let KeepSplit { keep, removed } = self.drain_split(&mut field.attrs); field.attrs = keep.clone(); - self.out - .observed - .push(AttrSite::StructFieldUnnamed { index }, keep); + self.out.observed.push(AttrSite::StructFieldUnnamed { index }, keep); if !removed.is_empty() { - self.out - .removed - .push(AttrSite::StructFieldUnnamed { index }, removed); + self.out.removed.push(AttrSite::StructFieldUnnamed { index }, removed); } } } @@ -317,19 +313,11 @@ impl VisitMut for Scrubber { // variant-level let KeepSplit { keep, removed } = self.drain_split(&mut variant.attrs); variant.attrs = keep.clone(); - self.out.observed.push( - AttrSite::Variant { - variant: variant_ident.clone(), - }, - keep, - ); + self.out.observed.push(AttrSite::Variant { variant: variant_ident.clone() }, keep); if !removed.is_empty() { - self.out.removed.push( - AttrSite::Variant { - variant: variant_ident.clone(), - }, - removed, - ); + self.out + .removed + .push(AttrSite::Variant { variant: variant_ident.clone() }, removed); } // fields @@ -362,10 +350,7 @@ impl VisitMut for Scrubber { let KeepSplit { keep, removed } = self.drain_split(&mut field.attrs); field.attrs = keep.clone(); self.out.observed.push( - AttrSite::VariantFieldUnnamed { - variant: variant_ident.clone(), - index, - }, + AttrSite::VariantFieldUnnamed { variant: variant_ident.clone(), index }, keep, ); if !removed.is_empty() { @@ -386,29 +371,27 @@ impl VisitMut for Scrubber { } } -pub fn scrub_helpers_and_ident( - input: proc_macro2::TokenStream, +pub fn scrub_helpers( + input_item: impl AsRef, is_helper: fn(&Attribute) -> bool, - resolve_ident: fn(&Item) -> syn::Result<&Ident>, -) -> syn::Result { - scrub_helpers_and_ident_with_filter(input, |_, _| true, is_helper, resolve_ident) +) -> Result { + scrub_helpers_with_filter(input_item, |_, _| true, is_helper) } -pub fn scrub_helpers_and_ident_with_filter( - input: proc_macro2::TokenStream, +pub fn scrub_helpers_with_filter( + input_item: impl AsRef, is_site_allowed: fn(&AttrSite, &Attribute) -> bool, is_helper: fn(&Attribute) -> bool, - resolve_ident: fn(&Item) -> syn::Result<&Ident>, -) -> syn::Result { - let mut item: Item = syn::parse2(input)?; - let ident = resolve_ident(&item)?.clone(); - - let mut scrubber = Scrubber { - is_helper, - out: ScrubberOut::default(), - errors: vec![], - }; - scrubber.visit_item_mut(&mut item); +) -> Result { + let mut input_item = input_item.as_ref().clone(); + let ident = input_item.ident()?.clone(); + let original_item = input_item.ensure_ast()?.clone(); + let mut scrubbed_item = original_item.clone(); + + // Must be infallible beyond this point to ensure `ScrubOutcome` is valid + + let mut scrubber = Scrubber { is_helper, out: ScrubberOut::default(), errors: vec![] }; + scrubber.visit_item_mut(&mut scrubbed_item); // validate “removed” helpers against the site filter for group in &scrubber.out.removed.0 { @@ -430,7 +413,8 @@ pub fn scrub_helpers_and_ident_with_filter( } Ok(ScrubOutcome { - item, + original_item, + scrubbed_item, ident, observed: scrubber.out.observed, removed: scrubber.out.removed, @@ -448,10 +432,12 @@ pub fn parse_removed_as( #[cfg(test)] mod tests { use super::*; - use crate::syntax::analysis::item::resolve_ident_from_struct_or_enum; use crate::syntax::extensions::path::PathExt; use internal_test_proc_macro::xtest; - use quote::{ToTokens, quote}; + use quote::{ + ToTokens, + quote, + }; use syn::parse_quote; impl SiteAttrsVec { @@ -494,29 +480,26 @@ mod tests { Self::StructFieldUnnamed { index } => { format!("StructFieldUnnamed {{ index: {} }}", index) } - Self::Variant { variant } => format!("Variant {{ variant: {} }}", variant), - Self::VariantFieldNamed { variant, field } => format!( - "VariantFieldNamed {{ variant: {}, field: {} }}", - variant, field - ), - Self::VariantFieldUnnamed { variant, index } => format!( - "VariantFieldUnnamed {{ variant: {}, index: {} }}", - variant, index - ), + Self::Variant { variant } => { + format!("Variant {{ variant: {} }}", variant) + } + Self::VariantFieldNamed { variant, field } => { + format!("VariantFieldNamed {{ variant: {}, field: {} }}", variant, field) + } + Self::VariantFieldUnnamed { variant, index } => { + format!("VariantFieldUnnamed {{ variant: {}, index: {} }}", variant, index) + } } } } mod scrub_helpers_and_ident { use super::*; + use internal_test_util::assert_ts_eq; #[inline] fn assert_no_errors(scrub_outcome: &ScrubOutcome) { assert_eq!( - scrub_outcome - .errors - .iter() - .map(|e| e.to_string()) - .collect::>(), + scrub_outcome.errors.iter().map(|e| e.to_string()).collect::>(), Vec::::new() ); } @@ -528,12 +511,8 @@ mod tests { #[inline] fn assert_no_helpers_remain_on_item(scrub_outcome: &ScrubOutcome) { - let ts = scrub_outcome.item.to_token_stream().to_string(); - assert!( - !ts.contains("::helper"), - "item still has helper attributes: {}", - ts - ); + let ts = scrub_outcome.scrubbed_item.to_token_stream().to_string(); + assert!(!ts.contains("::helper"), "item still has helper attributes: {}", ts); } fn is_helper(attr: &Attribute) -> bool { @@ -541,9 +520,7 @@ mod tests { } fn resolve_ident(item: &Item) -> syn::Result<&Ident> { - resolve_ident_from_struct_or_enum(item).map_err(|err| { - syn::Error::new(item.span(), format!("failed to resolve ident: {err}")) - }) + item.ident() } #[xtest] @@ -559,26 +536,20 @@ mod tests { z: i32, } }; - let scrub_outcome = scrub_helpers_and_ident(input, is_helper, resolve_ident)?; + let input_item = InputItem::from_ts_validated(input).expect("should be valid item"); + let scrub_outcome = scrub_helpers(input_item, is_helper)?; assert_no_errors(&scrub_outcome); assert_ident(&scrub_outcome, "Foo"); assert_no_helpers_remain_on_item(&scrub_outcome); let got = scrub_outcome.removed; let expected = SiteAttrsVec::from_vec(vec![ + SiteAttrs { site: AttrSite::Item, attrs: vec![parse_quote!(#[item::helper])] }, SiteAttrs { - site: AttrSite::Item, - attrs: vec![parse_quote!(#[item::helper])], - }, - SiteAttrs { - site: AttrSite::StructFieldNamed { - field: parse_quote!(x), - }, + site: AttrSite::StructFieldNamed { field: parse_quote!(x) }, attrs: vec![parse_quote!(#[field::helper])], }, SiteAttrs { - site: AttrSite::StructFieldNamed { - field: parse_quote!(z), - }, + site: AttrSite::StructFieldNamed { field: parse_quote!(z) }, attrs: vec![ parse_quote!(#[field::_1::helper]), parse_quote!(#[field::_2::helper]), @@ -608,26 +579,20 @@ mod tests { C, } }; - let scrub_outcome = scrub_helpers_and_ident(input, is_helper, resolve_ident)?; + let input_item = InputItem::from_ts_validated(input).expect("should be valid item"); + let scrub_outcome = scrub_helpers(input_item, is_helper)?; assert_no_errors(&scrub_outcome); assert_ident(&scrub_outcome, "Foo"); assert_no_helpers_remain_on_item(&scrub_outcome); let got = scrub_outcome.removed; let expected = SiteAttrsVec::from_vec(vec![ + SiteAttrs { site: AttrSite::Item, attrs: vec![parse_quote!(#[item::helper])] }, SiteAttrs { - site: AttrSite::Item, - attrs: vec![parse_quote!(#[item::helper])], - }, - SiteAttrs { - site: AttrSite::Variant { - variant: parse_quote!(A), - }, + site: AttrSite::Variant { variant: parse_quote!(A) }, attrs: vec![parse_quote!(#[field::helper])], }, SiteAttrs { - site: AttrSite::Variant { - variant: parse_quote!(C), - }, + site: AttrSite::Variant { variant: parse_quote!(C) }, attrs: vec![ parse_quote!(#[field::_1::helper]), parse_quote!(#[field::_2::helper]), @@ -664,13 +629,14 @@ mod tests { Y, } }; - let scrub_outcome = scrub_helpers_and_ident(input, is_helper, resolve_ident)?; + let input_item = InputItem::from_ts_validated(input).expect("should be valid item"); + let scrub_outcome = scrub_helpers(input_item, is_helper)?; assert_no_errors(&scrub_outcome); assert_ident(&scrub_outcome, "Foo"); assert_no_helpers_remain_on_item(&scrub_outcome); - let item = scrub_outcome.item; - assert_eq!( - item.to_token_stream().to_string(), + let item = scrub_outcome.scrubbed_item; + assert_ts_eq!( + item.to_token_stream(), quote! { #[non_helper] enum Foo { @@ -684,7 +650,6 @@ mod tests { Y, } } - .to_string(), ); Ok(()) } @@ -706,34 +671,23 @@ mod tests { C, } }; - let scrub_outcome = scrub_helpers_and_ident(input, is_helper, resolve_ident)?; + let input_item = InputItem::from_ts_validated(input).expect("should be valid item"); + let scrub_outcome = scrub_helpers(input_item, is_helper)?; assert_no_errors(&scrub_outcome); assert_ident(&scrub_outcome, "Foo"); assert_no_helpers_remain_on_item(&scrub_outcome); let got = scrub_outcome.observed; let expected = SiteAttrsVec::from_vec(vec![ + SiteAttrs { site: AttrSite::Item, attrs: vec![parse_quote!(#[non_helper])] }, SiteAttrs { - site: AttrSite::Item, + site: AttrSite::Variant { variant: parse_quote!(A) }, attrs: vec![parse_quote!(#[non_helper])], }, SiteAttrs { - site: AttrSite::Variant { - variant: parse_quote!(A), - }, - attrs: vec![parse_quote!(#[non_helper])], - }, - SiteAttrs { - site: AttrSite::Variant { - variant: parse_quote!(B), - }, + site: AttrSite::Variant { variant: parse_quote!(B) }, attrs: vec![parse_quote!(#[non_helper]), parse_quote!(#[non_helper])], }, - SiteAttrs { - site: AttrSite::Variant { - variant: parse_quote!(C), - }, - attrs: vec![], - }, + SiteAttrs { site: AttrSite::Variant { variant: parse_quote!(C) }, attrs: vec![] }, ]); assert_eq!( got.to_test_string(), @@ -766,47 +720,29 @@ mod tests { } }; - let scrub_outcome = scrub_helpers_and_ident(input, is_helper, resolve_ident)?; + let input_item = InputItem::from_ts_validated(input).expect("should be valid item"); + let scrub_outcome = scrub_helpers(input_item, is_helper)?; let got = SiteAttrsVec::from_vec(scrub_outcome.all_with_removed_attrs()); let expected = SiteAttrsVec::from_vec(vec![ + SiteAttrs { site: AttrSite::Item, attrs: vec![parse_quote!(#[item::helper])] }, SiteAttrs { - site: AttrSite::Item, - attrs: vec![parse_quote!(#[item::helper])], - }, - SiteAttrs { - site: AttrSite::Variant { - variant: parse_quote!(A), - }, + site: AttrSite::Variant { variant: parse_quote!(A) }, attrs: vec![parse_quote!(#[field::helper])], }, + SiteAttrs { site: AttrSite::Variant { variant: parse_quote!(B) }, attrs: vec![] }, SiteAttrs { - site: AttrSite::Variant { - variant: parse_quote!(B), - }, - attrs: vec![], - }, - SiteAttrs { - site: AttrSite::Variant { - variant: parse_quote!(C), - }, + site: AttrSite::Variant { variant: parse_quote!(C) }, attrs: vec![ parse_quote!(#[field::_1::helper]), parse_quote!(#[field::_2::helper]), ], }, SiteAttrs { - site: AttrSite::Variant { - variant: parse_quote!(X), - }, + site: AttrSite::Variant { variant: parse_quote!(X) }, attrs: vec![parse_quote!(#[field::helper])], }, - SiteAttrs { - site: AttrSite::Variant { - variant: parse_quote!(Y), - }, - attrs: vec![], - }, + SiteAttrs { site: AttrSite::Variant { variant: parse_quote!(Y) }, attrs: vec![] }, ]); assert_eq!( diff --git a/crates/bevy_auto_plugin_shared/src/syntax/validated/concrete_path.rs b/crates/bevy_auto_plugin_shared/src/syntax/validated/concrete_path.rs deleted file mode 100644 index 84b0ff39..00000000 --- a/crates/bevy_auto_plugin_shared/src/syntax/validated/concrete_path.rs +++ /dev/null @@ -1,171 +0,0 @@ -use crate::codegen::with_target_path::WithTargetPath; -use crate::macro_api::attributes::prelude::GenericsArgs; -use crate::syntax::ast::type_list::TypeList; -use crate::syntax::validated::generics::{Generics, GenericsCollection}; -use crate::syntax::validated::path_without_generics::{ - PathWithoutGenerics, TryFromPathWithoutGenericsError, -}; -use proc_macro2::TokenStream as MacroStream; -use quote::{ToTokens, quote}; -use syn::{Path, PathArguments, parse2}; - -#[derive(Debug, Clone, PartialEq, Hash)] -pub struct ConcreteTargetPath { - pub target: PathWithoutGenerics, - pub generics: Generics, - pub turbofish: bool, -} - -impl ToTokens for ConcreteTargetPath { - fn to_tokens(&self, tokens: &mut MacroStream) { - let path = &self.target; - let generics = &self.generics; - tokens.extend(if generics.is_empty() { - // TODO: generics already handles tokens properly when empty but we shoehorned the turbofish flag - // seemed more appropriate than forcing Generics to inherit the complexity and become an enum for both variants - // but there's likely a better way? - quote! { #path } - } else if self.turbofish { - quote! { #path :: #generics } - } else { - quote! { #path #generics } - }); - } -} - -#[derive(Debug, Clone, PartialEq, Hash)] -pub struct ConcreteTargetPathWithGenericsCollection { - pub target: PathWithoutGenerics, - pub generics: GenericsCollection, - pub turbofish: bool, -} - -impl ConcreteTargetPathWithGenericsCollection { - pub fn from_args(path: PathWithoutGenerics, args: &T) -> Self { - Self { - target: path, - generics: args.generics(), - turbofish: T::TURBOFISH, - } - } -} - -impl IntoIterator for ConcreteTargetPathWithGenericsCollection { - type Item = ConcreteTargetPath; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - // TODO: better way? - self.generics - .iter_with_default_generics_when_empty() - .into_iter() - .map(|generics| ConcreteTargetPath { - target: self.target.clone(), - generics, - turbofish: self.turbofish, - }) - .collect::>() - .into_iter() - } -} - -impl From<(PathWithoutGenerics, T)> for ConcreteTargetPathWithGenericsCollection { - fn from(value: (PathWithoutGenerics, T)) -> Self { - let (target, args) = value; - Self::from_args(target, &args) - } -} - -impl TryFrom<(Path, T)> for ConcreteTargetPathWithGenericsCollection { - type Error = TryFromPathWithoutGenericsError; - fn try_from(value: (Path, T)) -> Result { - let (target, args) = value; - Ok(Self::from((target.try_into()?, args))) - } -} - -impl From> for ConcreteTargetPathWithGenericsCollection { - fn from(value: WithTargetPath) -> Self { - let (target, args) = value.into(); - Self::from((target, args)) - } -} - -pub fn generics_from_path(path: &Path) -> syn::Result { - let mut generics = TypeList::empty(); - for segment in &path.segments { - if let PathArguments::AngleBracketed(angle_bracketed) = &segment.arguments { - let type_list = parse2::(angle_bracketed.args.to_token_stream())?; - generics.0.extend(type_list.0); - } - } - Ok(generics) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::syntax::validated::generics::Generics; - use internal_test_proc_macro::xtest; - - #[xtest] - fn test_generics_from_path() -> Result<(), syn::Error> { - let item = parse2::(quote! { - foo::bar:: - })?; - let generics = generics_from_path(&item).expect("no generics"); - let generics = quote! { #generics }; - assert_eq!("u32 , i32", generics.to_string()); - Ok(()) - } - - #[xtest] - fn test_concrete_target_path_to_tokens() -> syn::Result<()> { - assert_eq!( - ConcreteTargetPath { - target: parse2::(quote! { Foo })?, - generics: Generics(parse2::(quote! {})?), - turbofish: false, - } - .to_token_stream() - .to_string(), - quote! { Foo }.to_string() - ); - assert_eq!( - ConcreteTargetPath { - target: parse2::(quote! { Foo })?, - generics: Generics(parse2::(quote! { u8 })?), - turbofish: false, - } - .to_token_stream() - .to_string(), - quote! { Foo }.to_string() - ); - Ok(()) - } - - #[xtest] - fn test_concrete_target_path_to_tokens_turbofish() -> syn::Result<()> { - assert_eq!( - ConcreteTargetPath { - target: parse2::(quote! { Foo })?, - generics: Generics(parse2::(quote! {})?), - turbofish: true, - } - .to_token_stream() - .to_string(), - quote! { Foo }.to_string() - ); - assert_eq!( - ConcreteTargetPath { - target: parse2::(quote! { Foo })?, - generics: Generics(parse2::(quote! { u8 })?), - turbofish: true, - } - .to_token_stream() - .to_string(), - quote! { Foo:: }.to_string() - ); - Ok(()) - } -} diff --git a/crates/bevy_auto_plugin_shared/src/syntax/validated/generics.rs b/crates/bevy_auto_plugin_shared/src/syntax/validated/generics.rs deleted file mode 100644 index ba831a92..00000000 --- a/crates/bevy_auto_plugin_shared/src/syntax/validated/generics.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::syntax::ast::type_list::TypeList; -use proc_macro2::TokenStream as MacroStream; -use quote::{ToTokens, quote}; - -#[derive(Debug, Clone, PartialEq, Hash)] -pub struct GenericsCollection(pub Vec); - -impl GenericsCollection { - pub fn iter_with_default_generics_when_empty(self) -> impl IntoIterator { - let mut vec = self.0.into_iter().map(Generics).collect::>(); - - if vec.is_empty() { - vec.push(Generics::default()); - } - - vec.into_iter() - } - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - pub fn to_attribute_arg_vec_tokens(&self) -> Vec { - self.0 - .iter() - .map(|type_list| quote!(generics(#type_list))) - .collect() - } - pub fn to_attribute_arg_tokens(&self) -> MacroStream { - let tokens = self.to_attribute_arg_vec_tokens(); - quote!(#(#tokens),*) - } -} - -impl IntoIterator for GenericsCollection { - type Item = Generics; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0 - .into_iter() - .map(Generics) - .collect::>() - .into_iter() - } -} - -#[derive(Debug, Clone, Default, PartialEq, Hash)] -pub struct Generics(pub TypeList); - -impl Generics { - pub fn empty() -> Self { - Generics(TypeList::empty()) - } - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -impl ToTokens for Generics { - fn to_tokens(&self, tokens: &mut MacroStream) { - if self.is_empty() { - return; - } - let types = &self.0; - tokens.extend(quote!(< #types >)); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use darling::FromMeta; - use internal_test_proc_macro::xtest; - use quote::quote; - use syn::Type; - use syn::TypePath; - - fn types() -> syn::Result { - let ty_u32 = Type::Path(TypePath::from_string("u32")?); - let ty_bool = Type::Path(TypePath::from_string("bool")?); - Ok(TypeList(vec![ty_u32, ty_bool])) - } - - #[xtest] - fn test_generics() -> syn::Result<()> { - assert_eq!( - Generics(TypeList::empty()).to_token_stream().to_string(), - quote!().to_string() - ); - assert_eq!( - Generics(types()?).to_token_stream().to_string(), - quote!().to_string() - ); - assert_eq!( - Generics(types()?).to_token_stream().to_string(), - quote!().to_string() - ); - Ok(()) - } - - #[xtest] - fn test_generics_collection() -> syn::Result<()> { - let generics = GenericsCollection(vec![types()?]); - let mut iter = generics.into_iter(); - assert_eq!(iter.next(), Some(Generics(types()?))); - assert_eq!(iter.next(), None); - Ok(()) - } -} diff --git a/crates/bevy_auto_plugin_shared/src/syntax/validated/mod.rs b/crates/bevy_auto_plugin_shared/src/syntax/validated/mod.rs index 3e2abd2c..f16fc98a 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/validated/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/validated/mod.rs @@ -1,4 +1,2 @@ -pub mod concrete_path; -pub mod generics; pub mod non_empty_path; pub mod path_without_generics; diff --git a/crates/bevy_auto_plugin_shared/src/syntax/validated/non_empty_path.rs b/crates/bevy_auto_plugin_shared/src/syntax/validated/non_empty_path.rs index fbcd4236..c12febed 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/validated/non_empty_path.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/validated/non_empty_path.rs @@ -1,9 +1,18 @@ use darling::FromMeta; -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::{ + Ident, + TokenStream, +}; use quote::ToTokens; -use syn::parse::{Parse, ParseStream}; -use syn::spanned::Spanned; -use syn::{Meta, Path}; +use syn::{ + Meta, + Path, + parse::{ + Parse, + ParseStream, + }, + spanned::Spanned, +}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[repr(transparent)] @@ -20,12 +29,7 @@ impl NonEmptyPath { Self(path) } pub fn into_last_ident(self) -> Ident { - self.0 - .segments - .into_iter() - .last() - .expect("non-empty path") - .ident + self.0.segments.into_iter().last().expect("non-empty path").ident } pub fn last_ident(&self) -> &Ident { &self.0.segments.last().expect("non-empty path").ident @@ -92,8 +96,12 @@ mod tests { use super::*; use internal_test_proc_macro::xtest; use quote::quote; - use syn::punctuated::Punctuated; - use syn::{PathSegment, Token, parse_quote}; + use syn::{ + PathSegment, + Token, + parse_quote, + punctuated::Punctuated, + }; #[xtest] fn test_parse() { @@ -114,22 +122,13 @@ mod tests { fn test_to_tokens() { let input_tokens = quote!(::this); let input: NonEmptyPath = parse_quote!(#input_tokens); - assert_eq!( - input.to_token_stream().to_string(), - input_tokens.to_string() - ); + assert_eq!(input.to_token_stream().to_string(), input_tokens.to_string()); let input_tokens = quote!(Foo); let input: NonEmptyPath = parse_quote!(#input_tokens); - assert_eq!( - input.to_token_stream().to_string(), - input_tokens.to_string() - ); + assert_eq!(input.to_token_stream().to_string(), input_tokens.to_string()); let input_tokens = quote!(this::Foo); let input: NonEmptyPath = parse_quote!(#input_tokens); - assert_eq!( - input.to_token_stream().to_string(), - input_tokens.to_string() - ); + assert_eq!(input.to_token_stream().to_string(), input_tokens.to_string()); } #[xtest] @@ -137,7 +136,7 @@ mod tests { assert!( NonEmptyPath::new(Path { leading_colon: None, - segments: Punctuated::::new(), + segments: Punctuated::::new() }) .is_err() ); diff --git a/crates/bevy_auto_plugin_shared/src/syntax/validated/path_without_generics.rs b/crates/bevy_auto_plugin_shared/src/syntax/validated/path_without_generics.rs index 5ded5f47..8d9fb15f 100644 --- a/crates/bevy_auto_plugin_shared/src/syntax/validated/path_without_generics.rs +++ b/crates/bevy_auto_plugin_shared/src/syntax/validated/path_without_generics.rs @@ -1,10 +1,17 @@ use crate::syntax::extensions::path::PathExt; -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::{ + Ident, + TokenStream, +}; use quote::ToTokens; -use syn::parse::Parse; -use syn::punctuated::Punctuated; -use syn::spanned::Spanned; -use syn::{Path, PathArguments, PathSegment}; +use syn::{ + Path, + PathArguments, + PathSegment, + parse::Parse, + punctuated::Punctuated, + spanned::Spanned, +}; use thiserror::Error; #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -24,10 +31,7 @@ impl From for syn::Error { TryFromPathWithoutGenericsError::HasGenerics(path) => path, TryFromPathWithoutGenericsError::InvalidPath(err) => return err, }; - syn::Error::new( - path.span(), - TryFromPathWithoutGenericsError::HasGenerics(path).to_string(), - ) + syn::Error::new(path.span(), TryFromPathWithoutGenericsError::HasGenerics(path).to_string()) } } @@ -71,10 +75,7 @@ pub fn ident_to_path_without_generics(ident: Ident) -> PathWithoutGenerics { leading_colon: None, segments: { let mut segments = Punctuated::new(); - segments.push(PathSegment { - ident, - arguments: PathArguments::None, - }); + segments.push(PathSegment { ident, arguments: PathArguments::None }); segments }, }) diff --git a/crates/bevy_auto_plugin_shared/src/test_util/combo.rs b/crates/bevy_auto_plugin_shared/src/test_util/combo.rs index 5524b852..f411b454 100644 --- a/crates/bevy_auto_plugin_shared/src/test_util/combo.rs +++ b/crates/bevy_auto_plugin_shared/src/test_util/combo.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] +/// might still need when rewriting the old tests that were removed use internal_test_util::vec_spread; use std::borrow::Borrow; @@ -59,9 +61,5 @@ where } // filter out empty sets - combos - .into_iter() - .filter(|c| !c.is_empty()) - .map(|g| vec_spread![with.clone(), ..g,]) - .collect() + combos.into_iter().filter(|c| !c.is_empty()).map(|g| vec_spread![with.clone(), ..g,]).collect() } diff --git a/crates/bevy_auto_plugin_shared/src/test_util/macros.rs b/crates/bevy_auto_plugin_shared/src/test_util/macros.rs deleted file mode 100644 index 1b9d9f0e..00000000 --- a/crates/bevy_auto_plugin_shared/src/test_util/macros.rs +++ /dev/null @@ -1,61 +0,0 @@ -macro_rules! parse_attribute_args_with_plugin { - // with meta args - ($plugin:expr, $args_ident:ident, $tokens:expr $(,)?) => {{ - use quote::quote; - use $crate::codegen::tokens::ArgsWithPlugin; - use $crate::macro_api::with_plugin::{ WithPlugin}; - let plugin = $plugin.clone(); - let macro_path = <$args_ident as AttributeIdent>::full_attribute_path(); - - let mut args = vec![ - quote!(plugin = #plugin) - ]; - - if !$tokens.is_empty() { - args.push($tokens); - } - - let input = quote! { #[#macro_path( #(#args),* )] }; - let attr: syn::Attribute = syn::parse_quote! { #input }; - let args_with_plugin = ArgsWithPlugin::from(WithPlugin::<$args_ident>::from_meta(&attr.meta)?); - (plugin, input, args_with_plugin) - }}; -} - -macro_rules! parse_vec_args { - ($plugin:expr, $args_ident:ident, $args:expr $(,)?) => {{ - let args = $args; - $crate::test_util::macros::parse_attribute_args_with_plugin!( - $plugin, - $args_ident, - quote! { #(#args),* } - ) - }}; - - ($plugin:expr, $args_ident:ident $(,)?) => {{ $crate::test_util::macros::parse_attribute_args_with_plugin!($plugin, $args_ident) }}; -} - -macro_rules! assert_vec_args_expand { - ($plugin:expr, $args_ident:ident, $args:expr $(,)?) => {{ - let (plugin, input, args) = - $crate::test_util::macros::parse_vec_args!($plugin, $args_ident, $args); - $crate::test_util::assert_tokens_match(plugin, input, args); - }}; - - ($plugin:expr, $args_ident:ident $(,)?) => {{ - let (plugin, input, args) = $crate::parse_vec_args!($plugin, $args_ident); - $crate::test_util::assert_tokens_match(plugin, input, args); - }}; -} - -macro_rules! plugin { - ($plugin:expr) => {{ $crate::syntax::validated::non_empty_path::NonEmptyPath::new_unchecked($plugin) }}; -} - -#[rustfmt::skip] -pub(crate) use { - parse_attribute_args_with_plugin, - parse_vec_args, - assert_vec_args_expand, - plugin, -}; diff --git a/crates/bevy_auto_plugin_shared/src/test_util/mod.rs b/crates/bevy_auto_plugin_shared/src/test_util/mod.rs index a00d214c..323ddc10 100644 --- a/crates/bevy_auto_plugin_shared/src/test_util/mod.rs +++ b/crates/bevy_auto_plugin_shared/src/test_util/mod.rs @@ -1,22 +1 @@ pub(crate) mod combo; -pub(crate) mod macros; -pub(crate) mod test_params; - -pub(crate) fn assert_tokens_match( - plugin: impl std::fmt::Debug, - input: impl ToString, - args: impl quote::ToTokens, -) { - let input = input.to_string(); - assert_eq!( - args.to_token_stream().to_string(), - input, - concat!( - "failed to expand into expected tokens - args: ", - stringify!($args_ident), - ", plugin: {:?}, args_inner: {}" - ), - plugin, - input, - ); -} diff --git a/crates/bevy_auto_plugin_shared/src/test_util/test_params.rs b/crates/bevy_auto_plugin_shared/src/test_util/test_params.rs deleted file mode 100644 index ab56da1a..00000000 --- a/crates/bevy_auto_plugin_shared/src/test_util/test_params.rs +++ /dev/null @@ -1,166 +0,0 @@ -use crate::__private::attribute::RewriteAttribute; -use crate::codegen::{ExpandAttrs, tokens}; -use crate::macro_api::attributes::prelude::*; -use crate::macro_api::with_plugin::WithPlugin; -use crate::syntax::validated::non_empty_path::NonEmptyPath; -use anyhow::anyhow; -use darling::ast::NestedMeta; -use darling::{FromMeta, ToTokens}; -use proc_macro2::TokenStream; -use syn::__private::quote::quote; -use syn::parse::Parser; -use syn::{Attribute, Meta}; - -pub(crate) enum Side { - Left, - #[allow(dead_code)] - Right, -} - -pub(crate) fn _inject_derive( - derives: &mut Vec, - additional_derive_items: &[NonEmptyPath], - side: Side, -) { - if additional_derive_items.is_empty() { - return; - } - // hacky but works - if let Some(index) = derives - .iter() - .position(|ts| ts.to_string().contains("derive")) - { - let existing_ts = derives.remove(index); - let mut attrs = Attribute::parse_outer - .parse2(existing_ts) - .expect("existing derive not in expected format") - .into_iter(); - let derive_attr = attrs.next().expect("empty - impossible?"); - assert!( - attrs.next().is_none(), - "expected exactly 1 derive per entry" - ); - let list = match &derive_attr.meta { - Meta::List(list) => list, - _ => panic!("expected list attribute #[derive(...)]"), - }; - let path = &list.path; - let inner = &list.tokens; - - // Check if there are existing items for comma placement - let combined = if inner.is_empty() { - quote!(#[#path( #(#additional_derive_items),* )]) - } else { - match side { - Side::Left => quote!(#[#path( #(#additional_derive_items),*, #inner )]), - Side::Right => quote!(#[#path( #inner, #(#additional_derive_items),* )]), - } - }; - derives.insert(index, combined); - } else { - derives.insert(0, tokens::derive_from(additional_derive_items)); - } -} - -#[derive(Debug, Clone)] -pub(crate) struct TestParams { - pub args: WithPlugin, - pub expected_derives: ExpandAttrs, - pub expected_reflect: ExpandAttrs, - pub expected_extras: ExpandAttrs, -} - -impl TestParams -where - for<'a> RegisterTypeArgs: From<&'a T>, -{ - pub(crate) fn new(args: WithPlugin) -> Self { - Self { - args, - expected_derives: ExpandAttrs::default(), - expected_reflect: ExpandAttrs::default(), - expected_extras: ExpandAttrs::default(), - } - } - #[allow(dead_code)] - pub(crate) fn from_list(nested_metas: &[NestedMeta]) -> syn::Result { - let args = WithPlugin::::from_list(nested_metas)?; - Ok(Self::new(args)) - } - #[allow(dead_code)] - pub(crate) fn from_nested_meta(nested_meta: NestedMeta) -> syn::Result { - let args = WithPlugin::::from_nested_meta(&nested_meta)?; - Ok(Self::new(args)) - } - - pub(crate) fn from_args(args: TokenStream) -> syn::Result { - let items = NestedMeta::parse_meta_list(args)?; - Self::from_list(&items) - } - - #[allow(dead_code)] - pub(crate) fn from_attribute(attr: Attribute) -> syn::Result { - let list = match &attr.meta { - Meta::List(l) => l, - _ => panic!("expected list"), - }; - let nested = NestedMeta::parse_meta_list(list.tokens.clone())?; - Self::from_list(&nested) - } - - /// calling order matters - pub(crate) fn with_derive(mut self, extras: Vec) -> Self { - self.expected_derives - .attrs - .push(tokens::derive_from(&extras)); - self - } - - /// calling order matters - pub(crate) fn with_reflect(mut self, extras: Vec, derive: bool) -> Self { - if derive { - self.expected_derives - .attrs - .push(tokens::derive_from(&[tokens::derive_reflect_path()])); - } - if !extras.is_empty() { - self.expected_reflect - .append(tokens::reflect(extras.iter().map(NonEmptyPath::last_ident))); - } - self - } - - /// calling order matters - pub(crate) fn with_register(mut self) -> Self { - self.expected_extras.attrs.insert( - 0, - tokens::auto_register_type(self.args.plugin(), (&self.args.inner).into()), - ); - self - } - - fn build_into(self) -> ExpandAttrs { - ExpandAttrs::default() - .with(self.expected_derives) - .with(self.expected_reflect) - .with(self.expected_extras) - } - - pub(crate) fn build_clone(&self) -> ExpandAttrs { - self.clone().build_into() - } - - pub(crate) fn test(&self) -> anyhow::Result<()> { - let got = self - .args - .inner - .expand_attrs(&self.args.plugin()) - .to_token_stream() - .to_string(); - let expected = self.build_clone().to_token_stream().to_string(); - if got != expected { - return Err(anyhow!("\nexpected: {expected:#?}\n got: {got:#?}")); - } - Ok(()) - } -} diff --git a/crates/bevy_auto_plugin_shared/src/util/macros.rs b/crates/bevy_auto_plugin_shared/src/util/macros.rs index e954cc31..d57b4750 100644 --- a/crates/bevy_auto_plugin_shared/src/util/macros.rs +++ b/crates/bevy_auto_plugin_shared/src/util/macros.rs @@ -85,6 +85,8 @@ macro_rules! bevy_crate_path { use ::syn::parse2; use ::std::{concat, stringify}; use ::syn::Path; + // unused import for tests + #[cfg(not(test))] use $crate::util::macros::as_cargo_alias; #[allow(clippy::result_large_err)] let res: Result:: = match crate_name(concat!("bevy_", stringify!($target_crate))) { @@ -121,6 +123,18 @@ macro_rules! bevy_crate_path { }}; } +macro_rules! impl_from_default { + ($from:ident => ($($to:ident),* $(,)?)) => { + $( + impl From<$from> for $to { + fn from(_: $from) -> Self { + Self::default() + } + } + )* + }; +} + #[allow(unused_imports)] #[rustfmt::skip] pub(crate) use { @@ -131,14 +145,20 @@ pub(crate) use { parse_macro_input2_or_emit_with, as_cargo_alias, bevy_crate_path, + impl_from_default, }; #[cfg(test)] mod tests { - use super::*; use internal_test_proc_macro::xtest; - use proc_macro2::{Span, TokenStream}; - use quote::{ToTokens, quote}; + use proc_macro2::{ + Span, + TokenStream, + }; + use quote::{ + ToTokens, + quote, + }; #[xtest] fn test_bevy_crate_path() { @@ -157,10 +177,7 @@ mod tests { fn process(ts: syn::Result) -> TokenStream { ok_or_emit!(ts) } - assert_eq!( - process(Ok(quote! { foo_bar })).to_string(), - quote! { foo_bar }.to_string() - ); + assert_eq!(process(Ok(quote! { foo_bar })).to_string(), quote! { foo_bar }.to_string()); } #[test] diff --git a/crates/internal_test_proc_macro/src/lib.rs b/crates/internal_test_proc_macro/src/lib.rs index 2aed1cb0..4e7c2a31 100644 --- a/crates/internal_test_proc_macro/src/lib.rs +++ b/crates/internal_test_proc_macro/src/lib.rs @@ -1,6 +1,10 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{Attribute, ItemFn, parse_macro_input}; +use syn::{ + Attribute, + ItemFn, + parse_macro_input, +}; /// #[xtest] => #[wasm_bindgen_test] on wasm32, else #[xtest] #[proc_macro_attribute] diff --git a/crates/internal_test_util/Cargo.toml b/crates/internal_test_util/Cargo.toml index 238580f9..492c35e4 100644 --- a/crates/internal_test_util/Cargo.toml +++ b/crates/internal_test_util/Cargo.toml @@ -20,6 +20,8 @@ internal_test_proc_macro = { workspace = true } trybuild = { workspace = true } darling = { workspace = true } syn = { workspace = true } +proc-macro2 = { workspace = true } +quote = { workspace = true } [dev-dependencies] wasm-bindgen-test = { workspace = true } \ No newline at end of file diff --git a/crates/internal_test_util/src/lib.rs b/crates/internal_test_util/src/lib.rs index 01a48006..a4d347b7 100644 --- a/crates/internal_test_util/src/lib.rs +++ b/crates/internal_test_util/src/lib.rs @@ -1,11 +1,14 @@ +pub mod token_stream; pub mod ui_util; use bevy_app::App; use bevy_internal::MinimalPlugins; use darling::ast::NestedMeta; use std::any::TypeId; -use syn::punctuated::Punctuated; -use syn::token::Comma; +use syn::{ + punctuated::Punctuated, + token::Comma, +}; pub fn create_minimal_app() -> App { let mut app = App::new(); @@ -24,10 +27,9 @@ pub fn extract_punctuated_paths(punctuated: Punctuated) -> Ve punctuated .into_iter() .map(|nested_meta| match nested_meta { - NestedMeta::Meta(meta) => meta - .require_path_only() - .cloned() - .expect("expected path only"), + NestedMeta::Meta(meta) => { + meta.require_path_only().cloned().expect("expected path only") + } NestedMeta::Lit(_) => panic!("unexpected literal"), }) .collect::>() @@ -69,22 +71,15 @@ macro_rules! vec_spread { #[cfg(test)] mod tests { - use super::*; use internal_test_proc_macro::xtest; #[xtest] fn test_vec_spread() { assert_eq!(vec_spread![1], vec![1]); assert_eq!(vec_spread![1, 2, 3], vec![1, 2, 3]); assert_eq!(vec_spread![1, 2, 3, 4, 5, 6], vec![1, 2, 3, 4, 5, 6]); - assert_eq!( - vec_spread![1, 2, 3, ..[4, 5, 6], 7, 8], - vec![1, 2, 3, 4, 5, 6, 7, 8] - ); + assert_eq!(vec_spread![1, 2, 3, ..[4, 5, 6], 7, 8], vec![1, 2, 3, 4, 5, 6, 7, 8]); assert_eq!(vec_spread![..[4, 5, 6], 7, 8], vec![4, 5, 6, 7, 8]); assert_eq!(vec_spread![1, 2, 3, ..[4, 5, 6]], vec![1, 2, 3, 4, 5, 6]); - assert_eq!( - vec_spread![1, 2, 3, ..[4, 5, 6], 7, 8, ..[9]], - vec![1, 2, 3, 4, 5, 6, 7, 8, 9] - ); + assert_eq!(vec_spread![1, 2, 3, ..[4, 5, 6], 7, 8, ..[9]], vec![1, 2, 3, 4, 5, 6, 7, 8, 9]); } } diff --git a/crates/internal_test_util/src/token_stream.rs b/crates/internal_test_util/src/token_stream.rs new file mode 100644 index 00000000..4d4af448 --- /dev/null +++ b/crates/internal_test_util/src/token_stream.rs @@ -0,0 +1,48 @@ +#![allow(unused)] + +use proc_macro2::TokenStream; +use quote::ToTokens; +use std::fmt::{ + Debug, + Formatter, +}; + +#[inline] +pub fn token_string(ts: impl ToTokens) -> String { + ts.to_token_stream().to_string() +} + +#[repr(transparent)] +pub struct Ts(TokenStream); + +impl Debug for Ts { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0.to_string()) + } +} + +impl PartialEq for Ts { + fn eq(&self, other: &Self) -> bool { + self.0.to_string() == other.0.to_string() + } +} + +pub fn ts(token_stream: impl ToTokens) -> Ts { + Ts(token_stream.to_token_stream()) +} + +impl From for Ts +where + T: ToTokens, +{ + fn from(value: T) -> Self { + Self(value.into_token_stream()) + } +} + +#[macro_export] +macro_rules! assert_ts_eq { + ($left:expr, $right:expr $(, $msg:expr)?) => { + assert_eq!($crate::token_stream::ts($left), $crate::token_stream::ts($right), $($msg)?); + }; +} diff --git a/crates/internal_test_util/src/ui_util.rs b/crates/internal_test_util/src/ui_util.rs index 5cb2346c..a69070ff 100644 --- a/crates/internal_test_util/src/ui_util.rs +++ b/crates/internal_test_util/src/ui_util.rs @@ -1,4 +1,7 @@ -use std::fmt::{Display, Formatter}; +use std::fmt::{ + Display, + Formatter, +}; pub enum SubDir { Root, @@ -44,9 +47,21 @@ pub trait UiTest { pub mod utils { - use std::fs::{DirEntry, File, read_dir}; - use std::io::{Read, Result}; - use std::path::{Path, PathBuf}; + use std::{ + fs::{ + DirEntry, + File, + read_dir, + }, + io::{ + Read, + Result, + }, + path::{ + Path, + PathBuf, + }, + }; fn is_extension_factory(extension: Option<&str>) -> impl Fn(&DirEntry) -> bool { move |entry: &DirEntry| { @@ -86,9 +101,7 @@ pub mod utils { path: impl AsRef, extension: Option<&str>, ) -> Result> { - let rs_files = files_in_dir(path, extension)? - .map(|entry| entry.path()) - .collect::>(); + let rs_files = files_in_dir(path, extension)?.map(|entry| entry.path()).collect::>(); Ok(rs_files) } @@ -124,9 +137,11 @@ macro_rules! ui_tests { mod tests { use super::*; use internal_test_proc_macro::xtest; - use internal_test_util::ui_util::SubDir; - use internal_test_util::ui_util::UiTest; - use internal_test_util::ui_util::utils; + use internal_test_util::ui_util::{ + SubDir, + UiTest, + utils, + }; #[xtest] fn ui_tests() { let t = trybuild::TestCases::new(); diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/derive_auto_plugin.md b/docs/derives/AutoPlugin.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/derive_auto_plugin.md rename to docs/derives/AutoPlugin.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_add_message.md b/docs/proc_attributes/actions/auto_add_message.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_add_message.md rename to docs/proc_attributes/actions/auto_add_message.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_add_observer.md b/docs/proc_attributes/actions/auto_add_observer.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_add_observer.md rename to docs/proc_attributes/actions/auto_add_observer.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_add_plugin.md b/docs/proc_attributes/actions/auto_add_plugin.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_add_plugin.md rename to docs/proc_attributes/actions/auto_add_plugin.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_add_system.md b/docs/proc_attributes/actions/auto_add_system.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_add_system.md rename to docs/proc_attributes/actions/auto_add_system.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_bind_plugin.md b/docs/proc_attributes/actions/auto_bind_plugin.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_bind_plugin.md rename to docs/proc_attributes/actions/auto_bind_plugin.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_configure_system_set.md b/docs/proc_attributes/actions/auto_configure_system_set.md similarity index 97% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_configure_system_set.md rename to docs/proc_attributes/actions/auto_configure_system_set.md index 689fcc6f..bac1b867 100644 --- a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_configure_system_set.md +++ b/docs/proc_attributes/actions/auto_configure_system_set.md @@ -36,7 +36,7 @@ struct MySet; # Example Enum -See [auto_configure_system_set_config](./auto_configure_system_set_config.md) for details. +See [auto_configure_system_set_config](auto_configure_system_set_config.md) for details. ```rust use bevy::prelude::*; diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_configure_system_set_config.md b/docs/proc_attributes/actions/auto_configure_system_set_config.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_configure_system_set_config.md rename to docs/proc_attributes/actions/auto_configure_system_set_config.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_init_resource.md b/docs/proc_attributes/actions/auto_init_resource.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_init_resource.md rename to docs/proc_attributes/actions/auto_init_resource.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_init_state.md b/docs/proc_attributes/actions/auto_init_state.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_init_state.md rename to docs/proc_attributes/actions/auto_init_state.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_init_sub_state.md b/docs/proc_attributes/actions/auto_init_sub_state.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_init_sub_state.md rename to docs/proc_attributes/actions/auto_init_sub_state.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_insert_resource.md b/docs/proc_attributes/actions/auto_insert_resource.md similarity index 86% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_insert_resource.md rename to docs/proc_attributes/actions/auto_insert_resource.md index 1cbdcd30..47edc49e 100644 --- a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_insert_resource.md +++ b/docs/proc_attributes/actions/auto_insert_resource.md @@ -18,7 +18,7 @@ struct MyPlugin; #[derive(Resource, Debug, Default, PartialEq, Reflect)] #[reflect(Resource)] #[auto_register_type(plugin = MyPlugin)] -#[auto_insert_resource(plugin = MyPlugin, resource(FooResource(42)))] +#[auto_insert_resource(plugin = MyPlugin, init(FooResource(42)))] struct FooResource(usize); ``` @@ -34,6 +34,6 @@ struct MyPlugin; #[derive(Resource, Debug, Default, PartialEq, Reflect)] #[reflect(Resource)] #[auto_register_type(plugin = MyPlugin, generics(usize))] -#[auto_insert_resource(plugin = MyPlugin, resource(FooResourceWithGeneric(42)), generics(usize))] +#[auto_insert_resource(plugin = MyPlugin, init(FooResourceWithGeneric(42)), generics(usize))] struct FooResourceWithGeneric(T); ``` \ No newline at end of file diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_name.md b/docs/proc_attributes/actions/auto_name.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_name.md rename to docs/proc_attributes/actions/auto_name.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_register_state_type.md b/docs/proc_attributes/actions/auto_register_state_type.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_register_state_type.md rename to docs/proc_attributes/actions/auto_register_state_type.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_register_type.md b/docs/proc_attributes/actions/auto_register_type.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_register_type.md rename to docs/proc_attributes/actions/auto_register_type.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_run_on_build.md b/docs/proc_attributes/actions/auto_run_on_build.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_run_on_build.md rename to docs/proc_attributes/actions/auto_run_on_build.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_plugin.md b/docs/proc_attributes/auto_plugin.md similarity index 64% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_plugin.md rename to docs/proc_attributes/auto_plugin.md index 144771ea..67e4402e 100644 --- a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_plugin.md +++ b/docs/proc_attributes/auto_plugin.md @@ -3,8 +3,6 @@ Attribute to mark the build function for the plugin, or impl Plugin trait build # Parameters - `plugin = PluginType` - **Required for bare functions only.** Specifies the plugin this build function belongs to. **Not allowed on `impl Plugin` methods**, since the plugin type is already known. -- `app_param = identifier` - *(Optional)* Specifies the name of the `App` parameter that code will be injected into. - Defaults to `app` if omitted. # Example - impl Plugin ```rust @@ -15,8 +13,8 @@ use bevy_auto_plugin::prelude::*; struct MyPlugin; impl Plugin for MyPlugin { - #[auto_plugin(app_param=non_default_app_param_name)] - fn build(&self, non_default_app_param_name: &mut App) { + #[auto_plugin] + fn build(&self, app: &mut App) { // code injected here // your code @@ -32,8 +30,8 @@ use bevy_auto_plugin::prelude::*; #[derive(AutoPlugin)] struct MyPlugin; -#[auto_plugin(plugin = MyPlugin, app_param=non_default_app_param_name)] -fn build(non_default_app_param_name: &mut App) { +#[auto_plugin(plugin = MyPlugin)] +fn build(app: &mut App) { // code injected here // your code diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_component.md b/docs/proc_attributes/rewrites/auto_component.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_component.md rename to docs/proc_attributes/rewrites/auto_component.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_event.md b/docs/proc_attributes/rewrites/auto_event.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_event.md rename to docs/proc_attributes/rewrites/auto_event.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_message.md b/docs/proc_attributes/rewrites/auto_message.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_message.md rename to docs/proc_attributes/rewrites/auto_message.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_observer.md b/docs/proc_attributes/rewrites/auto_observer.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_observer.md rename to docs/proc_attributes/rewrites/auto_observer.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_resource.md b/docs/proc_attributes/rewrites/auto_resource.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_resource.md rename to docs/proc_attributes/rewrites/auto_resource.md diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_states.md b/docs/proc_attributes/rewrites/auto_states.md similarity index 94% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_states.md rename to docs/proc_attributes/rewrites/auto_states.md index 3499bae6..f740c6d5 100644 --- a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_states.md +++ b/docs/proc_attributes/rewrites/auto_states.md @@ -18,7 +18,7 @@ Automatically initializes a state in the app. Passes through any additional reflects listed. If enabled in tandem with `derive` it also includes `#[derive(Reflect)]` - `register` - Enables type registration for the `States` - Same as having `#[auto_register_type]` + Same as having `#[auto_register_type]` and `#[auto_register_state_type]` - `init` - Initializes the `States` with default values Same as having `#[auto_init_state]` diff --git a/docs/proc_attributes/rewrites/auto_sub_states.md b/docs/proc_attributes/rewrites/auto_sub_states.md new file mode 100644 index 00000000..318b1cea --- /dev/null +++ b/docs/proc_attributes/rewrites/auto_sub_states.md @@ -0,0 +1,48 @@ +Automatically initializes a sub-state in the app. + +# Parameters +- `plugin = PluginType` - Required. Specifies which plugin should initialize this state. +- `generics(T1, T2, ...)` - Optional. Specifies concrete types for generic parameters. + When provided, the states will be registered with these specific generic parameters. +- `derive` | `derive(Debug, Default, ..)` - Optional. Specifies that the macro should handle deriving `SubStates`. + Passes through any additional derives listed. + When enabled, `SubStates` include these additional derives: + - `SubStates` + - `Debug` + - `Default` + - `Clone` + - `PartialEq` + - `Eq` + - `Hash` +- `reflect` | `reflect(Debug, Default, ..)` - Optional. Specifies that the macro should handle emitting the single `#[reflect(...)]`. + Passes through any additional reflects listed. + If enabled in tandem with `derive` it also includes `#[derive(Reflect)]` +- `register` - Enables type registration for the `SubStates` + Same as having `#[auto_register_type]` and `#[auto_register_state_type]` +- `init` - Initializes the `SubStates` with default values + Same as having `#[auto_init_sub_state]` + +# Example +```rust +use bevy::prelude::*; +use bevy_auto_plugin::prelude::*; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct MyPlugin; + +#[auto_states(plugin = MyPlugin, derive, init)] +enum MainState { + #[default] + InGame, + Menu, +} + +#[auto_sub_states(plugin = MyPlugin, derive, init)] +#[source(MainState = MainState::InGame)] +enum InGameState { + #[default] + Playing, + Paused, +} +``` diff --git a/crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_system.md b/docs/proc_attributes/rewrites/auto_system.md similarity index 100% rename from crates/bevy_auto_plugin_proc_macros/docs/proc_attributes/auto_system.md rename to docs/proc_attributes/rewrites/auto_system.md diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..40b2b528 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,11 @@ +use_small_heuristics = "Max" # prefer breaking earlier +normalize_comments = true +use_field_init_shorthand = true +newline_style = "Unix" +style_edition = "2024" +array_width = 60 + +unstable_features = true +imports_granularity = "Crate" # consolidate use by crate +reorder_imports = true # required to merge imports +imports_layout = "Vertical" # one per line in braces (if any) \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 588561a4..0b4c45f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,152 +1,192 @@ +//! # Bevy Auto Plugin +//! [GitHub repository](https://github.com/strikeforcezero/bevy_auto_plugin) +//! +//! ## Getting Started: +//! +//! ### Plugin +//! +//! There are three distinct ways to make a bindable plugin: +//! +//! ```rust +//! use bevy::prelude::*; +//! use bevy_auto_plugin::prelude::*; +//! +//! #[derive(AutoPlugin)] +//! #[auto_plugin(impl_plugin_trait)] +//! struct MyPlugin; +//! ``` +//! +//! ```rust +//! use bevy::prelude::*; +//! use bevy_auto_plugin::prelude::*; +//! +//! #[derive(AutoPlugin)] +//! struct MyPlugin; +//! +//! impl Plugin for MyPlugin { +//! #[auto_plugin] +//! fn build(&self, app: &mut App) { +//! // +//! } +//! } +//! ``` +//! +//! ```rust +//! use bevy::prelude::*; +//! use bevy_auto_plugin::prelude::*; +//! +//! #[derive(AutoPlugin)] +//! struct MyPlugin; +//! +//! #[auto_plugin(plugin = MyPlugin)] +//! fn plugin(app: &mut App) { +//! // +//! } +//! ``` +//! +//! ### Using Attributes +//! When `Plugin::build` is called on `MyPlugin` (i.e., `app.add_plugins(MyPlugin)`), the code for each attribute will be executed. +//! +//! You can use the `auto_*` attributes in several different ways: +//! +//! ```rust +//! use bevy::prelude::*; +//! use bevy_auto_plugin::prelude::*; +//! +//! #[derive(AutoPlugin)] +//! #[auto_plugin(impl_plugin_trait)] +//! struct MyPlugin; +//! +//! #[auto_component( +//! plugin = MyPlugin, +//! derive(Debug, Default), +//! reflect(Debug, Default), +//! register, +//! auto_name, +//! )] +//! struct FooComponent; +//! ``` +//! +//! which gets rewritten into: +//! ```rust +//! use bevy::prelude::*; +//! use bevy_auto_plugin::prelude::*; +//! +//! #[derive(AutoPlugin)] +//! #[auto_plugin(impl_plugin_trait)] +//! struct MyPlugin; +//! +//! #[derive(Component, Reflect, Debug, Default)] +//! #[reflect(Component, Debug, Default)] +//! #[auto_name(plugin = MyPlugin)] +//! #[auto_register_type(plugin = MyPlugin)] +//! struct FooComponent; +//! ``` +//! +//! or maybe you want a template: +//! ```rust +//! use bevy::prelude::*; +//! use bevy_auto_plugin::prelude::*; +//! use meta_merge::*; +//! +//! #[derive(AutoPlugin)] +//! #[auto_plugin(impl_plugin_trait)] +//! struct MyPlugin; +//! +//! #[export(copy(prepend))] +//! #[derive(Component, Reflect, Debug, Default)] +//! #[reflect(Component, Debug, Default)] +//! #[auto_name(plugin = MyPlugin)] +//! #[auto_register_type(plugin = MyPlugin)] +//! struct DefaultComponentTemplate; +//! +//! #[apply(CopyDefaultComponentTemplate!)] +//! struct FooComponent; +//! +//! #[apply(CopyDefaultComponentTemplate!)] +//! struct BarComponent; +//! ``` + +/// Private Re-exports #[doc(hidden)] pub mod __private { pub use bevy_auto_plugin_shared as shared; } pub mod prelude { - #[doc(inline)] + #[doc = include_str!("../docs/derives/AutoPlugin.md")] pub use bevy_auto_plugin_proc_macros::AutoPlugin; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_add_message.md")] pub use bevy_auto_plugin_proc_macros::auto_add_message; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_add_plugin.md")] #[deprecated(since = "0.6.0", note = "Use `auto_add_message` instead.")] pub use bevy_auto_plugin_proc_macros::auto_add_message as auto_add_event; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_add_plugin.md")] pub use bevy_auto_plugin_proc_macros::auto_add_plugin; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_add_system.md")] pub use bevy_auto_plugin_proc_macros::auto_add_system; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_init_resource.md")] pub use bevy_auto_plugin_proc_macros::auto_init_resource; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_init_state.md")] pub use bevy_auto_plugin_proc_macros::auto_init_state; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_init_sub_state.md")] pub use bevy_auto_plugin_proc_macros::auto_init_sub_state; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_insert_resource.md")] pub use bevy_auto_plugin_proc_macros::auto_insert_resource; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_name.md")] pub use bevy_auto_plugin_proc_macros::auto_name; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/auto_plugin.md")] pub use bevy_auto_plugin_proc_macros::auto_plugin; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_register_state_type.md")] pub use bevy_auto_plugin_proc_macros::auto_register_state_type; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_register_type.md")] pub use bevy_auto_plugin_proc_macros::auto_register_type; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_add_observer.md")] pub use bevy_auto_plugin_proc_macros::auto_add_observer; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/rewrites/auto_component.md")] pub use bevy_auto_plugin_proc_macros::auto_component; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/rewrites/auto_resource.md")] pub use bevy_auto_plugin_proc_macros::auto_resource; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/rewrites/auto_event.md")] pub use bevy_auto_plugin_proc_macros::auto_event; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/rewrites/auto_message.md")] pub use bevy_auto_plugin_proc_macros::auto_message; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/rewrites/auto_states.md")] pub use bevy_auto_plugin_proc_macros::auto_states; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/rewrites/auto_sub_states.md")] + pub use bevy_auto_plugin_proc_macros::auto_sub_states; + + #[doc = include_str!("../docs/proc_attributes/rewrites/auto_system.md")] pub use bevy_auto_plugin_proc_macros::auto_system; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/rewrites/auto_observer.md")] pub use bevy_auto_plugin_proc_macros::auto_observer; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_run_on_build.md")] pub use bevy_auto_plugin_proc_macros::auto_run_on_build; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_bind_plugin.md")] pub use bevy_auto_plugin_proc_macros::auto_bind_plugin; - #[doc(inline)] + #[doc = include_str!("../docs/proc_attributes/actions/auto_configure_system_set.md")] pub use bevy_auto_plugin_proc_macros::auto_configure_system_set; } - -#[deprecated(since = "0.7.0", note = "bevy_auto_plugin::prelude::* instead")] -pub mod modes { - #[deprecated(since = "0.7.0", note = "bevy_auto_plugin::prelude::* instead")] - pub mod global { - #[deprecated(since = "0.7.0", note = "bevy_auto_plugin::prelude::* instead")] - pub mod prelude { - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::AutoPlugin; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_add_message; - - #[doc(inline)] - #[deprecated(since = "0.6.0", note = "Use `auto_add_message` instead.")] - pub use bevy_auto_plugin_proc_macros::auto_add_message as auto_add_event; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_add_system; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_init_resource; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_init_state; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_insert_resource; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_name; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_plugin; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_register_state_type; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_register_type; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_add_observer; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_component; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_resource; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_event; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_message; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_states; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_system; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_observer; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_run_on_build; - - #[doc(inline)] - pub use bevy_auto_plugin_proc_macros::auto_bind_plugin; - } - } -} diff --git a/tests/e2e/auto_add_message.rs b/tests/e2e/actions/auto_add_message.rs similarity index 100% rename from tests/e2e/auto_add_message.rs rename to tests/e2e/actions/auto_add_message.rs diff --git a/tests/e2e/auto_add_message_generic.rs b/tests/e2e/actions/auto_add_message_generic.rs similarity index 85% rename from tests/e2e/auto_add_message_generic.rs rename to tests/e2e/actions/auto_add_message_generic.rs index 10bd8a2c..8890e510 100644 --- a/tests/e2e/auto_add_message_generic.rs +++ b/tests/e2e/actions/auto_add_message_generic.rs @@ -22,10 +22,6 @@ fn test_auto_add_message_generic() { let mut app = app(); let mut messages = app.world_mut().resource_mut::>>(); messages.write(Test(true)); - assert_eq!( - messages.drain().next(), - Some(Test(true)), - "did not auto add event" - ); + assert_eq!(messages.drain().next(), Some(Test(true)), "did not auto add event"); assert_eq!(messages.drain().next(), None, "expected only 1 event"); } diff --git a/tests/e2e/auto_add_observer.rs b/tests/e2e/actions/auto_add_observer.rs similarity index 72% rename from tests/e2e/auto_add_observer.rs rename to tests/e2e/actions/auto_add_observer.rs index 4d5cd0fb..ef8b20f1 100644 --- a/tests/e2e/auto_add_observer.rs +++ b/tests/e2e/actions/auto_add_observer.rs @@ -22,12 +22,7 @@ fn test_observer( added_foo_q: Query, Added>, mut foo_component_added: ResMut, ) { - assert!( - added_foo_q - .get(add.event().entity) - .expect("FooComponent not spawned") - .is_added() - ); + assert!(added_foo_q.get(add.event().entity).expect("FooComponent not spawned").is_added()); foo_component_added.is_added = true; } @@ -41,18 +36,12 @@ fn app() -> App { fn test_auto_add_observer() { let mut app = app(); assert!( - !app.world() - .get_resource::() - .unwrap() - .is_added, + !app.world().get_resource::().unwrap().is_added, "FooComponent should not be added yet" ); app.world_mut().spawn(FooComponent); assert!( - app.world() - .get_resource::() - .unwrap() - .is_added, + app.world().get_resource::().unwrap().is_added, "FooComponent should be added" ); } diff --git a/tests/e2e/auto_add_plugin.rs b/tests/e2e/actions/auto_add_plugin.rs similarity index 84% rename from tests/e2e/auto_add_plugin.rs rename to tests/e2e/actions/auto_add_plugin.rs index 71d76947..6ca7b099 100644 --- a/tests/e2e/auto_add_plugin.rs +++ b/tests/e2e/actions/auto_add_plugin.rs @@ -25,8 +25,5 @@ fn app() -> App { #[xtest] fn test_auto_add_plugin() { let app = app(); - assert!( - app.world().get_resource::().is_some(), - "did not auto add plugin" - ); + assert!(app.world().get_resource::().is_some(), "did not auto add plugin"); } diff --git a/tests/e2e/auto_add_plugin_with_default_init.rs b/tests/e2e/actions/auto_add_plugin_with_default_init.rs similarity index 86% rename from tests/e2e/auto_add_plugin_with_default_init.rs rename to tests/e2e/actions/auto_add_plugin_with_default_init.rs index 006cb968..083c27ef 100644 --- a/tests/e2e/auto_add_plugin_with_default_init.rs +++ b/tests/e2e/actions/auto_add_plugin_with_default_init.rs @@ -36,9 +36,5 @@ fn app() -> App { #[xtest] fn test_auto_add_plugin_with_default_init() { let app = app(); - assert_eq!( - app.world().get_resource::(), - Some(&Test(1)), - "did not auto add plugin" - ); + assert_eq!(app.world().get_resource::(), Some(&Test(1)), "did not auto add plugin"); } diff --git a/tests/e2e/auto_add_plugin_with_generics_and_default_init.rs b/tests/e2e/actions/auto_add_plugin_with_generics_and_default_init.rs similarity index 100% rename from tests/e2e/auto_add_plugin_with_generics_and_default_init.rs rename to tests/e2e/actions/auto_add_plugin_with_generics_and_default_init.rs diff --git a/tests/e2e/auto_add_plugin_with_generics_and_init.rs b/tests/e2e/actions/auto_add_plugin_with_generics_and_init.rs similarity index 100% rename from tests/e2e/auto_add_plugin_with_generics_and_init.rs rename to tests/e2e/actions/auto_add_plugin_with_generics_and_init.rs diff --git a/tests/e2e/auto_add_plugin_with_init.rs b/tests/e2e/actions/auto_add_plugin_with_init.rs similarity index 85% rename from tests/e2e/auto_add_plugin_with_init.rs rename to tests/e2e/actions/auto_add_plugin_with_init.rs index c7f86564..2ec7c622 100644 --- a/tests/e2e/auto_add_plugin_with_init.rs +++ b/tests/e2e/actions/auto_add_plugin_with_init.rs @@ -30,9 +30,5 @@ fn app() -> App { #[xtest] fn test_auto_add_plugin_with_init() { let app = app(); - assert_eq!( - app.world().get_resource::(), - Some(&Test(1)), - "did not auto add plugin" - ); + assert_eq!(app.world().get_resource::(), Some(&Test(1)), "did not auto add plugin"); } diff --git a/tests/e2e/auto_add_systems.rs b/tests/e2e/actions/auto_add_systems.rs similarity index 100% rename from tests/e2e/auto_add_systems.rs rename to tests/e2e/actions/auto_add_systems.rs diff --git a/tests/e2e/auto_add_systems_complex_with_generics.rs b/tests/e2e/actions/auto_add_systems_complex_with_generics.rs similarity index 96% rename from tests/e2e/auto_add_systems_complex_with_generics.rs rename to tests/e2e/actions/auto_add_systems_complex_with_generics.rs index 5d618769..452b86d0 100644 --- a/tests/e2e/auto_add_systems_complex_with_generics.rs +++ b/tests/e2e/actions/auto_add_systems_complex_with_generics.rs @@ -2,8 +2,13 @@ use bevy_app::prelude::*; use bevy_auto_plugin::prelude::*; use bevy_ecs::prelude::*; use internal_test_proc_macro::xtest; -use std::fmt::Debug; -use std::ops::{Add, AddAssign}; +use std::{ + fmt::Debug, + ops::{ + Add, + AddAssign, + }, +}; trait One { const ONE: Self; diff --git a/tests/e2e/auto_add_systems_with_generics.rs b/tests/e2e/actions/auto_add_systems_with_generics.rs similarity index 95% rename from tests/e2e/auto_add_systems_with_generics.rs rename to tests/e2e/actions/auto_add_systems_with_generics.rs index fd271cf0..6ba54e24 100644 --- a/tests/e2e/auto_add_systems_with_generics.rs +++ b/tests/e2e/actions/auto_add_systems_with_generics.rs @@ -2,8 +2,13 @@ use bevy_app::prelude::*; use bevy_auto_plugin::prelude::*; use bevy_ecs::prelude::*; use internal_test_proc_macro::xtest; -use std::fmt::Debug; -use std::ops::{Add, AddAssign}; +use std::{ + fmt::Debug, + ops::{ + Add, + AddAssign, + }, +}; trait One { const ONE: Self; diff --git a/tests/e2e/auto_add_systems_with_set.rs b/tests/e2e/actions/auto_add_systems_with_set.rs similarity index 90% rename from tests/e2e/auto_add_systems_with_set.rs rename to tests/e2e/actions/auto_add_systems_with_set.rs index 92d59be0..a634b116 100644 --- a/tests/e2e/auto_add_systems_with_set.rs +++ b/tests/e2e/actions/auto_add_systems_with_set.rs @@ -10,10 +10,7 @@ impl Plugin for TestPlugin { #[auto_plugin] fn build(&self, app: &mut App) { app.init_resource::(); - app.configure_sets( - Update, - (TestSet::First, TestSet::Second, TestSet::Third).chain(), - ); + app.configure_sets(Update, (TestSet::First, TestSet::Second, TestSet::Third).chain()); } } diff --git a/tests/e2e/auto_bind_plugin.rs b/tests/e2e/actions/auto_bind_plugin.rs similarity index 81% rename from tests/e2e/auto_bind_plugin.rs rename to tests/e2e/actions/auto_bind_plugin.rs index f13f887e..a009e9e4 100644 --- a/tests/e2e/auto_bind_plugin.rs +++ b/tests/e2e/actions/auto_bind_plugin.rs @@ -4,7 +4,10 @@ use bevy::prelude::*; use bevy_auto_plugin::prelude::*; use bevy_state::app::StatesPlugin; use internal_test_proc_macro::xtest; -use internal_test_util::{create_minimal_app, type_id_of}; +use internal_test_util::{ + create_minimal_app, + type_id_of, +}; use std::ops::Deref; #[derive(AutoPlugin)] @@ -38,7 +41,7 @@ struct FooDefaultRes(usize); #[reflect(Resource)] #[auto_register_type] #[auto_init_resource] -#[auto_insert_resource(resource(FooRes(1)))] +#[auto_insert_resource(init(FooRes(1)))] struct FooRes(usize); #[auto_bind_plugin(plugin = Test)] @@ -97,12 +100,7 @@ fn foo_observer( added_foo_q: Query, Added>, mut foo_component_added: ResMut, ) { - assert!( - added_foo_q - .get(add.event().entity) - .expect("FooComponent not spawned") - .is_added() - ); + assert!(added_foo_q.get(add.event().entity).expect("FooComponent not spawned").is_added()); foo_component_added.is_added = true; } @@ -118,10 +116,7 @@ fn test_auto_register_type_foo_component() { let app = app(); let type_registry = app.world().resource::().0.clone(); let type_registry = type_registry.read(); - assert!( - type_registry.contains(type_id_of::()), - "did not auto register type" - ); + assert!(type_registry.contains(type_id_of::()), "did not auto register type"); } #[xtest] @@ -133,11 +128,7 @@ fn test_auto_name_foo_component() { .query_filtered::<&Name, With>() .single(app.world()) .expect("failed to query FooComponent"); - assert_eq!( - name, - &Name::new("FooComponent"), - "did not auto name FooComponent" - ); + assert_eq!(name, &Name::new("FooComponent"), "did not auto name FooComponent"); } #[xtest] @@ -169,11 +160,7 @@ fn test_auto_add_system_foo_system() { "did not auto init resource" ); app.update(); - assert_eq!( - app.world().get_resource::(), - Some(&FooRes(2)), - "did not register system" - ); + assert_eq!(app.world().get_resource::(), Some(&FooRes(2)), "did not register system"); } #[xtest] @@ -187,10 +174,7 @@ fn test_auto_register_state_type_foo_state() { let app = app(); let type_registry = app.world().resource::().0.clone(); let type_registry = type_registry.read(); - assert!( - type_registry.contains(type_id_of::>()), - "did not auto register type" - ); + assert!(type_registry.contains(type_id_of::>()), "did not auto register type"); assert!( type_registry.contains(type_id_of::>()), "did not auto register type" @@ -201,9 +185,7 @@ fn test_auto_register_state_type_foo_state() { fn test_auto_init_state_type_foo_state() { let app = app(); assert_eq!( - app.world() - .get_resource::>() - .map(Deref::deref), + app.world().get_resource::>().map(Deref::deref), Some(&FooState::Start), "did not auto init state" ); @@ -213,18 +195,12 @@ fn test_auto_init_state_type_foo_state() { fn test_auto_add_observer_foo_observer() { let mut app = app(); assert!( - !app.world() - .get_resource::() - .unwrap() - .is_added, + !app.world().get_resource::().unwrap().is_added, "FooComponent should not be added yet" ); app.world_mut().spawn(FooComponent); assert!( - app.world() - .get_resource::() - .unwrap() - .is_added, + app.world().get_resource::().unwrap().is_added, "FooComponent should be added" ); } diff --git a/tests/e2e/auto_configure_system_set.rs b/tests/e2e/actions/auto_configure_system_set.rs similarity index 100% rename from tests/e2e/auto_configure_system_set.rs rename to tests/e2e/actions/auto_configure_system_set.rs diff --git a/tests/e2e/auto_configure_system_set_schedule_config.rs b/tests/e2e/actions/auto_configure_system_set_schedule_config.rs similarity index 100% rename from tests/e2e/auto_configure_system_set_schedule_config.rs rename to tests/e2e/actions/auto_configure_system_set_schedule_config.rs diff --git a/tests/e2e/auto_configure_system_set_schedule_config_multiple_groups.rs b/tests/e2e/actions/auto_configure_system_set_schedule_config_multiple_groups.rs similarity index 98% rename from tests/e2e/auto_configure_system_set_schedule_config_multiple_groups.rs rename to tests/e2e/actions/auto_configure_system_set_schedule_config_multiple_groups.rs index 673c0a70..932234d8 100644 --- a/tests/e2e/auto_configure_system_set_schedule_config_multiple_groups.rs +++ b/tests/e2e/actions/auto_configure_system_set_schedule_config_multiple_groups.rs @@ -1,4 +1,8 @@ -use bevy::prelude::{Fixed, Time, Virtual}; +use bevy::prelude::{ + Fixed, + Time, + Virtual, +}; use bevy_app::prelude::*; use bevy_auto_plugin::prelude::*; use bevy_ecs::prelude::*; diff --git a/tests/e2e/auto_init_resource.rs b/tests/e2e/actions/auto_init_resource.rs similarity index 80% rename from tests/e2e/auto_init_resource.rs rename to tests/e2e/actions/auto_init_resource.rs index 6c07533d..dbf75dee 100644 --- a/tests/e2e/auto_init_resource.rs +++ b/tests/e2e/actions/auto_init_resource.rs @@ -20,8 +20,5 @@ fn app() -> App { #[xtest] fn test_auto_init_resource() { let app = app(); - assert!( - app.world().get_resource::().is_some(), - "did not auto init resource" - ); + assert!(app.world().get_resource::().is_some(), "did not auto init resource"); } diff --git a/tests/e2e/auto_init_resource_generic.rs b/tests/e2e/actions/auto_init_resource_generic.rs similarity index 81% rename from tests/e2e/auto_init_resource_generic.rs rename to tests/e2e/actions/auto_init_resource_generic.rs index 68664ffb..89dbe05f 100644 --- a/tests/e2e/auto_init_resource_generic.rs +++ b/tests/e2e/actions/auto_init_resource_generic.rs @@ -20,8 +20,5 @@ fn app() -> App { #[xtest] fn test_auto_init_resource_generic() { let app = app(); - assert!( - app.world().get_resource::>().is_some(), - "did not auto init resource" - ); + assert!(app.world().get_resource::>().is_some(), "did not auto init resource"); } diff --git a/tests/e2e/auto_init_state.rs b/tests/e2e/actions/auto_init_state.rs similarity index 65% rename from tests/e2e/auto_init_state.rs rename to tests/e2e/actions/auto_init_state.rs index 0902a3cd..bd6704b8 100644 --- a/tests/e2e/auto_init_state.rs +++ b/tests/e2e/actions/auto_init_state.rs @@ -1,7 +1,9 @@ use bevy_app::prelude::*; use bevy_auto_plugin::prelude::*; -use bevy_state::app::StatesPlugin; -use bevy_state::prelude::*; +use bevy_state::{ + app::StatesPlugin, + prelude::*, +}; use internal_test_proc_macro::xtest; #[derive(AutoPlugin)] @@ -27,12 +29,6 @@ fn app() -> App { #[xtest] fn test_auto_init_state() { let app = app(); - assert!( - app.world().get_resource::>().is_some(), - "did not auto init state" - ); - assert!( - app.world().get_resource::>().is_some(), - "did not auto init state" - ); + assert!(app.world().get_resource::>().is_some(), "did not auto init state"); + assert!(app.world().get_resource::>().is_some(), "did not auto init state"); } diff --git a/tests/e2e/auto_init_sub_state.rs b/tests/e2e/actions/auto_init_sub_state.rs similarity index 60% rename from tests/e2e/auto_init_sub_state.rs rename to tests/e2e/actions/auto_init_sub_state.rs index d4ba5f18..3105c2bf 100644 --- a/tests/e2e/auto_init_sub_state.rs +++ b/tests/e2e/actions/auto_init_sub_state.rs @@ -1,7 +1,9 @@ use bevy_app::prelude::*; use bevy_auto_plugin::prelude::*; -use bevy_state::app::StatesPlugin; -use bevy_state::prelude::*; +use bevy_state::{ + app::StatesPlugin, + prelude::*, +}; use internal_test_proc_macro::xtest; #[derive(AutoPlugin)] @@ -59,48 +61,23 @@ fn test_update_state() { app.update(); - assert_eq!( - app.world() - .get_resource::>() - .map(|state| state.get()), - None, - ); + assert_eq!(app.world().get_resource::>().map(|state| state.get()), None,); - app.world_mut() - .resource_mut::>() - .into_inner() - .set(AppState::InGame); + app.world_mut().resource_mut::>().into_inner().set(AppState::InGame); app.update(); - assert_eq!( - app.world().resource::>().get(), - &IsPaused::Running, - ); + assert_eq!(app.world().resource::>().get(), &IsPaused::Running,); - app.world_mut() - .resource_mut::>() - .into_inner() - .set(IsPaused::Paused); + app.world_mut().resource_mut::>().into_inner().set(IsPaused::Paused); app.update(); - assert_eq!( - app.world().resource::>().get(), - &IsPaused::Paused, - ); + assert_eq!(app.world().resource::>().get(), &IsPaused::Paused,); - app.world_mut() - .resource_mut::>() - .into_inner() - .set(AppState::Menu); + app.world_mut().resource_mut::>().into_inner().set(AppState::Menu); app.update(); - assert_eq!( - app.world() - .get_resource::>() - .map(|state| state.get()), - None, - ); + assert_eq!(app.world().get_resource::>().map(|state| state.get()), None,); } diff --git a/tests/e2e/auto_insert_resource.rs b/tests/e2e/actions/auto_insert_resource.rs similarity index 71% rename from tests/e2e/auto_insert_resource.rs rename to tests/e2e/actions/auto_insert_resource.rs index 00bb09d6..bb3ad85b 100644 --- a/tests/e2e/auto_insert_resource.rs +++ b/tests/e2e/actions/auto_insert_resource.rs @@ -8,7 +8,7 @@ use internal_test_proc_macro::xtest; struct TestPlugin; #[auto_init_resource(plugin = TestPlugin)] -#[auto_insert_resource(plugin = TestPlugin, resource(Test(1)))] +#[auto_insert_resource(plugin = TestPlugin, init(Test(1)))] #[derive(Resource, Debug, Default, PartialEq)] struct Test(usize); @@ -21,9 +21,5 @@ fn app() -> App { #[xtest] fn test_auto_insert_resource() { let app = app(); - assert_eq!( - app.world().get_resource::(), - Some(&Test(1)), - "did not auto insert resource" - ); + assert_eq!(app.world().get_resource::(), Some(&Test(1)), "did not auto insert resource"); } diff --git a/tests/e2e/auto_insert_resource_with_generics.rs b/tests/e2e/actions/auto_insert_resource_with_generics.rs similarity index 96% rename from tests/e2e/auto_insert_resource_with_generics.rs rename to tests/e2e/actions/auto_insert_resource_with_generics.rs index 58ffe656..01a1dd0f 100644 --- a/tests/e2e/auto_insert_resource_with_generics.rs +++ b/tests/e2e/actions/auto_insert_resource_with_generics.rs @@ -8,7 +8,7 @@ use internal_test_proc_macro::xtest; struct TestPlugin; #[auto_init_resource(plugin = TestPlugin, generics(usize, bool))] -#[auto_insert_resource(plugin = TestPlugin, generics(usize, bool), resource(Test(1, true)))] +#[auto_insert_resource(plugin = TestPlugin, generics(usize, bool), init(Test(1, true)))] #[derive(Resource, Debug, Default, PartialEq)] struct Test(T1, T2); diff --git a/tests/e2e/auto_name.rs b/tests/e2e/actions/auto_name.rs similarity index 73% rename from tests/e2e/auto_name.rs rename to tests/e2e/actions/auto_name.rs index 29618ad4..f428138a 100644 --- a/tests/e2e/auto_name.rs +++ b/tests/e2e/actions/auto_name.rs @@ -1,7 +1,9 @@ use bevy_app::prelude::*; use bevy_auto_plugin::prelude::*; -use bevy_ecs::name::Name; -use bevy_ecs::prelude::*; +use bevy_ecs::{ + name::Name, + prelude::*, +}; use internal_test_proc_macro::xtest; #[derive(AutoPlugin)] @@ -27,10 +29,7 @@ fn test_auto_name() { let mut app = app(); let entity = app.world_mut().spawn(Test).id(); app.update(); - assert_eq!( - app.world().entity(entity).get::(), - Some(&Name::new("Test")) - ); + assert_eq!(app.world().entity(entity).get::(), Some(&Name::new("Test"))); } #[xtest] @@ -38,8 +37,5 @@ fn test_auto_name_with_custom_name() { let mut app = app(); let entity = app.world_mut().spawn(TestCustom).id(); app.update(); - assert_eq!( - app.world().entity(entity).get::(), - Some(&Name::new("barfoo")) - ); + assert_eq!(app.world().entity(entity).get::(), Some(&Name::new("barfoo"))); } diff --git a/tests/e2e/auto_name_with_generics.rs b/tests/e2e/actions/auto_name_with_generics.rs similarity index 76% rename from tests/e2e/auto_name_with_generics.rs rename to tests/e2e/actions/auto_name_with_generics.rs index 07f18a83..9d7e936f 100644 --- a/tests/e2e/auto_name_with_generics.rs +++ b/tests/e2e/actions/auto_name_with_generics.rs @@ -1,7 +1,9 @@ use bevy_app::prelude::*; use bevy_auto_plugin::prelude::*; -use bevy_ecs::name::Name; -use bevy_ecs::prelude::*; +use bevy_ecs::{ + name::Name, + prelude::*, +}; use internal_test_proc_macro::xtest; #[derive(AutoPlugin)] @@ -23,8 +25,5 @@ fn test_auto_name() { let mut app = app(); let entity = app.world_mut().spawn(Test(true)).id(); app.update(); - assert_eq!( - app.world().entity(entity).get::(), - Some(&Name::new("Test")) - ); + assert_eq!(app.world().entity(entity).get::(), Some(&Name::new("Test"))); } diff --git a/tests/e2e/auto_plugin_default_param.rs b/tests/e2e/actions/auto_plugin_default_param.rs similarity index 100% rename from tests/e2e/auto_plugin_default_param.rs rename to tests/e2e/actions/auto_plugin_default_param.rs diff --git a/tests/e2e/auto_plugin_default_param_method.rs b/tests/e2e/actions/auto_plugin_default_param_method.rs similarity index 100% rename from tests/e2e/auto_plugin_default_param_method.rs rename to tests/e2e/actions/auto_plugin_default_param_method.rs diff --git a/tests/e2e/auto_plugin_param.rs b/tests/e2e/actions/auto_plugin_param.rs similarity index 85% rename from tests/e2e/auto_plugin_param.rs rename to tests/e2e/actions/auto_plugin_param.rs index 8922ef9f..66cc6a7e 100644 --- a/tests/e2e/auto_plugin_param.rs +++ b/tests/e2e/actions/auto_plugin_param.rs @@ -24,8 +24,5 @@ fn test_auto_plugin_param() { let app = app(); let type_registry = app.world().resource::().0.clone(); let type_registry = type_registry.read(); - assert!( - type_registry.contains(type_id_of::()), - "did not auto register type" - ); + assert!(type_registry.contains(type_id_of::()), "did not auto register type"); } diff --git a/tests/e2e/auto_plugin_with_generics.rs b/tests/e2e/actions/auto_plugin_with_generics.rs similarity index 82% rename from tests/e2e/auto_plugin_with_generics.rs rename to tests/e2e/actions/auto_plugin_with_generics.rs index 1060d1ae..bbd3dccd 100644 --- a/tests/e2e/auto_plugin_with_generics.rs +++ b/tests/e2e/actions/auto_plugin_with_generics.rs @@ -3,10 +3,16 @@ use bevy_auto_plugin::prelude::*; use bevy_ecs::entity::EntityHashMap; use bevy_state::app::StatesPlugin; use internal_test_proc_macro::xtest; -use internal_test_util::{create_minimal_app, type_id_of, vec_spread}; -use std::fmt::Debug; -use std::hash::Hash; -use std::ops::AddAssign; +use internal_test_util::{ + create_minimal_app, + type_id_of, + vec_spread, +}; +use std::{ + fmt::Debug, + hash::Hash, + ops::AddAssign, +}; #[derive(AutoPlugin, Default)] #[auto_plugin(impl_plugin_trait)] @@ -25,7 +31,7 @@ where #[reflect(Resource)] #[auto_register_type(plugin = Test::, generics(u8, bool))] #[auto_init_resource(plugin = Test::, generics(u8, bool))] -#[auto_insert_resource(plugin = Test::, generics(u8, bool), resource(FooRes(1, true)))] +#[auto_insert_resource(plugin = Test::, generics(u8, bool), init(FooRes(1, true)))] struct FooRes(T1, T2) where T1: Default + Send + Sync + 'static, @@ -83,12 +89,7 @@ fn foo_observer( T1: Debug + Default + Copy + Clone + PartialEq + Eq + Hash + Send + Sync + 'static, T2: Debug + Default + Copy + Clone + PartialEq + Eq + Hash + Send + Sync + 'static, { - assert!( - added_foo_q - .get(add.event().entity) - .expect("FooComponent not spawned") - .is_added() - ); + assert!(added_foo_q.get(add.event().entity).expect("FooComponent not spawned").is_added()); foo_component_added.is_added = true; } @@ -149,11 +150,7 @@ fn test_auto_name_foo_component() { .query_filtered::<&Name, With>>() .single(app.world()) .expect("failed to query FooComponent"); - assert_eq!( - name, - &Name::new("FooComponent"), - "did not auto name FooComponent" - ); + assert_eq!(name, &Name::new("FooComponent"), "did not auto name FooComponent"); } #[xtest] @@ -196,9 +193,7 @@ fn test_auto_add_system_foo_system() { fn test_auto_add_message_foo_event() { let mut app = app(); assert!( - app.world_mut() - .write_message(FooMessage(1u8, false)) - .is_some(), + app.world_mut().write_message(FooMessage(1u8, false)).is_some(), "did not add message type FooMessage" ); } @@ -212,12 +207,10 @@ fn test_auto_event_foo_global_event() { app.insert_resource(Counter(0)); - app.add_observer( - |on: On>, mut counter: ResMut| { - counter.0 += 1; - assert_eq!(counter.0, on.event().0); - }, - ); + app.add_observer(|on: On>, mut counter: ResMut| { + counter.0 += 1; + assert_eq!(counter.0, on.event().0); + }); app.world_mut().trigger(FooGlobalEvent(1, false)); app.world_mut().trigger(FooGlobalEvent(2, false)); @@ -252,32 +245,26 @@ fn test_auto_event_foo_entity_event() { } for e in entities { - app.world_mut() - .entity_mut(e) - .trigger(|entity| FooEntityEvent { - entity, - value1: 1, - value2: false, - }); + app.world_mut().entity_mut(e).trigger(|entity| FooEntityEvent { + entity, + value1: 1, + value2: false, + }); } for e in entities { - app.world_mut() - .entity_mut(e) - .trigger(|entity| FooEntityEvent { - entity, - value1: 2, - value2: false, - }); - } - - app.world_mut() - .entity_mut(c) - .trigger(|entity| FooEntityEvent { + app.world_mut().entity_mut(e).trigger(|entity| FooEntityEvent { entity, - value1: 1, + value1: 2, value2: false, }); + } + + app.world_mut().entity_mut(c).trigger(|entity| FooEntityEvent { + entity, + value1: 1, + value2: false, + }); for e in entities { let &v = app.world_mut().resource::().0.get(&e).unwrap(); @@ -289,18 +276,12 @@ fn test_auto_event_foo_entity_event() { fn test_auto_add_observer_foo_observer() { let mut app = app(); assert!( - !app.world() - .get_resource::() - .unwrap() - .is_added, + !app.world().get_resource::().unwrap().is_added, "FooComponent should not be added yet" ); app.world_mut().spawn(FooComponent::::default()); assert!( - app.world() - .get_resource::() - .unwrap() - .is_added, + app.world().get_resource::().unwrap().is_added, "FooComponent should be added" ); } @@ -314,10 +295,7 @@ where #[xtest] fn test_auto_component_auto_name_regression_test() { let mut app = app(); - let a = app - .world_mut() - .spawn(FooAutoNameComponent::::default()) - .id(); + let a = app.world_mut().spawn(FooAutoNameComponent::::default()).id(); let name = app .world_mut() .query::<&Name>() diff --git a/tests/e2e/auto_register_state_type.rs b/tests/e2e/actions/auto_register_state_type.rs similarity index 96% rename from tests/e2e/auto_register_state_type.rs rename to tests/e2e/actions/auto_register_state_type.rs index af358d9a..6a886e4c 100644 --- a/tests/e2e/auto_register_state_type.rs +++ b/tests/e2e/actions/auto_register_state_type.rs @@ -2,8 +2,10 @@ use bevy_app::prelude::*; use bevy_auto_plugin::prelude::*; use bevy_ecs::prelude::*; use bevy_reflect::prelude::*; -use bevy_state::app::StatesPlugin; -use bevy_state::prelude::*; +use bevy_state::{ + app::StatesPlugin, + prelude::*, +}; use internal_test_proc_macro::xtest; use internal_test_util::type_id_of; diff --git a/tests/e2e/auto_register_type.rs b/tests/e2e/actions/auto_register_type.rs similarity index 85% rename from tests/e2e/auto_register_type.rs rename to tests/e2e/actions/auto_register_type.rs index 3ad09fb2..3f09b978 100644 --- a/tests/e2e/auto_register_type.rs +++ b/tests/e2e/actions/auto_register_type.rs @@ -24,8 +24,5 @@ fn test_auto_register_type() { let app = app(); let type_registry = app.world().resource::().0.clone(); let type_registry = type_registry.read(); - assert!( - type_registry.contains(type_id_of::()), - "did not auto register type" - ); + assert!(type_registry.contains(type_id_of::()), "did not auto register type"); } diff --git a/tests/e2e/auto_register_type_generic.rs b/tests/e2e/actions/auto_register_type_generic.rs similarity index 85% rename from tests/e2e/auto_register_type_generic.rs rename to tests/e2e/actions/auto_register_type_generic.rs index c750910b..acb4a700 100644 --- a/tests/e2e/auto_register_type_generic.rs +++ b/tests/e2e/actions/auto_register_type_generic.rs @@ -24,8 +24,5 @@ fn test_auto_register_type_generic() { let app = app(); let type_registry = app.world().resource::().0.clone(); let type_registry = type_registry.read(); - assert!( - type_registry.contains(type_id_of::>()), - "did not auto register type" - ); + assert!(type_registry.contains(type_id_of::>()), "did not auto register type"); } diff --git a/tests/e2e/auto_run_on_build.rs b/tests/e2e/actions/auto_run_on_build.rs similarity index 100% rename from tests/e2e/auto_run_on_build.rs rename to tests/e2e/actions/auto_run_on_build.rs diff --git a/tests/e2e/auto_run_on_build_with_generics.rs b/tests/e2e/actions/auto_run_on_build_with_generics.rs similarity index 83% rename from tests/e2e/auto_run_on_build_with_generics.rs rename to tests/e2e/actions/auto_run_on_build_with_generics.rs index d01a5921..c0f74251 100644 --- a/tests/e2e/auto_run_on_build_with_generics.rs +++ b/tests/e2e/actions/auto_run_on_build_with_generics.rs @@ -29,14 +29,8 @@ struct FooResource(usize); #[auto_run_on_build(plugin = Test, generics(Foo), generics(Bar))] fn run_this(app: &mut App) { - let value = app - .world_mut() - .get_resource::() - .copied() - .unwrap_or_default() - .0; - app.world_mut() - .insert_resource(FooResource(value + T::AMOUNT)); + let value = app.world_mut().get_resource::().copied().unwrap_or_default().0; + app.world_mut().insert_resource(FooResource(value + T::AMOUNT)); } fn foo(mut res: ResMut) { diff --git a/tests/e2e/auto_system_schedule_multiple.rs b/tests/e2e/actions/auto_system_schedule_multiple.rs similarity index 90% rename from tests/e2e/auto_system_schedule_multiple.rs rename to tests/e2e/actions/auto_system_schedule_multiple.rs index 66f90ecb..4673343a 100644 --- a/tests/e2e/auto_system_schedule_multiple.rs +++ b/tests/e2e/actions/auto_system_schedule_multiple.rs @@ -76,17 +76,11 @@ mod test { assert_eq!(app.world().get_resource::().unwrap().0, 0); - assert_eq!( - app.world().get_resource::().unwrap().get(), - (0, None) - ); + assert_eq!(app.world().get_resource::().unwrap().get(), (0, None)); app.update(); - assert_eq!( - app.world().get_resource::().unwrap().get(), - (3, Some("system_c")) - ); + assert_eq!(app.world().get_resource::().unwrap().get(), (3, Some("system_c"))); assert_eq!(app.world().get_resource::().unwrap().0, 0); } diff --git a/tests/e2e/auto_system_schedule_non_path.rs b/tests/e2e/actions/auto_system_schedule_non_path.rs similarity index 88% rename from tests/e2e/auto_system_schedule_non_path.rs rename to tests/e2e/actions/auto_system_schedule_non_path.rs index 841187f9..a6695ac2 100644 --- a/tests/e2e/auto_system_schedule_non_path.rs +++ b/tests/e2e/actions/auto_system_schedule_non_path.rs @@ -75,16 +75,10 @@ mod test { let mut app = app(); app.add_plugins(TestPlugin); - assert_eq!( - app.world().get_resource::().unwrap().get(), - (0, None) - ); + assert_eq!(app.world().get_resource::().unwrap().get(), (0, None)); app.update(); - assert_eq!( - app.world().get_resource::().unwrap().get(), - (3, Some("system_c")) - ); + assert_eq!(app.world().get_resource::().unwrap().get(), (3, Some("system_c"))); } } diff --git a/tests/e2e/auto_system_schedule_on_state.rs b/tests/e2e/actions/auto_system_schedule_on_state.rs similarity index 92% rename from tests/e2e/auto_system_schedule_on_state.rs rename to tests/e2e/actions/auto_system_schedule_on_state.rs index e9aff6e9..0556b913 100644 --- a/tests/e2e/auto_system_schedule_on_state.rs +++ b/tests/e2e/actions/auto_system_schedule_on_state.rs @@ -51,9 +51,7 @@ mod test { app.update(); assert_eq!(app.world().get_resource::().unwrap().0, 0); - app.world_mut() - .resource_mut::>() - .set(TestState::Run); + app.world_mut().resource_mut::>().set(TestState::Run); app.update(); assert_eq!(app.world().get_resource::().unwrap().0, 1); diff --git a/tests/e2e/actions/mod.rs b/tests/e2e/actions/mod.rs new file mode 100644 index 00000000..652d29c0 --- /dev/null +++ b/tests/e2e/actions/mod.rs @@ -0,0 +1,36 @@ +mod auto_add_message; +mod auto_add_message_generic; +mod auto_add_observer; +mod auto_add_plugin; +mod auto_add_plugin_with_default_init; +mod auto_add_plugin_with_generics_and_default_init; +mod auto_add_plugin_with_generics_and_init; +mod auto_add_plugin_with_init; +mod auto_add_systems; +mod auto_add_systems_complex_with_generics; +mod auto_add_systems_with_generics; +mod auto_add_systems_with_set; +mod auto_bind_plugin; +mod auto_configure_system_set; +mod auto_configure_system_set_schedule_config; +mod auto_configure_system_set_schedule_config_multiple_groups; +mod auto_init_resource; +mod auto_init_resource_generic; +mod auto_init_state; +mod auto_init_sub_state; +mod auto_insert_resource; +mod auto_insert_resource_with_generics; +mod auto_name; +mod auto_name_with_generics; +mod auto_plugin_default_param; +mod auto_plugin_default_param_method; +mod auto_plugin_param; +mod auto_plugin_with_generics; +mod auto_register_state_type; +mod auto_register_type; +mod auto_register_type_generic; +mod auto_run_on_build; +mod auto_run_on_build_with_generics; +mod auto_system_schedule_multiple; +mod auto_system_schedule_non_path; +mod auto_system_schedule_on_state; diff --git a/tests/e2e/bare_fn.rs b/tests/e2e/auto_plugin/bare_fn.rs similarity index 79% rename from tests/e2e/bare_fn.rs rename to tests/e2e/auto_plugin/bare_fn.rs index baced1d8..c99574fa 100644 --- a/tests/e2e/bare_fn.rs +++ b/tests/e2e/auto_plugin/bare_fn.rs @@ -1,6 +1,9 @@ use bevy::prelude::*; use bevy_auto_plugin::prelude::*; -use bevy_auto_plugin_proc_macros::{auto_init_resource, auto_insert_resource}; +use bevy_auto_plugin_proc_macros::{ + auto_init_resource, + auto_insert_resource, +}; use internal_test_proc_macro::xtest; #[derive(AutoPlugin)] @@ -8,14 +11,14 @@ pub struct MyPlugin; #[derive(Resource, Default, PartialEq, Debug)] #[auto_init_resource(plugin = MyPlugin)] -#[auto_insert_resource(plugin = MyPlugin, resource(MyResourceAuto(1)))] +#[auto_insert_resource(plugin = MyPlugin, init(MyResourceAuto(1)))] pub struct MyResourceAuto(usize); #[derive(Resource, Default, PartialEq, Debug)] #[auto_init_resource(plugin = MyPlugin)] pub struct MyResourceBuild(usize); -#[auto_plugin(plugin = MyPlugin, app_param = non_default_app_param_name)] +#[auto_plugin(plugin = MyPlugin)] fn build(non_default_app_param_name: &mut App) { non_default_app_param_name.insert_resource(MyResourceBuild(1)); } diff --git a/tests/e2e/bare_fn_default_app_param.rs b/tests/e2e/auto_plugin/bare_fn_default_app_param.rs similarity index 84% rename from tests/e2e/bare_fn_default_app_param.rs rename to tests/e2e/auto_plugin/bare_fn_default_app_param.rs index 41669843..7cd5e9f4 100644 --- a/tests/e2e/bare_fn_default_app_param.rs +++ b/tests/e2e/auto_plugin/bare_fn_default_app_param.rs @@ -1,6 +1,9 @@ use bevy::prelude::*; use bevy_auto_plugin::prelude::*; -use bevy_auto_plugin_proc_macros::{auto_init_resource, auto_insert_resource}; +use bevy_auto_plugin_proc_macros::{ + auto_init_resource, + auto_insert_resource, +}; use internal_test_proc_macro::xtest; #[derive(AutoPlugin)] @@ -8,7 +11,7 @@ pub struct MyPlugin; #[derive(Resource, Default, PartialEq, Debug)] #[auto_init_resource(plugin = MyPlugin)] -#[auto_insert_resource(plugin = MyPlugin, resource(MyResourceAuto(1)))] +#[auto_insert_resource(plugin = MyPlugin, init(MyResourceAuto(1)))] pub struct MyResourceAuto(usize); #[derive(Resource, Default, PartialEq, Debug)] diff --git a/tests/e2e/auto_plugin/mod.rs b/tests/e2e/auto_plugin/mod.rs new file mode 100644 index 00000000..2dea6eb4 --- /dev/null +++ b/tests/e2e/auto_plugin/mod.rs @@ -0,0 +1,5 @@ +mod bare_fn; +mod bare_fn_default_app_param; +mod self_impl_plugin; +mod self_impl_plugin_default_app_param; +mod self_impl_plugin_with_generics; diff --git a/tests/e2e/self_impl_plugin.rs b/tests/e2e/auto_plugin/self_impl_plugin.rs similarity index 81% rename from tests/e2e/self_impl_plugin.rs rename to tests/e2e/auto_plugin/self_impl_plugin.rs index 16aba578..e85795f8 100644 --- a/tests/e2e/self_impl_plugin.rs +++ b/tests/e2e/auto_plugin/self_impl_plugin.rs @@ -1,6 +1,9 @@ use bevy::prelude::*; use bevy_auto_plugin::prelude::*; -use bevy_auto_plugin_proc_macros::{auto_init_resource, auto_insert_resource}; +use bevy_auto_plugin_proc_macros::{ + auto_init_resource, + auto_insert_resource, +}; use internal_test_proc_macro::xtest; #[derive(AutoPlugin)] @@ -8,7 +11,7 @@ pub struct MyPlugin; #[derive(Resource, Default, PartialEq, Debug)] #[auto_init_resource(plugin = MyPlugin)] -#[auto_insert_resource(plugin = MyPlugin, resource(MyResourceAuto(1)))] +#[auto_insert_resource(plugin = MyPlugin, init(MyResourceAuto(1)))] pub struct MyResourceAuto(usize); #[derive(Resource, Default, PartialEq, Debug)] @@ -16,7 +19,7 @@ pub struct MyResourceAuto(usize); pub struct MyResourceBuild(usize); impl Plugin for MyPlugin { - #[auto_plugin(app_param=non_default_app_param_name)] + #[auto_plugin] fn build(&self, non_default_app_param_name: &mut App) { non_default_app_param_name.insert_resource(MyResourceBuild(1)); } diff --git a/tests/e2e/self_impl_plugin_default_app_param.rs b/tests/e2e/auto_plugin/self_impl_plugin_default_app_param.rs similarity index 85% rename from tests/e2e/self_impl_plugin_default_app_param.rs rename to tests/e2e/auto_plugin/self_impl_plugin_default_app_param.rs index 054dc692..0cb69e30 100644 --- a/tests/e2e/self_impl_plugin_default_app_param.rs +++ b/tests/e2e/auto_plugin/self_impl_plugin_default_app_param.rs @@ -1,6 +1,9 @@ use bevy::prelude::*; use bevy_auto_plugin::prelude::*; -use bevy_auto_plugin_proc_macros::{auto_init_resource, auto_insert_resource}; +use bevy_auto_plugin_proc_macros::{ + auto_init_resource, + auto_insert_resource, +}; use internal_test_proc_macro::xtest; #[derive(AutoPlugin)] @@ -8,7 +11,7 @@ pub struct MyPlugin; #[derive(Resource, Default, PartialEq, Debug)] #[auto_init_resource(plugin = MyPlugin)] -#[auto_insert_resource(plugin = MyPlugin, resource(MyResourceAuto(1)))] +#[auto_insert_resource(plugin = MyPlugin, init(MyResourceAuto(1)))] pub struct MyResourceAuto(usize); #[derive(Resource, Default, PartialEq, Debug)] diff --git a/tests/e2e/self_impl_plugin_with_generics.rs b/tests/e2e/auto_plugin/self_impl_plugin_with_generics.rs similarity index 89% rename from tests/e2e/self_impl_plugin_with_generics.rs rename to tests/e2e/auto_plugin/self_impl_plugin_with_generics.rs index 17f99213..3b8642d9 100644 --- a/tests/e2e/self_impl_plugin_with_generics.rs +++ b/tests/e2e/auto_plugin/self_impl_plugin_with_generics.rs @@ -1,6 +1,9 @@ use bevy::prelude::*; use bevy_auto_plugin::prelude::*; -use bevy_auto_plugin_proc_macros::{auto_init_resource, auto_insert_resource}; +use bevy_auto_plugin_proc_macros::{ + auto_init_resource, + auto_insert_resource, +}; use internal_test_proc_macro::xtest; #[derive(AutoPlugin, Default)] @@ -12,7 +15,7 @@ where #[derive(Resource, Default, PartialEq, Debug)] #[auto_init_resource(plugin = MyPlugin::, generics(u8, bool))] -#[auto_insert_resource(plugin = MyPlugin::, generics(u8, bool), resource(MyResourceAuto(1, true)))] +#[auto_insert_resource(plugin = MyPlugin::, generics(u8, bool), init(MyResourceAuto(1, true)))] pub struct MyResourceAuto(T1, T2) where T1: Default + Send + Sync + 'static, @@ -30,7 +33,7 @@ where T1: Default + Send + Sync + 'static, T2: Default + Send + Sync + 'static, { - #[auto_plugin(app_param=non_default_app_param_name)] + #[auto_plugin] fn build(&self, non_default_app_param_name: &mut App) { non_default_app_param_name.insert_resource(MyResourceBuild(1u8, true)); } diff --git a/tests/e2e/auto_plugin.rs b/tests/e2e/general.rs similarity index 81% rename from tests/e2e/auto_plugin.rs rename to tests/e2e/general.rs index 0d947e89..6ab32f0d 100644 --- a/tests/e2e/auto_plugin.rs +++ b/tests/e2e/general.rs @@ -5,7 +5,11 @@ use bevy_auto_plugin::prelude::*; use bevy_ecs::entity::EntityHashMap; use bevy_state::app::StatesPlugin; use internal_test_proc_macro::xtest; -use internal_test_util::{create_minimal_app, type_id_of, vec_spread}; +use internal_test_util::{ + create_minimal_app, + type_id_of, + vec_spread, +}; use std::ops::Deref; #[derive(AutoPlugin)] @@ -33,7 +37,7 @@ struct FooDefaultRes(usize); #[reflect(Resource)] #[auto_register_type(plugin = Test)] #[auto_init_resource(plugin = Test)] -#[auto_insert_resource(plugin = Test, resource(FooRes(1)))] +#[auto_insert_resource(plugin = Test, init(FooRes(1)))] struct FooRes(usize); #[auto_resource(plugin = Test, derive, register, reflect, init)] @@ -97,12 +101,7 @@ fn foo_observer( added_foo_q: Query, Added>, mut foo_component_added: ResMut, ) { - assert!( - added_foo_q - .get(add.event().entity) - .expect("FooComponent not spawned") - .is_added() - ); + assert!(added_foo_q.get(add.event().entity).expect("FooComponent not spawned").is_added()); foo_component_added.is_added = true; } @@ -121,10 +120,7 @@ fn test_auto_register_type_foo_component() { let app = app(); let type_registry = app.world().resource::().0.clone(); let type_registry = type_registry.read(); - assert!( - type_registry.contains(type_id_of::()), - "did not auto register type" - ); + assert!(type_registry.contains(type_id_of::()), "did not auto register type"); } #[xtest] @@ -136,11 +132,7 @@ fn test_auto_name_foo_component() { .query_filtered::<&Name, With>() .single(app.world()) .expect("failed to query FooComponent"); - assert_eq!( - name, - &Name::new("FooComponent"), - "did not auto name FooComponent" - ); + assert_eq!(name, &Name::new("FooComponent"), "did not auto name FooComponent"); } #[xtest] @@ -172,11 +164,7 @@ fn test_auto_add_system_foo_system() { "did not auto init resource" ); app.update(); - assert_eq!( - app.world().get_resource::(), - Some(&FooRes(2)), - "did not register system" - ); + assert_eq!(app.world().get_resource::(), Some(&FooRes(2)), "did not register system"); } #[xtest] @@ -232,20 +220,14 @@ fn test_auto_event_foo_entity_event() { } for e in entities { - app.world_mut() - .entity_mut(e) - .trigger(|entity| FooEntityEvent { entity, value: 1 }); + app.world_mut().entity_mut(e).trigger(|entity| FooEntityEvent { entity, value: 1 }); } for e in entities { - app.world_mut() - .entity_mut(e) - .trigger(|entity| FooEntityEvent { entity, value: 2 }); + app.world_mut().entity_mut(e).trigger(|entity| FooEntityEvent { entity, value: 2 }); } - app.world_mut() - .entity_mut(c) - .trigger(|entity| FooEntityEvent { entity, value: 1 }); + app.world_mut().entity_mut(c).trigger(|entity| FooEntityEvent { entity, value: 1 }); for e in entities { let &v = app.world_mut().resource::().0.get(&e).unwrap(); @@ -258,10 +240,7 @@ fn test_auto_register_state_type_foo_state() { let app = app(); let type_registry = app.world().resource::().0.clone(); let type_registry = type_registry.read(); - assert!( - type_registry.contains(type_id_of::>()), - "did not auto register type" - ); + assert!(type_registry.contains(type_id_of::>()), "did not auto register type"); assert!( type_registry.contains(type_id_of::>()), "did not auto register type" @@ -272,9 +251,7 @@ fn test_auto_register_state_type_foo_state() { fn test_auto_init_state_type_foo_state() { let app = app(); assert_eq!( - app.world() - .get_resource::>() - .map(Deref::deref), + app.world().get_resource::>().map(Deref::deref), Some(&FooState::Start), "did not auto init state" ); @@ -284,18 +261,12 @@ fn test_auto_init_state_type_foo_state() { fn test_auto_add_observer_foo_observer() { let mut app = app(); assert!( - !app.world() - .get_resource::() - .unwrap() - .is_added, + !app.world().get_resource::().unwrap().is_added, "FooComponent should not be added yet" ); app.world_mut().spawn(FooComponent); assert!( - app.world() - .get_resource::() - .unwrap() - .is_added, + app.world().get_resource::().unwrap().is_added, "FooComponent should be added" ); } diff --git a/tests/e2e/mod.rs b/tests/e2e/mod.rs index 1c0ba179..6f7e1f30 100644 --- a/tests/e2e/mod.rs +++ b/tests/e2e/mod.rs @@ -1,44 +1,6 @@ -mod auto_add_message; -mod auto_add_message_generic; -mod auto_add_observer; -mod auto_add_plugin; -mod auto_add_plugin_with_default_init; -mod auto_add_plugin_with_generics_and_default_init; -mod auto_add_plugin_with_generics_and_init; -mod auto_add_plugin_with_init; -mod auto_add_systems; -mod auto_add_systems_complex_with_generics; -mod auto_add_systems_with_generics; -mod auto_add_systems_with_set; -mod auto_bind_plugin; -mod auto_configure_system_set; -mod auto_configure_system_set_schedule_config; -mod auto_configure_system_set_schedule_config_multiple_groups; -mod auto_init_resource; -mod auto_init_resource_generic; -mod auto_init_state; -mod auto_init_sub_state; -mod auto_insert_resource; -mod auto_insert_resource_with_generics; -mod auto_name; -mod auto_name_with_generics; +mod actions; mod auto_plugin; -mod auto_plugin_default_param; -mod auto_plugin_default_param_method; -mod auto_plugin_param; -mod auto_plugin_with_generics; -mod auto_register_state_type; -mod auto_register_type; -mod auto_register_type_generic; -mod auto_run_on_build; -mod auto_run_on_build_with_generics; -mod auto_system_schedule_multiple; -mod auto_system_schedule_non_path; -mod auto_system_schedule_on_state; -mod bare_fn; -mod bare_fn_default_app_param; -mod self_impl_plugin; -mod self_impl_plugin_default_app_param; -mod self_impl_plugin_with_generics; +mod general; +mod rewrites; #[cfg(not(wasm))] mod ui_tests; diff --git a/tests/e2e/rewrites/auto_component.rs b/tests/e2e/rewrites/auto_component.rs new file mode 100644 index 00000000..d4a1e6ba --- /dev/null +++ b/tests/e2e/rewrites/auto_component.rs @@ -0,0 +1,70 @@ +#![allow(dead_code)] + +use bevy::prelude::*; +use bevy_auto_plugin::prelude::*; +use bevy_state::app::StatesPlugin; +use internal_test_proc_macro::xtest; +use internal_test_util::{ + create_minimal_app, + type_id_of, +}; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct Test; + +#[auto_component(plugin = Test, derive, register, reflect, auto_name)] +struct FooComponent; + +#[auto_component(plugin = Test, generics(usize), derive, register, reflect, auto_name)] +struct GenericComponent(T); + +fn app() -> App { + let mut app = create_minimal_app(); + app.add_plugins(StatesPlugin); + app.add_plugins(Test); + app +} + +#[xtest] +fn test_auto_register_type_foo_component() { + let app = app(); + let type_registry = app.world().resource::().0.clone(); + let type_registry = type_registry.read(); + assert!(type_registry.contains(type_id_of::()), "did not auto register type"); +} + +#[xtest] +fn test_auto_register_type_generic_component() { + let app = app(); + let type_registry = app.world().resource::().0.clone(); + let type_registry = type_registry.read(); + assert!( + type_registry.contains(type_id_of::>()), + "did not auto register generic type" + ); +} + +#[xtest] +fn test_auto_name_foo_component() { + let mut app = app(); + app.world_mut().spawn(FooComponent); + let name = app + .world_mut() + .query_filtered::<&Name, With>() + .single(app.world()) + .expect("failed to query FooComponent"); + assert_eq!(name, &Name::new("FooComponent"), "did not auto name FooComponent"); +} + +#[xtest] +fn test_auto_name_generic_component() { + let mut app = app(); + app.world_mut().spawn(GenericComponent(10usize)); + let name = app + .world_mut() + .query_filtered::<&Name, With>>() + .single(app.world()) + .expect("failed to query GenericComponent"); + assert_eq!(name, &Name::new("GenericComponent"), "did not auto name GenericComponent"); +} diff --git a/tests/e2e/rewrites/auto_event.rs b/tests/e2e/rewrites/auto_event.rs new file mode 100644 index 00000000..26626567 --- /dev/null +++ b/tests/e2e/rewrites/auto_event.rs @@ -0,0 +1,58 @@ +use bevy::prelude::*; +use bevy_auto_plugin::prelude::*; +use internal_test_proc_macro::xtest; +use internal_test_util::{ + create_minimal_app, + type_id_of, +}; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct Test; + +#[auto_event(plugin = Test, target(global), derive(Debug, Default, PartialEq), reflect, register)] +struct FooGlobalEvent(usize); + +#[auto_event(plugin = Test, target(entity), derive(Debug, PartialEq), reflect, register)] +struct FooEntityEvent(#[event_target] Entity); + +fn app() -> App { + let mut app = create_minimal_app(); + app.add_plugins(Test); + app +} + +#[xtest] +fn test_auto_register_type_foo_global_event() { + let app = app(); + let type_registry = app.world().resource::().0.clone(); + let type_registry = type_registry.read(); + assert!( + type_registry.contains(type_id_of::()), + "did not auto register global event type" + ); +} + +#[xtest] +fn test_auto_register_type_foo_entity_event() { + let app = app(); + let type_registry = app.world().resource::().0.clone(); + let type_registry = type_registry.read(); + assert!( + type_registry.contains(type_id_of::()), + "did not auto register entity event type" + ); +} + +#[xtest] +fn test_global_event_trigger() { + let mut app = app(); + app.world_mut().trigger(FooGlobalEvent(42)); +} + +#[xtest] +fn test_entity_event_trigger() { + let mut app = app(); + let entity = app.world_mut().spawn_empty().id(); + app.world_mut().trigger(FooEntityEvent(entity)); +} diff --git a/tests/e2e/rewrites/auto_message.rs b/tests/e2e/rewrites/auto_message.rs new file mode 100644 index 00000000..456e9a88 --- /dev/null +++ b/tests/e2e/rewrites/auto_message.rs @@ -0,0 +1,45 @@ +use bevy::prelude::*; +use bevy_auto_plugin::prelude::*; +use internal_test_proc_macro::xtest; +use internal_test_util::{ + create_minimal_app, + type_id_of, +}; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct Test; + +#[auto_message(plugin = Test, derive(Debug, Default, PartialEq), reflect, register)] +struct FooMessage(usize); + +#[auto_message(plugin = Test, generics(usize), derive(Debug, Default, PartialEq), reflect, register)] +struct GenericMessage(T); + +fn app() -> App { + let mut app = create_minimal_app(); + app.add_plugins(Test); + app +} + +#[xtest] +fn test_auto_register_type_foo_message() { + let app = app(); + let type_registry = app.world().resource::().0.clone(); + let type_registry = type_registry.read(); + assert!( + type_registry.contains(type_id_of::()), + "did not auto register message type" + ); +} + +#[xtest] +fn test_auto_register_type_generic_message() { + let app = app(); + let type_registry = app.world().resource::().0.clone(); + let type_registry = type_registry.read(); + assert!( + type_registry.contains(type_id_of::>()), + "did not auto register generic message type" + ); +} diff --git a/tests/e2e/rewrites/auto_observer.rs b/tests/e2e/rewrites/auto_observer.rs new file mode 100644 index 00000000..3fb3db12 --- /dev/null +++ b/tests/e2e/rewrites/auto_observer.rs @@ -0,0 +1,33 @@ +use bevy::prelude::*; +use bevy_auto_plugin::prelude::*; +use internal_test_proc_macro::xtest; +use internal_test_util::create_minimal_app; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct Test; + +#[derive(Component)] +struct Foo; + +#[derive(Resource, Default)] +struct ObserverCount(usize); + +#[auto_observer(plugin = Test)] +fn foo_observer(_trigger: On, mut count: ResMut) { + count.0 += 1; +} + +fn app() -> App { + let mut app = create_minimal_app(); + app.init_resource::(); + app.add_plugins(Test); + app +} + +#[xtest] +fn test_auto_observer() { + let mut app = app(); + app.world_mut().spawn(Foo); + assert_eq!(app.world().resource::().0, 1); +} diff --git a/tests/e2e/rewrites/auto_resource.rs b/tests/e2e/rewrites/auto_resource.rs new file mode 100644 index 00000000..9ec40f29 --- /dev/null +++ b/tests/e2e/rewrites/auto_resource.rs @@ -0,0 +1,48 @@ +use bevy::prelude::*; +use bevy_auto_plugin::prelude::*; +use internal_test_proc_macro::xtest; +use internal_test_util::{ + create_minimal_app, + type_id_of, +}; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct Test; + +#[auto_resource(plugin = Test, derive(Debug, Default, PartialEq), reflect, register, init)] +struct FooResource(usize); + +#[auto_resource(plugin = Test, generics(usize), derive(Debug, Default, PartialEq), reflect, register, init)] +struct GenericResource(T); + +fn app() -> App { + let mut app = create_minimal_app(); + app.add_plugins(Test); + app +} + +#[xtest] +fn test_auto_register_type_foo_resource() { + let app = app(); + let type_registry = app.world().resource::().0.clone(); + let type_registry = type_registry.read(); + assert!( + type_registry.contains(type_id_of::()), + "did not auto register resource type" + ); +} + +#[xtest] +fn test_auto_init_resource() { + let app = app(); + assert!(app.world().contains_resource::()); + assert_eq!(app.world().resource::().0, 0); +} + +#[xtest] +fn test_auto_init_generic_resource() { + let app = app(); + assert!(app.world().contains_resource::>()); + assert_eq!(app.world().resource::>().0, 0); +} diff --git a/tests/e2e/rewrites/auto_states.rs b/tests/e2e/rewrites/auto_states.rs new file mode 100644 index 00000000..c22782b1 --- /dev/null +++ b/tests/e2e/rewrites/auto_states.rs @@ -0,0 +1,45 @@ +use bevy::prelude::*; +use bevy_auto_plugin::prelude::*; +use bevy_state::app::StatesPlugin; +use internal_test_proc_macro::xtest; +use internal_test_util::type_id_of; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct Test; + +#[auto_states(plugin = Test, derive, reflect, register, init)] +enum FooState { + #[default] + Start, + End, +} + +fn app() -> App { + let mut app = App::new(); + app.add_plugins(StatesPlugin); + app.add_plugins(Test); + app +} + +#[xtest] +fn test_auto_init_state() { + let app = app(); + assert_eq!(app.world().resource::>().get(), &FooState::Start); +} + +#[xtest] +fn test_auto_register_types() { + let app = app(); + let type_registry = app.world().resource::().0.clone(); + let type_registry = type_registry.read(); + assert!(type_registry.contains(type_id_of::()), "did not auto register type"); + assert!( + type_registry.contains(type_id_of::>()), + "did not auto register State type" + ); + assert!( + type_registry.contains(type_id_of::>()), + "did not auto register NextState type" + ); +} diff --git a/tests/e2e/rewrites/auto_sub_states.rs b/tests/e2e/rewrites/auto_sub_states.rs new file mode 100644 index 00000000..e89630aa --- /dev/null +++ b/tests/e2e/rewrites/auto_sub_states.rs @@ -0,0 +1,61 @@ +use bevy::prelude::*; +use bevy_auto_plugin::prelude::*; +use bevy_state::app::StatesPlugin; +use internal_test_proc_macro::xtest; +use internal_test_util::type_id_of; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct Test; + +#[auto_states(plugin = Test, derive, reflect, register, init)] +enum FooState { + #[default] + Start, + End, +} + +#[auto_sub_states(plugin = Test, derive, reflect, register, init)] +#[source(FooState = FooState::End)] +enum BarState { + #[default] + A, + B, +} + +fn app() -> App { + let mut app = App::new(); + app.add_plugins(StatesPlugin); + app.add_plugins(Test); + app +} + +#[xtest] +fn test_auto_init_state() { + let mut app = app(); + assert_eq!(app.world().get_resource::>().map(State::get), None); + + app.world_mut().resource_mut::>().set(FooState::End); + app.update(); + assert_eq!(app.world().get_resource::>().map(State::get), Some(&BarState::A)); + + app.world_mut().resource_mut::>().set(FooState::Start); + app.update(); + assert_eq!(app.world().get_resource::>().map(State::get), None); +} + +#[xtest] +fn test_auto_register_types() { + let app = app(); + let type_registry = app.world().resource::().0.clone(); + let type_registry = type_registry.read(); + assert!(type_registry.contains(type_id_of::()), "did not auto register type"); + assert!( + type_registry.contains(type_id_of::>()), + "did not auto register State type" + ); + assert!( + type_registry.contains(type_id_of::>()), + "did not auto register NextState type" + ); +} diff --git a/tests/e2e/rewrites/auto_system.rs b/tests/e2e/rewrites/auto_system.rs new file mode 100644 index 00000000..8e632047 --- /dev/null +++ b/tests/e2e/rewrites/auto_system.rs @@ -0,0 +1,61 @@ +use bevy::prelude::*; +use bevy_auto_plugin::prelude::*; +use internal_test_proc_macro::xtest; +use internal_test_util::create_minimal_app; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct Test; + +#[derive(Resource, Default)] +struct SystemCounter(Vec<&'static str>); + +#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] +enum TestSet { + Set, +} + +#[auto_system(plugin = Test, schedule = Update)] +fn basic_system(mut counter: ResMut) { + counter.0.push("basic"); +} + +#[auto_system(plugin = Test, schedule = Update, config(in_set = TestSet::Set))] +fn set_system(mut counter: ResMut) { + counter.0.push("set"); +} + +#[auto_system(plugin = Test, schedule = Update, config(after = basic_system))] +fn after_system(mut counter: ResMut) { + counter.0.push("after"); +} + +#[auto_system(plugin = Test, schedule = Update, generics(usize))] +fn generic_system(mut counter: ResMut) { + counter.0.push("generic"); +} + +fn app() -> App { + let mut app = create_minimal_app(); + app.init_resource::(); + app.configure_sets(Update, TestSet::Set); + app.add_plugins(Test); + app +} + +#[xtest] +fn test_auto_systems() { + let mut app = app(); + app.update(); + let counter = &app.world().resource::().0; + assert!(counter.contains(&"basic"), "did not auto register basic system"); + assert!(counter.contains(&"set"), "did not auto register set system"); + assert!(counter.contains(&"after"), "did not auto register after system"); + assert!(counter.contains(&"generic"), "did not auto register generic system"); + assert_eq!(counter.len(), 4); + assert!( + counter.iter().position(|s| s == &"after").unwrap() + > counter.iter().position(|s| s == &"basic").unwrap(), + "after system not executed after basic system" + ); +} diff --git a/tests/e2e/rewrites/mod.rs b/tests/e2e/rewrites/mod.rs new file mode 100644 index 00000000..d69dbb81 --- /dev/null +++ b/tests/e2e/rewrites/mod.rs @@ -0,0 +1,8 @@ +mod auto_component; +mod auto_event; +mod auto_message; +mod auto_observer; +mod auto_resource; +mod auto_states; +mod auto_sub_states; +mod auto_system; diff --git a/tests/e2e/ui/auto_register_type_wrong_item.stderr b/tests/e2e/ui/auto_register_type_wrong_item.stderr index 04e8839f..b4b6dae1 100644 --- a/tests/e2e/ui/auto_register_type_wrong_item.stderr +++ b/tests/e2e/ui/auto_register_type_wrong_item.stderr @@ -1,4 +1,4 @@ -error: Attribute macro is not allowed on Fn: Expected Struct or Enum +error: Only allowed on Struct Or Enum items --> tests/e2e/ui/auto_register_type_wrong_item.rs:7:1 | 7 | #[auto_register_type(plugin = TestPlugin)] diff --git a/tests/e2e/ui/bad_generics_type.rs b/tests/e2e/ui/nightly/bad_generics_type.rs similarity index 100% rename from tests/e2e/ui/bad_generics_type.rs rename to tests/e2e/ui/nightly/bad_generics_type.rs diff --git a/tests/e2e/ui/nightly/bad_generics_type.stderr b/tests/e2e/ui/nightly/bad_generics_type.stderr new file mode 100644 index 00000000..c420ab04 --- /dev/null +++ b/tests/e2e/ui/nightly/bad_generics_type.stderr @@ -0,0 +1,11 @@ +error: Failed to parse TypeList: expected a list of *types*, but found literal value(s). Use types like `Foo`, `Vec`, `Option`; not `1`, `true`, or string literals. + --> tests/e2e/ui/nightly/bad_generics_type.rs:7:52 + | +7 | #[auto_register_type(plugin = TestPlugin, generics(1, 1))] + | ^^^^ + +error: expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, `dyn`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime + --> tests/e2e/ui/nightly/bad_generics_type.rs:7:52 + | +7 | #[auto_register_type(plugin = TestPlugin, generics(1, 1))] + | ^ diff --git a/tests/e2e/ui/stable/bad_generics_type.rs b/tests/e2e/ui/stable/bad_generics_type.rs new file mode 100644 index 00000000..cb4c39b7 --- /dev/null +++ b/tests/e2e/ui/stable/bad_generics_type.rs @@ -0,0 +1,11 @@ +use bevy_auto_plugin::prelude::*; + +#[derive(AutoPlugin)] +#[auto_plugin(impl_plugin_trait)] +struct TestPlugin; + +#[auto_register_type(plugin = TestPlugin, generics(1, 1))] +struct Foo(T1, T2); + +// dummy main +fn main() {} diff --git a/tests/e2e/ui/bad_generics_type.stderr b/tests/e2e/ui/stable/bad_generics_type.stderr similarity index 59% rename from tests/e2e/ui/bad_generics_type.stderr rename to tests/e2e/ui/stable/bad_generics_type.stderr index adc6739d..3d7d33fe 100644 --- a/tests/e2e/ui/bad_generics_type.stderr +++ b/tests/e2e/ui/stable/bad_generics_type.stderr @@ -1,11 +1,11 @@ -error: failed to parse TypeList - --> tests/e2e/ui/bad_generics_type.rs:7:52 +error: Failed to parse TypeList: expected a list of *types*, but found literal value(s). Use types like `Foo`, `Vec`, `Option`; not `1`, `true`, or string literals. + --> tests/e2e/ui/stable/bad_generics_type.rs:7:52 | 7 | #[auto_register_type(plugin = TestPlugin, generics(1, 1))] | ^ error: expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, `dyn`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime - --> tests/e2e/ui/bad_generics_type.rs:7:52 + --> tests/e2e/ui/stable/bad_generics_type.rs:7:52 | 7 | #[auto_register_type(plugin = TestPlugin, generics(1, 1))] | ^ diff --git a/tests/e2e/ui_tests.rs b/tests/e2e/ui_tests.rs index 67f01b8c..da52212b 100644 --- a/tests/e2e/ui_tests.rs +++ b/tests/e2e/ui_tests.rs @@ -1,5 +1,7 @@ -use internal_test_util::ui_tests; -use internal_test_util::ui_util::UiTest; +use internal_test_util::{ + ui_tests, + ui_util::UiTest, +}; struct UiTests;