1use either::Either;
8use proc_macro2::{Ident, Span, TokenStream};
9use quote::{quote, quote_spanned};
10use syn::spanned::Spanned as _;
11use syn::{Block, Data, DeriveInput, Error, Fields, Generics, ItemImpl, Result, parse_quote, parse2};
12
13use crate::attribute::ParseAttribute as _;
14use crate::attribute::krate::crate_from_attributes;
15use crate::attribute::value::{DataAttribute, FieldAttribute, Tag, VariantAttribute};
16use crate::utils::{add_lifetime_generic, add_trait_bounds, wrap_in_fields_group};
17use crate::value::field_to_ident_key;
18
19pub(crate) fn impl_to_value(mut input: DeriveInput) -> Result<ItemImpl> {
20 let ion = &crate_from_attributes(&mut input.attrs);
21
22 add_trait_bounds(&mut input.generics, &parse_quote!(#ion::conversions::ToValue));
23 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
24 let mut impl_generics: Generics = parse2(quote_spanned!(impl_generics.span() => #impl_generics))?;
25 add_lifetime_generic(&mut impl_generics, parse_quote!('cx));
26
27 let attribute = DataAttribute::from_attributes("ion", &mut input.attrs, ())?;
28
29 let name = &input.ident;
30 let (body, requires_object) = impl_body(ion, input.span(), &mut input.data, &attribute)?;
31
32 let prefix = requires_object.then(|| quote!(let __ion_object = #ion::Object::new(__ion_cx);));
33 let postfix = requires_object.then(|| {
34 quote!(
35 #ion::conversions::ToValue::to_value(&__ion_object, __ion_cx, __ion_value);
36 )
37 });
38
39 parse2(quote_spanned!(input.span() =>
40 #[automatically_derived]
41 impl #impl_generics #ion::conversions::ToValue<'cx> for #name #ty_generics #where_clause {
42 fn to_value(&self, __ion_cx: &'cx #ion::Context, __ion_value: &mut #ion::Value) {
43 #prefix
44 #body
45 #postfix
46 }
47 }
48 ))
49}
50
51fn impl_body(ion: &TokenStream, span: Span, data: &mut Data, attribute: &DataAttribute) -> Result<(TokenStream, bool)> {
52 let (variants, idents, blocks, requires_object) = match data {
53 Data::Struct(r#struct) => {
54 let (idents, statements, requires_object) = map_fields(ion, &mut r#struct.fields, Either::Left(attribute))?;
55 let wrapped = wrap_in_fields_group(idents, &r#struct.fields);
56 let requirement = tag_requirement(ion, attribute.tag.0.as_ref(), None, requires_object)?;
57
58 let block: Block = parse2(quote_spanned!(span => {
59 #requirement
60 #(#statements)*
61 }))?;
62 (None, vec![wrapped], vec![block], requires_object)
63 }
64 Data::Enum(r#enum) => {
65 let mut variants = Vec::with_capacity(r#enum.variants.len());
66 let mut variant_idents = Vec::with_capacity(r#enum.variants.len());
67 let mut blocks = Vec::with_capacity(r#enum.variants.len());
68 let mut requires_object = false;
69
70 for variant in &mut r#enum.variants {
71 let variant_string = variant.ident.to_string();
72
73 let attribute = VariantAttribute::from_attributes("ion", &mut variant.attrs, attribute)?;
74 if attribute.skip {
75 continue;
76 }
77
78 let (idents, statements, req_object) = map_fields(ion, &mut variant.fields, Either::Right(&attribute))?;
79 let wrapped = wrap_in_fields_group(idents, &variant.fields);
80 let requirement = tag_requirement(ion, attribute.tag.0.as_ref(), Some(variant_string), req_object)?;
81 requires_object =
82 requires_object
83 || req_object || matches!(attribute.tag.0.as_ref(), Some(Tag::External | Tag::Internal(_)));
84
85 let block = parse2(quote_spanned!(span => {
86 #requirement
87 #(#statements)*
88 }))?;
89
90 variants.push(variant.ident.clone());
91 variant_idents.push(wrapped);
92 blocks.push(block);
93 }
94
95 (Some(variants), variant_idents, blocks, requires_object)
96 }
97 Data::Union(_) => {
98 return Err(Error::new(
99 span,
100 "#[derive(ToValue)] is not implemented for union types",
101 ));
102 }
103 };
104
105 let body = if let Some(variants) = variants {
106 quote_spanned!(span => match self {
107 #(Self::#variants #idents => #blocks)*
108 })
109 } else {
110 quote_spanned!(span => match self {
111 #(Self #idents => #blocks)*
112 })
113 };
114
115 Ok((body, requires_object))
116}
117
118fn tag_requirement(
119 ion: &TokenStream, tag: Option<&Tag>, variant: Option<String>, requires_object: bool,
120) -> Result<Option<TokenStream>> {
121 let Some(variant) = variant else {
122 return if matches!(&tag, Some(Tag::External | Tag::Internal(_))) {
123 Err(Error::new(Span::call_site(), "Cannot have Tag for Struct"))
124 } else {
125 Ok(None)
126 };
127 };
128
129 let object = requires_object.then(|| {
130 quote!(
131 let __ion_object = __ion_object.get_as(__ion_cx, #variant).unwrap().unwrap();
132 )
133 });
134 match tag {
135 Some(Tag::External) => Ok(Some(quote!(
136 __ion_object.set_as(__ion_cx, #variant, #ion::Object::new(__ion_cx));
137 let __ion_value = __ion_object.get(__ion_cx, #variant).unwrap().unwrap();
138 #object
139 ))),
140 Some(Tag::Internal(tag)) => Ok(Some(quote!(
141 __ion_object.set_as(__ion_cx, #tag, #variant);
142 ))),
143 _ => Ok(None),
144 }
145}
146
147fn map_fields(
148 ion: &TokenStream, fields: &mut Fields, attribute: Either<&DataAttribute, &VariantAttribute>,
149) -> Result<(Vec<Ident>, Vec<TokenStream>, bool)> {
150 let mut idents = Vec::with_capacity(fields.len());
151 let mut statements = Vec::with_capacity(fields.len());
152 let mut requires_object = false;
153
154 for (index, field) in fields.iter_mut().enumerate() {
155 let (ident, mut key) = field_to_ident_key(field, index);
156
157 let attribute = FieldAttribute::from_attributes("ion", &mut field.attrs, attribute)?;
158 if let Some(name) = attribute.name {
159 key = name.value();
160 }
161 if attribute.skip {
162 continue;
163 }
164
165 let statement = if attribute.inherit {
166 quote_spanned!(field.span() => #ion::conversions::ToValue::to_value(#ident, __ion_cx, __ion_value);)
167 } else {
168 requires_object = true;
169 quote_spanned!(field.span() => __ion_object.set_as(__ion_cx, #key, #ident);)
170 };
171
172 idents.push(ident);
173 statements.push(statement);
174 }
175
176 Ok((idents, statements, requires_object))
177}