xso_proc/
structs.rs

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, ToTokens};
11use syn::{spanned::Spanned, *};
12
13use crate::common::{AsXmlParts, FromXmlParts, ItemDef, XmlNameMatcher};
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            attribute,
78            value,
79        } = meta;
80
81        // These must've been cleared by the caller. Because these being set
82        // is a programming error (in xso-proc) and not a usage error, we
83        // assert here instead of using reject_key!.
84        assert!(builder.is_none());
85        assert!(iterator.is_none());
86        assert!(!debug.is_set());
87        assert!(deserialize_callback.is_none());
88
89        reject_key!(exhaustive flag not on "structs" only on "enums");
90        reject_key!(attribute not on "structs" only on "enums");
91        reject_key!(value not on "structs" only on "attribute-switched enum variants");
92
93        if let Flag::Present(_) = transparent {
94            reject_key!(namespace not on "transparent structs");
95            reject_key!(name not on "transparent structs");
96            reject_key!(on_unknown_attribute not on "transparent structs");
97            reject_key!(on_unknown_child not on "transparent structs");
98            reject_key!(discard vec not on "transparent structs");
99
100            let fields_span = fields.span();
101            let fields = match fields {
102                Fields::Unit => {
103                    return Err(Error::new(
104                        fields_span,
105                        "transparent structs or enum variants must have exactly one field",
106                    ))
107                }
108                Fields::Named(FieldsNamed {
109                    named: ref fields, ..
110                })
111                | Fields::Unnamed(FieldsUnnamed {
112                    unnamed: ref fields,
113                    ..
114                }) => fields,
115            };
116
117            if fields.len() != 1 {
118                return Err(Error::new(
119                    fields_span,
120                    "transparent structs or enum variants must have exactly one field",
121                ));
122            }
123
124            let field = &fields[0];
125            for attr in field.attrs.iter() {
126                if attr.meta.path().is_ident("xml") {
127                    return Err(Error::new_spanned(
128                        attr,
129                        "#[xml(..)] attributes are not allowed inside transparent structs",
130                    ));
131                }
132            }
133            let member = match field.ident.as_ref() {
134                Some(v) => Member::Named(v.clone()),
135                None => Member::Unnamed(Index {
136                    span: field.ty.span(),
137                    index: 0,
138                }),
139            };
140            let ty = field.ty.clone();
141            Ok(Self::Transparent { ty, member })
142        } else {
143            let Some(xml_namespace) = namespace else {
144                return Err(Error::new(
145                    meta_span,
146                    "`namespace` is required on non-transparent structs",
147                ));
148            };
149
150            let Some(xml_name) = name else {
151                return Err(Error::new(
152                    meta_span,
153                    "`name` is required on non-transparent structs",
154                ));
155            };
156
157            Ok(Self::Compound {
158                inner: Compound::from_fields(
159                    fields,
160                    &xml_namespace,
161                    on_unknown_attribute,
162                    on_unknown_child,
163                    discard,
164                )?,
165                xml_namespace,
166                xml_name,
167            })
168        }
169    }
170
171    pub(crate) fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
172        match self {
173            Self::Transparent { ty, .. } => Ok(XmlNameMatcher::Custom(quote! {
174                <#ty as ::xso::FromXml>::xml_name_matcher()
175            })),
176            Self::Compound {
177                xml_namespace,
178                xml_name,
179                ..
180            } => Ok(XmlNameMatcher::Specific(
181                xml_namespace.to_token_stream(),
182                xml_name.to_token_stream(),
183            )),
184        }
185    }
186
187    pub(crate) fn make_from_events_statemachine(
188        &self,
189        state_ty_ident: &Ident,
190        output_name: &ParentRef,
191        state_prefix: &str,
192    ) -> Result<FromEventsSubmachine> {
193        match self {
194            Self::Transparent { ty, member } => {
195                let from_xml_builder_ty = from_xml_builder_ty(ty.clone());
196                let from_events_fn = from_events_fn(ty.clone());
197                let feed_fn = feed_fn(from_xml_builder_ty.clone());
198
199                let output_cons = match output_name {
200                    ParentRef::Named(ref path) => quote! {
201                        #path { #member: result }
202                    },
203                    ParentRef::Unnamed { .. } => quote! {
204                        ( result, )
205                    },
206                };
207
208                let state_name = quote::format_ident!("{}Default", state_prefix);
209                let builder_data_ident = quote::format_ident!("__xso_data");
210
211                // Here, we generate a partial statemachine which really only
212                // proxies the FromXmlBuilder implementation of the inner
213                // type.
214                Ok(FromEventsSubmachine {
215                    defs: TokenStream::default(),
216                    states: vec![
217                        State::new_with_builder(
218                            state_name.clone(),
219                            &builder_data_ident,
220                            &from_xml_builder_ty,
221                        )
222                            .with_impl(quote! {
223                                match #feed_fn(&mut #builder_data_ident, ev, ctx)? {
224                                    ::core::option::Option::Some(result) => {
225                                        ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(#output_cons))
226                                    }
227                                    ::core::option::Option::None => {
228                                        ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
229                                            #builder_data_ident,
230                                        }))
231                                    }
232                                }
233                            })
234                    ],
235                    init: quote! {
236                        #from_events_fn(name, attrs, ctx).map(|#builder_data_ident| Self::#state_name { #builder_data_ident })
237                    },
238                })
239            }
240
241            Self::Compound {
242                ref inner,
243                ref xml_namespace,
244                ref xml_name,
245            } => Ok(inner
246                .make_from_events_statemachine(state_ty_ident, output_name, state_prefix)?
247                .with_augmented_init(|init| {
248                    quote! {
249                        if name.0 != #xml_namespace || name.1 != #xml_name {
250                            ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
251                                name,
252                                attrs,
253                            })
254                        } else {
255                            #init
256                        }
257                    }
258                })),
259        }
260    }
261
262    pub(crate) fn make_as_item_iter_statemachine(
263        &self,
264        input_name: &ParentRef,
265        state_ty_ident: &Ident,
266        state_prefix: &str,
267        item_iter_ty_lifetime: &Lifetime,
268    ) -> Result<AsItemsSubmachine> {
269        match self {
270            Self::Transparent { ty, member } => {
271                let item_iter_ty = item_iter_ty(ty.clone(), item_iter_ty_lifetime.clone());
272                let as_xml_iter_fn = as_xml_iter_fn(ty.clone());
273
274                let state_name = quote::format_ident!("{}Default", state_prefix);
275                let iter_ident = quote::format_ident!("__xso_data");
276
277                let destructure = match input_name {
278                    ParentRef::Named(ref path) => quote! {
279                        #path { #member: #iter_ident }
280                    },
281                    ParentRef::Unnamed { .. } => quote! {
282                        (#iter_ident, )
283                    },
284                };
285
286                // Here, we generate a partial statemachine which really only
287                // proxies the AsXml iterator implementation from the inner
288                // type.
289                Ok(AsItemsSubmachine {
290                    defs: TokenStream::default(),
291                    states: vec![State::new_with_builder(
292                        state_name.clone(),
293                        &iter_ident,
294                        &item_iter_ty,
295                    )
296                    .with_mut(&iter_ident)
297                    .with_impl(quote! {
298                        #iter_ident.next().transpose()?
299                    })],
300                    destructure,
301                    init: quote! {
302                        #as_xml_iter_fn(#iter_ident).map(|#iter_ident| Self::#state_name { #iter_ident })?
303                    },
304                })
305            }
306
307            Self::Compound {
308                ref inner,
309                ref xml_namespace,
310                ref xml_name,
311            } => Ok(inner
312                .make_as_item_iter_statemachine(
313                    input_name,
314                    state_ty_ident,
315                    state_prefix,
316                    item_iter_ty_lifetime,
317                )?
318                .with_augmented_init(|init| {
319                    quote! {
320                        let name = (
321                            ::xso::exports::rxml::Namespace::from(#xml_namespace),
322                            ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name),
323                        );
324                        #init
325                    }
326                })),
327        }
328    }
329}
330
331/// Definition of a struct and how to parse it.
332pub(crate) struct StructDef {
333    /// Name of the target type.
334    target_ty_ident: Ident,
335
336    /// Name of the builder type.
337    builder_ty_ident: Ident,
338
339    /// Name of the iterator type.
340    item_iter_ty_ident: Ident,
341
342    /// Flag whether debug mode is enabled.
343    debug: bool,
344
345    /// The matching logic and contents of the struct.
346    inner: StructInner,
347
348    /// Optional validator function to call.
349    deserialize_callback: Option<Path>,
350}
351
352impl StructDef {
353    /// Create a new struct from its name, meta, and fields.
354    pub(crate) fn new(ident: &Ident, mut meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
355        let builder_ty_ident = match meta.builder.take() {
356            Some(v) => v,
357            None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
358        };
359
360        let item_iter_ty_ident = match meta.iterator.take() {
361            Some(v) => v,
362            None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
363        };
364
365        let debug = meta.debug.take();
366        let deserialize_callback = meta.deserialize_callback.take();
367
368        let inner = StructInner::new(meta, fields)?;
369
370        Ok(Self {
371            inner,
372            target_ty_ident: ident.clone(),
373            builder_ty_ident,
374            item_iter_ty_ident,
375            debug: debug.is_set(),
376            deserialize_callback,
377        })
378    }
379}
380
381impl ItemDef for StructDef {
382    fn make_from_events_builder(
383        &self,
384        vis: &Visibility,
385        name_ident: &Ident,
386        attrs_ident: &Ident,
387    ) -> Result<FromXmlParts> {
388        let target_ty_ident = &self.target_ty_ident;
389        let builder_ty_ident = &self.builder_ty_ident;
390        let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
391
392        let defs = self
393            .inner
394            .make_from_events_statemachine(
395                &state_ty_ident,
396                &Path::from(target_ty_ident.clone()).into(),
397                "Struct",
398            )?
399            .compile()
400            .render(
401                vis,
402                builder_ty_ident,
403                &state_ty_ident,
404                &TypePath {
405                    qself: None,
406                    path: target_ty_ident.clone().into(),
407                }
408                .into(),
409                self.deserialize_callback.as_ref(),
410            )?;
411
412        Ok(FromXmlParts {
413            defs,
414            from_events_body: quote! {
415                #builder_ty_ident::new(#name_ident, #attrs_ident, ctx)
416            },
417            builder_ty_ident: builder_ty_ident.clone(),
418            name_matcher: self.inner.xml_name_matcher()?,
419        })
420    }
421
422    fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
423        let target_ty_ident = &self.target_ty_ident;
424        let item_iter_ty_ident = &self.item_iter_ty_ident;
425        let item_iter_ty_lifetime = Lifetime {
426            apostrophe: Span::call_site(),
427            ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
428        };
429        let item_iter_ty = Type::Path(TypePath {
430            qself: None,
431            path: Path {
432                leading_colon: None,
433                segments: [PathSegment {
434                    ident: item_iter_ty_ident.clone(),
435                    arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
436                        colon2_token: None,
437                        lt_token: token::Lt {
438                            spans: [Span::call_site()],
439                        },
440                        args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
441                            .into_iter()
442                            .collect(),
443                        gt_token: token::Gt {
444                            spans: [Span::call_site()],
445                        },
446                    }),
447                }]
448                .into_iter()
449                .collect(),
450            },
451        });
452        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
453
454        let defs = self
455            .inner
456            .make_as_item_iter_statemachine(
457                &Path::from(target_ty_ident.clone()).into(),
458                &state_ty_ident,
459                "Struct",
460                &item_iter_ty_lifetime,
461            )?
462            .compile()
463            .render(
464                vis,
465                &ref_ty(
466                    ty_from_ident(target_ty_ident.clone()).into(),
467                    item_iter_ty_lifetime.clone(),
468                ),
469                &state_ty_ident,
470                &item_iter_ty_lifetime,
471                &item_iter_ty,
472            )?;
473
474        Ok(AsXmlParts {
475            defs,
476            as_xml_iter_body: quote! {
477                #item_iter_ty_ident::new(self)
478            },
479            item_iter_ty,
480            item_iter_ty_lifetime,
481        })
482    }
483
484    fn debug(&self) -> bool {
485        self.debug
486    }
487}