Skip to main content

derives/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span;
3use quote::*;
4use syn::{parse_macro_input, spanned::Spanned, Expr};
5
6macro_rules! try_compile_error {
7    ($x: expr) => {
8        match $x {
9            Ok(x) => x,
10            Err(e) => return e.into_compile_error().into(),
11        }
12    };
13}
14
15mod promote;
16mod vulkan_structure;
17
18fn find_vkhandle_source(fields: &syn::Fields) -> (proc_macro2::TokenStream, &syn::Type) {
19    #[inline]
20    fn has_handle_attribute(f: &syn::Field) -> bool {
21        f.attrs
22            .iter()
23            .any(|a| matches!(a.meta, syn::Meta::Path(ref path) if path.is_ident("handle")))
24    }
25
26    match fields {
27        syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed, .. }) => match unnamed.iter().position(has_handle_attribute)
28        {
29            Some(n) => (quote! { self.#n }, &unnamed[n].ty),
30            None => (quote! { self.0 }, &unnamed[0].ty),
31        },
32        syn::Fields::Named(syn::FieldsNamed { named, .. }) => {
33            let Some(f) = named.iter().find(|&f| has_handle_attribute(f)) else {
34                panic!("Named fields struct must have one field that marked by #[handle]");
35            };
36            let n = f.ident.as_ref().unwrap();
37
38            (quote! { self.#n }, &f.ty)
39        }
40        syn::Fields::Unit => panic!("Unit struct cannot auto-derive VkHandle"),
41    }
42}
43
44fn find_parent_field(fields: &syn::Fields) -> (usize, &syn::Field) {
45    #[inline]
46    fn has_parent_attribute(f: &syn::Field) -> bool {
47        f.attrs
48            .iter()
49            .any(|a| matches!(a.meta, syn::Meta::Path(ref path) if path.is_ident("parent")))
50    }
51
52    match fields {
53        syn::Fields::Named(ref n) => {
54            let mut parents = n.named.iter().enumerate().filter(|(_, n)| has_parent_attribute(n));
55
56            let Some(p) = parents.next() else {
57                panic!("could not find parent field");
58            };
59            if parents.next().is_some() {
60                panic!("too many parent fields");
61            }
62
63            p
64        }
65        syn::Fields::Unnamed(ref u) => {
66            let mut parents = u.unnamed.iter().enumerate().filter(|(_, n)| has_parent_attribute(n));
67
68            let Some(p) = parents.next() else {
69                panic!("could not find parent field");
70            };
71            if parents.next().is_some() {
72                panic!("too many parent fields");
73            }
74
75            p
76        }
77        syn::Fields::Unit => panic!("unit structure has no parent field"),
78    }
79}
80
81#[proc_macro_derive(VkHandle, attributes(handle))]
82pub fn derive_handle(tok: TokenStream) -> TokenStream {
83    let input = parse_macro_input!(tok as syn::DeriveInput);
84    let name = &input.ident;
85    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
86    let syn::Data::Struct(syn::DataStruct { ref fields, .. }) = input.data else {
87        panic!("AutoDerive VkHandle can only be applied for structs");
88    };
89    let (handle_field_ref, handle_ty) = find_vkhandle_source(fields);
90
91    quote! {
92        impl #impl_generics crate::VkHandle for #name #ty_generics #where_clause {
93            type Handle = #handle_ty;
94
95            #[inline(always)]
96            fn native_ptr(&self) -> Self::Handle {
97                #handle_field_ref
98            }
99        }
100        impl #impl_generics crate::VkHandleMut for #name #ty_generics #where_clause {
101            #[inline(always)]
102            fn native_ptr_mut(&mut self) -> Self::Handle {
103                #handle_field_ref
104            }
105        }
106    }
107    .into()
108}
109
110struct VkObjectAttributes {
111    span: Span,
112    r#type: Option<Expr>,
113}
114impl VkObjectAttributes {
115    #[inline]
116    pub fn find_and_parse(input: &syn::DeriveInput) -> syn::Result<Option<Self>> {
117        input
118            .attrs
119            .iter()
120            .find(|a| a.path().is_ident("VkObject"))
121            .map(Self::parse)
122            .transpose()
123    }
124
125    pub fn parse(attr: &syn::Attribute) -> syn::Result<Self> {
126        let mut r#type = None;
127        attr.parse_nested_meta(|meta| {
128            if meta.path.is_ident("type") {
129                r#type = Some(meta.value()?.parse::<Expr>()?);
130                return Ok(());
131            }
132
133            Err(meta.error("unkown attribute"))
134        })?;
135
136        Ok(Self {
137            span: attr.span(),
138            r#type,
139        })
140    }
141}
142
143#[proc_macro_derive(VkObject, attributes(VkObject))]
144pub fn derive_object(tok: TokenStream) -> TokenStream {
145    let input = parse_macro_input!(tok as syn::DeriveInput);
146    let name = &input.ident;
147    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
148
149    let object_attr = match VkObjectAttributes::find_and_parse(&input) {
150        Ok(Some(x)) => x,
151        Ok(None) => {
152            return syn::Error::new(Span::call_site(), "VkObject attribute required")
153                .into_compile_error()
154                .into()
155        }
156        Err(e) => return e.into_compile_error().into(),
157    };
158    let Some(object_type) = object_attr.r#type else {
159        return syn::Error::new(object_attr.span, "No type specified")
160            .into_compile_error()
161            .into();
162    };
163
164    quote! {
165        impl #impl_generics crate::VkObject for #name #ty_generics #where_clause {
166            const TYPE: bedrock_vk::VkObjectType = #object_type;
167        }
168    }
169    .into()
170}
171
172#[proc_macro_derive(InstanceChild, attributes(parent))]
173pub fn derive_instance_child(tok: TokenStream) -> TokenStream {
174    let input = parse_macro_input!(tok as syn::DeriveInput);
175    let name = &input.ident;
176    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
177
178    let syn::Data::Struct(ref s) = input.data else {
179        return syn::Error::new(Span::call_site(), "No type except structure can derive InstanceChild")
180            .into_compile_error()
181            .into();
182    };
183    let (parent_index, parent_field) = find_parent_field(&s.fields);
184    let parent_ty = &parent_field.ty;
185    let parent_field = match parent_field.ident {
186        Some(ref f) => quote! { &self.#f },
187        None => {
188            let x = syn::Index::from(parent_index);
189            quote! { &self.#x }
190        }
191    };
192
193    quote! {
194        impl #impl_generics crate::InstanceChild for #name #ty_generics #where_clause {
195            type ConcreteInstance = #parent_ty;
196
197            fn instance(&self) -> &Self::ConcreteInstance { #parent_field }
198        }
199    }
200    .into()
201}
202
203#[proc_macro_derive(InstanceChildTransferrable, attributes(parent))]
204pub fn derive_instance_child_transferrable(tok: TokenStream) -> TokenStream {
205    let input = parse_macro_input!(tok as syn::DeriveInput);
206    let name = &input.ident;
207    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
208    let syn::Data::Struct(ref s) = input.data else {
209        panic!("no type except structure can derive InstanceChild");
210    };
211    let (parent_index, parent_field) = find_parent_field(&s.fields);
212    let parent_field = match parent_field.ident {
213        Some(ref f) => quote! { self.#f },
214        None => {
215            let x = syn::Index::from(parent_index);
216            quote! { self.#x }
217        }
218    };
219
220    quote! {
221        impl #impl_generics crate::InstanceChildTransferrable for #name #ty_generics #where_clause {
222            fn transfer_instance(self) -> Self::ConcreteInstance { #parent_field }
223        }
224    }
225    .into()
226}
227
228#[proc_macro_derive(DeviceChild, attributes(parent))]
229pub fn derive_device_child(tok: TokenStream) -> TokenStream {
230    let input = parse_macro_input!(tok as syn::DeriveInput);
231    let name = &input.ident;
232    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
233    let syn::Data::Struct(ref s) = input.data else {
234        panic!("no type except structure can derive DeviceChild");
235    };
236    let (parent_index, parent_field) = find_parent_field(&s.fields);
237    let parent_ty = &parent_field.ty;
238    let parent_field = match parent_field.ident {
239        Some(ref f) => quote! { &self.#f },
240        None => {
241            let x = syn::Index::from(parent_index);
242            quote! { &self.#x }
243        }
244    };
245
246    quote! {
247        impl #impl_generics crate::DeviceChild for #name #ty_generics #where_clause {
248            type ConcreteDevice = #parent_ty;
249
250            fn device(&self) -> &Self::ConcreteDevice { #parent_field }
251        }
252    }
253    .into()
254}
255
256#[proc_macro_derive(DeviceChildTransferrable, attributes(parent))]
257pub fn derive_device_child_transferrable(tok: TokenStream) -> TokenStream {
258    let input = parse_macro_input!(tok as syn::DeriveInput);
259    let name = &input.ident;
260    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
261    let syn::Data::Struct(ref s) = input.data else {
262        panic!("no type except structure can derive DeviceChild");
263    };
264    let (parent_index, parent_field) = find_parent_field(&s.fields);
265    let parent_field = match parent_field.ident {
266        Some(ref f) => quote! { self.#f },
267        None => {
268            let x = syn::Index::from(parent_index);
269            quote! { self.#x }
270        }
271    };
272
273    quote! {
274        impl #impl_generics crate::DeviceChildTransferrable for #name #ty_generics #where_clause {
275            fn transfer_device(self) -> Self::ConcreteDevice { #parent_field }
276        }
277    }
278    .into()
279}
280
281#[proc_macro_derive(TypedVulkanStructure, attributes(VulkanStructure))]
282#[inline(always)]
283pub fn derive_vulkan_structure(input: TokenStream) -> TokenStream {
284    vulkan_structure::derive(input)
285}
286
287#[proc_macro_derive(TypedVulkanSinkStructure, attributes(VulkanSinkStructure))]
288#[inline(always)]
289pub fn derive_vulkan_sink_structure(input: TokenStream) -> TokenStream {
290    vulkan_structure::derive_sink(input)
291}
292
293#[proc_macro_attribute]
294#[inline(always)]
295pub fn promote_1_1(args: TokenStream, item: TokenStream) -> TokenStream {
296    promote::core(
297        parse_macro_input!(item as syn::Item),
298        args,
299        syn::LitStr::new("Allow1_1APIs", Span::call_site()),
300    )
301}
302#[proc_macro_attribute]
303#[inline(always)]
304pub fn promote_1_2(args: TokenStream, item: TokenStream) -> TokenStream {
305    promote::core(
306        parse_macro_input!(item as syn::Item),
307        args,
308        syn::LitStr::new("Allow1_2APIs", Span::call_site()),
309    )
310}
311#[proc_macro_attribute]
312#[inline(always)]
313pub fn promote_1_3(args: TokenStream, item: TokenStream) -> TokenStream {
314    promote::core(
315        parse_macro_input!(item as syn::Item),
316        args,
317        syn::LitStr::new("Allow1_3APIs", Span::call_site()),
318    )
319}
320#[proc_macro_attribute]
321#[inline(always)]
322pub fn promote_1_4(args: TokenStream, item: TokenStream) -> TokenStream {
323    promote::core(
324        parse_macro_input!(item as syn::Item),
325        args,
326        syn::LitStr::new("Allow1_4APIs", Span::call_site()),
327    )
328}
329
330#[inline]
331fn newtype_struct_org_type(d: &syn::DataStruct) -> syn::Result<&syn::Type> {
332    match d.fields {
333        syn::Fields::Unnamed(ref f) if f.unnamed.len() == 1 => Ok(&unsafe { f.unnamed.first().unwrap_unchecked() }.ty),
334        _ => Err(syn::Error::new_spanned(&d.fields, "not a newtype struct")),
335    }
336}
337
338#[proc_macro_attribute]
339pub fn vk_raw_handle(args: TokenStream, input: TokenStream) -> TokenStream {
340    let input = parse_macro_input!(input as syn::DeriveInput);
341    let name = &input.ident;
342    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
343    let syn::Data::Struct(ref s) = input.data else {
344        panic!("cannot derive VkRawHandle from this type");
345    };
346
347    let mut object_type = None::<Expr>;
348    let parser = syn::meta::parser(|p| {
349        if p.path.is_ident("object_type") {
350            object_type = Some(p.value()?.parse()?);
351            Ok(())
352        } else {
353            Err(p.error("unknown attr"))
354        }
355    });
356    parse_macro_input!(args with parser);
357
358    let dispatchable = matches!(try_compile_error!(newtype_struct_org_type(s)), syn::Type::Ptr(_));
359    let raw_handle_conversion = if dispatchable {
360        quote! { self.0.as_ptr().addr() as _ }
361    } else {
362        quote! { self.0.get() }
363    };
364
365    quote! {
366        #input
367        impl #impl_generics crate::handle::VkRawHandle for #name #ty_generics #where_clause {
368            const OBJECT_TYPE: VkObjectType = #object_type;
369
370            #[inline]
371            fn raw_handle_value(&self) -> u64 {
372                #raw_handle_conversion
373            }
374        }
375    }
376    .into()
377}
378
379#[proc_macro_derive(PFN, attributes(pfn_of))]
380pub fn derive_pfn(input: TokenStream) -> TokenStream {
381    let input = parse_macro_input!(input as syn::DeriveInput);
382    let impl_name = &input.ident;
383
384    let Some(org_attr) = input.attrs.iter().find_map(|a| match a.meta {
385        syn::Meta::List(ref ml) if ml.path.is_ident("pfn_of") => Some(ml),
386        _ => None,
387    }) else {
388        return syn::Error::new_spanned(&input, "no #[pfn_of(...)] found")
389            .into_compile_error()
390            .into();
391    };
392    let org_fn: syn::Path = try_compile_error!(org_attr.parse_args());
393    let Some(org_fn_name) = org_fn.get_ident().or_else(|| org_fn.segments.last().map(|l| &l.ident)) else {
394        return syn::Error::new_spanned(org_attr, "invalid pfn_of fn path")
395            .into_compile_error()
396            .into();
397    };
398    let org_fn_cstr = syn::LitCStr::new(
399        &unsafe { std::ffi::CString::from_vec_unchecked(org_fn_name.to_string().into_bytes()) },
400        org_fn.span(),
401    );
402
403    quote! {
404        unsafe impl crate::resolver::PFN for #impl_name {
405            const NAME_CSTR: &'static core::ffi::CStr = #org_fn_cstr;
406
407            #[inline(always)]
408            unsafe fn from_ptr(p: *const core::ffi::c_void) -> Self {
409                core::mem::transmute(p)
410            }
411            #[inline(always)]
412            unsafe fn from_void_fn(p: crate::vk::PFN_vkVoidFunction) -> Self {
413                core::mem::transmute(p)
414            }
415        }
416    }
417    .into()
418}
419
420#[proc_macro_derive(StaticCallable)]
421pub fn derive_static_callable(input: TokenStream) -> TokenStream {
422    let input = parse_macro_input!(input as syn::DeriveInput);
423    let impl_name = &input.ident;
424    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
425    let org_attr = &input
426        .attrs
427        .iter()
428        .filter_map(|a| a.meta.require_list().ok())
429        .find(|a| a.path.is_ident("pfn_of"))
430        .expect("no #[pfn_of] found");
431    let org_fn: syn::Path = org_attr.parse_args().expect("invalid arg for pfn_of");
432
433    quote! {
434        #[cfg(all(not(feature = "DynamicLoaded"), feature = "Implements"))]
435        impl #impl_generics crate::resolver::StaticCallable for #impl_name #ty_generics #where_clause {
436            const STATIC: Self = Self(#org_fn);
437        }
438    }
439    .into()
440}
441
442/// alias for `#[cfg(feature = "Implements")]`, with additional feature requirements
443#[proc_macro_attribute]
444pub fn implements(args: TokenStream, target: TokenStream) -> TokenStream {
445    let mut out: TokenStream = quote! { #[cfg(feature = "Implements")] }.into();
446
447    if !args.is_empty() {
448        let extra_feature_requirements =
449            parse_macro_input!(args with syn::punctuated::Punctuated<syn::LitStr, syn::Token![,]>::parse_terminated);
450
451        out.extend(
452            extra_feature_requirements
453                .into_iter()
454                .flat_map(|x| TokenStream::from(quote! { #[cfg(feature = #x)] })),
455        );
456    }
457
458    out.extend(target);
459    out
460}
461
462#[proc_macro_attribute]
463pub fn bitflags_newtype(_args: TokenStream, target: TokenStream) -> TokenStream {
464    let input = parse_macro_input!(target as syn::DeriveInput);
465    let name = &input.ident;
466    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
467
468    let syn::Data::Struct(ref s) = input.data else {
469        return syn::Error::new(input.span(), "cannot use as bitflags struct")
470            .into_compile_error()
471            .into();
472    };
473    let org_type = try_compile_error!(newtype_struct_org_type(s));
474
475    quote! {
476        #[repr(transparent)]
477        #input
478        impl #impl_generics core::ops::BitOr for #name #ty_generics #where_clause {
479            type Output = Self;
480
481            #[inline(always)]
482            fn bitor(self, rhs: Self) -> Self {
483                Self(self.0 | rhs.0)
484            }
485        }
486        impl #impl_generics core::ops::BitAnd for #name #ty_generics #where_clause {
487            type Output = Self;
488
489            #[inline(always)]
490            fn bitand(self, rhs: Self) -> Self {
491                Self(self.0 & rhs.0)
492            }
493        }
494        impl #impl_generics core::ops::Not for #name #ty_generics #where_clause {
495            type Output = Self;
496
497            #[inline(always)]
498            fn not(self) -> Self {
499                Self(!self.0)
500            }
501        }
502        impl #impl_generics core::ops::BitOrAssign for #name #ty_generics #where_clause {
503            #[inline(always)]
504            fn bitor_assign(&mut self, rhs: Self) {
505                self.0 |= rhs.0;
506            }
507        }
508        impl #impl_generics core::ops::BitAndAssign for #name #ty_generics #where_clause {
509            #[inline(always)]
510            fn bitand_assign(&mut self, rhs: Self) {
511                self.0 &= rhs.0;
512            }
513        }
514
515        impl #impl_generics From<#name #ty_generics> for #org_type #where_clause {
516            #[inline(always)]
517            fn from(value: #name #ty_generics) -> Self {
518                value.0
519            }
520        }
521
522        impl #impl_generics #name #ty_generics #where_clause {
523            /// Returns bits in this flags
524            #[inline(always)]
525            pub const fn bits(&self) -> #org_type {
526                self.0
527            }
528
529            /// Returns true if any of specified bits are contained in this flag.
530            #[inline(always)]
531            pub const fn has_any(self, other: Self) -> bool {
532                (self.0 & other.0) != 0
533            }
534
535            /// Returns true if all of specified bits are contained in this flag.
536            #[inline(always)]
537            pub const fn has_all(self, other: Self) -> bool {
538                (self.0 & other.0) == other.0
539            }
540
541            /// merge two flags (const alias of BitOr)
542            #[inline(always)]
543            pub const fn merge(self, other: Self) -> Self {
544                Self(self.0 | other.0)
545            }
546        }
547    }
548    .into()
549}
550
551struct VkExtCommandInput {
552    span: Span,
553    base_define: syn::ForeignItemFn,
554    suffix: Option<syn::LitStr>,
555    promote: Option<syn::LitStr>,
556    static_callable: bool,
557}
558impl syn::parse::Parse for VkExtCommandInput {
559    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
560        let span = input.span();
561        let base_define = syn::ForeignItemFn::parse(input)?;
562
563        let (mut suffix, mut promote, mut static_callable) = (None, None, false);
564        while input.peek(syn::Ident) {
565            let extra_ident = syn::Ident::parse(input)?;
566
567            match &extra_ident.to_string() as &str {
568                "suffix" => {
569                    if suffix.is_some() {
570                        return Err(syn::Error::new(input.span(), "duplicated suffix extra"));
571                    }
572
573                    input.parse::<syn::Token![=]>()?;
574                    suffix = Some(input.parse()?);
575                    input.parse::<syn::Token![;]>()?;
576                }
577                "promote" => {
578                    if promote.is_some() {
579                        return Err(syn::Error::new(input.span(), "duplicated promote extra"));
580                    }
581
582                    input.parse::<syn::Token![=]>()?;
583                    promote = Some(input.parse()?);
584                    input.parse::<syn::Token![;]>()?;
585                }
586                "static_callable" => {
587                    input.parse::<syn::Token![;]>()?;
588
589                    static_callable = true;
590                }
591                unknown => return Err(syn::Error::new(input.span(), format!("unknown extra: {unknown}"))),
592            }
593        }
594
595        Ok(Self {
596            span,
597            base_define,
598            suffix,
599            promote,
600            static_callable,
601        })
602    }
603}
604
605#[proc_macro]
606pub fn vk_ext_command(input: TokenStream) -> TokenStream {
607    let input = parse_macro_input!(input as VkExtCommandInput);
608
609    let base_vis = &input.base_define.vis;
610    let pfn_name = syn::Ident::new(
611        &format!("PFN_{}", input.base_define.sig.ident),
612        input.base_define.sig.ident.span(),
613    );
614    let pfn_ty = syn::TypeFnPtr {
615        attrs: Vec::new(),
616        lifetimes: None,
617        unsafety: Some(syn::Token![unsafe](Span::call_site())),
618        abi: Some(syn::Abi {
619            extern_token: syn::Token![extern](Span::call_site()),
620            name: Some(syn::LitStr::new("system", Span::call_site())),
621        }),
622        fn_token: input.base_define.sig.fn_token,
623        paren_token: input.base_define.sig.paren_token,
624        inputs: input
625            .base_define
626            .sig
627            .inputs
628            .iter()
629            .map(|a| match a {
630                syn::FnArg::Receiver(_) => unreachable!("vk command cannot have receivers"),
631                syn::FnArg::Typed(t) => syn::NamedArg {
632                    attrs: t.attrs.clone(),
633                    name: match *t.pat {
634                        syn::Pat::Ident(ref x) => Some((x.ident.clone(), t.colon_token)),
635                        _ => None,
636                    },
637                    ty: *t.ty.clone(),
638                },
639            })
640            .collect(),
641        variadic: input.base_define.sig.variadic.as_ref().map(|v| syn::FnPtrVariadic {
642            attrs: v.attrs.clone(),
643            name: match v.pat {
644                Some((ref p, c)) => match &**p {
645                    syn::Pat::Ident(x) => Some((x.ident.clone(), c)),
646                    _ => None,
647                },
648                _ => None,
649            },
650            dots: v.dots,
651            comma: v.comma,
652        }),
653        output: input.base_define.sig.output.clone(),
654    };
655    let fname_cstr = syn::LitCStr::new(
656        &unsafe { std::ffi::CString::from_vec_unchecked(input.base_define.sig.ident.to_string().into_bytes()) },
657        input.base_define.sig.ident.span(),
658    );
659
660    let ext_static_link_impl = if input.static_callable {
661        let fn_ident = &input.base_define.sig.ident;
662        let define = &input.base_define;
663
664        Some(quote! {
665            #[cfg(all(feature = "Implements", not(feature = "DynamicLoaded")))]
666            impl crate::StaticCallable for #pfn_name {
667                const STATIC: Self = Self(#fn_ident);
668            }
669
670            #[cfg(all(feature = "Implements", not(feature = "DynamicLoaded")))]
671            extern "system" {
672                #define
673            }
674        })
675    } else {
676        None
677    };
678
679    let promoted_pfn_impl = if let Some(ref p) = input.promote {
680        let base_fn_name = input.base_define.sig.ident.to_string();
681        let suffix = try_compile_error!(input
682            .suffix
683            .ok_or_else(|| syn::Error::new(input.span, "suffix extra required for promote")));
684        let Some(promoted_fn_name) = base_fn_name.strip_suffix(&suffix.value()) else {
685            return syn::Error::new_spanned(&input.base_define.sig.ident, "not suffixed")
686                .into_compile_error()
687                .into();
688        };
689        let promoted_fn_ident = syn::Ident::new(promoted_fn_name, input.base_define.sig.ident.span());
690        let pfn_name = syn::Ident::new(&format!("PFN_{promoted_fn_name}"), input.base_define.sig.ident.span());
691        let promoted_fn_cstr = syn::LitCStr::new(
692            &unsafe { std::ffi::CString::from_vec_unchecked(promoted_fn_name.as_bytes().to_vec()) },
693            input.base_define.sig.ident.span(),
694        );
695        let promoted_fn = syn::ForeignItemFn {
696            sig: syn::Signature {
697                ident: promoted_fn_ident.clone(),
698                ..input.base_define.sig.clone()
699            },
700            ..input.base_define.clone()
701        };
702
703        let promote_feature_name = syn::LitStr::new(&format!("Allow{}APIs", p.value().replace('.', "_")), p.span());
704
705        Some(quote! {
706            #[cfg(feature = #promote_feature_name)]
707            #[repr(transparent)]
708            #[derive(Clone, Copy, Debug)]
709            #base_vis struct #pfn_name(pub #pfn_ty);
710            #[cfg(feature = #promote_feature_name)]
711            unsafe impl crate::FromPtr for #pfn_name {
712                #[inline(always)]
713                unsafe fn from_ptr(p: *const core::ffi::c_void) -> Self {
714                    core::mem::transmute(p)
715                }
716            }
717            #[cfg(feature = #promote_feature_name)]
718            unsafe impl crate::PFN for #pfn_name {
719                const NAME_CSTR: &core::ffi::CStr = #promoted_fn_cstr;
720
721                #[inline(always)]
722                unsafe fn from_void_fn(p: crate::PFN_vkVoidFunction) -> Self {
723                    core::mem::transmute(p)
724                }
725            }
726
727            #[cfg(all(feature = "Implements", feature = #promote_feature_name, not(feature = "DynamicLoaded")))]
728            impl crate::StaticCallable for #pfn_name {
729                const STATIC: Self = Self(#promoted_fn_ident);
730            }
731
732            #[cfg(all(feature = "Implements", feature = #promote_feature_name, not(feature = "DynamicLoaded")))]
733            extern "system" {
734                #promoted_fn
735            }
736        })
737    } else {
738        None
739    };
740
741    quote! {
742        #[repr(transparent)]
743        #[derive(Clone, Copy, Debug)]
744        #base_vis struct #pfn_name(pub #pfn_ty);
745        unsafe impl crate::FromPtr for #pfn_name {
746            #[inline(always)]
747            unsafe fn from_ptr(p: *const core::ffi::c_void) -> Self {
748                core::mem::transmute(p)
749            }
750        }
751        unsafe impl crate::PFN for #pfn_name {
752            const NAME_CSTR: &core::ffi::CStr = #fname_cstr;
753
754            #[inline(always)]
755            unsafe fn from_void_fn(p: crate::PFN_vkVoidFunction) -> Self {
756                core::mem::transmute(p)
757            }
758        }
759
760        #ext_static_link_impl
761        #promoted_pfn_impl
762    }
763    .into()
764}
765
766struct SpecializationConstantsFieldAttr {
767    constant_id: syn::Expr,
768}
769impl SpecializationConstantsFieldAttr {
770    fn parse(field: &syn::Field) -> Result<Self, syn::Error> {
771        let mut constant_ids = field.attrs.iter().filter_map(|a| match a {
772            syn::Attribute {
773                style: syn::AttrStyle::Outer,
774                meta: syn::Meta::NameValue(ref nv),
775                ..
776            } if nv.path.is_ident("constant_id") => Some(Ok(nv.value.clone())),
777            syn::Attribute {
778                style: syn::AttrStyle::Outer,
779                meta: syn::Meta::List(ref ml),
780                ..
781            } if ml.path.is_ident("constant_id") => Some(ml.parse_args::<syn::Expr>()),
782            _ => None,
783        });
784        let Some(first_cid) = constant_ids.next() else {
785            return Err(syn::Error::new_spanned(field, "Missing constant_id attribute"));
786        };
787        if constant_ids.next().is_some() {
788            return Err(syn::Error::new_spanned(
789                field,
790                "One or more constant_id attributes found on same field",
791            ));
792        }
793
794        Ok(Self {
795            constant_id: first_cid?,
796        })
797    }
798}
799
800/// Provides safe implementation for [`SpecializationConstants`] by deriving from structs.
801#[proc_macro_derive(SpecializationConstants, attributes(constant_id))]
802pub fn safe_derive_spec_constant(tok: TokenStream) -> TokenStream {
803    let input = parse_macro_input!(tok as syn::DeriveInput);
804    let name = &input.ident;
805    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
806
807    match input.data {
808        syn::Data::Struct(syn::DataStruct { ref fields, .. }) => {
809            let mut entries = Vec::with_capacity(fields.len());
810            for f in fields {
811                let SpecializationConstantsFieldAttr { constant_id } =
812                    try_compile_error!(SpecializationConstantsFieldAttr::parse(f));
813
814                let ty = &f.ty;
815                let ident = &f.ident;
816                entries.push(
817                    quote! { bedrock::SpecializationMapEntry(bedrock::vk::VkSpecializationMapEntry {
818                        constantID: #constant_id,
819                        offset: core::mem::offset_of!(Self, #ident) as _,
820                        size: core::mem::size_of::<#ty>(),
821                    }) },
822                );
823            }
824
825            quote! {
826                unsafe impl<#impl_generics> bedrock::SpecializationConstants for #name #ty_generics #where_clause {
827                    const ENTRIES: &'static [bedrock::SpecializationMapEntry] = &[#(#entries),*];
828
829                    #[inline(always)]
830                    fn as_ptr(&self) -> *const core::ffi::c_void {
831                        core::ptr::from_ref(self).cast()
832                    }
833                }
834            }
835            .into()
836        }
837        _ => unimplemented!("unsupported"),
838    }
839}