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