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}