ion_proc/value/
from.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::{
12	Block, Data, DeriveInput, Error, Expr, Field, Fields, Generics, ItemImpl, Result, Type, parse_quote,
13	parse_quote_spanned, parse2,
14};
15
16use crate::attribute::ParseAttribute as _;
17use crate::attribute::krate::crate_from_attributes;
18use crate::attribute::value::{DataAttribute, DefaultValue, FieldFromAttribute, Tag, VariantAttribute};
19use crate::utils::{
20	add_lifetime_generic, add_trait_bounds, find_repr, format_type, path_ends_with, wrap_in_fields_group,
21};
22use crate::value::field_to_ident_key;
23
24pub(crate) fn impl_from_value(mut input: DeriveInput) -> Result<ItemImpl> {
25	let ion = &crate_from_attributes(&mut input.attrs);
26
27	add_trait_bounds(&mut input.generics, &parse_quote!(#ion::conversions::FromValue));
28	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
29	let mut impl_generics: Generics = parse2(quote_spanned!(impl_generics.span() => #impl_generics))?;
30	add_lifetime_generic(&mut impl_generics, parse_quote!('cx));
31
32	let attribute = DataAttribute::from_attributes("ion", &mut input.attrs, ())?;
33
34	let repr = find_repr(&input.attrs)?;
35	let name = &input.ident;
36
37	let (body, requires_object) = impl_body(ion, input.span(), &mut input.data, name, &attribute, repr)?;
38
39	let object = requires_object.then(|| {
40		quote_spanned!(input.span() =>
41			let __ion_object = #ion::Object::from_value(__ion_cx, __ion_value, true, ())?;
42		)
43	});
44
45	parse2(quote_spanned!(input.span() =>
46		#[automatically_derived]
47		impl #impl_generics #ion::conversions::FromValue<'cx> for #name #ty_generics #where_clause {
48			type Config = ();
49
50			fn from_value(__ion_cx: &'cx #ion::Context, __ion_value: &#ion::Value, strict: bool, _: ()) -> #ion::Result<Self> {
51				#object
52				#body
53			}
54		}
55	))
56}
57
58fn impl_body(
59	ion: &TokenStream, span: Span, data: &mut Data, ident: &Ident, attribute: &DataAttribute, repr: Option<Ident>,
60) -> Result<(Box<Block>, bool)> {
61	match data {
62		Data::Struct(r#struct) => match &r#struct.fields {
63			Fields::Named(_) | Fields::Unnamed(_) => {
64				let requirement = tag_requirement(ion, attribute.tag.0.as_ref(), None)?;
65				let (idents, declarations, requires_object) =
66					map_fields(ion, &mut r#struct.fields, Either::Left(attribute))?;
67				let wrapped = wrap_in_fields_group(idents, &r#struct.fields);
68
69				let block = parse2(quote_spanned!(span => {
70					#requirement
71					#(#declarations)*
72					::std::result::Result::Ok(Self #wrapped)
73				}))?;
74				Ok((block, requires_object))
75			}
76			Fields::Unit => Ok((parse_quote_spanned!(span => { ::std::result::Result::Ok(Self) }), false)),
77		},
78		Data::Enum(r#enum) => {
79			let mut requires_object = false;
80			let mut requires_discriminant = false;
81
82			let mut variants = Vec::with_capacity(r#enum.variants.len());
83
84			for variant in &mut r#enum.variants {
85				let variant_ident = &variant.ident;
86				let variant_string = variant_ident.to_string();
87
88				let attribute = VariantAttribute::from_attributes("ion", &mut variant.attrs, attribute)?;
89				if attribute.skip {
90					continue;
91				}
92
93				let handle_result = quote!(if let ::std::result::Result::Ok(success) = variant {
94					return ::std::result::Result::Ok(success);
95				});
96				let variant: Block = match &variant.fields {
97					Fields::Named(_) | Fields::Unnamed(_) => {
98						let requirement = tag_requirement(ion, attribute.tag.0.as_ref(), Some(variant_string))?;
99						let (idents, declarations, req_object) =
100							map_fields(ion, &mut variant.fields, Either::Right(&attribute))?;
101						let wrapped = wrap_in_fields_group(idents, &variant.fields);
102						requires_object = requires_object || req_object;
103
104						parse2(quote_spanned!(variant.span() => {
105							let variant: #ion::Result<Self> = (|| {
106								#requirement
107								#(#declarations)*
108								::std::result::Result::Ok(Self::#variant_ident #wrapped)
109							})();
110							#handle_result
111						}))?
112					}
113					Fields::Unit => match &variant.discriminant {
114						Some((_, discriminant)) => {
115							if repr.is_none() {
116								continue;
117							}
118
119							requires_discriminant = true;
120							parse2(quote_spanned!(
121								variant.fields.span() => {
122									if discriminant == #discriminant {
123										return ::std::result::Result::Ok(Self::#variant_ident);
124									}
125								}
126							))?
127						}
128						None => parse_quote!({return ::std::result::Result::Ok(Self::#variant_ident);}),
129					},
130				};
131
132				variants.push(variant);
133			}
134
135			let error = format!("Value does not match any of the variants of enum {ident}");
136
137			let mut if_unit = None;
138
139			if requires_discriminant && let Some(repr) = repr {
140				if_unit = Some(quote_spanned!(repr.span() =>
141					let discriminant: #repr = #ion::conversions::FromValue::from_value(__ion_cx, __ion_value, true, #ion::conversions::ConversionBehavior::EnforceRange)?;
142				));
143			}
144
145			parse2(quote_spanned!(span => {
146				#if_unit
147				#(#variants)*
148
149				::std::result::Result::Err(#ion::Error::new(#error, #ion::ErrorKind::Type))
150			}))
151			.map(|b| (b, requires_object))
152		}
153		Data::Union(_) => Err(Error::new(
154			span,
155			"#[derive(FromValue)] is not implemented for union types",
156		)),
157	}
158}
159
160fn tag_requirement(ion: &TokenStream, tag: Option<&Tag>, variant: Option<String>) -> Result<Option<TokenStream>> {
161	if variant.is_none() {
162		return if matches!(tag, Some(Tag::External | Tag::Internal(_))) {
163			Err(Error::new(Span::call_site(), "Cannot have Tag for Struct"))
164		} else {
165			Ok(None)
166		};
167	}
168	let variant = variant.unwrap();
169
170	match tag {
171		Some(Tag::External) => {
172			let error = format!("Expected Object at External Tag {variant}");
173			Ok(Some(quote!(
174				let __ion_object: #ion::Object = __ion_object.get_as(__ion_cx, #variant, true, ())?
175					.ok_or_else(|_| #ion::Error::new(#error, #ion::ErrorKind::Type))?;
176			)))
177		}
178		Some(Tag::Internal(key)) => {
179			let missing_error = format!("Expected Internal Tag key {}", key.value());
180			let error = format!("Expected Internal Tag {variant} at key {}", key.value());
181
182			Ok(Some(quote!(
183				let __ion_key: ::std::string::String = __ion_object.get_as(__ion_cx, #key, true, ())?
184					.ok_or_else(|| #ion::Error::new(#missing_error, #ion::ErrorKind::Type))?;
185				if __ion_key != #variant {
186					return Err(#ion::Error::new(#error, #ion::ErrorKind::Type));
187				}
188			)))
189		}
190		_ => Ok(None),
191	}
192}
193
194fn map_fields(
195	ion: &TokenStream, fields: &mut Fields, attribute: Either<&DataAttribute, &VariantAttribute>,
196) -> Result<(Vec<Ident>, Vec<TokenStream>, bool)> {
197	let mut requires_object = matches!(get_tag(attribute), Some(Tag::External | Tag::Internal(_)));
198	let mut idents = Vec::with_capacity(fields.len());
199	let mut declarations = Vec::with_capacity(fields.len());
200
201	for (index, field) in fields.iter_mut().enumerate() {
202		let (ident, mut key) = field_to_ident_key(field, index);
203		let ty = &field.ty;
204
205		let mut optional = false;
206		if let Type::Path(ty) = ty
207			&& path_ends_with(&ty.path, "Option")
208		{
209			optional = true;
210		}
211
212		let attribute = FieldFromAttribute::from_attributes("ion", &mut field.attrs, attribute)?;
213		if let Some(name) = attribute.base.name {
214			key = name.value();
215		}
216		if attribute.base.skip {
217			continue;
218		}
219
220		let strict = attribute.strict;
221		let convert = attribute.convert;
222
223		let convert = convert.unwrap_or_else(|| parse_quote!(()));
224		let base = field_base(
225			ion,
226			field,
227			&ident,
228			ty,
229			&key,
230			attribute.base.inherit,
231			&mut requires_object,
232			strict,
233			&convert,
234			attribute.parser.as_deref(),
235		)?;
236
237		let stmt = if optional {
238			quote_spanned!(field.span() => #base.ok();)
239		} else {
240			match attribute.default.0 {
241				Some(DefaultValue::Expr(expr)) => {
242					if attribute.base.inherit {
243						return Err(Error::new(
244							field.span(),
245							"Cannot have Default Expression with Inherited Field. Use a Closure with One Argument Instead",
246						));
247					}
248					quote_spanned!(field.span() => #base.unwrap_or_else(|_| #expr);)
249				}
250				Some(DefaultValue::Closure(closure)) => {
251					quote_spanned!(field.span() => #base.unwrap_or_else(#closure);)
252				}
253				Some(DefaultValue::Literal(lit)) => quote_spanned!(field.span() => #base.unwrap_or(#lit);),
254				Some(DefaultValue::Default) => quote_spanned!(field.span() => #base.unwrap_or_default();),
255				None => quote_spanned!(field.span() => #base?;),
256			}
257		};
258
259		idents.push(ident);
260		declarations.push(stmt);
261	}
262
263	Ok((idents, declarations, requires_object))
264}
265
266#[expect(clippy::too_many_arguments)]
267fn field_base(
268	ion: &TokenStream, field: &Field, ident: &Ident, ty: &Type, key: &str, inherit: bool, requires_object: &mut bool,
269	strict: bool, convert: &Expr, parser: Option<&Expr>,
270) -> Result<TokenStream> {
271	if inherit {
272		if *requires_object {
273			return Err(Error::new(
274				field.span(),
275				"Inherited Field cannot be parsed from a Tagged Enum",
276			));
277		}
278		Ok(quote_spanned!(field.span() =>
279			let #ident: #ty = <#ty as #ion::conversions::FromValue>::from_value(__ion_cx, __ion_value, #strict || strict, #convert)
280		))
281	} else if let Some(parser) = parser {
282		*requires_object = true;
283		let error = format!("Expected Value at Key {key}");
284		Ok(quote_spanned!(field.span() =>
285			let #ident: #ty = __ion_object.get(__ion_cx, #key)?.map(#parser).transpose()?
286					.ok_or_else(|| #ion::Error::new(#error, #ion::ErrorKind::Type))
287		))
288	} else {
289		*requires_object = true;
290		let error = format!("Expected Value at key {key} of Type {}", format_type(ty));
291		Ok(quote_spanned!(field.span() =>
292			let #ident: #ty = __ion_object.get_as(__ion_cx, #key, #strict || strict, #convert)?
293					.ok_or_else(|| #ion::Error::new(#error, #ion::ErrorKind::Type))
294		))
295	}
296}
297
298fn get_tag<'a>(attribute: Either<&'a DataAttribute, &'a VariantAttribute>) -> Option<&'a Tag> {
299	match attribute {
300		Either::Left(data) => data.tag.0.as_ref(),
301		Either::Right(variant) => variant.tag.0.as_ref(),
302	}
303}