rust_libretro_proc/
lib.rs

1#![doc(
2    html_logo_url = "https://raw.githubusercontent.com/max-m/rust-libretro/master/media/logo.png",
3    html_favicon_url = "https://raw.githubusercontent.com/max-m/rust-libretro/master/media/favicon.png"
4)]
5
6use proc_macro::{self, TokenStream};
7use quote::{quote, ToTokens};
8use rust_libretro_sys::RETRO_NUM_CORE_OPTION_VALUES_MAX;
9use syn::{
10    braced, parenthesized,
11    parse::{discouraged::Speculative, Parse, ParseStream, Result},
12    parse2, parse_macro_input, parse_quote,
13    punctuated::Punctuated,
14    Attribute, DeriveInput, LitByteStr, LitStr, NestedMeta, Token,
15};
16
17mod util;
18use util::*;
19
20trait Concat<T> {
21    fn concat(self) -> T;
22}
23
24#[derive(Debug)]
25struct CoreOptionValue {
26    value: LitStr,
27    label: Option<LitStr>,
28}
29
30impl Parse for CoreOptionValue {
31    fn parse(input: ParseStream) -> Result<Self> {
32        let content;
33        braced!(content in input);
34
35        let value = content.parse()?;
36
37        if !content.is_empty() {
38            content.parse::<Token![,]>()?;
39        }
40
41        let label = if !content.is_empty() {
42            Some(content.parse()?)
43        } else {
44            None
45        };
46
47        if !content.is_empty() {
48            content.parse::<Token![,]>()?;
49        }
50
51        Ok(Self { value, label })
52    }
53}
54
55#[derive(Debug)]
56struct CoreOption {
57    key: LitStr,
58    desc: LitStr,
59    info: LitStr,
60    values: Vec<CoreOptionValue>,
61    default_value: Option<LitStr>,
62}
63
64impl Parse for CoreOption {
65    fn parse(input: ParseStream) -> Result<Self> {
66        let key: LitStr = input.parse()?;
67        input.parse::<Token![,]>()?;
68
69        let desc: LitStr = input.parse()?;
70        input.parse::<Token![,]>()?;
71
72        let info: LitStr = input.parse()?;
73        input.parse::<Token![,]>()?;
74
75        let options_content;
76        braced!(options_content in input);
77
78        let default_value: Option<LitStr> = if !input.is_empty() {
79            input.parse::<Token![,]>()?;
80            input.parse()?
81        } else {
82            None
83        };
84
85        let mut values = Vec::new();
86        while !options_content.is_empty() {
87            let value = options_content.parse::<CoreOptionValue>()?;
88            values.push(value);
89
90            if !options_content.is_empty() {
91                options_content.parse::<Token![,]>()?;
92            }
93        }
94
95        Ok(Self {
96            key,
97            desc,
98            info,
99            values,
100            default_value,
101        })
102    }
103}
104
105#[derive(Debug)]
106struct CoreOptionV2 {
107    key: LitStr,
108    desc: LitStr,
109    desc_categorized: Option<LitStr>,
110    info: LitStr,
111    info_categorized: Option<LitStr>,
112    category_key: Option<LitStr>,
113    values: Vec<CoreOptionValue>,
114    default_value: Option<LitStr>,
115}
116
117impl Parse for CoreOptionV2 {
118    fn parse(input: ParseStream) -> Result<Self> {
119        let key: LitStr = input.parse()?;
120        input.parse::<Token![,]>()?;
121
122        let desc: LitStr = input.parse()?;
123        input.parse::<Token![,]>()?;
124
125        let desc_categorized: LitStr = input.parse()?;
126        input.parse::<Token![,]>()?;
127
128        let info: LitStr = input.parse()?;
129        input.parse::<Token![,]>()?;
130
131        let info_categorized: LitStr = input.parse()?;
132        input.parse::<Token![,]>()?;
133
134        let category_key: LitStr = input.parse()?;
135        input.parse::<Token![,]>()?;
136
137        let options_content;
138        braced!(options_content in input);
139
140        if !input.is_empty() {
141            input.parse::<Token![,]>()?;
142        }
143
144        let default_value: Option<LitStr> = if !input.is_empty() {
145            input.parse()?
146        } else {
147            None
148        };
149
150        let mut values = Vec::new();
151        while !options_content.is_empty() {
152            let value = options_content.parse::<CoreOptionValue>()?;
153            values.push(value);
154
155            if !options_content.is_empty() {
156                options_content.parse::<Token![,]>()?;
157            }
158        }
159
160        let ret = Ok(Self {
161            key,
162            desc,
163            desc_categorized: Some(desc_categorized),
164            info,
165            info_categorized: Some(info_categorized),
166            category_key: Some(category_key),
167            values,
168            default_value,
169        });
170
171        // allow trailing comma
172        if input.is_empty() {
173            return ret;
174        }
175        input.parse::<Token![,]>()?;
176
177        ret
178    }
179}
180
181impl From<CoreOption> for CoreOptionV2 {
182    fn from(option: CoreOption) -> Self {
183        Self {
184            key: option.key,
185            desc: option.desc,
186            desc_categorized: None,
187            info: option.info,
188            info_categorized: None,
189            category_key: None,
190            values: option.values,
191            default_value: option.default_value,
192        }
193    }
194}
195
196#[derive(Debug, Default)]
197struct CoreOptions(Vec<CoreOptionV2>);
198
199impl Parse for CoreOptions {
200    fn parse(outer: ParseStream) -> Result<Self> {
201        let input;
202        parenthesized!(input in outer);
203
204        let mut options = Self::default();
205
206        while !input.is_empty() {
207            let option;
208            braced!(option in input);
209
210            let core_option = {
211                let fork = option.fork();
212                if let Ok(option_v2) = fork.parse::<CoreOptionV2>() {
213                    option.advance_to(&fork);
214                    option_v2
215                } else {
216                    option.parse::<CoreOption>()?.into()
217                }
218            };
219
220            options.0.push(core_option);
221
222            // allow trailing comma
223            if input.is_empty() {
224                break;
225            }
226            input.parse::<Token![,]>()?;
227        }
228
229        Ok(options)
230    }
231}
232
233impl Concat<CoreOptions> for Vec<CoreOptions> {
234    fn concat(self) -> CoreOptions {
235        CoreOptions(self.into_iter().flat_map(|x| x.0).collect::<Vec<_>>())
236    }
237}
238
239#[derive(Debug)]
240struct CoreOptionCategory {
241    key: LitStr,
242    desc: LitStr,
243    info: LitStr,
244}
245
246impl Parse for CoreOptionCategory {
247    fn parse(input: ParseStream) -> Result<Self> {
248        let key: LitStr = input.parse()?;
249        input.parse::<Token![,]>()?;
250
251        let desc: LitStr = input.parse()?;
252        input.parse::<Token![,]>()?;
253
254        let info: LitStr = input.parse()?;
255
256        let ret = Ok(Self { key, desc, info });
257
258        // allow trailing comma
259        if input.is_empty() {
260            return ret;
261        }
262        input.parse::<Token![,]>()?;
263
264        ret
265    }
266}
267
268#[derive(Debug, Default)]
269struct CoreOptionCategories(Vec<CoreOptionCategory>);
270
271impl Parse for CoreOptionCategories {
272    fn parse(outer: ParseStream) -> Result<Self> {
273        let input;
274        parenthesized!(input in outer);
275
276        let mut categories = Self::default();
277
278        while !input.is_empty() {
279            let category;
280            braced!(category in input);
281
282            let category = category.parse::<CoreOptionCategory>()?;
283
284            categories.0.push(category);
285
286            // allow trailing comma
287            if input.is_empty() {
288                break;
289            }
290            input.parse::<Token![,]>()?;
291        }
292
293        Ok(categories)
294    }
295}
296
297impl Concat<CoreOptionCategories> for Vec<CoreOptionCategories> {
298    fn concat(self) -> CoreOptionCategories {
299        CoreOptionCategories(self.into_iter().flat_map(|x| x.0).collect::<Vec<_>>())
300    }
301}
302
303/// Implements the CoreOptions trait by generating a `set_core_options()` implementation
304/// that checks whether the frontend supports “options v2” or “options v1”
305/// and uses `retro_variable`s as fallback.
306///
307/// Initializes the following data structures from the given input:
308/// - `retro_core_option_definition`
309/// - `retro_variable`
310/// - `retro_core_option_v2_category`
311/// - `retro_core_option_v2_definition`
312/// - `retro_core_options_v2`
313///
314/// # Examples
315///
316/// ```ignore
317/// #[derive(CoreOptions)]
318/// #[options({
319///     "foo_option_1",
320///     "Speed hack coprocessor X",
321///     "Provides increased performance at the expense of reduced accuracy",
322///     {
323///         { "false" },
324///         { "true" },
325///         { "unstable", "Turbo (Unstable)" },
326///     },
327///     "true"
328/// }, {
329///     "foo_option_2",
330///     "Speed hack main processor Y",
331///     "Provides increased performance at the expense of reduced accuracy",
332///     {
333///         { "false" },
334///         { "true" },
335///         { "unstable", "Turbo (Unstable)" },
336///     },
337/// })]
338/// struct TestCore;
339/// ```
340///
341/// **TODO**:
342/// - Add V2 (category support) documentation
343/// - Support `*_intl` variants
344#[proc_macro_derive(CoreOptions, attributes(options, categories))]
345pub fn derive_core_options(input: TokenStream) -> TokenStream {
346    let input = parse_macro_input!(input as DeriveInput);
347
348    impl_derive_core_options(input)
349}
350
351fn impl_derive_core_options(input: DeriveInput) -> TokenStream {
352    let name = &input.ident;
353    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
354    let attrs = &input.attrs;
355
356    let options = attrs
357        .iter()
358        .filter(|attr| attr.path.is_ident("options"))
359        .map(|attr| -> Result<CoreOptions> { parse2(attr.tokens.clone()) })
360        .collect::<Result<Vec<_>>>();
361
362    let options = match options {
363        Ok(options) => options.concat(),
364        Err(err) => return TokenStream::from(err.to_compile_error()),
365    };
366
367    let categories = attrs
368        .iter()
369        .filter(|attr| attr.path.is_ident("categories"))
370        .map(|attr| -> Result<CoreOptionCategories> { parse2(attr.tokens.clone()) })
371        .collect::<Result<Vec<_>>>();
372
373    let categories = match categories {
374        Ok(categories) => categories.concat(),
375        Err(err) => return TokenStream::from(err.to_compile_error()),
376    };
377
378    let option_count = options.0.len();
379    let category_count = categories.0.len();
380
381    fn lit_byte_str(lit: &LitStr) -> LitByteStr {
382        let span = lit.span();
383        let mut bytes = lit.value().into_bytes();
384        bytes.push(0x00); // add terminating NULL byte
385
386        LitByteStr::new(&bytes, span)
387    }
388
389    fn get_option_values(option: &CoreOptionV2) -> proc_macro2::TokenStream {
390        let mut values = Vec::new();
391
392        for index in 0..(RETRO_NUM_CORE_OPTION_VALUES_MAX as usize - 1) {
393            values.push(if index < option.values.len() {
394                let value = lit_byte_str(&option.values[index].value);
395
396                if let Some(label) = &option.values[index].label {
397                    let label = lit_byte_str(label);
398
399                    quote! {
400                        retro_core_option_value {
401                            value: #value as *const u8 as *const libc::c_char,
402                            label: #label as *const u8 as *const libc::c_char,
403                        }
404                    }
405                } else {
406                    quote! {
407                        retro_core_option_value {
408                            value: #value as *const u8 as *const libc::c_char,
409                            label: 0 as *const libc::c_char,
410                        }
411                    }
412                }
413            } else {
414                quote! {
415                    retro_core_option_value {
416                        value: 0 as *const libc::c_char,
417                        label: 0 as *const libc::c_char,
418                    }
419                }
420            });
421        }
422
423        values.push(quote! {
424            retro_core_option_value {
425                value: 0 as *const libc::c_char,
426                label: 0 as *const libc::c_char,
427            }
428        });
429
430        quote! {
431            [ #(#values),* ]
432        }
433    }
434
435    fn get_option_default_value(option: &CoreOptionV2) -> proc_macro2::TokenStream {
436        if let Some(ref default_value) = option.default_value {
437            let default_value = lit_byte_str(default_value);
438
439            quote! {
440                #default_value as *const u8 as *const libc::c_char
441            }
442        } else {
443            quote! {
444                0 as *const libc::c_char
445            }
446        }
447    }
448
449    let core_options = options
450        .0
451        .iter()
452        .map(|option| {
453            let key = lit_byte_str(&option.key);
454            let desc = lit_byte_str(&option.desc);
455            let info = lit_byte_str(&option.info);
456            let values = get_option_values(option);
457            let default_value = get_option_default_value(option);
458
459            quote! {
460                retro_core_option_definition {
461                    key:    #key  as *const u8 as *const libc::c_char,
462                    desc:   #desc as *const u8 as *const libc::c_char,
463                    info:   #info as *const u8 as *const libc::c_char,
464                    values: #values,
465                    default_value: #default_value,
466                }
467            }
468        })
469        .collect::<Vec<_>>();
470
471    let core_variables = options
472        .0
473        .iter()
474        .map(|option| {
475            let key = lit_byte_str(&option.key);
476
477            let value = &format!(
478                "{}; {}",
479                &option.desc.value(),
480                option
481                    .values
482                    .iter()
483                    .map(|value| value.value.value())
484                    .collect::<Vec<_>>()
485                    .join("|")
486            )
487            .into_bytes();
488            let value = LitByteStr::new(value, option.desc.span());
489
490            quote! {
491                retro_variable {
492                    key:   #key   as *const u8 as *const libc::c_char,
493                    value: #value as *const u8 as *const libc::c_char,
494                }
495            }
496        })
497        .collect::<Vec<_>>();
498
499    let core_options_v2 = options
500        .0
501        .iter()
502        .map(|option| {
503            let key = lit_byte_str(&option.key);
504            let desc = lit_byte_str(&option.desc);
505            let info = lit_byte_str(&option.info);
506            let values = get_option_values(option);
507            let default_value = get_option_default_value(option);
508
509            let desc_categorized = lit_byte_str(
510                option
511                    .desc_categorized
512                    .as_ref()
513                    .unwrap_or(&LitStr::new("", proc_macro2::Span::call_site())),
514            );
515            let info_categorized = lit_byte_str(
516                option
517                    .info_categorized
518                    .as_ref()
519                    .unwrap_or(&LitStr::new("", proc_macro2::Span::call_site())),
520            );
521            let category_key = lit_byte_str(
522                option
523                    .category_key
524                    .as_ref()
525                    .unwrap_or(&LitStr::new("", proc_macro2::Span::call_site())),
526            );
527
528            quote! {
529                retro_core_option_v2_definition {
530                    key:  #key  as *const u8 as *const libc::c_char,
531                    desc: #desc as *const u8 as *const libc::c_char,
532                    info: #info as *const u8 as *const libc::c_char,
533
534                    desc_categorized: #desc_categorized as *const u8 as *const libc::c_char,
535                    info_categorized: #info_categorized as *const u8 as *const libc::c_char,
536                    category_key:     #category_key     as *const u8 as *const libc::c_char,
537
538                    values: #values,
539                    default_value: #default_value,
540                }
541            }
542        })
543        .collect::<Vec<_>>();
544
545    let core_option_categories = categories
546        .0
547        .iter()
548        .map(|category| {
549            let key = lit_byte_str(&category.key);
550            let desc = lit_byte_str(&category.desc);
551            let info = lit_byte_str(&category.info);
552
553            quote! {
554                retro_core_option_v2_category {
555                    key:    #key  as *const u8 as *const libc::c_char,
556                    desc:   #desc as *const u8 as *const libc::c_char,
557                    info:   #info as *const u8 as *const libc::c_char,
558                }
559            }
560        })
561        .collect::<Vec<_>>();
562
563    let expanded = quote! {
564        impl #impl_generics ::rust_libretro::core::CoreOptions for #name #ty_generics #where_clause {
565            fn set_core_options(&self, ctx: &SetEnvironmentContext) -> bool {
566                let gctx: GenericContext = ctx.into();
567
568                // For some reason the call to `supports_set_core_options` only works on the initial call of `on_set_environment`.
569                // On subsequent calls of `on_set_environment` querying `RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION` returns NULL pointers.
570                // But our `retro_set_environment` wrapper makes sure to call us on the initial call of `on_set_environment` only.
571                match gctx.get_core_options_version() {
572                    n if n >= 2 => ctx.set_core_options_v2(&Self::__RETRO_CORE_OPTIONS_V2),
573                    n if n >= 1 => ctx.set_core_options(&Self::__RETRO_CORE_OPTIONS),
574                    _ => ctx.set_variables(&Self::__RETRO_CORE_VARIABLES)
575                }
576            }
577        }
578
579        impl #impl_generics #name #ty_generics #where_clause {
580            #[doc(hidden)]
581            const __RETRO_CORE_OPTIONS: [retro_core_option_definition; #option_count + 1] = [
582                #(#core_options,)*
583
584                // List terminator
585                retro_core_option_definition {
586                    key:    0 as *const libc::c_char,
587                    desc:   0 as *const libc::c_char,
588                    info:   0 as *const libc::c_char,
589                    values: [retro_core_option_value {
590                        value: 0 as *const libc::c_char,
591                        label: 0 as *const libc::c_char,
592                    }; #RETRO_NUM_CORE_OPTION_VALUES_MAX as usize],
593                    default_value: 0 as *const libc::c_char,
594                }
595            ];
596
597            #[doc(hidden)]
598            const __RETRO_CORE_VARIABLES: [retro_variable; #option_count + 1] = [
599                #(#core_variables,)*
600
601                // List terminator
602                retro_variable {
603                    key:   0 as *const libc::c_char,
604                    value: 0 as *const libc::c_char,
605                }
606            ];
607
608            #[doc(hidden)]
609            const __RETRO_CORE_OPTION_V2_CATEGORIES: [retro_core_option_v2_category; 1 + #category_count] = [
610                #(#core_option_categories,)*
611
612                retro_core_option_v2_category {
613                    key: 0 as *const libc::c_char,
614                    desc: 0 as *const libc::c_char,
615                    info: 0 as *const libc::c_char,
616                }
617            ];
618
619            #[doc(hidden)]
620            const __RETRO_CORE_OPTION_V2_DEFINITIONS: [retro_core_option_v2_definition; #option_count + 1] = [
621                #(#core_options_v2,)*
622
623                // List terminator
624                retro_core_option_v2_definition {
625                    key: 0 as *const libc::c_char,
626                    desc: 0 as *const libc::c_char,
627                    desc_categorized: 0 as *const libc::c_char,
628                    info: 0 as *const libc::c_char,
629                    info_categorized: 0 as *const libc::c_char,
630                    category_key: 0 as *const libc::c_char,
631                    values: [retro_core_option_value {
632                        value: 0 as *const libc::c_char,
633                        label: 0 as *const libc::c_char,
634                    }; 128],
635                    default_value: 0 as *const libc::c_char,
636                }
637            ];
638
639            #[doc(hidden)]
640            const __RETRO_CORE_OPTIONS_V2: retro_core_options_v2 = retro_core_options_v2 {
641                /// HERE BE DRAGONS, but mutable references are not allowed
642                categories: &Self::__RETRO_CORE_OPTION_V2_CATEGORIES as *const _ as *mut _,
643                /// HERE BE DRAGONS, but mutable references are not allowed
644                definitions: &Self::__RETRO_CORE_OPTION_V2_DEFINITIONS as *const _ as *mut _,
645            };
646        }
647    };
648
649    TokenStream::from(expanded)
650}
651
652const UNSTABLE_TAG: &str = "<span class='stab unstable'>Unstable</span>";
653
654fn get_unstable_text(feature_name: &str) -> String {
655    format!(
656        "# This feature is unstable and guarded by the `{}` feature flag.\
657        \n\
658        Please be advised that this feature might change without further notice \
659        and no guarantees about its stability can be made.",
660        feature_name
661    )
662}
663
664fn add_unstable_text(attrs: &mut Vec<Attribute>, feature_name: &str) {
665    prepend_doc(attrs, UNSTABLE_TAG);
666
667    let unstable_doc = get_unstable_text(feature_name);
668
669    attrs.push(syn::parse_quote! {
670        #[doc = #unstable_doc]
671    });
672}
673
674/// Marks a function or struct (item) as unstable and guards it behind a feature flag.
675///
676/// The defining crate is allowed to use functions marked as unstable even when the feature is disabled.
677///
678/// # Examples
679///
680/// ```rust
681/// #[rust_libretro_proc::unstable(feature = "name")]
682/// fn my_example_function() { }
683/// ```
684///
685/// ```rust
686/// // We must add an empty `rust_libretro_proc::unstable` attribute to the struct,
687/// // in order to process the struct items.
688/// #[rust_libretro_proc::unstable]
689/// struct Example {
690///     pub stable_struct_item: bool,
691///
692///     #[unstable(feature = "name")]
693///     pub unstable_struct_item: bool,
694/// }
695/// ```
696#[proc_macro_attribute]
697pub fn unstable(args: TokenStream, input: TokenStream) -> TokenStream {
698    use syn::{AttributeArgs, Item, Lit, Meta, MetaList, Visibility};
699
700    let args = parse_macro_input!(args as AttributeArgs);
701    let mut item = parse_macro_input!(input as Item);
702
703    // Handle unstable struct items
704    if let Item::Struct(ref mut item) = item {
705        if args.is_empty() {
706            if let syn::Fields::Named(fields) = &mut item.fields {
707                let len = fields.named.len();
708
709                for index in 0..len {
710                    let field = &mut fields.named[index];
711                    let metas = field
712                        .attrs
713                        .iter()
714                        .filter(|attr| attr.path.is_ident("unstable"))
715                        .filter_map(|attr| attr.parse_meta().ok())
716                        .collect::<Vec<_>>();
717
718                    field.attrs.retain(|attr| !attr.path.is_ident("unstable"));
719
720                    if matches!(field.vis, Visibility::Public(_)) && !metas.is_empty() {
721                        let mut private_item = field.clone();
722                        private_item.vis = parse_quote!(pub(crate));
723
724                        for meta in &metas {
725                            let mut feature_name = "unstable".to_owned();
726
727                            if let Meta::List(MetaList { nested, .. }) = meta {
728                                if let NestedMeta::Meta(Meta::NameValue(ref named_value)) =
729                                    nested[0]
730                                {
731                                    if let Lit::Str(custom_name) = &named_value.lit {
732                                        feature_name = format!("unstable-{}", custom_name.value());
733                                    }
734                                }
735                            }
736
737                            add_unstable_text(&mut field.attrs, &feature_name);
738                            field.attrs.push(syn::parse_quote! {
739                                #[cfg(feature = #feature_name)]
740                            });
741
742                            add_unstable_text(&mut private_item.attrs, &feature_name);
743                            private_item.attrs.push(syn::parse_quote! {
744                                #[cfg(not(feature = #feature_name))]
745                            });
746                        }
747
748                        fields.named.push(private_item);
749                    }
750                }
751            }
752
753            return item.into_token_stream().into();
754        }
755    }
756
757    let feature_name = {
758        let mut name = "unstable".to_owned();
759
760        for arg in args.iter() {
761            if let NestedMeta::Lit(Lit::Str(custom_name)) = arg {
762                name = format!("unstable-{}", custom_name.value());
763                break;
764            } else if let NestedMeta::Meta(Meta::NameValue(named_value)) = arg {
765                if let Lit::Str(custom_name) = &named_value.lit {
766                    name = format!("unstable-{}", custom_name.value());
767                    break;
768                }
769            }
770        }
771
772        name
773    };
774
775    if let Item::Fn(ref mut item) = item {
776        // Mark the function as unsafe
777        item.sig.unsafety = Some(parse_quote!(unsafe));
778    }
779
780    if is_public(&item) {
781        if let Some(attrs) = get_attrs_mut(&mut item) {
782            add_unstable_text(attrs, &feature_name);
783        }
784
785        let mut private_item = item.clone();
786        if let Some(vis) = get_visibility_mut(&mut private_item) {
787            *vis = parse_quote!(pub(crate));
788        }
789
790        return TokenStream::from(quote! {
791            #[cfg(feature = #feature_name)]
792            #[allow(unused_unsafe)]
793            #item
794
795            #[cfg(not(feature = #feature_name))]
796            #[allow(unused_unsafe)]
797            #[allow(dead_code)]
798            #private_item
799        });
800    }
801
802    item.into_token_stream().into()
803}
804
805#[doc(hidden)]
806#[proc_macro_attribute]
807pub fn context(args: TokenStream, input: TokenStream) -> TokenStream {
808    let ctx_name = parse_macro_input!(args as syn::Ident);
809
810    let item = parse_macro_input!(input as syn::ItemFn);
811    let mut fun = item.clone();
812
813    // Mark functions as safe in this context
814    fun.sig.unsafety = None;
815
816    let mut inputs: Punctuated<syn::FnArg, Token![,]> = Punctuated::new();
817    inputs.push(parse_quote!(&self));
818
819    // Remove the environment callback argument
820    for arg in fun.sig.inputs.iter().filter(|input| {
821        if let syn::FnArg::Typed(arg) = input {
822            if let syn::Type::Path(ty) = &*arg.ty {
823                if ty.path.is_ident("retro_environment_t")
824                    || ty.path.segments.last().unwrap().ident == "retro_environment_t"
825                {
826                    return false;
827                }
828            }
829        }
830
831        true
832    }) {
833        inputs.push(arg.clone());
834    }
835
836    // Remove the `context` attribute
837    fun.attrs
838        .retain(|attr| attr.path.segments.last().unwrap().ident != "context");
839
840    // Replace the function arguments
841    fun.sig.inputs = inputs;
842
843    // Create the function call
844    let fun_name = &fun.sig.ident;
845    let mut fun_call_args: Punctuated<syn::Expr, Token![,]> = Punctuated::new();
846    fun_call_args.push(parse_quote!(*self.environment_callback));
847
848    // Skip the `self` argument
849    for arg in fun.sig.inputs.iter().skip(1) {
850        if let syn::FnArg::Typed(arg) = arg {
851            if let syn::Pat::Ident(pat_ident) = &*arg.pat {
852                let ident = &pat_ident.ident;
853                fun_call_args.push(parse_quote!(#ident));
854            }
855        }
856    }
857
858    fun.block = parse_quote! {{
859        unsafe {
860            environment::#fun_name(#fun_call_args)
861        }
862    }};
863
864    let ctx_impl = quote! {
865        #item
866
867        impl #ctx_name<'_> {
868            #[inline]
869            #[allow(deprecated)]
870            #fun
871        }
872    };
873
874    TokenStream::from(ctx_impl)
875}