Skip to main content

bevy_trait_query_impl/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::{format_ident, quote};
4use syn::{ItemTrait, Result, TraitItem, parse_quote};
5
6/// When added to a trait declaration, generates the impls required to use that trait in queries.
7///
8/// # Poor use cases
9///
10/// You should avoid using trait queries for very simple cases that can be solved with more direct solutions.
11///
12/// One naive use would be querying for a trait that looks something like:
13///
14/// ```
15/// trait Person {
16///     fn name(&self) -> &str;
17/// }
18/// ```
19///
20/// A far better way of expressing this would be to store the name in a separate component
21/// and query for that directly, making `Person` a simple marker component.
22///
23/// Trait queries are often the most *obvious* solution to a problem, but not always the best one.
24/// For examples of strong real-world use-cases, check out the RFC for trait queries in `bevy`:
25/// <https://github.com/bevyengine/rfcs/pull/39>.
26///
27/// # Note
28///
29/// This will add the trait bound `'static` to the trait and all of its type parameters.
30///
31/// You may opt out of this by using the form `#[queryable(no_bounds)]`,
32/// but you will have to add the bounds yourself to make it compile.
33#[proc_macro_attribute]
34pub fn queryable(attr: TokenStream, item: TokenStream) -> TokenStream {
35    impl_trait_query(attr, item)
36        .unwrap_or_else(syn::Error::into_compile_error)
37        .into()
38}
39
40fn impl_trait_query(arg: TokenStream, item: TokenStream) -> Result<TokenStream2> {
41    syn::custom_keyword!(no_bounds);
42    let no_bounds: Option<no_bounds> = syn::parse(arg).map_err(|e| {
43        syn::Error::new(
44            e.span(),
45            "Valid forms are: `#[queryable]` and `#[queryable(no_bounds)]`",
46        )
47    })?;
48
49    let mut trait_definition = syn::parse::<ItemTrait>(item)?;
50    let trait_name = trait_definition.ident.clone();
51
52    // Add `'static` bounds, unless the user asked us not to.
53    if no_bounds.is_none() {
54        trait_definition.supertraits.push(parse_quote!('static));
55
56        for param in &mut trait_definition.generics.params {
57            // Make sure the parameters to the trait are `'static`.
58            if let syn::GenericParam::Type(param) = param {
59                param.bounds.push(parse_quote!('static));
60            }
61        }
62
63        for item in &mut trait_definition.items {
64            // Make sure all associated types are `'static`.
65            if let TraitItem::Type(assoc) = item {
66                assoc.bounds.push(parse_quote!('static));
67            }
68        }
69    }
70
71    let mut impl_generics_list = vec![];
72    let mut trait_generics_list = vec![];
73    let where_clause = trait_definition.generics.where_clause.clone();
74
75    for param in &trait_definition.generics.params {
76        impl_generics_list.push(param.clone());
77        match param {
78            syn::GenericParam::Type(param) => {
79                let ident = &param.ident;
80                trait_generics_list.push(quote! { #ident });
81            }
82            syn::GenericParam::Lifetime(param) => {
83                let ident = &param.lifetime;
84                trait_generics_list.push(quote! { #ident });
85            }
86            syn::GenericParam::Const(param) => {
87                let ident = &param.ident;
88                trait_generics_list.push(quote! { #ident });
89            }
90        }
91    }
92
93    // Add generics for unbounded associated types.
94    for item in &trait_definition.items {
95        if let TraitItem::Type(assoc) = item {
96            if !assoc.generics.params.is_empty() {
97                return Err(syn::Error::new(
98                    assoc.ident.span(),
99                    "Generic associated types are not supported in trait queries",
100                ));
101            }
102            let ident = &assoc.ident;
103            let lower_ident = format_ident!("__{ident}");
104            let bound = &assoc.bounds;
105            impl_generics_list.push(parse_quote! { #lower_ident: #bound });
106            trait_generics_list.push(quote! { #ident = #lower_ident });
107        }
108    }
109
110    let impl_generics = quote! { <#( #impl_generics_list ,)*> };
111    let trait_generics = quote! { <#( #trait_generics_list ,)*> };
112
113    let trait_object = quote! { dyn #trait_name #trait_generics };
114
115    let my_crate = proc_macro_crate::crate_name("bevy-trait-query").unwrap();
116    let my_crate = match my_crate {
117        proc_macro_crate::FoundCrate::Itself => quote! { bevy_trait_query },
118        proc_macro_crate::FoundCrate::Name(x) => {
119            let ident = quote::format_ident!("{x}");
120            quote! { #ident }
121        }
122    };
123
124    let imports = quote! { #my_crate::imports };
125
126    let trait_query = quote! { #my_crate::TraitQuery };
127
128    let mut marker_impl_generics_list = impl_generics_list.clone();
129    marker_impl_generics_list
130        .push(parse_quote!(__Component: #trait_name #trait_generics + #imports::Component));
131    let marker_impl_generics = quote! { <#( #marker_impl_generics_list ,)*> };
132
133    let marker_impl_code = quote! {
134        impl #impl_generics #trait_query for #trait_object #where_clause {}
135
136        impl #marker_impl_generics #my_crate::TraitQueryMarker::<#trait_object> for (__Component,)
137        #where_clause
138        {
139            type Covered = __Component;
140            fn cast(ptr: *mut u8) -> *mut #trait_object {
141                ptr as *mut __Component as *mut _
142            }
143        }
144    };
145
146    let mut impl_generics_with_lifetime = impl_generics_list.clone();
147    impl_generics_with_lifetime.insert(0, parse_quote!('__a));
148    let impl_generics_with_lifetime = quote! { <#( #impl_generics_with_lifetime ,)*> };
149
150    let trait_object_query_code = quote! {
151        unsafe impl #impl_generics #imports::SingleEntityQueryData for &#trait_object {}
152        unsafe impl #impl_generics #imports::IterQueryData for &#trait_object {}
153        unsafe impl #impl_generics #imports::QueryData for &#trait_object
154        #where_clause
155        {
156            type ReadOnly = Self;
157
158            const IS_READ_ONLY: bool = true;
159            const IS_ARCHETYPAL: bool = false;
160
161            type Item<'__w, '__s> = #my_crate::ReadTraits<'__w, #trait_object>;
162
163            #[inline]
164            fn shrink<'wlong: 'wshort, 'wshort, 's>(
165                item: Self::Item<'wlong, 's>,
166            ) -> Self::Item<'wshort, 's> {
167                item
168            }
169
170            #[inline]
171            unsafe fn fetch<'w, 's>(
172                state: &'s Self::State,
173                fetch: &mut Self::Fetch<'w>,
174                entity: #imports::Entity,
175                table_row: #imports::TableRow,
176            ) -> ::core::option::Option<Self::Item<'w, 's>> {
177                <#my_crate::All<&#trait_object> as #imports::QueryData>::fetch(
178                    state,
179                    fetch,
180                    entity,
181                    table_row,
182                )
183            }
184
185            fn iter_access(
186                _state: &Self::State,
187            ) -> impl ::core::iter::Iterator<Item = #imports::EcsAccessType<'_>> {
188                ::core::iter::empty()
189            }
190        }
191        unsafe impl #impl_generics #imports::ReadOnlyQueryData for &#trait_object
192        #where_clause
193        {}
194
195        unsafe impl #impl_generics_with_lifetime #imports::WorldQuery for &'__a #trait_object
196        #where_clause
197        {
198            type Fetch<'__w> = <#my_crate::All<&'__a #trait_object> as #imports::WorldQuery>::Fetch<'__w>;
199            type State = #my_crate::TraitQueryState<#trait_object>;
200
201            #[inline]
202            unsafe fn init_fetch<'w>(
203                world: #imports::UnsafeWorldCell<'w>,
204                state: &Self::State,
205                last_run: #imports::Tick,
206                this_run: #imports::Tick,
207            ) -> Self::Fetch<'w> {
208                <#my_crate::All<&#trait_object> as #imports::WorldQuery>::init_fetch(
209                    world,
210                    state,
211                    last_run,
212                    this_run,
213                )
214            }
215
216            const IS_DENSE: bool = <#my_crate::All<&#trait_object> as #imports::WorldQuery>::IS_DENSE;
217
218            #[inline]
219            unsafe fn set_archetype<'w>(
220                fetch: &mut Self::Fetch<'w>,
221                state: &Self::State,
222                archetype: &'w #imports::Archetype,
223                tables: &'w #imports::Table,
224            ) {
225                <#my_crate::All<&#trait_object> as #imports::WorldQuery>::set_archetype(
226                    fetch, state, archetype, tables,
227                );
228            }
229
230            #[inline]
231            unsafe fn set_table<'w>(
232                fetch: &mut Self::Fetch<'w>,
233                state: &Self::State,
234                table: &'w #imports::Table,
235            ) {
236                <#my_crate::All<&#trait_object> as #imports::WorldQuery>::set_table(fetch, state, table);
237            }
238
239            #[inline]
240            fn update_component_access(
241                state: &Self::State,
242                access: &mut #imports::FilteredAccess,
243            ) {
244                <#my_crate::All<&#trait_object> as #imports::WorldQuery>::update_component_access(
245                    state, access,
246                );
247            }
248
249            #[inline]
250            fn init_state(world: &mut #imports::World) -> Self::State {
251                <#my_crate::All<&#trait_object> as #imports::WorldQuery>::init_state(world)
252            }
253
254            #[inline]
255            fn get_state(_: &#imports::Components) -> ::core::option::Option<Self::State> {
256                // TODO: fix this https://github.com/bevyengine/bevy/issues/13798
257                ::core::panic!("transmuting and any other operations concerning the state of a query are currently broken and shouldn't be used. See https://github.com/JoJoJet/bevy-trait-query/issues/59");
258            }
259
260            #[inline]
261            fn matches_component_set(
262                state: &Self::State,
263                set_contains_id: &impl ::core::ops::Fn(#imports::ComponentId) -> bool,
264            ) -> bool {
265                <#my_crate::All<&#trait_object> as #imports::WorldQuery>::matches_component_set(state, set_contains_id)
266            }
267
268            #[inline]
269            fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
270                fetch
271            }
272        }
273
274        unsafe impl #impl_generics_with_lifetime #imports::SingleEntityQueryData for &'__a mut #trait_object {}
275        unsafe impl #impl_generics_with_lifetime #imports::IterQueryData for &'__a mut #trait_object {}
276        unsafe impl #impl_generics_with_lifetime #imports::QueryData for &'__a mut #trait_object
277        #where_clause
278        {
279            type ReadOnly = &'__a #trait_object;
280
281            type Item<'__w, '__s> = #my_crate::WriteTraits<'__w, #trait_object>;
282
283            const IS_READ_ONLY: bool = false;
284            const IS_ARCHETYPAL: bool = false;
285
286            #[inline]
287            fn shrink<'wlong: 'wshort, 'wshort, 's>(
288                item: Self::Item<'wlong, 's>,
289            ) -> Self::Item<'wshort, 's> {
290                item
291            }
292
293            #[inline]
294            unsafe fn fetch<'w, 's>(
295                state: &'s Self::State,
296                fetch: &mut Self::Fetch<'w>,
297                entity: #imports::Entity,
298                table_row: #imports::TableRow,
299            ) -> ::core::option::Option<Self::Item<'w, 's>> {
300                <#my_crate::All<&mut #trait_object> as #imports::QueryData>::fetch(
301                    state,
302                    fetch,
303                    entity,
304                    table_row,
305                )
306            }
307
308            fn iter_access(
309                _state: &Self::State,
310            ) -> impl ::core::iter::Iterator<Item = #imports::EcsAccessType<'_>> {
311                ::core::iter::empty()
312            }
313        }
314
315        unsafe impl #impl_generics_with_lifetime #imports::WorldQuery for &'__a mut #trait_object
316        #where_clause
317        {
318            type Fetch<'__w> = <#my_crate::All<&'__a #trait_object> as #imports::WorldQuery>::Fetch<'__w>;
319            type State = #my_crate::TraitQueryState<#trait_object>;
320
321            #[inline]
322            unsafe fn init_fetch<'w>(
323                world: #imports::UnsafeWorldCell<'w>,
324                state: &Self::State,
325                last_run: #imports::Tick,
326                this_run: #imports::Tick,
327            ) -> Self::Fetch<'w> {
328                <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::init_fetch(
329                    world,
330                    state,
331                    last_run,
332                    this_run,
333                )
334            }
335
336            const IS_DENSE: bool = <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::IS_DENSE;
337
338            #[inline]
339            unsafe fn set_archetype<'w>(
340                fetch: &mut Self::Fetch<'w>,
341                state: &Self::State,
342                archetype: &'w #imports::Archetype,
343                table: &'w #imports::Table,
344            ) {
345                <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::set_archetype(
346                    fetch, state, archetype, table,
347                );
348            }
349
350            #[inline]
351            unsafe fn set_table<'w>(
352                fetch: &mut Self::Fetch<'w>,
353                state: &Self::State,
354                table: &'w #imports::Table,
355            ) {
356                <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::set_table(fetch, state, table);
357            }
358
359            #[inline]
360            fn update_component_access(
361                state: &Self::State,
362                access: &mut #imports::FilteredAccess,
363            ) {
364                <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::update_component_access(
365                    state, access,
366                );
367            }
368
369            #[inline]
370            fn init_state(world: &mut #imports::World) -> Self::State {
371                <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::init_state(world)
372            }
373
374            #[inline]
375            fn get_state(_: &#imports::Components) -> ::core::option::Option<Self::State> {
376                // TODO: fix this https://github.com/bevyengine/bevy/issues/13798
377                ::core::panic!("transmuting and any other operations concerning the state of a query are currently broken and shouldn't be used. See https://github.com/JoJoJet/bevy-trait-query/issues/59");
378            }
379
380            #[inline]
381            fn matches_component_set(
382                state: &Self::State,
383                set_contains_id: &impl Fn(#imports::ComponentId) -> bool,
384            ) -> bool {
385                <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::matches_component_set(state, set_contains_id)
386            }
387
388            #[inline]
389            fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
390                fetch
391            }
392        }
393    };
394
395    Ok(quote! {
396        #trait_definition
397
398        #marker_impl_code
399
400        #trait_object_query_code
401    })
402}