1use 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
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 } = meta;
77
78 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 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 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
309pub(crate) struct StructDef {
311 target_ty_ident: Ident,
313
314 builder_ty_ident: Ident,
316
317 item_iter_ty_ident: Ident,
319
320 debug: bool,
322
323 inner: StructInner,
325}
326
327impl StructDef {
328 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}