use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{parse_quote, ItemTrait, Result, TraitItem};
#[proc_macro_attribute]
pub fn queryable(attr: TokenStream, item: TokenStream) -> TokenStream {
impl_trait_query(attr, item)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
fn impl_trait_query(arg: TokenStream, item: TokenStream) -> Result<TokenStream2> {
syn::custom_keyword!(no_bounds);
let no_bounds: Option<no_bounds> = syn::parse(arg).map_err(|e| {
syn::Error::new(
e.span(),
"Valid forms are: `#[queryable]` and `#[queryable(no_bounds)]`",
)
})?;
let mut trait_definition = syn::parse::<ItemTrait>(item)?;
let trait_name = trait_definition.ident.clone();
if !no_bounds.is_some() {
trait_definition.supertraits.push(parse_quote!('static));
for param in &mut trait_definition.generics.params {
if let syn::GenericParam::Type(param) = param {
param.bounds.push(parse_quote!('static));
}
}
for item in &mut trait_definition.items {
if let TraitItem::Type(assoc) = item {
assoc.bounds.push(parse_quote!('static));
}
}
}
let mut impl_generics_list = vec![];
let mut trait_generics_list = vec![];
let where_clause = trait_definition.generics.where_clause.clone();
for param in &trait_definition.generics.params {
impl_generics_list.push(param.clone());
match param {
syn::GenericParam::Type(param) => {
let ident = ¶m.ident;
trait_generics_list.push(quote! { #ident });
}
syn::GenericParam::Lifetime(param) => {
let ident = ¶m.lifetime;
trait_generics_list.push(quote! { #ident });
}
syn::GenericParam::Const(param) => {
let ident = ¶m.ident;
trait_generics_list.push(quote! { #ident });
}
}
}
for item in &trait_definition.items {
if let TraitItem::Type(assoc) = item {
if !assoc.generics.params.is_empty() {
return Err(syn::Error::new(
assoc.ident.span(),
"Generic associated types are not supported in trait queries",
));
}
let ident = &assoc.ident;
let lower_ident = format_ident!("__{ident}");
let bound = &assoc.bounds;
impl_generics_list.push(parse_quote! { #lower_ident: #bound });
trait_generics_list.push(quote! { #ident = #lower_ident });
}
}
let impl_generics = quote! { <#( #impl_generics_list ,)*> };
let trait_generics = quote! { <#( #trait_generics_list ,)*> };
let trait_object = quote! { dyn #trait_name #trait_generics };
let my_crate = proc_macro_crate::crate_name("bevy-trait-query").unwrap();
let my_crate = match my_crate {
proc_macro_crate::FoundCrate::Itself => quote! { bevy_trait_query },
proc_macro_crate::FoundCrate::Name(x) => {
let ident = quote::format_ident!("{x}");
quote! { #ident }
}
};
let imports = quote! { #my_crate::imports };
let trait_query = quote! { #my_crate::TraitQuery };
let mut marker_impl_generics_list = impl_generics_list.clone();
marker_impl_generics_list
.push(parse_quote!(__Component: #trait_name #trait_generics + #imports::Component));
let marker_impl_generics = quote! { <#( #marker_impl_generics_list ,)*> };
let marker_impl_code = quote! {
impl #impl_generics #trait_query for #trait_object #where_clause {}
impl #marker_impl_generics #my_crate::TraitQueryMarker::<#trait_object> for (__Component,)
#where_clause
{
type Covered = __Component;
fn cast(ptr: *mut u8) -> *mut #trait_object {
ptr as *mut __Component as *mut _
}
}
};
let mut impl_generics_with_lifetime = impl_generics_list.clone();
impl_generics_with_lifetime.insert(0, parse_quote!('__a));
let impl_generics_with_lifetime = quote! { <#( #impl_generics_with_lifetime ,)*> };
let trait_object_query_code = quote! {
unsafe impl #impl_generics #imports::QueryData for &#trait_object
#where_clause
{
type ReadOnly = Self;
}
unsafe impl #impl_generics #imports::ReadOnlyQueryData for &#trait_object
#where_clause
{}
unsafe impl #impl_generics_with_lifetime #imports::WorldQuery for &'__a #trait_object
#where_clause
{
type Item<'__w> = #my_crate::ReadTraits<'__w, #trait_object>;
type Fetch<'__w> = <#my_crate::All<&'__a #trait_object> as #imports::WorldQuery>::Fetch<'__w>;
type State = #my_crate::TraitQueryState<#trait_object>;
#[inline]
unsafe fn init_fetch<'w>(
world: #imports::UnsafeWorldCell<'w>,
state: &Self::State,
last_run: #imports::Tick,
this_run: #imports::Tick,
) -> Self::Fetch<'w> {
<#my_crate::All<&#trait_object> as #imports::WorldQuery>::init_fetch(
world,
state,
last_run,
this_run,
)
}
#[inline]
fn shrink<'wlong: 'wshort, 'wshort>(
item: Self::Item<'wlong>,
) -> Self::Item<'wshort> {
item
}
const IS_DENSE: bool = <#my_crate::All<&#trait_object> as #imports::WorldQuery>::IS_DENSE;
#[inline]
unsafe fn set_archetype<'w>(
fetch: &mut Self::Fetch<'w>,
state: &Self::State,
archetype: &'w #imports::Archetype,
tables: &'w #imports::Table,
) {
<#my_crate::All<&#trait_object> as #imports::WorldQuery>::set_archetype(
fetch, state, archetype, tables,
);
}
#[inline]
unsafe fn set_table<'w>(
fetch: &mut Self::Fetch<'w>,
state: &Self::State,
table: &'w #imports::Table,
) {
<#my_crate::All<&#trait_object> as #imports::WorldQuery>::set_table(fetch, state, table);
}
#[inline]
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: #imports::Entity,
table_row: #imports::TableRow,
) -> Self::Item<'w> {
<#my_crate::All<&#trait_object> as #imports::WorldQuery>::fetch(
fetch,
entity,
table_row,
)
}
#[inline]
fn update_component_access(
state: &Self::State,
access: &mut #imports::FilteredAccess<#imports::ComponentId>,
) {
<#my_crate::All<&#trait_object> as #imports::WorldQuery>::update_component_access(
state, access,
);
}
#[inline]
fn init_state(world: &mut #imports::World) -> Self::State {
<#my_crate::All<&#trait_object> as #imports::WorldQuery>::init_state(world)
}
#[inline]
fn get_state(world: &#imports::World) -> Option<Self::State> {
<#my_crate::All<&#trait_object> as #imports::WorldQuery>::get_state(world)
}
#[inline]
fn matches_component_set(
state: &Self::State,
set_contains_id: &impl Fn(#imports::ComponentId) -> bool,
) -> bool {
<#my_crate::All<&#trait_object> as #imports::WorldQuery>::matches_component_set(state, set_contains_id)
}
}
unsafe impl #impl_generics_with_lifetime #imports::QueryData for &'__a mut #trait_object
#where_clause
{
type ReadOnly = &'__a #trait_object;
}
unsafe impl #impl_generics_with_lifetime #imports::WorldQuery for &'__a mut #trait_object
#where_clause
{
type Item<'__w> = #my_crate::WriteTraits<'__w, #trait_object>;
type Fetch<'__w> = <#my_crate::All<&'__a #trait_object> as #imports::WorldQuery>::Fetch<'__w>;
type State = #my_crate::TraitQueryState<#trait_object>;
#[inline]
unsafe fn init_fetch<'w>(
world: #imports::UnsafeWorldCell<'w>,
state: &Self::State,
last_run: #imports::Tick,
this_run: #imports::Tick,
) -> Self::Fetch<'w> {
<#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::init_fetch(
world,
state,
last_run,
this_run,
)
}
#[inline]
fn shrink<'wlong: 'wshort, 'wshort>(
item: Self::Item<'wlong>,
) -> Self::Item<'wshort> {
item
}
const IS_DENSE: bool = <#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::IS_DENSE;
#[inline]
unsafe fn set_archetype<'w>(
fetch: &mut Self::Fetch<'w>,
state: &Self::State,
archetype: &'w #imports::Archetype,
table: &'w #imports::Table,
) {
<#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::set_archetype(
fetch, state, archetype, table,
);
}
#[inline]
unsafe fn set_table<'w>(
fetch: &mut Self::Fetch<'w>,
state: &Self::State,
table: &'w #imports::Table,
) {
<#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::set_table(fetch, state, table);
}
#[inline]
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: #imports::Entity,
table_row: #imports::TableRow,
) -> Self::Item<'w> {
<#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::fetch(
fetch,
entity,
table_row,
)
}
#[inline]
fn update_component_access(
state: &Self::State,
access: &mut #imports::FilteredAccess<#imports::ComponentId>,
) {
<#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::update_component_access(
state, access,
);
}
#[inline]
fn init_state(world: &mut #imports::World) -> Self::State {
<#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::init_state(world)
}
#[inline]
fn get_state(world: &#imports::World) -> Option<Self::State> {
<#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::get_state(world)
}
#[inline]
fn matches_component_set(
state: &Self::State,
set_contains_id: &impl Fn(#imports::ComponentId) -> bool,
) -> bool {
<#my_crate::All<&mut #trait_object> as #imports::WorldQuery>::matches_component_set(state, set_contains_id)
}
}
};
Ok(quote! {
#trait_definition
#marker_impl_code
#trait_object_query_code
})
}