update syn, encase, glam and hexasphere (#8573)
# Objective - Fixes #8282 - Update `syn` to 2.0, `encase` to 0.6, `glam` to 0.24 and `hexasphere` to 9.0 Blocked ~~on https://github.com/teoxoy/encase/pull/42~~ and ~~on https://github.com/OptimisticPeach/hexasphere/pull/17~~ --------- Co-authored-by: Nicola Papale <nicopap@users.noreply.github.com> Co-authored-by: JoJoJet <21144246+JoJoJet@users.noreply.github.com>
This commit is contained in:
parent
6b0986a6e8
commit
0736195a1e
@ -7,7 +7,7 @@ publish = false
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
glam = "0.23"
|
glam = "0.24"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rand_chacha = "0.3"
|
rand_chacha = "0.3"
|
||||||
criterion = { version = "0.3", features = ["html_reports"] }
|
criterion = { version = "0.3", features = ["html_reports"] }
|
||||||
|
|||||||
@ -15,4 +15,4 @@ proc-macro = true
|
|||||||
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.11.0-dev" }
|
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.11.0-dev" }
|
||||||
|
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = { version = "1.0", features = ["full"] }
|
syn = { version = "2.0", features = ["full"] }
|
||||||
|
|||||||
@ -11,6 +11,6 @@ proc-macro = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.11.0-dev" }
|
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.11.0-dev" }
|
||||||
|
|
||||||
syn = "1.0"
|
syn = "2.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
use bevy_macro_utils::{get_lit_str, Symbol};
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
use quote::{quote, ToTokens};
|
use quote::quote;
|
||||||
use syn::{parse_macro_input, parse_quote, DeriveInput, Error, Ident, Path, Result};
|
use syn::{parse_macro_input, parse_quote, DeriveInput, Ident, LitStr, Path, Result};
|
||||||
|
|
||||||
pub fn derive_resource(input: TokenStream) -> TokenStream {
|
pub fn derive_resource(input: TokenStream) -> TokenStream {
|
||||||
let mut ast = parse_macro_input!(input as DeriveInput);
|
let mut ast = parse_macro_input!(input as DeriveInput);
|
||||||
@ -48,8 +47,8 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const COMPONENT: Symbol = Symbol("component");
|
pub const COMPONENT: &str = "component";
|
||||||
pub const STORAGE: Symbol = Symbol("storage");
|
pub const STORAGE: &str = "storage";
|
||||||
|
|
||||||
struct Attrs {
|
struct Attrs {
|
||||||
storage: StorageTy,
|
storage: StorageTy,
|
||||||
@ -66,48 +65,27 @@ const TABLE: &str = "Table";
|
|||||||
const SPARSE_SET: &str = "SparseSet";
|
const SPARSE_SET: &str = "SparseSet";
|
||||||
|
|
||||||
fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
|
fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
|
||||||
let meta_items = bevy_macro_utils::parse_attrs(ast, COMPONENT)?;
|
|
||||||
|
|
||||||
let mut attrs = Attrs {
|
let mut attrs = Attrs {
|
||||||
storage: StorageTy::Table,
|
storage: StorageTy::Table,
|
||||||
};
|
};
|
||||||
|
|
||||||
for meta in meta_items {
|
for meta in ast.attrs.iter().filter(|a| a.path().is_ident(COMPONENT)) {
|
||||||
use syn::{
|
meta.parse_nested_meta(|nested| {
|
||||||
Meta::NameValue,
|
if nested.path.is_ident(STORAGE) {
|
||||||
NestedMeta::{Lit, Meta},
|
attrs.storage = match nested.value()?.parse::<LitStr>()?.value() {
|
||||||
};
|
s if s == TABLE => StorageTy::Table,
|
||||||
match meta {
|
s if s == SPARSE_SET => StorageTy::SparseSet,
|
||||||
Meta(NameValue(m)) if m.path == STORAGE => {
|
|
||||||
attrs.storage = match get_lit_str(STORAGE, &m.lit)?.value().as_str() {
|
|
||||||
TABLE => StorageTy::Table,
|
|
||||||
SPARSE_SET => StorageTy::SparseSet,
|
|
||||||
s => {
|
s => {
|
||||||
return Err(Error::new_spanned(
|
return Err(nested.error(format!(
|
||||||
m.lit,
|
"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",
|
||||||
format!(
|
)));
|
||||||
"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'.",
|
|
||||||
),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(nested.error("Unsuported attribute"))
|
||||||
}
|
}
|
||||||
Meta(meta_item) => {
|
})?;
|
||||||
return Err(Error::new_spanned(
|
|
||||||
meta_item.path(),
|
|
||||||
format!(
|
|
||||||
"unknown component attribute `{}`",
|
|
||||||
meta_item.path().into_token_stream()
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Lit(lit) => {
|
|
||||||
return Err(Error::new_spanned(
|
|
||||||
lit,
|
|
||||||
"unexpected literal in component attribute",
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(attrs)
|
Ok(attrs)
|
||||||
|
|||||||
@ -6,7 +6,8 @@ use syn::{
|
|||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
parse_macro_input, parse_quote,
|
parse_macro_input, parse_quote,
|
||||||
punctuated::Punctuated,
|
punctuated::Punctuated,
|
||||||
Attribute, Data, DataStruct, DeriveInput, Field, Index,
|
token::Comma,
|
||||||
|
Attribute, Data, DataStruct, DeriveInput, Field, Index, Meta,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::bevy_ecs_path;
|
use crate::bevy_ecs_path;
|
||||||
@ -14,7 +15,7 @@ use crate::bevy_ecs_path;
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct FetchStructAttributes {
|
struct FetchStructAttributes {
|
||||||
pub is_mutable: bool,
|
pub is_mutable: bool,
|
||||||
pub derive_args: Punctuated<syn::NestedMeta, syn::token::Comma>,
|
pub derive_args: Punctuated<syn::Meta, syn::token::Comma>,
|
||||||
}
|
}
|
||||||
|
|
||||||
static MUTABLE_ATTRIBUTE_NAME: &str = "mutable";
|
static MUTABLE_ATTRIBUTE_NAME: &str = "mutable";
|
||||||
@ -35,7 +36,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
|
|||||||
let mut fetch_struct_attributes = FetchStructAttributes::default();
|
let mut fetch_struct_attributes = FetchStructAttributes::default();
|
||||||
for attr in &ast.attrs {
|
for attr in &ast.attrs {
|
||||||
if !attr
|
if !attr
|
||||||
.path
|
.path()
|
||||||
.get_ident()
|
.get_ident()
|
||||||
.map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME)
|
.map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME)
|
||||||
{
|
{
|
||||||
@ -43,7 +44,7 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attr.parse_args_with(|input: ParseStream| {
|
attr.parse_args_with(|input: ParseStream| {
|
||||||
let meta = input.parse_terminated::<syn::Meta, syn::token::Comma>(syn::Meta::parse)?;
|
let meta = input.parse_terminated(syn::Meta::parse, Comma)?;
|
||||||
for meta in meta {
|
for meta in meta {
|
||||||
let ident = meta.path().get_ident().unwrap_or_else(|| {
|
let ident = meta.path().get_ident().unwrap_or_else(|| {
|
||||||
panic!(
|
panic!(
|
||||||
@ -61,9 +62,10 @@ pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
} else if ident == DERIVE_ATTRIBUTE_NAME {
|
} else if ident == DERIVE_ATTRIBUTE_NAME {
|
||||||
if let syn::Meta::List(meta_list) = meta {
|
if let syn::Meta::List(meta_list) = meta {
|
||||||
fetch_struct_attributes
|
meta_list.parse_nested_meta(|meta| {
|
||||||
.derive_args
|
fetch_struct_attributes.derive_args.push(Meta::Path(meta.path));
|
||||||
.extend(meta_list.nested.iter().cloned());
|
Ok(())
|
||||||
|
})?;
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
panic!(
|
||||||
"Expected a structured list within the `{DERIVE_ATTRIBUTE_NAME}` attribute",
|
"Expected a structured list within the `{DERIVE_ATTRIBUTE_NAME}` attribute",
|
||||||
@ -463,7 +465,7 @@ fn read_world_query_field_info(field: &Field) -> syn::Result<WorldQueryFieldInfo
|
|||||||
let mut attrs = Vec::new();
|
let mut attrs = Vec::new();
|
||||||
for attr in &field.attrs {
|
for attr in &field.attrs {
|
||||||
if attr
|
if attr
|
||||||
.path
|
.path()
|
||||||
.get_ident()
|
.get_ident()
|
||||||
.map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME)
|
.map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -13,8 +13,8 @@ use proc_macro::TokenStream;
|
|||||||
use proc_macro2::Span;
|
use proc_macro2::Span;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use syn::{
|
use syn::{
|
||||||
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, ConstParam,
|
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma,
|
||||||
DeriveInput, GenericParam, Ident, Index, Meta, MetaList, NestedMeta, Token, TypeParam,
|
ConstParam, DeriveInput, GenericParam, Ident, Index, TypeParam,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum BundleFieldKind {
|
enum BundleFieldKind {
|
||||||
@ -37,28 +37,23 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
|||||||
|
|
||||||
let mut field_kind = Vec::with_capacity(named_fields.len());
|
let mut field_kind = Vec::with_capacity(named_fields.len());
|
||||||
|
|
||||||
'field_loop: for field in named_fields.iter() {
|
for field in named_fields.iter() {
|
||||||
for attr in &field.attrs {
|
for attr in field
|
||||||
if attr.path.is_ident(BUNDLE_ATTRIBUTE_NAME) {
|
.attrs
|
||||||
if let Ok(Meta::List(MetaList { nested, .. })) = attr.parse_meta() {
|
.iter()
|
||||||
if let Some(&NestedMeta::Meta(Meta::Path(ref path))) = nested.first() {
|
.filter(|a| a.path().is_ident(BUNDLE_ATTRIBUTE_NAME))
|
||||||
if path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) {
|
{
|
||||||
field_kind.push(BundleFieldKind::Ignore);
|
if let Err(error) = attr.parse_nested_meta(|meta| {
|
||||||
continue 'field_loop;
|
if meta.path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) {
|
||||||
}
|
field_kind.push(BundleFieldKind::Ignore);
|
||||||
|
Ok(())
|
||||||
return syn::Error::new(
|
} else {
|
||||||
path.span(),
|
Err(meta.error(format!(
|
||||||
format!(
|
"Invalid bundle attribute. Use `{BUNDLE_ATTRIBUTE_IGNORE_NAME}`"
|
||||||
"Invalid bundle attribute. Use `{BUNDLE_ATTRIBUTE_IGNORE_NAME}`"
|
)))
|
||||||
),
|
|
||||||
)
|
|
||||||
.into_compile_error()
|
|
||||||
.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
return syn::Error::new(attr.span(), format!("Invalid bundle attribute. Use `#[{BUNDLE_ATTRIBUTE_NAME}({BUNDLE_ATTRIBUTE_IGNORE_NAME})]`")).into_compile_error().into();
|
|
||||||
}
|
}
|
||||||
|
}) {
|
||||||
|
return error.into_compile_error().into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +303,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
|||||||
|
|
||||||
let shadowed_lifetimes: Vec<_> = generics.lifetimes().map(|_| quote!('_)).collect();
|
let shadowed_lifetimes: Vec<_> = generics.lifetimes().map(|_| quote!('_)).collect();
|
||||||
|
|
||||||
let mut punctuated_generics = Punctuated::<_, Token![,]>::new();
|
let mut punctuated_generics = Punctuated::<_, Comma>::new();
|
||||||
punctuated_generics.extend(lifetimeless_generics.iter().map(|g| match g {
|
punctuated_generics.extend(lifetimeless_generics.iter().map(|g| match g {
|
||||||
GenericParam::Type(g) => GenericParam::Type(TypeParam {
|
GenericParam::Type(g) => GenericParam::Type(TypeParam {
|
||||||
default: None,
|
default: None,
|
||||||
@ -321,14 +316,14 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let mut punctuated_generic_idents = Punctuated::<_, Token![,]>::new();
|
let mut punctuated_generic_idents = Punctuated::<_, Comma>::new();
|
||||||
punctuated_generic_idents.extend(lifetimeless_generics.iter().map(|g| match g {
|
punctuated_generic_idents.extend(lifetimeless_generics.iter().map(|g| match g {
|
||||||
GenericParam::Type(g) => &g.ident,
|
GenericParam::Type(g) => &g.ident,
|
||||||
GenericParam::Const(g) => &g.ident,
|
GenericParam::Const(g) => &g.ident,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let punctuated_generics_no_bounds: Punctuated<_, Token![,]> = lifetimeless_generics
|
let punctuated_generics_no_bounds: Punctuated<_, Comma> = lifetimeless_generics
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&g| match g.clone() {
|
.map(|&g| match g.clone() {
|
||||||
GenericParam::Type(mut g) => {
|
GenericParam::Type(mut g) => {
|
||||||
|
|||||||
@ -13,4 +13,4 @@ proc-macro = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.11.0-dev" }
|
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.11.0-dev" }
|
||||||
encase_derive_impl = "0.5.0"
|
encase_derive_impl = "0.6.1"
|
||||||
|
|||||||
@ -10,6 +10,6 @@ keywords = ["bevy"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
toml_edit = "0.19"
|
toml_edit = "0.19"
|
||||||
syn = "1.0"
|
syn = "2.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
rustc-hash = "1.0"
|
rustc-hash = "1.0"
|
||||||
|
|||||||
@ -1,41 +1,32 @@
|
|||||||
use syn::DeriveInput;
|
use syn::{Expr, ExprLit, Lit};
|
||||||
|
|
||||||
use crate::symbol::Symbol;
|
use crate::symbol::Symbol;
|
||||||
|
|
||||||
pub fn parse_attrs(ast: &DeriveInput, attr_name: Symbol) -> syn::Result<Vec<syn::NestedMeta>> {
|
pub fn get_lit_str(attr_name: Symbol, value: &Expr) -> syn::Result<&syn::LitStr> {
|
||||||
let mut list = Vec::new();
|
if let Expr::Lit(ExprLit {
|
||||||
for attr in ast.attrs.iter().filter(|a| a.path == attr_name) {
|
lit: Lit::Str(lit), ..
|
||||||
match attr.parse_meta()? {
|
}) = &value
|
||||||
syn::Meta::List(meta) => list.extend(meta.nested.into_iter()),
|
{
|
||||||
other => {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
other,
|
|
||||||
format!("expected #[{attr_name}(...)]"),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(list)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_lit_str(attr_name: Symbol, lit: &syn::Lit) -> syn::Result<&syn::LitStr> {
|
|
||||||
if let syn::Lit::Str(lit) = lit {
|
|
||||||
Ok(lit)
|
Ok(lit)
|
||||||
} else {
|
} else {
|
||||||
Err(syn::Error::new_spanned(
|
Err(syn::Error::new_spanned(
|
||||||
lit,
|
value,
|
||||||
format!("expected {attr_name} attribute to be a string: `{attr_name} = \"...\"`"),
|
format!("expected {attr_name} attribute to be a string: `{attr_name} = \"...\"`"),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_lit_bool(attr_name: Symbol, lit: &syn::Lit) -> syn::Result<bool> {
|
pub fn get_lit_bool(attr_name: Symbol, value: &Expr) -> syn::Result<bool> {
|
||||||
if let syn::Lit::Bool(lit) = lit {
|
if let Expr::Lit(ExprLit {
|
||||||
|
lit: Lit::Bool(lit),
|
||||||
|
..
|
||||||
|
}) = &value
|
||||||
|
{
|
||||||
Ok(lit.value())
|
Ok(lit.value())
|
||||||
} else {
|
} else {
|
||||||
Err(syn::Error::new_spanned(
|
Err(syn::Error::new_spanned(
|
||||||
lit,
|
value,
|
||||||
format!("expected {attr_name} attribute to be a bool value, `true` or `false`: `{attr_name} = ...`"),
|
format!("expected {attr_name} attribute to be a bool value, `true` or `false`: `{attr_name} = ...`"),
|
||||||
))
|
))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -214,7 +214,7 @@ pub fn derive_label(
|
|||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
// return true if the variant specified is an `ignore_fields` attribute
|
// return true if the variant specified is an `ignore_fields` attribute
|
||||||
fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool {
|
fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool {
|
||||||
if attr.path.get_ident().as_ref().unwrap() != &attr_name {
|
if attr.path().get_ident().as_ref().unwrap() != &attr_name {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
|
|||||||
keywords = ["bevy"]
|
keywords = ["bevy"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glam = { version = "0.23", features = ["bytemuck"] }
|
glam = { version = "0.24", features = ["bytemuck"] }
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@ -11,7 +11,7 @@ license = "Zlib AND (MIT OR Apache-2.0)"
|
|||||||
keywords = ["bevy", "3D", "graphics", "algorithm", "tangent"]
|
keywords = ["bevy", "3D", "graphics", "algorithm", "tangent"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glam = "0.23"
|
glam = "0.24"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "generate"
|
name = "generate"
|
||||||
|
|||||||
@ -31,7 +31,7 @@ thiserror = "1.0"
|
|||||||
once_cell = "1.11"
|
once_cell = "1.11"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
smallvec = { version = "1.6", features = ["serde", "union", "const_generics"], optional = true }
|
smallvec = { version = "1.6", features = ["serde", "union", "const_generics"], optional = true }
|
||||||
glam = { version = "0.23", features = ["serde"], optional = true }
|
glam = { version = "0.24", features = ["serde"], optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ron = "0.8.0"
|
ron = "0.8.0"
|
||||||
|
|||||||
@ -19,7 +19,7 @@ documentation = []
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.11.0-dev" }
|
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.11.0-dev" }
|
||||||
|
|
||||||
syn = { version = "1.0", features = ["full"] }
|
syn = { version = "2.0", features = ["full"] }
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
uuid = { version = "1.1", features = ["v4"] }
|
uuid = { version = "1.1", features = ["v4"] }
|
||||||
|
|||||||
@ -13,7 +13,7 @@ use syn::parse::{Parse, ParseStream};
|
|||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::token::Comma;
|
use syn::token::Comma;
|
||||||
use syn::{Meta, NestedMeta, Path};
|
use syn::{Meta, Path};
|
||||||
|
|
||||||
// The "special" trait idents that are used internally for reflection.
|
// The "special" trait idents that are used internally for reflection.
|
||||||
// Received via attributes like `#[reflect(PartialEq, Hash, ...)]`
|
// Received via attributes like `#[reflect(PartialEq, Hash, ...)]`
|
||||||
@ -45,12 +45,16 @@ pub(crate) enum TraitImpl {
|
|||||||
impl TraitImpl {
|
impl TraitImpl {
|
||||||
/// Merges this [`TraitImpl`] with another.
|
/// Merges this [`TraitImpl`] with another.
|
||||||
///
|
///
|
||||||
/// Returns whichever value is not [`TraitImpl::NotImplemented`].
|
/// Update `self` with whichever value is not [`TraitImpl::NotImplemented`].
|
||||||
/// If both values are [`TraitImpl::NotImplemented`], then that is returned.
|
/// If `other` is [`TraitImpl::NotImplemented`], then `self` is not modified.
|
||||||
/// Otherwise, an error is returned if neither value is [`TraitImpl::NotImplemented`].
|
/// An error is returned if neither value is [`TraitImpl::NotImplemented`].
|
||||||
pub fn merge(self, other: TraitImpl) -> Result<TraitImpl, syn::Error> {
|
pub fn merge(&mut self, other: TraitImpl) -> Result<(), syn::Error> {
|
||||||
match (self, other) {
|
match (&self, other) {
|
||||||
(TraitImpl::NotImplemented, value) | (value, TraitImpl::NotImplemented) => Ok(value),
|
(TraitImpl::NotImplemented, value) => {
|
||||||
|
*self = value;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
(_, TraitImpl::NotImplemented) => Ok(()),
|
||||||
(_, TraitImpl::Implemented(span) | TraitImpl::Custom(_, span)) => {
|
(_, TraitImpl::Implemented(span) | TraitImpl::Custom(_, span)) => {
|
||||||
Err(syn::Error::new(span, CONFLICTING_TYPE_DATA_MESSAGE))
|
Err(syn::Error::new(span, CONFLICTING_TYPE_DATA_MESSAGE))
|
||||||
}
|
}
|
||||||
@ -128,15 +132,12 @@ pub(crate) struct ReflectTraits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ReflectTraits {
|
impl ReflectTraits {
|
||||||
/// Create a new [`ReflectTraits`] instance from a set of nested metas.
|
pub fn from_metas(metas: Punctuated<Meta, Comma>) -> Result<Self, syn::Error> {
|
||||||
pub fn from_nested_metas(
|
|
||||||
nested_metas: &Punctuated<NestedMeta, Comma>,
|
|
||||||
) -> Result<Self, syn::Error> {
|
|
||||||
let mut traits = ReflectTraits::default();
|
let mut traits = ReflectTraits::default();
|
||||||
for nested_meta in nested_metas.iter() {
|
for meta in &metas {
|
||||||
match nested_meta {
|
match meta {
|
||||||
// Handles `#[reflect( Hash, Default, ... )]`
|
// Handles `#[reflect( Hash, Default, ... )]`
|
||||||
NestedMeta::Meta(Meta::Path(path)) => {
|
Meta::Path(path) => {
|
||||||
// Get the first ident in the path (hopefully the path only contains one and not `std::hash::Hash`)
|
// Get the first ident in the path (hopefully the path only contains one and not `std::hash::Hash`)
|
||||||
let Some(segment) = path.segments.iter().next() else {
|
let Some(segment) = path.segments.iter().next() else {
|
||||||
continue;
|
continue;
|
||||||
@ -149,14 +150,13 @@ impl ReflectTraits {
|
|||||||
|
|
||||||
match ident_name.as_str() {
|
match ident_name.as_str() {
|
||||||
DEBUG_ATTR => {
|
DEBUG_ATTR => {
|
||||||
traits.debug = traits.debug.merge(TraitImpl::Implemented(span))?;
|
traits.debug.merge(TraitImpl::Implemented(span))?;
|
||||||
}
|
}
|
||||||
PARTIAL_EQ_ATTR => {
|
PARTIAL_EQ_ATTR => {
|
||||||
traits.partial_eq =
|
traits.partial_eq.merge(TraitImpl::Implemented(span))?;
|
||||||
traits.partial_eq.merge(TraitImpl::Implemented(span))?;
|
|
||||||
}
|
}
|
||||||
HASH_ATTR => {
|
HASH_ATTR => {
|
||||||
traits.hash = traits.hash.merge(TraitImpl::Implemented(span))?;
|
traits.hash.merge(TraitImpl::Implemented(span))?;
|
||||||
}
|
}
|
||||||
// We only track reflected idents for traits not considered special
|
// We only track reflected idents for traits not considered special
|
||||||
_ => {
|
_ => {
|
||||||
@ -170,7 +170,7 @@ impl ReflectTraits {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Handles `#[reflect( Hash(custom_hash_fn) )]`
|
// Handles `#[reflect( Hash(custom_hash_fn) )]`
|
||||||
NestedMeta::Meta(Meta::List(list)) => {
|
Meta::List(list) => {
|
||||||
// Get the first ident in the path (hopefully the path only contains one and not `std::hash::Hash`)
|
// Get the first ident in the path (hopefully the path only contains one and not `std::hash::Hash`)
|
||||||
let Some(segment) = list.path.segments.iter().next() else {
|
let Some(segment) = list.path.segments.iter().next() else {
|
||||||
continue;
|
continue;
|
||||||
@ -181,23 +181,25 @@ impl ReflectTraits {
|
|||||||
// Track the span where the trait is implemented for future errors
|
// Track the span where the trait is implemented for future errors
|
||||||
let span = ident.span();
|
let span = ident.span();
|
||||||
|
|
||||||
let list_meta = list.nested.iter().next();
|
list.parse_nested_meta(|meta| {
|
||||||
if let Some(NestedMeta::Meta(Meta::Path(path))) = list_meta {
|
|
||||||
// This should be the path of the custom function
|
// This should be the path of the custom function
|
||||||
let trait_func_ident = TraitImpl::Custom(path.clone(), span);
|
let trait_func_ident = TraitImpl::Custom(meta.path, span);
|
||||||
match ident.as_str() {
|
match ident.as_str() {
|
||||||
DEBUG_ATTR => {
|
DEBUG_ATTR => {
|
||||||
traits.debug = traits.debug.merge(trait_func_ident)?;
|
traits.debug.merge(trait_func_ident)?;
|
||||||
}
|
}
|
||||||
PARTIAL_EQ_ATTR => {
|
PARTIAL_EQ_ATTR => {
|
||||||
traits.partial_eq = traits.partial_eq.merge(trait_func_ident)?;
|
traits.partial_eq.merge(trait_func_ident)?;
|
||||||
}
|
}
|
||||||
HASH_ATTR => {
|
HASH_ATTR => {
|
||||||
traits.hash = traits.hash.merge(trait_func_ident)?;
|
traits.hash.merge(trait_func_ident)?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(syn::Error::new(span, "Can only use custom functions for special traits (i.e. `Hash`, `PartialEq`, `Debug`)"));
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -289,26 +291,20 @@ impl ReflectTraits {
|
|||||||
/// Merges the trait implementations of this [`ReflectTraits`] with another one.
|
/// Merges the trait implementations of this [`ReflectTraits`] with another one.
|
||||||
///
|
///
|
||||||
/// An error is returned if the two [`ReflectTraits`] have conflicting implementations.
|
/// An error is returned if the two [`ReflectTraits`] have conflicting implementations.
|
||||||
pub fn merge(self, other: ReflectTraits) -> Result<Self, syn::Error> {
|
pub fn merge(&mut self, other: ReflectTraits) -> Result<(), syn::Error> {
|
||||||
Ok(ReflectTraits {
|
self.debug.merge(other.debug)?;
|
||||||
debug: self.debug.merge(other.debug)?,
|
self.hash.merge(other.hash)?;
|
||||||
hash: self.hash.merge(other.hash)?,
|
self.partial_eq.merge(other.partial_eq)?;
|
||||||
partial_eq: self.partial_eq.merge(other.partial_eq)?,
|
for ident in other.idents {
|
||||||
idents: {
|
add_unique_ident(&mut self.idents, ident)?;
|
||||||
let mut idents = self.idents;
|
}
|
||||||
for ident in other.idents {
|
Ok(())
|
||||||
add_unique_ident(&mut idents, ident)?;
|
|
||||||
}
|
|
||||||
idents
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for ReflectTraits {
|
impl Parse for ReflectTraits {
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
let result = Punctuated::<NestedMeta, Comma>::parse_terminated(input)?;
|
ReflectTraits::from_metas(Punctuated::<Meta, Comma>::parse_terminated(input)?)
|
||||||
ReflectTraits::from_nested_metas(&result)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,11 +4,12 @@ use crate::fq_std::{FQAny, FQDefault, FQSend, FQSync};
|
|||||||
use crate::utility::{members_to_serialization_denylist, WhereClauseOptions};
|
use crate::utility::{members_to_serialization_denylist, WhereClauseOptions};
|
||||||
use bit_set::BitSet;
|
use bit_set::BitSet;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
use syn::token::Comma;
|
||||||
|
|
||||||
use crate::{utility, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME};
|
use crate::{utility, REFLECT_ATTRIBUTE_NAME, REFLECT_VALUE_ATTRIBUTE_NAME};
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::{Data, DeriveInput, Field, Fields, Generics, Ident, Meta, Path, Token, Variant};
|
use syn::{Data, DeriveInput, Field, Fields, Generics, Ident, Meta, Path, Variant};
|
||||||
|
|
||||||
pub(crate) enum ReflectDerive<'a> {
|
pub(crate) enum ReflectDerive<'a> {
|
||||||
Struct(ReflectStruct<'a>),
|
Struct(ReflectStruct<'a>),
|
||||||
@ -136,8 +137,8 @@ impl<'a> ReflectDerive<'a> {
|
|||||||
#[cfg(feature = "documentation")]
|
#[cfg(feature = "documentation")]
|
||||||
let mut doc = crate::documentation::Documentation::default();
|
let mut doc = crate::documentation::Documentation::default();
|
||||||
|
|
||||||
for attribute in input.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) {
|
for attribute in &input.attrs {
|
||||||
match attribute {
|
match &attribute.meta {
|
||||||
Meta::List(meta_list) if meta_list.path.is_ident(REFLECT_ATTRIBUTE_NAME) => {
|
Meta::List(meta_list) if meta_list.path.is_ident(REFLECT_ATTRIBUTE_NAME) => {
|
||||||
if !matches!(reflect_mode, None | Some(ReflectMode::Normal)) {
|
if !matches!(reflect_mode, None | Some(ReflectMode::Normal)) {
|
||||||
return Err(syn::Error::new(
|
return Err(syn::Error::new(
|
||||||
@ -147,8 +148,10 @@ impl<'a> ReflectDerive<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reflect_mode = Some(ReflectMode::Normal);
|
reflect_mode = Some(ReflectMode::Normal);
|
||||||
let new_traits = ReflectTraits::from_nested_metas(&meta_list.nested)?;
|
let new_traits = ReflectTraits::from_metas(
|
||||||
traits = traits.merge(new_traits)?;
|
meta_list.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)?,
|
||||||
|
)?;
|
||||||
|
traits.merge(new_traits)?;
|
||||||
}
|
}
|
||||||
Meta::List(meta_list) if meta_list.path.is_ident(REFLECT_VALUE_ATTRIBUTE_NAME) => {
|
Meta::List(meta_list) if meta_list.path.is_ident(REFLECT_VALUE_ATTRIBUTE_NAME) => {
|
||||||
if !matches!(reflect_mode, None | Some(ReflectMode::Value)) {
|
if !matches!(reflect_mode, None | Some(ReflectMode::Value)) {
|
||||||
@ -159,8 +162,10 @@ impl<'a> ReflectDerive<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reflect_mode = Some(ReflectMode::Value);
|
reflect_mode = Some(ReflectMode::Value);
|
||||||
let new_traits = ReflectTraits::from_nested_metas(&meta_list.nested)?;
|
let new_traits = ReflectTraits::from_metas(
|
||||||
traits = traits.merge(new_traits)?;
|
meta_list.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated)?,
|
||||||
|
)?;
|
||||||
|
traits.merge(new_traits)?;
|
||||||
}
|
}
|
||||||
Meta::Path(path) if path.is_ident(REFLECT_VALUE_ATTRIBUTE_NAME) => {
|
Meta::Path(path) if path.is_ident(REFLECT_VALUE_ATTRIBUTE_NAME) => {
|
||||||
if !matches!(reflect_mode, None | Some(ReflectMode::Value)) {
|
if !matches!(reflect_mode, None | Some(ReflectMode::Value)) {
|
||||||
@ -174,7 +179,11 @@ impl<'a> ReflectDerive<'a> {
|
|||||||
}
|
}
|
||||||
#[cfg(feature = "documentation")]
|
#[cfg(feature = "documentation")]
|
||||||
Meta::NameValue(pair) if pair.path.is_ident("doc") => {
|
Meta::NameValue(pair) if pair.path.is_ident("doc") => {
|
||||||
if let syn::Lit::Str(lit) = pair.lit {
|
if let syn::Expr::Lit(syn::ExprLit {
|
||||||
|
lit: syn::Lit::Str(lit),
|
||||||
|
..
|
||||||
|
}) = &pair.value
|
||||||
|
{
|
||||||
doc.push(lit.value());
|
doc.push(lit.value());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -247,7 +256,7 @@ impl<'a> ReflectDerive<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn collect_enum_variants(
|
fn collect_enum_variants(
|
||||||
variants: &'a Punctuated<Variant, Token![,]>,
|
variants: &'a Punctuated<Variant, Comma>,
|
||||||
) -> Result<Vec<EnumVariant<'a>>, syn::Error> {
|
) -> Result<Vec<EnumVariant<'a>>, syn::Error> {
|
||||||
let sifter: utility::ResultSifter<EnumVariant<'a>> = variants
|
let sifter: utility::ResultSifter<EnumVariant<'a>> = variants
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
use crate::fq_std::FQOption;
|
use crate::fq_std::FQOption;
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{quote, ToTokens};
|
use quote::{quote, ToTokens};
|
||||||
use syn::{Attribute, Lit, Meta};
|
use syn::{Attribute, Expr, ExprLit, Lit, Meta};
|
||||||
|
|
||||||
/// A struct used to represent a type's documentation, if any.
|
/// A struct used to represent a type's documentation, if any.
|
||||||
///
|
///
|
||||||
@ -21,18 +21,18 @@ impl Documentation {
|
|||||||
pub fn from_attributes<'a>(attributes: impl IntoIterator<Item = &'a Attribute>) -> Self {
|
pub fn from_attributes<'a>(attributes: impl IntoIterator<Item = &'a Attribute>) -> Self {
|
||||||
let docs = attributes
|
let docs = attributes
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|attr| {
|
.filter_map(|attr| match &attr.meta {
|
||||||
let meta = attr.parse_meta().ok()?;
|
Meta::NameValue(pair) if pair.path.is_ident("doc") => {
|
||||||
match meta {
|
if let Expr::Lit(ExprLit {
|
||||||
Meta::NameValue(pair) if pair.path.is_ident("doc") => {
|
lit: Lit::Str(lit), ..
|
||||||
if let Lit::Str(lit) = pair.lit {
|
}) = &pair.value
|
||||||
Some(lit.value())
|
{
|
||||||
} else {
|
Some(lit.value())
|
||||||
None
|
} else {
|
||||||
}
|
None
|
||||||
}
|
}
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
|
_ => None,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
use crate::REFLECT_ATTRIBUTE_NAME;
|
use crate::REFLECT_ATTRIBUTE_NAME;
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::{Attribute, Lit, Meta, NestedMeta};
|
use syn::{Attribute, Expr, ExprLit, Lit, Meta};
|
||||||
|
|
||||||
pub(crate) static IGNORE_SERIALIZATION_ATTR: &str = "skip_serializing";
|
pub(crate) static IGNORE_SERIALIZATION_ATTR: &str = "skip_serializing";
|
||||||
pub(crate) static IGNORE_ALL_ATTR: &str = "ignore";
|
pub(crate) static IGNORE_ALL_ATTR: &str = "ignore";
|
||||||
@ -76,10 +76,9 @@ pub(crate) fn parse_field_attrs(attrs: &[Attribute]) -> Result<ReflectFieldAttr,
|
|||||||
|
|
||||||
let attrs = attrs
|
let attrs = attrs
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|a| a.path.is_ident(REFLECT_ATTRIBUTE_NAME));
|
.filter(|a| a.path().is_ident(REFLECT_ATTRIBUTE_NAME));
|
||||||
for attr in attrs {
|
for attr in attrs {
|
||||||
let meta = attr.parse_meta()?;
|
if let Err(err) = parse_meta(&mut args, &attr.meta) {
|
||||||
if let Err(err) = parse_meta(&mut args, &meta) {
|
|
||||||
if let Some(ref mut error) = errors {
|
if let Some(ref mut error) = errors {
|
||||||
error.combine(err);
|
error.combine(err);
|
||||||
} else {
|
} else {
|
||||||
@ -117,18 +116,15 @@ fn parse_meta(args: &mut ReflectFieldAttr, meta: &Meta) -> Result<(), syn::Error
|
|||||||
format!("unknown attribute parameter: {}", path.to_token_stream()),
|
format!("unknown attribute parameter: {}", path.to_token_stream()),
|
||||||
)),
|
)),
|
||||||
Meta::NameValue(pair) if pair.path.is_ident(DEFAULT_ATTR) => {
|
Meta::NameValue(pair) if pair.path.is_ident(DEFAULT_ATTR) => {
|
||||||
let lit = &pair.lit;
|
if let Expr::Lit(ExprLit {lit: Lit::Str(lit_str), ..}) = &pair.value {
|
||||||
match lit {
|
args.default = DefaultBehavior::Func(lit_str.parse()?);
|
||||||
Lit::Str(lit_str) => {
|
Ok(())
|
||||||
args.default = DefaultBehavior::Func(lit_str.parse()?);
|
}
|
||||||
Ok(())
|
else {
|
||||||
}
|
Err(syn::Error::new(
|
||||||
err => {
|
pair.span(),
|
||||||
Err(syn::Error::new(
|
format!("expected a string literal containing the name of a function, but found: {}", pair.to_token_stream()),
|
||||||
err.span(),
|
))?
|
||||||
format!("expected a string literal containing the name of a function, but found: {}", err.to_token_stream()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Meta::NameValue(pair) => {
|
Meta::NameValue(pair) => {
|
||||||
@ -142,10 +138,8 @@ fn parse_meta(args: &mut ReflectFieldAttr, meta: &Meta) -> Result<(), syn::Error
|
|||||||
Err(syn::Error::new(list.path.span(), "unexpected property"))
|
Err(syn::Error::new(list.path.span(), "unexpected property"))
|
||||||
}
|
}
|
||||||
Meta::List(list) => {
|
Meta::List(list) => {
|
||||||
for nested in &list.nested {
|
if let Ok(meta) = list.parse_args() {
|
||||||
if let NestedMeta::Meta(meta) = nested {
|
parse_meta(args, &meta)?;
|
||||||
parse_meta(args, meta)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ extern crate proc_macro;
|
|||||||
use bevy_macro_utils::BevyManifest;
|
use bevy_macro_utils::BevyManifest;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::parse::{Parse, ParseStream};
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
use syn::token::Comma;
|
||||||
use syn::*;
|
use syn::*;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@ -15,22 +16,13 @@ pub(crate) fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::To
|
|||||||
let type_ident = ast.ident;
|
let type_ident = ast.ident;
|
||||||
|
|
||||||
let mut uuid = None;
|
let mut uuid = None;
|
||||||
for attribute in ast.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) {
|
for attribute in ast.attrs.iter().filter(|attr| attr.path().is_ident("uuid")) {
|
||||||
let Meta::NameValue(name_value) = attribute else {
|
let Meta::NameValue(ref name_value) = attribute.meta else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if name_value
|
let uuid_str = match &name_value.value {
|
||||||
.path
|
Expr::Lit(ExprLit{lit: Lit::Str(lit_str), ..}) => lit_str,
|
||||||
.get_ident()
|
|
||||||
.map(|i| i != "uuid")
|
|
||||||
.unwrap_or(true)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let uuid_str = match name_value.lit {
|
|
||||||
Lit::Str(lit_str) => lit_str,
|
|
||||||
_ => panic!("`uuid` attribute must take the form `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"`."),
|
_ => panic!("`uuid` attribute must take the form `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"`."),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -101,7 +93,7 @@ impl Parse for TypeUuidDef {
|
|||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
let type_ident = input.parse::<Ident>()?;
|
let type_ident = input.parse::<Ident>()?;
|
||||||
let generics = input.parse::<Generics>()?;
|
let generics = input.parse::<Generics>()?;
|
||||||
input.parse::<Token![,]>()?;
|
input.parse::<Comma>()?;
|
||||||
let uuid = input.parse::<LitStr>()?.value();
|
let uuid = input.parse::<LitStr>()?.value();
|
||||||
let uuid = Uuid::parse_str(&uuid).map_err(|err| input.error(format!("{err}")))?;
|
let uuid = Uuid::parse_str(&uuid).map_err(|err| input.error(format!("{err}")))?;
|
||||||
|
|
||||||
|
|||||||
@ -1636,6 +1636,24 @@ bevy_reflect::tests::should_reflect_debug::Test {
|
|||||||
assert_eq!("Foo".to_string(), format!("{foo:?}"));
|
assert_eq!("Foo".to_string(), format!("{foo:?}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_debug_function() {
|
||||||
|
#[derive(Reflect)]
|
||||||
|
#[reflect(Debug(custom_debug))]
|
||||||
|
struct Foo {
|
||||||
|
a: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn custom_debug(_x: &Foo, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "123")
|
||||||
|
}
|
||||||
|
|
||||||
|
let foo = Foo { a: 1 };
|
||||||
|
let foo: &dyn Reflect = &foo;
|
||||||
|
|
||||||
|
assert_eq!("123", format!("{:?}", foo));
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "glam")]
|
#[cfg(feature = "glam")]
|
||||||
mod glam {
|
mod glam {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@ -69,7 +69,7 @@ thread_local = "1.1"
|
|||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
futures-lite = "1.4.0"
|
futures-lite = "1.4.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
hexasphere = "8.1"
|
hexasphere = "9.0"
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
regex = "1.5"
|
regex = "1.5"
|
||||||
ddsfile = { version = "0.5.0", optional = true }
|
ddsfile = { version = "0.5.0", optional = true }
|
||||||
@ -79,7 +79,7 @@ flate2 = { version = "1.0.22", optional = true }
|
|||||||
ruzstd = { version = "0.2.4", optional = true }
|
ruzstd = { version = "0.2.4", optional = true }
|
||||||
# For transcoding of UASTC/ETC1S universal formats, and for .basis file support
|
# For transcoding of UASTC/ETC1S universal formats, and for .basis file support
|
||||||
basis-universal = { version = "0.2.0", optional = true }
|
basis-universal = { version = "0.2.0", optional = true }
|
||||||
encase = { version = "0.5", features = ["glam"] }
|
encase = { version = "0.6.1", features = ["glam"] }
|
||||||
# For wgpu profiling using tracing. Use `RUST_LOG=info` to also capture the wgpu spans.
|
# For wgpu profiling using tracing. Use `RUST_LOG=info` to also capture the wgpu spans.
|
||||||
profiling = { version = "1", features = ["profile-with-tracing"], optional = true }
|
profiling = { version = "1", features = ["profile-with-tracing"], optional = true }
|
||||||
async-channel = "1.8"
|
async-channel = "1.8"
|
||||||
|
|||||||
@ -14,6 +14,6 @@ proc-macro = true
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.11.0-dev" }
|
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.11.0-dev" }
|
||||||
|
|
||||||
syn = "1.0"
|
syn = "2.0"
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
|
|||||||
@ -5,7 +5,8 @@ use quote::{quote, ToTokens};
|
|||||||
use syn::{
|
use syn::{
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
punctuated::Punctuated,
|
punctuated::Punctuated,
|
||||||
Data, DataStruct, Error, Fields, LitInt, LitStr, NestedMeta, Result, Token,
|
token::Comma,
|
||||||
|
Data, DataStruct, Error, Fields, LitInt, LitStr, Meta, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
const UNIFORM_ATTRIBUTE_NAME: Symbol = Symbol("uniform");
|
const UNIFORM_ATTRIBUTE_NAME: Symbol = Symbol("uniform");
|
||||||
@ -48,7 +49,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
|||||||
|
|
||||||
// Read struct-level attributes
|
// Read struct-level attributes
|
||||||
for attr in &ast.attrs {
|
for attr in &ast.attrs {
|
||||||
if let Some(attr_ident) = attr.path.get_ident() {
|
if let Some(attr_ident) = attr.path().get_ident() {
|
||||||
if attr_ident == BIND_GROUP_DATA_ATTRIBUTE_NAME {
|
if attr_ident == BIND_GROUP_DATA_ATTRIBUTE_NAME {
|
||||||
if let Ok(prepared_data_ident) =
|
if let Ok(prepared_data_ident) =
|
||||||
attr.parse_args_with(|input: ParseStream| input.parse::<Ident>())
|
attr.parse_args_with(|input: ParseStream| input.parse::<Ident>())
|
||||||
@ -117,7 +118,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
|||||||
// Read field-level attributes
|
// Read field-level attributes
|
||||||
for field in fields.iter() {
|
for field in fields.iter() {
|
||||||
for attr in &field.attrs {
|
for attr in &field.attrs {
|
||||||
let Some(attr_ident) = attr.path.get_ident() else {
|
let Some(attr_ident) = attr.path().get_ident() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -462,14 +463,14 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result<TokenStream> {
|
|||||||
/// like `#[uniform(LitInt, Ident)]`
|
/// like `#[uniform(LitInt, Ident)]`
|
||||||
struct UniformBindingMeta {
|
struct UniformBindingMeta {
|
||||||
lit_int: LitInt,
|
lit_int: LitInt,
|
||||||
_comma: Token![,],
|
_comma: Comma,
|
||||||
ident: Ident,
|
ident: Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the arguments for any general binding attribute.
|
/// Represents the arguments for any general binding attribute.
|
||||||
///
|
///
|
||||||
/// If parsed, represents an attribute
|
/// If parsed, represents an attribute
|
||||||
/// like `#[foo(LitInt, ...)]` where the rest is optional [`NestedMeta`].
|
/// like `#[foo(LitInt, ...)]` where the rest is optional [`Meta`].
|
||||||
enum BindingMeta {
|
enum BindingMeta {
|
||||||
IndexOnly(LitInt),
|
IndexOnly(LitInt),
|
||||||
IndexWithOptions(BindingIndexOptions),
|
IndexWithOptions(BindingIndexOptions),
|
||||||
@ -480,13 +481,13 @@ enum BindingMeta {
|
|||||||
/// This represents, for example, `#[texture(0, dimension = "2d_array")]`.
|
/// This represents, for example, `#[texture(0, dimension = "2d_array")]`.
|
||||||
struct BindingIndexOptions {
|
struct BindingIndexOptions {
|
||||||
lit_int: LitInt,
|
lit_int: LitInt,
|
||||||
_comma: Token![,],
|
_comma: Comma,
|
||||||
meta_list: Punctuated<NestedMeta, Token![,]>,
|
meta_list: Punctuated<Meta, Comma>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for BindingMeta {
|
impl Parse for BindingMeta {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
if input.peek2(Token![,]) {
|
if input.peek2(Comma) {
|
||||||
input.parse().map(Self::IndexWithOptions)
|
input.parse().map(Self::IndexWithOptions)
|
||||||
} else {
|
} else {
|
||||||
input.parse().map(Self::IndexOnly)
|
input.parse().map(Self::IndexOnly)
|
||||||
@ -499,7 +500,7 @@ impl Parse for BindingIndexOptions {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
lit_int: input.parse()?,
|
lit_int: input.parse()?,
|
||||||
_comma: input.parse()?,
|
_comma: input.parse()?,
|
||||||
meta_list: input.parse_terminated(NestedMeta::parse)?,
|
meta_list: input.parse_terminated(Meta::parse, Comma)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -523,7 +524,7 @@ fn get_uniform_binding_attr(attr: &syn::Attribute) -> Result<(u32, Ident)> {
|
|||||||
Ok((binding_index, ident))
|
Ok((binding_index, ident))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_binding_nested_attr(attr: &syn::Attribute) -> Result<(u32, Vec<NestedMeta>)> {
|
fn get_binding_nested_attr(attr: &syn::Attribute) -> Result<(u32, Vec<Meta>)> {
|
||||||
let binding_meta = attr.parse_args_with(BindingMeta::parse)?;
|
let binding_meta = attr.parse_args_with(BindingMeta::parse)?;
|
||||||
|
|
||||||
match binding_meta {
|
match binding_meta {
|
||||||
@ -598,43 +599,39 @@ const VISIBILITY_COMPUTE: Symbol = Symbol("compute");
|
|||||||
const VISIBILITY_ALL: Symbol = Symbol("all");
|
const VISIBILITY_ALL: Symbol = Symbol("all");
|
||||||
const VISIBILITY_NONE: Symbol = Symbol("none");
|
const VISIBILITY_NONE: Symbol = Symbol("none");
|
||||||
|
|
||||||
fn get_visibility_flag_value(
|
fn get_visibility_flag_value(meta: Meta) -> Result<ShaderStageVisibility> {
|
||||||
nested_metas: &Punctuated<NestedMeta, Token![,]>,
|
|
||||||
) -> Result<ShaderStageVisibility> {
|
|
||||||
let mut visibility = VisibilityFlags::vertex_fragment();
|
let mut visibility = VisibilityFlags::vertex_fragment();
|
||||||
|
|
||||||
for meta in nested_metas {
|
use syn::Meta::Path;
|
||||||
use syn::{Meta::Path, NestedMeta::Meta};
|
match meta {
|
||||||
match meta {
|
// Parse `#[visibility(all)]`.
|
||||||
// Parse `visibility(all)]`.
|
Path(path) if path == VISIBILITY_ALL => {
|
||||||
Meta(Path(path)) if path == VISIBILITY_ALL => {
|
return Ok(ShaderStageVisibility::All)
|
||||||
return Ok(ShaderStageVisibility::All)
|
|
||||||
}
|
|
||||||
// Parse `visibility(none)]`.
|
|
||||||
Meta(Path(path)) if path == VISIBILITY_NONE => {
|
|
||||||
return Ok(ShaderStageVisibility::None)
|
|
||||||
}
|
|
||||||
// Parse `visibility(vertex, ...)]`.
|
|
||||||
Meta(Path(path)) if path == VISIBILITY_VERTEX => {
|
|
||||||
visibility.vertex = true;
|
|
||||||
}
|
|
||||||
// Parse `visibility(fragment, ...)]`.
|
|
||||||
Meta(Path(path)) if path == VISIBILITY_FRAGMENT => {
|
|
||||||
visibility.fragment = true;
|
|
||||||
}
|
|
||||||
// Parse `visibility(compute, ...)]`.
|
|
||||||
Meta(Path(path)) if path == VISIBILITY_COMPUTE => {
|
|
||||||
visibility.compute = true;
|
|
||||||
}
|
|
||||||
Meta(Path(path)) => return Err(Error::new_spanned(
|
|
||||||
path,
|
|
||||||
"Not a valid visibility flag. Must be `all`, `none`, or a list-combination of `vertex`, `fragment` and/or `compute`."
|
|
||||||
)),
|
|
||||||
_ => return Err(Error::new_spanned(
|
|
||||||
meta,
|
|
||||||
"Invalid visibility format: `visibility(...)`.",
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
|
// Parse `#[visibility(none)]`.
|
||||||
|
Path(path) if path == VISIBILITY_NONE => {
|
||||||
|
return Ok(ShaderStageVisibility::None)
|
||||||
|
}
|
||||||
|
// Parse `#[visibility(vertex, ...)]`.
|
||||||
|
Path(path) if path == VISIBILITY_VERTEX => {
|
||||||
|
visibility.vertex = true;
|
||||||
|
}
|
||||||
|
// Parse `#[visibility(fragment, ...)]`.
|
||||||
|
Path(path) if path == VISIBILITY_FRAGMENT => {
|
||||||
|
visibility.fragment = true;
|
||||||
|
}
|
||||||
|
// Parse `#[visibility(compute, ...)]`.
|
||||||
|
Path(path) if path == VISIBILITY_COMPUTE => {
|
||||||
|
visibility.compute = true;
|
||||||
|
}
|
||||||
|
Path(path) => return Err(Error::new_spanned(
|
||||||
|
path,
|
||||||
|
"Not a valid visibility flag. Must be `all`, `none`, or a list-combination of `vertex`, `fragment` and/or `compute`."
|
||||||
|
)),
|
||||||
|
_ => return Err(Error::new_spanned(
|
||||||
|
meta,
|
||||||
|
"Invalid visibility format: `visibility(...)`.",
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ShaderStageVisibility::Flags(visibility))
|
Ok(ShaderStageVisibility::Flags(visibility))
|
||||||
@ -727,7 +724,7 @@ const DEPTH: &str = "depth";
|
|||||||
const S_INT: &str = "s_int";
|
const S_INT: &str = "s_int";
|
||||||
const U_INT: &str = "u_int";
|
const U_INT: &str = "u_int";
|
||||||
|
|
||||||
fn get_texture_attrs(metas: Vec<NestedMeta>) -> Result<TextureAttrs> {
|
fn get_texture_attrs(metas: Vec<Meta>) -> Result<TextureAttrs> {
|
||||||
let mut dimension = Default::default();
|
let mut dimension = Default::default();
|
||||||
let mut sample_type = Default::default();
|
let mut sample_type = Default::default();
|
||||||
let mut multisampled = Default::default();
|
let mut multisampled = Default::default();
|
||||||
@ -737,35 +734,32 @@ fn get_texture_attrs(metas: Vec<NestedMeta>) -> Result<TextureAttrs> {
|
|||||||
let mut visibility = ShaderStageVisibility::vertex_fragment();
|
let mut visibility = ShaderStageVisibility::vertex_fragment();
|
||||||
|
|
||||||
for meta in metas {
|
for meta in metas {
|
||||||
use syn::{
|
use syn::Meta::{List, NameValue};
|
||||||
Meta::{List, NameValue},
|
|
||||||
NestedMeta::Meta,
|
|
||||||
};
|
|
||||||
match meta {
|
match meta {
|
||||||
// Parse #[texture(0, dimension = "...")].
|
// Parse #[texture(0, dimension = "...")].
|
||||||
Meta(NameValue(m)) if m.path == DIMENSION => {
|
NameValue(m) if m.path == DIMENSION => {
|
||||||
let value = get_lit_str(DIMENSION, &m.lit)?;
|
let value = get_lit_str(DIMENSION, &m.value)?;
|
||||||
dimension = get_texture_dimension_value(value)?;
|
dimension = get_texture_dimension_value(value)?;
|
||||||
}
|
}
|
||||||
// Parse #[texture(0, sample_type = "...")].
|
// Parse #[texture(0, sample_type = "...")].
|
||||||
Meta(NameValue(m)) if m.path == SAMPLE_TYPE => {
|
NameValue(m) if m.path == SAMPLE_TYPE => {
|
||||||
let value = get_lit_str(SAMPLE_TYPE, &m.lit)?;
|
let value = get_lit_str(SAMPLE_TYPE, &m.value)?;
|
||||||
sample_type = get_texture_sample_type_value(value)?;
|
sample_type = get_texture_sample_type_value(value)?;
|
||||||
}
|
}
|
||||||
// Parse #[texture(0, multisampled = "...")].
|
// Parse #[texture(0, multisampled = "...")].
|
||||||
Meta(NameValue(m)) if m.path == MULTISAMPLED => {
|
NameValue(m) if m.path == MULTISAMPLED => {
|
||||||
multisampled = get_lit_bool(MULTISAMPLED, &m.lit)?;
|
multisampled = get_lit_bool(MULTISAMPLED, &m.value)?;
|
||||||
}
|
}
|
||||||
// Parse #[texture(0, filterable = "...")].
|
// Parse #[texture(0, filterable = "...")].
|
||||||
Meta(NameValue(m)) if m.path == FILTERABLE => {
|
NameValue(m) if m.path == FILTERABLE => {
|
||||||
filterable = get_lit_bool(FILTERABLE, &m.lit)?.into();
|
filterable = get_lit_bool(FILTERABLE, &m.value)?.into();
|
||||||
filterable_ident = m.path.into();
|
filterable_ident = m.path.into();
|
||||||
}
|
}
|
||||||
// Parse #[texture(0, visibility(...))].
|
// Parse #[texture(0, visibility(...))].
|
||||||
Meta(List(m)) if m.path == VISIBILITY => {
|
List(m) if m.path == VISIBILITY => {
|
||||||
visibility = get_visibility_flag_value(&m.nested)?;
|
visibility = get_visibility_flag_value(Meta::Path(m.path))?;
|
||||||
}
|
}
|
||||||
Meta(NameValue(m)) => {
|
NameValue(m) => {
|
||||||
return Err(Error::new_spanned(
|
return Err(Error::new_spanned(
|
||||||
m.path,
|
m.path,
|
||||||
"Not a valid name. Available attributes: `dimension`, `sample_type`, `multisampled`, or `filterable`."
|
"Not a valid name. Available attributes: `dimension`, `sample_type`, `multisampled`, or `filterable`."
|
||||||
@ -865,26 +859,23 @@ const FILTERING: &str = "filtering";
|
|||||||
const NON_FILTERING: &str = "non_filtering";
|
const NON_FILTERING: &str = "non_filtering";
|
||||||
const COMPARISON: &str = "comparison";
|
const COMPARISON: &str = "comparison";
|
||||||
|
|
||||||
fn get_sampler_attrs(metas: Vec<NestedMeta>) -> Result<SamplerAttrs> {
|
fn get_sampler_attrs(metas: Vec<Meta>) -> Result<SamplerAttrs> {
|
||||||
let mut sampler_binding_type = Default::default();
|
let mut sampler_binding_type = Default::default();
|
||||||
let mut visibility = ShaderStageVisibility::vertex_fragment();
|
let mut visibility = ShaderStageVisibility::vertex_fragment();
|
||||||
|
|
||||||
for meta in metas {
|
for meta in metas {
|
||||||
use syn::{
|
use syn::Meta::{List, NameValue};
|
||||||
Meta::{List, NameValue},
|
|
||||||
NestedMeta::Meta,
|
|
||||||
};
|
|
||||||
match meta {
|
match meta {
|
||||||
// Parse #[sampler(0, sampler_type = "..."))].
|
// Parse #[sampler(0, sampler_type = "..."))].
|
||||||
Meta(NameValue(m)) if m.path == SAMPLER_TYPE => {
|
NameValue(m) if m.path == SAMPLER_TYPE => {
|
||||||
let value = get_lit_str(DIMENSION, &m.lit)?;
|
let value = get_lit_str(DIMENSION, &m.value)?;
|
||||||
sampler_binding_type = get_sampler_binding_type_value(value)?;
|
sampler_binding_type = get_sampler_binding_type_value(value)?;
|
||||||
}
|
}
|
||||||
// Parse #[sampler(0, visibility(...))].
|
// Parse #[sampler(0, visibility(...))].
|
||||||
Meta(List(m)) if m.path == VISIBILITY => {
|
List(m) if m.path == VISIBILITY => {
|
||||||
visibility = get_visibility_flag_value(&m.nested)?;
|
visibility = get_visibility_flag_value(Meta::Path(m.path))?;
|
||||||
}
|
}
|
||||||
Meta(NameValue(m)) => {
|
NameValue(m) => {
|
||||||
return Err(Error::new_spanned(
|
return Err(Error::new_spanned(
|
||||||
m.path,
|
m.path,
|
||||||
"Not a valid name. Available attributes: `sampler_type`.",
|
"Not a valid name. Available attributes: `sampler_type`.",
|
||||||
@ -928,22 +919,22 @@ struct StorageAttrs {
|
|||||||
const READ_ONLY: Symbol = Symbol("read_only");
|
const READ_ONLY: Symbol = Symbol("read_only");
|
||||||
const BUFFER: Symbol = Symbol("buffer");
|
const BUFFER: Symbol = Symbol("buffer");
|
||||||
|
|
||||||
fn get_storage_binding_attr(metas: Vec<NestedMeta>) -> Result<StorageAttrs> {
|
fn get_storage_binding_attr(metas: Vec<Meta>) -> Result<StorageAttrs> {
|
||||||
let mut visibility = ShaderStageVisibility::vertex_fragment();
|
let mut visibility = ShaderStageVisibility::vertex_fragment();
|
||||||
let mut read_only = false;
|
let mut read_only = false;
|
||||||
let mut buffer = false;
|
let mut buffer = false;
|
||||||
|
|
||||||
for meta in metas {
|
for meta in metas {
|
||||||
use syn::{Meta::List, Meta::Path, NestedMeta::Meta};
|
use syn::{Meta::List, Meta::Path};
|
||||||
match meta {
|
match meta {
|
||||||
// Parse #[storage(0, visibility(...))].
|
// Parse #[storage(0, visibility(...))].
|
||||||
Meta(List(m)) if m.path == VISIBILITY => {
|
List(m) if m.path == VISIBILITY => {
|
||||||
visibility = get_visibility_flag_value(&m.nested)?;
|
visibility = get_visibility_flag_value(Meta::Path(m.path))?;
|
||||||
}
|
}
|
||||||
Meta(Path(path)) if path == READ_ONLY => {
|
Path(path) if path == READ_ONLY => {
|
||||||
read_only = true;
|
read_only = true;
|
||||||
}
|
}
|
||||||
Meta(Path(path)) if path == BUFFER => {
|
Path(path) if path == BUFFER => {
|
||||||
buffer = true;
|
buffer = true;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
@ -20,7 +20,7 @@ pub fn derive_extract_component(input: TokenStream) -> TokenStream {
|
|||||||
let filter = if let Some(attr) = ast
|
let filter = if let Some(attr) = ast
|
||||||
.attrs
|
.attrs
|
||||||
.iter()
|
.iter()
|
||||||
.find(|a| a.path.is_ident("extract_component_filter"))
|
.find(|a| a.path().is_ident("extract_component_filter"))
|
||||||
{
|
{
|
||||||
let filter = match attr.parse_args::<syn::Type>() {
|
let filter = match attr.parse_args::<syn::Type>() {
|
||||||
Ok(filter) => filter,
|
Ok(filter) => filter,
|
||||||
|
|||||||
@ -9,6 +9,6 @@ license = "MIT OR Apache-2.0"
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
syn = "1.0"
|
syn = "2.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user