Skip to main content

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, ToTokens};
11use syn::{spanned::Spanned, *};
12
13use crate::common::{AsXmlParts, FromXmlParts, GenericsInfo, ItemDef, XmlNameMatcher};
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};
21
22/// The inner parts of the struct.
23///
24/// This contains all data necessary for the matching logic.
25pub(crate) enum StructInner {
26    /// Single-field struct declared with `#[xml(transparent)]`.
27    ///
28    /// Transparent struct delegate all parsing and serialising to their
29    /// only field, which is why they do not need to store a lot of
30    /// information and come with extra restrictions, such as:
31    ///
32    /// - no XML namespace can be declared (it is determined by inner type)
33    /// - no XML name can be declared (it is determined by inner type)
34    /// - there must be only exactly one field
35    /// - that field has no `#[xml]` attribute
36    Transparent {
37        /// The member identifier of the only field.
38        member: Member,
39
40        /// Type of the only field.
41        ty: Type,
42    },
43
44    /// A compound of fields, *not* declared as transparent.
45    ///
46    /// This can be a unit, tuple-like, or named struct.
47    Compound {
48        /// The XML namespace of the element to map the struct to.
49        xml_namespace: NamespaceRef,
50
51        /// The XML name of the element to map the struct to.
52        xml_name: NameRef,
53
54        /// The field(s) of this struct.
55        inner: Compound,
56    },
57}
58
59impl StructInner {
60    pub(crate) fn new(meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
61        // We destructure here so that we get informed when new fields are
62        // added and can handle them, either by processing them or raising
63        // an error if they are present.
64        let XmlCompoundMeta {
65            span: meta_span,
66            qname: QNameRef { namespace, name },
67            exhaustive,
68            debug,
69            builder,
70            iterator,
71            on_unknown_attribute,
72            on_unknown_child,
73            transparent,
74            discard,
75            deserialize_callback,
76            attribute,
77            value,
78        } = meta;
79
80        // These must've been cleared by the caller. Because these being set
81        // is a programming error (in xso-proc) and not a usage error, we
82        // assert here instead of using reject_key!.
83        assert!(builder.is_none());
84        assert!(iterator.is_none());
85        assert!(!debug.is_set());
86        assert!(deserialize_callback.is_none());
87
88        reject_key!(exhaustive flag not on "structs" only on "enums");
89        reject_key!(attribute not on "structs" only on "enums");
90        reject_key!(value not on "structs" only on "attribute-switched enum variants");
91
92        if let Flag::Present(_) = transparent {
93            reject_key!(namespace not on "transparent structs");
94            reject_key!(name not on "transparent structs");
95            reject_key!(on_unknown_attribute not on "transparent structs");
96            reject_key!(on_unknown_child not on "transparent structs");
97            reject_key!(discard vec not on "transparent structs");
98
99            let fields_span = fields.span();
100            let fields = match fields {
101                Fields::Unit => {
102                    return Err(Error::new(
103                        fields_span,
104                        "transparent structs or enum variants must have exactly one field",
105                    ))
106                }
107                Fields::Named(FieldsNamed {
108                    named: ref fields, ..
109                })
110                | Fields::Unnamed(FieldsUnnamed {
111                    unnamed: ref fields,
112                    ..
113                }) => fields,
114            };
115
116            if fields.len() != 1 {
117                return Err(Error::new(
118                    fields_span,
119                    "transparent structs or enum variants must have exactly one field",
120                ));
121            }
122
123            let field = &fields[0];
124            for attr in field.attrs.iter() {
125                if attr.meta.path().is_ident("xml") {
126                    return Err(Error::new_spanned(
127                        attr,
128                        "#[xml(..)] attributes are not allowed inside transparent structs",
129                    ));
130                }
131            }
132            let member = match field.ident.as_ref() {
133                Some(v) => Member::Named(v.clone()),
134                None => Member::Unnamed(Index {
135                    span: field.ty.span(),
136                    index: 0,
137                }),
138            };
139            let ty = field.ty.clone();
140            Ok(Self::Transparent { ty, member })
141        } else {
142            let Some(xml_namespace) = namespace else {
143                return Err(Error::new(
144                    meta_span,
145                    "`namespace` is required on non-transparent structs",
146                ));
147            };
148
149            let Some(xml_name) = name else {
150                return Err(Error::new(
151                    meta_span,
152                    "`name` is required on non-transparent structs",
153                ));
154            };
155
156            Ok(Self::Compound {
157                inner: Compound::from_fields(
158                    fields,
159                    &xml_namespace,
160                    on_unknown_attribute,
161                    on_unknown_child,
162                    discard,
163                )?,
164                xml_namespace,
165                xml_name,
166            })
167        }
168    }
169
170    pub(crate) fn xml_name_matcher(&self) -> Result<XmlNameMatcher> {
171        match self {
172            Self::Transparent { ty, .. } => Ok(XmlNameMatcher::Custom(quote! {
173                <#ty as ::xso::FromXml>::xml_name_matcher()
174            })),
175            Self::Compound {
176                xml_namespace,
177                xml_name,
178                ..
179            } => Ok(XmlNameMatcher::Specific(
180                xml_namespace.to_token_stream(),
181                xml_name.to_token_stream(),
182            )),
183        }
184    }
185
186    pub(crate) fn make_from_events_statemachine(
187        &self,
188        generics: &mut GenericsInfo,
189        state_ty_ident: &Ident,
190        output_name: &ParentRef,
191        state_prefix: &str,
192    ) -> Result<FromEventsSubmachine> {
193        match self {
194            Self::Transparent { ty, member } => {
195                let from_xml_builder_ty = from_xml_builder_ty(ty.clone());
196                let from_events_fn = from_events_fn(ty.clone());
197                let feed_fn = feed_fn(from_xml_builder_ty.clone());
198
199                let output_cons = match output_name {
200                    ParentRef::Named(ref path) => quote! {
201                        #path { #member: result }
202                    },
203                    ParentRef::Unnamed { .. } => quote! {
204                        ( result, )
205                    },
206                };
207
208                let state_name = quote::format_ident!("{}Default", state_prefix);
209                let builder_data_ident = quote::format_ident!("__xso_data");
210
211                // Here, we generate a partial statemachine which really only
212                // proxies the FromXmlBuilder implementation of the inner
213                // type.
214                Ok(FromEventsSubmachine {
215                    defs: TokenStream::default(),
216                    states: vec![
217                        State::new_with_builder(
218                            state_name.clone(),
219                            &builder_data_ident,
220                            &from_xml_builder_ty,
221                        )
222                            .with_impl(quote! {
223                                match #feed_fn(&mut #builder_data_ident, ev, ctx)? {
224                                    ::core::option::Option::Some(result) => {
225                                        ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(#output_cons))
226                                    }
227                                    ::core::option::Option::None => {
228                                        ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
229                                            #builder_data_ident,
230                                        }))
231                                    }
232                                }
233                            })
234                    ],
235                    init: quote! {
236                        #from_events_fn(name, attrs, ctx).map(|#builder_data_ident| Self::#state_name { #builder_data_ident })
237                    },
238                })
239            }
240
241            Self::Compound {
242                ref inner,
243                ref xml_namespace,
244                ref xml_name,
245            } => Ok(inner
246                .make_from_events_statemachine(generics, state_ty_ident, output_name, state_prefix)?
247                .with_augmented_init(|init| {
248                    quote! {
249                        if name.0 != #xml_namespace || name.1 != #xml_name {
250                            ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
251                                name,
252                                attrs,
253                            })
254                        } else {
255                            #init
256                        }
257                    }
258                })),
259        }
260    }
261
262    pub(crate) fn make_as_item_iter_statemachine(
263        &self,
264        generics: &mut GenericsInfo,
265        input_name: &ParentRef,
266        state_ty_ident: &Ident,
267        state_prefix: &str,
268        item_iter_ty_lifetime: &Lifetime,
269    ) -> Result<AsItemsSubmachine> {
270        match self {
271            Self::Transparent { ty, member } => {
272                let item_iter_ty = item_iter_ty(ty.clone(), item_iter_ty_lifetime.clone());
273                let as_xml_iter_fn = as_xml_iter_fn(ty.clone());
274
275                let state_name = quote::format_ident!("{}Default", state_prefix);
276                let iter_ident = quote::format_ident!("__xso_data");
277
278                let destructure = match input_name {
279                    ParentRef::Named(ref path) => quote! {
280                        #path { #member: #iter_ident }
281                    },
282                    ParentRef::Unnamed { .. } => quote! {
283                        (#iter_ident, )
284                    },
285                };
286
287                // Here, we generate a partial statemachine which really only
288                // proxies the AsXml iterator implementation from the inner
289                // type.
290                Ok(AsItemsSubmachine {
291                    defs: TokenStream::default(),
292                    states: vec![State::new_with_builder(
293                        state_name.clone(),
294                        &iter_ident,
295                        &item_iter_ty,
296                    )
297                    .with_mut(&iter_ident)
298                    .with_impl(quote! {
299                        #iter_ident.next().transpose()?
300                    })],
301                    destructure,
302                    init: quote! {
303                        #as_xml_iter_fn(#iter_ident).map(|#iter_ident| Self::#state_name { #iter_ident })?
304                    },
305                })
306            }
307
308            Self::Compound {
309                ref inner,
310                ref xml_namespace,
311                ref xml_name,
312            } => Ok(inner
313                .make_as_item_iter_statemachine(
314                    generics,
315                    input_name,
316                    state_ty_ident,
317                    state_prefix,
318                    item_iter_ty_lifetime,
319                )?
320                .with_augmented_init(|init| {
321                    quote! {
322                        let name = (
323                            ::xso::exports::rxml::Namespace::from(#xml_namespace),
324                            ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name),
325                        );
326                        #init
327                    }
328                })),
329        }
330    }
331}
332
333/// Definition of a struct and how to parse it.
334pub(crate) struct StructDef {
335    /// Name of the target type.
336    target_ty_ident: Ident,
337
338    /// Name of the builder type.
339    builder_ty_ident: Ident,
340
341    /// Name of the iterator type.
342    item_iter_ty_ident: Ident,
343
344    /// Flag whether debug mode is enabled.
345    debug: bool,
346
347    /// The matching logic and contents of the struct.
348    inner: StructInner,
349
350    /// Optional validator function to call.
351    deserialize_callback: Option<Path>,
352}
353
354impl StructDef {
355    /// Create a new struct from its name, meta, and fields.
356    pub(crate) fn new(ident: &Ident, mut meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
357        let builder_ty_ident = match meta.builder.take() {
358            Some(v) => v,
359            None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
360        };
361
362        let item_iter_ty_ident = match meta.iterator.take() {
363            Some(v) => v,
364            None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
365        };
366
367        let debug = meta.debug.take();
368        let deserialize_callback = meta.deserialize_callback.take();
369
370        let inner = StructInner::new(meta, fields)?;
371
372        Ok(Self {
373            inner,
374            target_ty_ident: ident.clone(),
375            builder_ty_ident,
376            item_iter_ty_ident,
377            debug: debug.is_set(),
378            deserialize_callback,
379        })
380    }
381}
382
383impl ItemDef for StructDef {
384    fn make_from_events_builder(
385        &self,
386        vis: &Visibility,
387        generics: &mut GenericsInfo,
388        name_ident: &Ident,
389        attrs_ident: &Ident,
390    ) -> Result<FromXmlParts> {
391        let target_ty_ident = &self.target_ty_ident;
392        let builder_ty_ident = &self.builder_ty_ident;
393        let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
394
395        let defs = self
396            .inner
397            .make_from_events_statemachine(
398                generics,
399                &state_ty_ident,
400                &Path::from(target_ty_ident.clone()).into(),
401                "Struct",
402            )?
403            .compile()
404            .render(
405                vis,
406                generics,
407                builder_ty_ident,
408                &state_ty_ident,
409                &generics.ty_with_arguments(target_ty_ident.clone()),
410                self.deserialize_callback.as_ref(),
411            )?;
412
413        Ok(FromXmlParts {
414            defs,
415            from_events_body: quote! {
416                #builder_ty_ident::new(#name_ident, #attrs_ident, ctx)
417            },
418            builder_ty: generics.ty_with_arguments(builder_ty_ident.clone()),
419            name_matcher: self.inner.xml_name_matcher()?,
420        })
421    }
422
423    fn make_as_xml_iter(
424        &self,
425        vis: &Visibility,
426        generics: &mut GenericsInfo,
427    ) -> Result<AsXmlParts> {
428        let target_ty_ident = &self.target_ty_ident;
429        let item_iter_ty_ident = &self.item_iter_ty_ident;
430        let item_iter_ty_lifetime = Lifetime {
431            apostrophe: Span::call_site(),
432            ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
433        };
434        let target_ty = generics.ty_with_arguments(target_ty_ident.clone());
435        let mut inner_generics = generics.subscope();
436        inner_generics.insert_lifetime(&item_iter_ty_lifetime);
437        let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
438
439        let defs = self
440            .inner
441            .make_as_item_iter_statemachine(
442                &mut inner_generics,
443                &Path::from(target_ty_ident.clone()).into(),
444                &state_ty_ident,
445                "Struct",
446                &item_iter_ty_lifetime,
447            )?
448            .compile()
449            .render(
450                vis,
451                &inner_generics,
452                &ref_ty(target_ty, item_iter_ty_lifetime.clone()),
453                &state_ty_ident,
454                &item_iter_ty_lifetime,
455                &item_iter_ty_ident,
456            )?;
457
458        // XXX: this is a bit of a hack, but I don't see a good way around it
459        // for now: on the one hand, we need the where_clause as updated by
460        // the impls in order to collect the bounds needed for the traits to
461        // work. On the other hand, we mustn't propagate the addition of the
462        // item_iter_ty_lifetime to the caller.
463        let item_iter_ty = inner_generics.ty_with_arguments(item_iter_ty_ident.clone());
464        generics.extend_bounds(inner_generics.into_new_bounds());
465
466        Ok(AsXmlParts {
467            defs,
468            as_xml_iter_body: quote! {
469                #item_iter_ty_ident::new(self)
470            },
471            item_iter_ty,
472            item_iter_ty_lifetime,
473        })
474    }
475
476    fn debug(&self) -> bool {
477        self.debug
478    }
479}