1// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7//! Handling of structs
8
9use proc_macro2::{Span, TokenStream};
10use quote::quote;
11use syn::{spanned::Spanned, *};
12
13use crate::common::{AsXmlParts, FromXmlParts, ItemDef};
14use crate::compound::Compound;
15use crate::error_message::ParentRef;
16use crate::meta::{reject_key, Flag, NameRef, NamespaceRef, QNameRef, XmlCompoundMeta};
17use crate::state::{AsItemsSubmachine, FromEventsSubmachine, State};
18use crate::types::{
19 as_xml_iter_fn, feed_fn, from_events_fn, from_xml_builder_ty, item_iter_ty, ref_ty,
20 ty_from_ident,
21};
22
23/// The inner parts of the struct.
24///
25/// This contains all data necessary for the matching logic.
26pub(crate) enum StructInner {
27 /// Single-field struct declared with `#[xml(transparent)]`.
28 ///
29 /// Transparent struct delegate all parsing and serialising to their
30 /// only field, which is why they do not need to store a lot of
31 /// information and come with extra restrictions, such as:
32 ///
33 /// - no XML namespace can be declared (it is determined by inner type)
34 /// - no XML name can be declared (it is determined by inner type)
35 /// - there must be only exactly one field
36 /// - that field has no `#[xml]` attribute
37 Transparent {
38 /// The member identifier of the only field.
39 member: Member,
40
41 /// Type of the only field.
42 ty: Type,
43 },
44
45 /// A compound of fields, *not* declared as transparent.
46 ///
47 /// This can be a unit, tuple-like, or named struct.
48 Compound {
49 /// The XML namespace of the element to map the struct to.
50 xml_namespace: NamespaceRef,
51
52 /// The XML name of the element to map the struct to.
53 xml_name: NameRef,
54
55 /// The field(s) of this struct.
56 inner: Compound,
57 },
58}
59
60impl StructInner {
61 pub(crate) fn new(meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
62 // We destructure here so that we get informed when new fields are
63 // added and can handle them, either by processing them or raising
64 // an error if they are present.
65 let XmlCompoundMeta {
66 span: meta_span,
67 qname: QNameRef { namespace, name },
68 exhaustive,
69 debug,
70 builder,
71 iterator,
72 on_unknown_attribute,
73 on_unknown_child,
74 transparent,
75 discard,
76 deserialize_callback,
77 } = meta;
78
79 // These must've been cleared by the caller. Because these being set
80 // is a programming error (in xso-proc) and not a usage error, we
81 // assert here instead of using reject_key!.
82 assert!(builder.is_none());
83 assert!(iterator.is_none());
84 assert!(!debug.is_set());
85 assert!(deserialize_callback.is_none());
86
87 reject_key!(exhaustive flag not on "structs" only on "enums");
88
89 if let Flag::Present(_) = transparent {
90 reject_key!(namespace not on "transparent structs");
91 reject_key!(name not on "transparent structs");
92 reject_key!(on_unknown_attribute not on "transparent structs");
93 reject_key!(on_unknown_child not on "transparent structs");
94 reject_key!(discard vec not on "transparent structs");
95
96 let fields_span = fields.span();
97 let fields = match fields {
98 Fields::Unit => {
99 return Err(Error::new(
100 fields_span,
101 "transparent structs or enum variants must have exactly one field",
102 ))
103 }
104 Fields::Named(FieldsNamed {
105 named: ref fields, ..
106 })
107 | Fields::Unnamed(FieldsUnnamed {
108 unnamed: ref fields,
109 ..
110 }) => fields,
111 };
112
113 if fields.len() != 1 {
114 return Err(Error::new(
115 fields_span,
116 "transparent structs or enum variants must have exactly one field",
117 ));
118 }
119
120 let field = &fields[0];
121 for attr in field.attrs.iter() {
122 if attr.meta.path().is_ident("xml") {
123 return Err(Error::new_spanned(
124 attr,
125 "#[xml(..)] attributes are not allowed inside transparent structs",
126 ));
127 }
128 }
129 let member = match field.ident.as_ref() {
130 Some(v) => Member::Named(v.clone()),
131 None => Member::Unnamed(Index {
132 span: field.ty.span(),
133 index: 0,
134 }),
135 };
136 let ty = field.ty.clone();
137 Ok(Self::Transparent { ty, member })
138 } else {
139 let Some(xml_namespace) = namespace else {
140 return Err(Error::new(
141 meta_span,
142 "`namespace` is required on non-transparent structs",
143 ));
144 };
145
146 let Some(xml_name) = name else {
147 return Err(Error::new(
148 meta_span,
149 "`name` is required on non-transparent structs",
150 ));
151 };
152
153 Ok(Self::Compound {
154 inner: Compound::from_fields(
155 fields,
156 &xml_namespace,
157 on_unknown_attribute,
158 on_unknown_child,
159 discard,
160 )?,
161 xml_namespace,
162 xml_name,
163 })
164 }
165 }
166
167 pub(crate) fn make_from_events_statemachine(
168 &self,
169 state_ty_ident: &Ident,
170 output_name: &ParentRef,
171 state_prefix: &str,
172 ) -> Result<FromEventsSubmachine> {
173 match self {
174 Self::Transparent { ty, member } => {
175 let from_xml_builder_ty = from_xml_builder_ty(ty.clone());
176 let from_events_fn = from_events_fn(ty.clone());
177 let feed_fn = feed_fn(from_xml_builder_ty.clone());
178
179 let output_cons = match output_name {
180 ParentRef::Named(ref path) => quote! {
181 #path { #member: result }
182 },
183 ParentRef::Unnamed { .. } => quote! {
184 ( result, )
185 },
186 };
187
188 let state_name = quote::format_ident!("{}Default", state_prefix);
189 let builder_data_ident = quote::format_ident!("__xso_data");
190
191 // Here, we generate a partial statemachine which really only
192 // proxies the FromXmlBuilder implementation of the inner
193 // type.
194 Ok(FromEventsSubmachine {
195 defs: TokenStream::default(),
196 states: vec![
197 State::new_with_builder(
198 state_name.clone(),
199 &builder_data_ident,
200 &from_xml_builder_ty,
201 )
202 .with_impl(quote! {
203 match #feed_fn(&mut #builder_data_ident, ev, ctx)? {
204 ::core::option::Option::Some(result) => {
205 ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(#output_cons))
206 }
207 ::core::option::Option::None => {
208 ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
209 #builder_data_ident,
210 }))
211 }
212 }
213 })
214 ],
215 init: quote! {
216 #from_events_fn(name, attrs, ctx).map(|#builder_data_ident| Self::#state_name { #builder_data_ident })
217 },
218 })
219 }
220
221 Self::Compound {
222 ref inner,
223 ref xml_namespace,
224 ref xml_name,
225 } => Ok(inner
226 .make_from_events_statemachine(state_ty_ident, output_name, state_prefix)?
227 .with_augmented_init(|init| {
228 quote! {
229 if name.0 != #xml_namespace || name.1 != #xml_name {
230 ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
231 name,
232 attrs,
233 })
234 } else {
235 #init
236 }
237 }
238 })),
239 }
240 }
241
242 pub(crate) fn make_as_item_iter_statemachine(
243 &self,
244 input_name: &ParentRef,
245 state_ty_ident: &Ident,
246 state_prefix: &str,
247 item_iter_ty_lifetime: &Lifetime,
248 ) -> Result<AsItemsSubmachine> {
249 match self {
250 Self::Transparent { ty, member } => {
251 let item_iter_ty = item_iter_ty(ty.clone(), item_iter_ty_lifetime.clone());
252 let as_xml_iter_fn = as_xml_iter_fn(ty.clone());
253
254 let state_name = quote::format_ident!("{}Default", state_prefix);
255 let iter_ident = quote::format_ident!("__xso_data");
256
257 let destructure = match input_name {
258 ParentRef::Named(ref path) => quote! {
259 #path { #member: #iter_ident }
260 },
261 ParentRef::Unnamed { .. } => quote! {
262 (#iter_ident, )
263 },
264 };
265
266 // Here, we generate a partial statemachine which really only
267 // proxies the AsXml iterator implementation from the inner
268 // type.
269 Ok(AsItemsSubmachine {
270 defs: TokenStream::default(),
271 states: vec![State::new_with_builder(
272 state_name.clone(),
273 &iter_ident,
274 &item_iter_ty,
275 )
276 .with_mut(&iter_ident)
277 .with_impl(quote! {
278 #iter_ident.next().transpose()?
279 })],
280 destructure,
281 init: quote! {
282 #as_xml_iter_fn(#iter_ident).map(|#iter_ident| Self::#state_name { #iter_ident })?
283 },
284 })
285 }
286
287 Self::Compound {
288 ref inner,
289 ref xml_namespace,
290 ref xml_name,
291 } => Ok(inner
292 .make_as_item_iter_statemachine(
293 input_name,
294 state_ty_ident,
295 state_prefix,
296 item_iter_ty_lifetime,
297 )?
298 .with_augmented_init(|init| {
299 quote! {
300 let name = (
301 ::xso::exports::rxml::Namespace::from(#xml_namespace),
302 ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name),
303 );
304 #init
305 }
306 })),
307 }
308 }
309}
310
311/// Definition of a struct and how to parse it.
312pub(crate) struct StructDef {
313 /// Name of the target type.
314 target_ty_ident: Ident,
315
316 /// Name of the builder type.
317 builder_ty_ident: Ident,
318
319 /// Name of the iterator type.
320 item_iter_ty_ident: Ident,
321
322 /// Flag whether debug mode is enabled.
323 debug: bool,
324
325 /// The matching logic and contents of the struct.
326 inner: StructInner,
327
328 /// Optional validator function to call.
329 deserialize_callback: Option<Path>,
330}
331
332impl StructDef {
333 /// Create a new struct from its name, meta, and fields.
334 pub(crate) fn new(ident: &Ident, mut meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
335 let builder_ty_ident = match meta.builder.take() {
336 Some(v) => v,
337 None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
338 };
339
340 let item_iter_ty_ident = match meta.iterator.take() {
341 Some(v) => v,
342 None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
343 };
344
345 let debug = meta.debug.take();
346 let deserialize_callback = meta.deserialize_callback.take();
347
348 let inner = StructInner::new(meta, fields)?;
349
350 Ok(Self {
351 inner,
352 target_ty_ident: ident.clone(),
353 builder_ty_ident,
354 item_iter_ty_ident,
355 debug: debug.is_set(),
356 deserialize_callback,
357 })
358 }
359}
360
361impl ItemDef for StructDef {
362 fn make_from_events_builder(
363 &self,
364 vis: &Visibility,
365 name_ident: &Ident,
366 attrs_ident: &Ident,
367 ) -> Result<FromXmlParts> {
368 let target_ty_ident = &self.target_ty_ident;
369 let builder_ty_ident = &self.builder_ty_ident;
370 let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
371
372 let defs = self
373 .inner
374 .make_from_events_statemachine(
375 &state_ty_ident,
376 &Path::from(target_ty_ident.clone()).into(),
377 "Struct",
378 )?
379 .compile()
380 .render(
381 vis,
382 builder_ty_ident,
383 &state_ty_ident,
384 &TypePath {
385 qself: None,
386 path: target_ty_ident.clone().into(),
387 }
388 .into(),
389 self.deserialize_callback.as_ref(),
390 )?;
391
392 Ok(FromXmlParts {
393 defs,
394 from_events_body: quote! {
395 #builder_ty_ident::new(#name_ident, #attrs_ident, ctx)
396 },
397 builder_ty_ident: builder_ty_ident.clone(),
398 })
399 }
400
401 fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
402 let target_ty_ident = &self.target_ty_ident;
403 let item_iter_ty_ident = &self.item_iter_ty_ident;
404 let item_iter_ty_lifetime = Lifetime {
405 apostrophe: Span::call_site(),
406 ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
407 };
408 let item_iter_ty = Type::Path(TypePath {
409 qself: None,
410 path: Path {
411 leading_colon: None,
412 segments: [PathSegment {
413 ident: item_iter_ty_ident.clone(),
414 arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
415 colon2_token: None,
416 lt_token: token::Lt {
417 spans: [Span::call_site()],
418 },
419 args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
420 .into_iter()
421 .collect(),
422 gt_token: token::Gt {
423 spans: [Span::call_site()],
424 },
425 }),
426 }]
427 .into_iter()
428 .collect(),
429 },
430 });
431 let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
432
433 let defs = self
434 .inner
435 .make_as_item_iter_statemachine(
436 &Path::from(target_ty_ident.clone()).into(),
437 &state_ty_ident,
438 "Struct",
439 &item_iter_ty_lifetime,
440 )?
441 .compile()
442 .render(
443 vis,
444 &ref_ty(
445 ty_from_ident(target_ty_ident.clone()).into(),
446 item_iter_ty_lifetime.clone(),
447 ),
448 &state_ty_ident,
449 &item_iter_ty_lifetime,
450 &item_iter_ty,
451 )?;
452
453 Ok(AsXmlParts {
454 defs,
455 as_xml_iter_body: quote! {
456 #item_iter_ty_ident::new(self)
457 },
458 item_iter_ty,
459 item_iter_ty_lifetime,
460 })
461 }
462
463 fn debug(&self) -> bool {
464 self.debug
465 }
466}