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