1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use crate::utility::{StringExpr, WhereClauseOptions};
use quote::{quote, ToTokens};

use crate::{
    derive_data::{ReflectMeta, ReflectTypePath},
    utility::wrap_in_option,
};

/// Returns an expression for a `NonGenericTypeCell` or `GenericTypeCell`  to generate `'static` references.
fn static_type_cell(
    meta: &ReflectMeta,
    property: TypedProperty,
    generator: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
    let bevy_reflect_path = meta.bevy_reflect_path();
    if meta.type_path().impl_is_generic() {
        let cell_type = match property {
            TypedProperty::TypePath => quote!(GenericTypePathCell),
            TypedProperty::TypeInfo => quote!(GenericTypeInfoCell),
        };

        quote! {
            static CELL: #bevy_reflect_path::utility::#cell_type = #bevy_reflect_path::utility::#cell_type::new();
            CELL.get_or_insert::<Self, _>(|| {
                #generator
            })
        }
    } else {
        let cell_type = match property {
            TypedProperty::TypePath => unreachable!(
                "Cannot have a non-generic type path cell. Use string literals and core::concat instead."
            ),
            TypedProperty::TypeInfo => quote!(NonGenericTypeInfoCell),
        };

        quote! {
            static CELL: #bevy_reflect_path::utility::#cell_type = #bevy_reflect_path::utility::#cell_type::new();
            CELL.get_or_set(|| {
                #generator
            })
        }
    }
}

#[derive(Clone, Copy)]
pub(crate) enum TypedProperty {
    TypeInfo,
    TypePath,
}

pub(crate) fn impl_type_path(meta: &ReflectMeta) -> proc_macro2::TokenStream {
    let where_clause_options = WhereClauseOptions::new(meta);

    if !meta.attrs().type_path_attrs().should_auto_derive() {
        return proc_macro2::TokenStream::new();
    }

    let type_path = meta.type_path();
    let bevy_reflect_path = meta.bevy_reflect_path();

    let (long_type_path, short_type_path) = if type_path.impl_is_generic() {
        let long_path_cell = static_type_cell(
            meta,
            TypedProperty::TypePath,
            type_path.long_type_path(bevy_reflect_path).into_owned(),
        );
        let short_path_cell = static_type_cell(
            meta,
            TypedProperty::TypePath,
            type_path.short_type_path(bevy_reflect_path).into_owned(),
        );
        (
            long_path_cell.to_token_stream(),
            short_path_cell.to_token_stream(),
        )
    } else {
        (
            type_path.long_type_path(bevy_reflect_path).into_borrowed(),
            type_path.short_type_path(bevy_reflect_path).into_borrowed(),
        )
    };

    let type_ident = wrap_in_option(type_path.type_ident().map(StringExpr::into_borrowed));
    let module_path = wrap_in_option(type_path.module_path().map(StringExpr::into_borrowed));
    let crate_name = wrap_in_option(type_path.crate_name().map(StringExpr::into_borrowed));

    let primitive_assert = if let ReflectTypePath::Primitive(_) = type_path {
        Some(quote! {
            const _: () = {
                mod private_scope {
                    // Compiles if it can be named when there are no imports.
                    type AssertIsPrimitive = #type_path;
                }
            };
        })
    } else {
        None
    };

    let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();

    // Add Typed bound for each active field
    let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);

    quote! {
        #primitive_assert

        impl #impl_generics #bevy_reflect_path::TypePath for #type_path #ty_generics #where_reflect_clause {
            fn type_path() -> &'static str {
                #long_type_path
            }

            fn short_type_path() -> &'static str {
                #short_type_path
            }

            fn type_ident() -> Option<&'static str> {
                #type_ident
            }

            fn crate_name() -> Option<&'static str> {
                #crate_name
            }

            fn module_path() -> Option<&'static str> {
                #module_path
            }
        }
    }
}

pub(crate) fn impl_typed(
    meta: &ReflectMeta,
    where_clause_options: &WhereClauseOptions,
    type_info_generator: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
    let type_path = meta.type_path();
    let bevy_reflect_path = meta.bevy_reflect_path();

    let type_info_cell = static_type_cell(meta, TypedProperty::TypeInfo, type_info_generator);

    let (impl_generics, ty_generics, where_clause) = type_path.generics().split_for_impl();

    let where_reflect_clause = where_clause_options.extend_where_clause(where_clause);

    quote! {
        impl #impl_generics #bevy_reflect_path::Typed for #type_path #ty_generics #where_reflect_clause {
            fn type_info() -> &'static #bevy_reflect_path::TypeInfo {
                #type_info_cell
            }
        }
    }
}