1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::{format_ident, quote};
4use syn::{ItemTrait, Result, TraitItem, parse_quote};
5
6#[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 if no_bounds.is_none() {
54 trait_definition.supertraits.push(parse_quote!('static));
55
56 for param in &mut trait_definition.generics.params {
57 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 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 = ¶m.ident;
80 trait_generics_list.push(quote! { #ident });
81 }
82 syn::GenericParam::Lifetime(param) => {
83 let ident = ¶m.lifetime;
84 trait_generics_list.push(quote! { #ident });
85 }
86 syn::GenericParam::Const(param) => {
87 let ident = ¶m.ident;
88 trait_generics_list.push(quote! { #ident });
89 }
90 }
91 }
92
93 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 ::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 ::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}