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            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 make_from_events_statemachine(
172        &self,
173        state_ty_ident: &Ident,
174        output_name: &ParentRef,
175        state_prefix: &str,
176    ) -> Result<FromEventsSubmachine> {
177        match self {
178            Self::Transparent { ty, member } => {
179                let from_xml_builder_ty = from_xml_builder_ty(ty.clone());
180                let from_events_fn = from_events_fn(ty.clone());
181                let feed_fn = feed_fn(from_xml_builder_ty.clone());
182
183                let output_cons = match output_name {
184                    ParentRef::Named(ref path) => quote! {
185                        #path { #member: result }
186                    },
187                    ParentRef::Unnamed { .. } => quote! {
188                        ( result, )
189                    },
190                };
191
192                let state_name = quote::format_ident!("{}Default", state_prefix);
193                let builder_data_ident = quote::format_ident!("__xso_data");
194
195                // Here, we generate a partial statemachine which really only
196                // proxies the FromXmlBuilder implementation of the inner
197                // type.
198                Ok(FromEventsSubmachine {
199                    defs: TokenStream::default(),
200                    states: vec![
201                        State::new_with_builder(
202                            state_name.clone(),
203                            &builder_data_ident,
204                            &from_xml_builder_ty,
205                        )
206                            .with_impl(quote! {
207                                match #feed_fn(&mut #builder_data_ident, ev, ctx)? {
208                                    ::core::option::Option::Some(result) => {
209                                        ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(#output_cons))
210                                    }
211                                    ::core::option::Option::None => {
212                                        ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
213                                            #builder_data_ident,
214                                        }))
215                                    }
216                                }
217                            })
218                    ],
219                    init: quote! {
220                        #from_events_fn(name, attrs, ctx).map(|#builder_data_ident| Self::#state_name { #builder_data_ident })
221                    },
222                })
223            }
224
225            Self::Compound {
226                ref inner,
227                ref xml_namespace,
228                ref xml_name,
229            } => Ok(inner
230                .make_from_events_statemachine(state_ty_ident, output_name, state_prefix)?
231                .with_augmented_init(|init| {
232                    quote! {
233                        if name.0 != #xml_namespace || name.1 != #xml_name {
234                            ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
235                                name,
236                                attrs,
237                            })
238                        } else {
239                            #init
240                        }
241                    }
242                })),
243        }
244    }
245
246    pub(crate) fn make_as_item_iter_statemachine(
247        &self,
248        input_name: &ParentRef,
249        state_ty_ident: &Ident,
250        state_prefix: &str,
251        item_iter_ty_lifetime: &Lifetime,
252    ) -> Result<AsItemsSubmachine> {
253        match self {
254            Self::Transparent { ty, member } => {
255                let item_iter_ty = item_iter_ty(ty.clone(), item_iter_ty_lifetime.clone());
256                let as_xml_iter_fn = as_xml_iter_fn(ty.clone());
257
258                let state_name = quote::format_ident!("{}Default", state_prefix);
259                let iter_ident = quote::format_ident!("__xso_data");
260
261                let destructure = match input_name {
262                    ParentRef::Named(ref path) => quote! {
263                        #path { #member: #iter_ident }
264                    },
265                    ParentRef::Unnamed { .. } => quote! {
266                        (#iter_ident, )
267                    },
268                };
269
270                // Here, we generate a partial statemachine which really only
271                // proxies the AsXml iterator implementation from the inner
272                // type.
273                Ok(AsItemsSubmachine {
274                    defs: TokenStream::default(),
275                    states: vec![State::new_with_builder(
276                        state_name.clone(),
277                        &iter_ident,
278                        &item_iter_ty,
279                    )
280                    .with_mut(&iter_ident)
281                    .with_impl(quote! {
282                        #iter_ident.next().transpose()?
283                    })],
284                    destructure,
285                    init: quote! {
286                        #as_xml_iter_fn(#iter_ident).map(|#iter_ident| Self::#state_name { #iter_ident })?
287                    },
288                })
289            }
290
291            Self::Compound {
292                ref inner,
293                ref xml_namespace,
294                ref xml_name,
295            } => Ok(inner
296                .make_as_item_iter_statemachine(
297                    input_name,
298                    state_ty_ident,
299                    state_prefix,
300                    item_iter_ty_lifetime,
301                )?
302                .with_augmented_init(|init| {
303                    quote! {
304                        let name = (
305                            ::xso::exports::rxml::Namespace::from(#xml_namespace),
306                            ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name),
307                        );
308                        #init
309                    }
310                })),
311        }
312    }
313}
314
315/// Definition of a struct and how to parse it.
316pub(crate) struct StructDef {
317    /// Name of the target type.
318    target_ty_ident: Ident,
319
320    /// Name of the builder type.
321    builder_ty_ident: Ident,
322
323    /// Name of the iterator type.
324    item_iter_ty_ident: Ident,
325
326    /// Flag whether debug mode is enabled.
327    debug: bool,
328
329    /// The matching logic and contents of the struct.
330    inner: StructInner,
331
332    /// Optional validator function to call.
333    deserialize_callback: Option<Path>,
334}
335
336impl StructDef {
337    /// Create a new struct from its name, meta, and fields.
338    pub(crate) fn new(ident: &Ident, mut meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
339        let builder_ty_ident = match meta.builder.take() {
340            Some(v) => v,
341            None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
342        };
343
344        let item_iter_ty_ident = match meta.iterator.take() {
345            Some(v) => v,
346            None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
347        };
348
349        let debug = meta.debug.take();
350        let deserialize_callback = meta.deserialize_callback.take();
351
352        let inner = StructInner::new(meta, fields)?;
353
354        Ok(Self {
355            inner,
356            target_ty_ident: ident.clone(),
357            builder_ty_ident,
358            item_iter_ty_ident,
359            debug: debug.is_set(),
360            deserialize_callback,
361        })
362    }
363}
364
365impl ItemDef for StructDef {
366    fn make_from_events_builder(
367        &self,
368        vis: &Visibility,
369        name_ident: &Ident,
370        attrs_ident: &Ident,
371    ) -> Result<FromXmlParts> {
372        let target_ty_ident = &self.target_ty_ident;
373        let builder_ty_ident = &self.builder_ty_ident;
374        let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
375
376        let defs = self
377            .inner
378            .make_from_events_statemachine(
379                &state_ty_ident,
380                &Path::from(target_ty_ident.clone()).into(),
381                "Struct",
382            )?
383            .compile()
384            .render(
385                vis,
386                builder_ty_ident,
387                &state_ty_ident,
388                &TypePath {
389                    qself: None,
390                    path: target_ty_ident.clone().into(),
391                }
392                .into(),
393                self.deserialize_callback.as_ref(),
394            )?;
395
396        Ok(FromXmlParts {
397            defs,
398            from_events_body: quote! {
399                #builder_ty_ident::new(#name_ident, #attrs_ident, ctx)
400            },
401            builder_ty_ident: builder_ty_ident.clone(),
402        })
403    }
404
405    fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
406        let target_ty_ident = &self.target_ty_ident;
407        let item_iter_ty_ident = &self.item_iter_ty_ident;
408        let item_iter_ty_lifetime = Lifetime {
409            apostrophe: Span::call_site(),
410            ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
411        };
412        let item_iter_ty = Type::Path(TypePath {
413            qself: None,
414            path: Path {
415                leading_colon: None,
416                segments: [PathSegment {
417                    ident: item_iter_ty_ident.clone(),
418                    arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
419                        colon2_token: None,
420                        lt_token: token::Lt {
421                            spans: [Span::call_site()],
422                        },
423                        args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
424                            .into_iter()
425                            .collect(),
426                        gt_token: token::Gt {
427                            spans: [Span::call_site()],
428                        },
429                    }),
430                }]
431                .into_iter()
432                .collect(),
433            },
434        });
435        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
436
437        let defs = self
438            .inner
439            .make_as_item_iter_statemachine(
440                &Path::from(target_ty_ident.clone()).into(),
441                &state_ty_ident,
442                "Struct",
443                &item_iter_ty_lifetime,
444            )?
445            .compile()
446            .render(
447                vis,
448                &ref_ty(
449                    ty_from_ident(target_ty_ident.clone()).into(),
450                    item_iter_ty_lifetime.clone(),
451                ),
452                &state_ty_ident,
453                &item_iter_ty_lifetime,
454                &item_iter_ty,
455            )?;
456
457        Ok(AsXmlParts {
458            defs,
459            as_xml_iter_body: quote! {
460                #item_iter_ty_ident::new(self)
461            },
462            item_iter_ty,
463            item_iter_ty_lifetime,
464        })
465    }
466
467    fn debug(&self) -> bool {
468        self.debug
469    }
470}