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;
 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}