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}