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