ion_proc/value/
to.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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}