1use proc_macro2::{Span, TokenStream};
10use quote::{quote, ToTokens};
11use syn::{spanned::Spanned, *};
12
13use crate::common::{AsXmlParts, FromXmlParts, 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 ty_from_ident,
21};
22
23pub(crate) enum StructInner {
27 Transparent {
38 member: Member,
40
41 ty: Type,
43 },
44
45 Compound {
49 xml_namespace: NamespaceRef,
51
52 xml_name: NameRef,
54
55 inner: Compound,
57 },
58}
59
60impl StructInner {
61 pub(crate) fn new(meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
62 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 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 xml_name_matcher(&self) -> Result<XmlNameMatcher> {
172 match self {
173 Self::Transparent { ty, .. } => Ok(XmlNameMatcher::Custom(quote! {
174 <#ty as ::xso::FromXml>::xml_name_matcher()
175 })),
176 Self::Compound {
177 xml_namespace,
178 xml_name,
179 ..
180 } => Ok(XmlNameMatcher::Specific(
181 xml_namespace.to_token_stream(),
182 xml_name.to_token_stream(),
183 )),
184 }
185 }
186
187 pub(crate) fn make_from_events_statemachine(
188 &self,
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 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(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 input_name: &ParentRef,
265 state_ty_ident: &Ident,
266 state_prefix: &str,
267 item_iter_ty_lifetime: &Lifetime,
268 ) -> Result<AsItemsSubmachine> {
269 match self {
270 Self::Transparent { ty, member } => {
271 let item_iter_ty = item_iter_ty(ty.clone(), item_iter_ty_lifetime.clone());
272 let as_xml_iter_fn = as_xml_iter_fn(ty.clone());
273
274 let state_name = quote::format_ident!("{}Default", state_prefix);
275 let iter_ident = quote::format_ident!("__xso_data");
276
277 let destructure = match input_name {
278 ParentRef::Named(ref path) => quote! {
279 #path { #member: #iter_ident }
280 },
281 ParentRef::Unnamed { .. } => quote! {
282 (#iter_ident, )
283 },
284 };
285
286 Ok(AsItemsSubmachine {
290 defs: TokenStream::default(),
291 states: vec![State::new_with_builder(
292 state_name.clone(),
293 &iter_ident,
294 &item_iter_ty,
295 )
296 .with_mut(&iter_ident)
297 .with_impl(quote! {
298 #iter_ident.next().transpose()?
299 })],
300 destructure,
301 init: quote! {
302 #as_xml_iter_fn(#iter_ident).map(|#iter_ident| Self::#state_name { #iter_ident })?
303 },
304 })
305 }
306
307 Self::Compound {
308 ref inner,
309 ref xml_namespace,
310 ref xml_name,
311 } => Ok(inner
312 .make_as_item_iter_statemachine(
313 input_name,
314 state_ty_ident,
315 state_prefix,
316 item_iter_ty_lifetime,
317 )?
318 .with_augmented_init(|init| {
319 quote! {
320 let name = (
321 ::xso::exports::rxml::Namespace::from(#xml_namespace),
322 ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name),
323 );
324 #init
325 }
326 })),
327 }
328 }
329}
330
331pub(crate) struct StructDef {
333 target_ty_ident: Ident,
335
336 builder_ty_ident: Ident,
338
339 item_iter_ty_ident: Ident,
341
342 debug: bool,
344
345 inner: StructInner,
347
348 deserialize_callback: Option<Path>,
350}
351
352impl StructDef {
353 pub(crate) fn new(ident: &Ident, mut meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
355 let builder_ty_ident = match meta.builder.take() {
356 Some(v) => v,
357 None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
358 };
359
360 let item_iter_ty_ident = match meta.iterator.take() {
361 Some(v) => v,
362 None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
363 };
364
365 let debug = meta.debug.take();
366 let deserialize_callback = meta.deserialize_callback.take();
367
368 let inner = StructInner::new(meta, fields)?;
369
370 Ok(Self {
371 inner,
372 target_ty_ident: ident.clone(),
373 builder_ty_ident,
374 item_iter_ty_ident,
375 debug: debug.is_set(),
376 deserialize_callback,
377 })
378 }
379}
380
381impl ItemDef for StructDef {
382 fn make_from_events_builder(
383 &self,
384 vis: &Visibility,
385 name_ident: &Ident,
386 attrs_ident: &Ident,
387 ) -> Result<FromXmlParts> {
388 let target_ty_ident = &self.target_ty_ident;
389 let builder_ty_ident = &self.builder_ty_ident;
390 let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
391
392 let defs = self
393 .inner
394 .make_from_events_statemachine(
395 &state_ty_ident,
396 &Path::from(target_ty_ident.clone()).into(),
397 "Struct",
398 )?
399 .compile()
400 .render(
401 vis,
402 builder_ty_ident,
403 &state_ty_ident,
404 &TypePath {
405 qself: None,
406 path: target_ty_ident.clone().into(),
407 }
408 .into(),
409 self.deserialize_callback.as_ref(),
410 )?;
411
412 Ok(FromXmlParts {
413 defs,
414 from_events_body: quote! {
415 #builder_ty_ident::new(#name_ident, #attrs_ident, ctx)
416 },
417 builder_ty_ident: builder_ty_ident.clone(),
418 name_matcher: self.inner.xml_name_matcher()?,
419 })
420 }
421
422 fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
423 let target_ty_ident = &self.target_ty_ident;
424 let item_iter_ty_ident = &self.item_iter_ty_ident;
425 let item_iter_ty_lifetime = Lifetime {
426 apostrophe: Span::call_site(),
427 ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
428 };
429 let item_iter_ty = Type::Path(TypePath {
430 qself: None,
431 path: Path {
432 leading_colon: None,
433 segments: [PathSegment {
434 ident: item_iter_ty_ident.clone(),
435 arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
436 colon2_token: None,
437 lt_token: token::Lt {
438 spans: [Span::call_site()],
439 },
440 args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
441 .into_iter()
442 .collect(),
443 gt_token: token::Gt {
444 spans: [Span::call_site()],
445 },
446 }),
447 }]
448 .into_iter()
449 .collect(),
450 },
451 });
452 let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
453
454 let defs = self
455 .inner
456 .make_as_item_iter_statemachine(
457 &Path::from(target_ty_ident.clone()).into(),
458 &state_ty_ident,
459 "Struct",
460 &item_iter_ty_lifetime,
461 )?
462 .compile()
463 .render(
464 vis,
465 &ref_ty(
466 ty_from_ident(target_ty_ident.clone()).into(),
467 item_iter_ty_lifetime.clone(),
468 ),
469 &state_ty_ident,
470 &item_iter_ty_lifetime,
471 &item_iter_ty,
472 )?;
473
474 Ok(AsXmlParts {
475 defs,
476 as_xml_iter_body: quote! {
477 #item_iter_ty_ident::new(self)
478 },
479 item_iter_ty,
480 item_iter_ty_lifetime,
481 })
482 }
483
484 fn debug(&self) -> bool {
485 self.debug
486 }
487}