palette_derive/convert/
from_color_unclamped.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::quote;
4use syn::{parse_quote, DeriveInput, Generics, Ident, Result, Type};
5
6use crate::{
7    color_types::{ColorInfo, MetaTypeSource, XYZ_COLORS},
8    convert::util::{InputUser, WhitePointSource},
9    meta::{
10        parse_field_attributes, parse_namespaced_attributes, FieldAttributes, IdentOrIndex,
11        TypeItemAttributes,
12    },
13    util,
14};
15
16use super::util::{component_type, find_nearest_color, get_convert_color_type, white_point_type};
17
18pub fn derive(item: TokenStream) -> ::std::result::Result<TokenStream, Vec<::syn::parse::Error>> {
19    let DeriveInput {
20        ident,
21        generics,
22        data,
23        attrs,
24        ..
25    } = syn::parse(item).map_err(|error| vec![error])?;
26
27    let (mut item_meta, item_errors) = parse_namespaced_attributes::<TypeItemAttributes>(attrs);
28
29    let (fields_meta, field_errors) = if let syn::Data::Struct(struct_data) = data {
30        parse_field_attributes::<FieldAttributes>(struct_data.fields)
31    } else {
32        return Err(vec![syn::Error::new(
33            Span::call_site(),
34            "only structs are supported",
35        )]);
36    };
37
38    let component = component_type(item_meta.component.clone());
39    let white_point = white_point_type(
40        item_meta.white_point.as_ref(),
41        item_meta.rgb_standard.as_ref(),
42        item_meta.luma_standard.as_ref(),
43        item_meta.internal,
44    );
45
46    let alpha_field = fields_meta.alpha_property;
47
48    // Assume conversion from the root type (Xyz for the base group) by default
49    if item_meta.color_groups.is_empty() {
50        item_meta.color_groups.insert((&XYZ_COLORS).into());
51    }
52
53    if item_meta.skip_derives.is_empty() {
54        for group in &item_meta.color_groups {
55            item_meta.skip_derives.insert(group.root_type.name.into());
56        }
57    }
58
59    let (all_from_impl_params, impl_params_errors) =
60        prepare_from_impl(&component, white_point, &item_meta, &generics);
61
62    let mut implementations =
63        generate_from_implementations(&ident, &generics, &item_meta, &all_from_impl_params);
64
65    if let Some((alpha_property, alpha_type)) = alpha_field {
66        implementations.push(generate_from_alpha_implementation_with_internal(
67            &ident,
68            &generics,
69            &item_meta,
70            &alpha_property,
71            &alpha_type,
72        ));
73    } else {
74        implementations.push(generate_from_alpha_implementation(
75            &ident, &generics, &item_meta,
76        ));
77    }
78
79    let item_errors = item_errors
80        .into_iter()
81        .map(|error| error.into_compile_error());
82    let field_errors = field_errors
83        .into_iter()
84        .map(|error| error.into_compile_error());
85    let impl_params_errors = impl_params_errors
86        .into_iter()
87        .map(|error| error.into_compile_error());
88
89    Ok(quote! {
90        #(#item_errors)*
91        #(#field_errors)*
92        #(#impl_params_errors)*
93
94        #(#implementations)*
95    }
96    .into())
97}
98
99fn prepare_from_impl(
100    component: &Type,
101    white_point: Option<(Type, WhitePointSource)>,
102    meta: &TypeItemAttributes,
103    generics: &Generics,
104) -> (Vec<FromImplParameters>, Vec<syn::Error>) {
105    let included_colors = meta
106        .color_groups
107        .iter()
108        .flat_map(|group| group.color_names())
109        .filter(|&color| !meta.skip_derives.contains(color.name));
110
111    let mut parameters = Vec::new();
112    let mut errors = Vec::new();
113
114    for color in included_colors {
115        let impl_params = prepare_from_impl_for_pair(
116            color,
117            component,
118            white_point.clone(),
119            meta,
120            generics.clone(),
121        );
122
123        match impl_params {
124            Ok(Some(impl_params)) => parameters.push(impl_params),
125            Ok(None) => {}
126            Err(error) => errors.push(error),
127        }
128    }
129
130    (parameters, errors)
131}
132
133fn prepare_from_impl_for_pair(
134    color: &ColorInfo,
135    component: &Type,
136    white_point: Option<(Type, WhitePointSource)>,
137    meta: &TypeItemAttributes,
138    mut generics: Generics,
139) -> Result<Option<FromImplParameters>> {
140    let nearest_color = find_nearest_color(color, meta)?;
141
142    // Figures out which white point the target type prefers, unless it's specified in `white_point`.
143    let (white_point, white_point_source) = if let Some((white_point, source)) = white_point {
144        (white_point, source)
145    } else {
146        color.get_default_white_point(meta.internal)
147    };
148
149    let (color_ty, mut used_input) =
150        get_convert_color_type(color, &white_point, component, meta, &mut generics)?;
151
152    let nearest_color_ty = nearest_color.get_type(
153        MetaTypeSource::OtherColor(color),
154        component,
155        &white_point,
156        &mut used_input,
157        InputUser::Nearest,
158        meta,
159    )?;
160
161    // Skip implementing the trait where it wouldn't be able to constrain the
162    // white point. This is only happening when certain optional features are
163    // enabled.
164    if used_input.white_point.is_unconstrained()
165        && matches!(white_point_source, WhitePointSource::GeneratedGeneric)
166    {
167        return Ok(None);
168    }
169
170    if used_input.white_point.is_used() {
171        match white_point_source {
172            WhitePointSource::WhitePoint => {
173                let white_point_path = util::path(["white_point", "WhitePoint"], meta.internal);
174                generics
175                    .make_where_clause()
176                    .predicates
177                    .push(parse_quote!(#white_point: #white_point_path<#component>))
178            }
179            WhitePointSource::RgbStandard => {
180                let rgb_standard_path = util::path(["rgb", "RgbStandard"], meta.internal);
181                let rgb_standard = meta.rgb_standard.as_ref();
182                generics
183                    .make_where_clause()
184                    .predicates
185                    .push(parse_quote!(#rgb_standard: #rgb_standard_path));
186            }
187            WhitePointSource::LumaStandard => {
188                let luma_standard_path = util::path(["luma", "LumaStandard"], meta.internal);
189                let luma_standard = meta.luma_standard.as_ref();
190                generics
191                    .make_where_clause()
192                    .predicates
193                    .push(parse_quote!(#luma_standard: #luma_standard_path));
194            }
195            WhitePointSource::ConcreteType => {}
196            WhitePointSource::GeneratedGeneric => {
197                generics.params.push(parse_quote!(_Wp));
198            }
199        }
200    }
201
202    Ok(Some(FromImplParameters {
203        generics,
204        color_ty,
205        nearest_color_ty,
206    }))
207}
208
209struct FromImplParameters {
210    generics: Generics,
211    color_ty: Type,
212    nearest_color_ty: Type,
213}
214
215fn generate_from_implementations(
216    ident: &Ident,
217    generics: &Generics,
218    meta: &TypeItemAttributes,
219    all_parameters: &[FromImplParameters],
220) -> Vec<TokenStream2> {
221    let from_trait_path = util::path(["convert", "FromColorUnclamped"], meta.internal);
222    let into_trait_path = util::path(["convert", "IntoColorUnclamped"], meta.internal);
223
224    let (_, type_generics, _) = generics.split_for_impl();
225
226    let mut implementations = Vec::with_capacity(all_parameters.len());
227
228    for parameters in all_parameters {
229        let FromImplParameters {
230            color_ty,
231            generics,
232            nearest_color_ty,
233        } = parameters;
234
235        {
236            let mut generics = generics.clone();
237
238            {
239                let where_clause = generics.make_where_clause();
240                where_clause
241                    .predicates
242                    .push(parse_quote!(#nearest_color_ty: #from_trait_path<#color_ty>));
243                where_clause
244                    .predicates
245                    .push(parse_quote!(#nearest_color_ty: #into_trait_path<Self>));
246            }
247
248            let (impl_generics, _, where_clause) = generics.split_for_impl();
249
250            implementations.push(quote! {
251                #[automatically_derived]
252                impl #impl_generics #from_trait_path<#color_ty> for #ident #type_generics #where_clause {
253                    fn from_color_unclamped(color: #color_ty) -> Self {
254                        use #from_trait_path;
255                        use #into_trait_path;
256                        #nearest_color_ty::from_color_unclamped(color).into_color_unclamped()
257                    }
258                }
259            });
260        }
261
262        if !meta.internal || meta.internal_not_base_type {
263            let mut generics = generics.clone();
264
265            {
266                let where_clause = generics.make_where_clause();
267                where_clause
268                    .predicates
269                    .push(parse_quote!(#nearest_color_ty: #from_trait_path<#ident #type_generics>));
270                where_clause
271                    .predicates
272                    .push(parse_quote!(#nearest_color_ty: #into_trait_path<Self>));
273            }
274
275            let (impl_generics, _, where_clause) = generics.split_for_impl();
276
277            implementations.push(quote! {
278                #[automatically_derived]
279                impl #impl_generics #from_trait_path<#ident #type_generics> for #color_ty #where_clause {
280                    fn from_color_unclamped(color: #ident #type_generics) -> Self {
281                        use #from_trait_path;
282                        use #into_trait_path;
283                        #nearest_color_ty::from_color_unclamped(color).into_color_unclamped()
284                    }
285                }
286            });
287        }
288    }
289
290    implementations
291}
292
293fn generate_from_alpha_implementation(
294    ident: &Ident,
295    generics: &Generics,
296    meta: &TypeItemAttributes,
297) -> TokenStream2 {
298    let from_trait_path = util::path(["convert", "FromColorUnclamped"], meta.internal);
299    let into_trait_path = util::path(["convert", "IntoColorUnclamped"], meta.internal);
300    let alpha_path = util::path(["Alpha"], meta.internal);
301
302    let mut impl_generics = generics.clone();
303    impl_generics.params.push(parse_quote!(_C));
304    impl_generics.params.push(parse_quote!(_A));
305    {
306        let where_clause = impl_generics.make_where_clause();
307        where_clause
308            .predicates
309            .push(parse_quote!(_C: #into_trait_path<Self>));
310    }
311
312    let (_, type_generics, _) = generics.split_for_impl();
313    let (self_impl_generics, _, self_where_clause) = impl_generics.split_for_impl();
314
315    quote! {
316        #[automatically_derived]
317        impl #self_impl_generics #from_trait_path<#alpha_path<_C, _A>> for #ident #type_generics #self_where_clause {
318            fn from_color_unclamped(color: #alpha_path<_C, _A>) -> Self {
319                color.color.into_color_unclamped()
320            }
321        }
322    }
323}
324
325fn generate_from_alpha_implementation_with_internal(
326    ident: &Ident,
327    generics: &Generics,
328    meta: &TypeItemAttributes,
329    alpha_property: &IdentOrIndex,
330    alpha_type: &Type,
331) -> TokenStream2 {
332    let from_trait_path = util::path(["convert", "FromColorUnclamped"], meta.internal);
333    let into_trait_path = util::path(["convert", "IntoColorUnclamped"], meta.internal);
334    let alpha_path = util::path(["Alpha"], meta.internal);
335
336    let (_, type_generics, _) = generics.split_for_impl();
337    let mut impl_generics = generics.clone();
338    impl_generics.params.push(parse_quote!(_C));
339    {
340        let where_clause = impl_generics.make_where_clause();
341        where_clause
342            .predicates
343            .push(parse_quote!(_C: #into_trait_path<Self>));
344    }
345    let (impl_generics, _, where_clause) = impl_generics.split_for_impl();
346
347    quote! {
348        #[automatically_derived]
349        impl #impl_generics #from_trait_path<#alpha_path<_C, #alpha_type>> for #ident #type_generics #where_clause {
350            fn from_color_unclamped(color: #alpha_path<_C, #alpha_type>) -> Self {
351                use #from_trait_path;
352                use #into_trait_path;
353
354                let #alpha_path { color, alpha } = color;
355
356                let mut result: Self = color.into_color_unclamped();
357                result.#alpha_property = alpha;
358
359                result
360            }
361        }
362    }
363}