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 } = meta;
76
77 assert!(builder.is_none());
81 assert!(iterator.is_none());
82 assert!(!debug.is_set());
83
84 reject_key!(exhaustive flag not on "structs" only on "enums");
85
86 if let Flag::Present(_) = transparent {
87 reject_key!(namespace not on "transparent structs");
88 reject_key!(name not on "transparent structs");
89 reject_key!(on_unknown_attribute not on "transparent structs");
90 reject_key!(on_unknown_child not on "transparent structs");
91
92 let fields_span = fields.span();
93 let fields = match fields {
94 Fields::Unit => {
95 return Err(Error::new(
96 fields_span,
97 "transparent structs or enum variants must have exactly one field",
98 ))
99 }
100 Fields::Named(FieldsNamed {
101 named: ref fields, ..
102 })
103 | Fields::Unnamed(FieldsUnnamed {
104 unnamed: ref fields,
105 ..
106 }) => fields,
107 };
108
109 if fields.len() != 1 {
110 return Err(Error::new(
111 fields_span,
112 "transparent structs or enum variants must have exactly one field",
113 ));
114 }
115
116 let field = &fields[0];
117 for attr in field.attrs.iter() {
118 if attr.meta.path().is_ident("xml") {
119 return Err(Error::new_spanned(
120 attr,
121 "#[xml(..)] attributes are not allowed inside transparent structs",
122 ));
123 }
124 }
125 let member = match field.ident.as_ref() {
126 Some(v) => Member::Named(v.clone()),
127 None => Member::Unnamed(Index {
128 span: field.ty.span(),
129 index: 0,
130 }),
131 };
132 let ty = field.ty.clone();
133 Ok(Self::Transparent { ty, member })
134 } else {
135 let Some(xml_namespace) = namespace else {
136 return Err(Error::new(
137 meta_span,
138 "`namespace` is required on non-transparent structs",
139 ));
140 };
141
142 let Some(xml_name) = name else {
143 return Err(Error::new(
144 meta_span,
145 "`name` is required on non-transparent structs",
146 ));
147 };
148
149 Ok(Self::Compound {
150 inner: Compound::from_fields(
151 fields,
152 &xml_namespace,
153 on_unknown_attribute,
154 on_unknown_child,
155 )?,
156 xml_namespace,
157 xml_name,
158 })
159 }
160 }
161
162 pub(crate) fn make_from_events_statemachine(
163 &self,
164 state_ty_ident: &Ident,
165 output_name: &ParentRef,
166 state_prefix: &str,
167 ) -> Result<FromEventsSubmachine> {
168 match self {
169 Self::Transparent { ty, member } => {
170 let from_xml_builder_ty = from_xml_builder_ty(ty.clone());
171 let from_events_fn = from_events_fn(ty.clone());
172 let feed_fn = feed_fn(from_xml_builder_ty.clone());
173
174 let output_cons = match output_name {
175 ParentRef::Named(ref path) => quote! {
176 #path { #member: result }
177 },
178 ParentRef::Unnamed { .. } => quote! {
179 ( result, )
180 },
181 };
182
183 let state_name = quote::format_ident!("{}Default", state_prefix);
184 let builder_data_ident = quote::format_ident!("__xso_data");
185
186 Ok(FromEventsSubmachine {
190 defs: TokenStream::default(),
191 states: vec![
192 State::new_with_builder(
193 state_name.clone(),
194 &builder_data_ident,
195 &from_xml_builder_ty,
196 )
197 .with_impl(quote! {
198 match #feed_fn(&mut #builder_data_ident, ev)? {
199 ::core::option::Option::Some(result) => {
200 ::core::result::Result::Ok(::core::ops::ControlFlow::Continue(#output_cons))
201 }
202 ::core::option::Option::None => {
203 ::core::result::Result::Ok(::core::ops::ControlFlow::Break(Self::#state_name {
204 #builder_data_ident,
205 }))
206 }
207 }
208 })
209 ],
210 init: quote! {
211 #from_events_fn(name, attrs).map(|#builder_data_ident| Self::#state_name { #builder_data_ident })
212 },
213 })
214 }
215
216 Self::Compound {
217 ref inner,
218 ref xml_namespace,
219 ref xml_name,
220 } => Ok(inner
221 .make_from_events_statemachine(state_ty_ident, output_name, state_prefix)?
222 .with_augmented_init(|init| {
223 quote! {
224 if name.0 != #xml_namespace || name.1 != #xml_name {
225 ::core::result::Result::Err(::xso::error::FromEventsError::Mismatch {
226 name,
227 attrs,
228 })
229 } else {
230 #init
231 }
232 }
233 })),
234 }
235 }
236
237 pub(crate) fn make_as_item_iter_statemachine(
238 &self,
239 input_name: &ParentRef,
240 state_ty_ident: &Ident,
241 state_prefix: &str,
242 item_iter_ty_lifetime: &Lifetime,
243 ) -> Result<AsItemsSubmachine> {
244 match self {
245 Self::Transparent { ty, member } => {
246 let item_iter_ty = item_iter_ty(ty.clone(), item_iter_ty_lifetime.clone());
247 let as_xml_iter_fn = as_xml_iter_fn(ty.clone());
248
249 let state_name = quote::format_ident!("{}Default", state_prefix);
250 let iter_ident = quote::format_ident!("__xso_data");
251
252 let destructure = match input_name {
253 ParentRef::Named(ref path) => quote! {
254 #path { #member: #iter_ident }
255 },
256 ParentRef::Unnamed { .. } => quote! {
257 (#iter_ident, )
258 },
259 };
260
261 Ok(AsItemsSubmachine {
265 defs: TokenStream::default(),
266 states: vec![State::new_with_builder(
267 state_name.clone(),
268 &iter_ident,
269 &item_iter_ty,
270 )
271 .with_mut(&iter_ident)
272 .with_impl(quote! {
273 #iter_ident.next().transpose()?
274 })],
275 destructure,
276 init: quote! {
277 #as_xml_iter_fn(#iter_ident).map(|#iter_ident| Self::#state_name { #iter_ident })?
278 },
279 })
280 }
281
282 Self::Compound {
283 ref inner,
284 ref xml_namespace,
285 ref xml_name,
286 } => Ok(inner
287 .make_as_item_iter_statemachine(
288 input_name,
289 state_ty_ident,
290 state_prefix,
291 item_iter_ty_lifetime,
292 )?
293 .with_augmented_init(|init| {
294 quote! {
295 let name = (
296 ::xso::exports::rxml::Namespace::from(#xml_namespace),
297 ::xso::exports::alloc::borrow::Cow::Borrowed(#xml_name),
298 );
299 #init
300 }
301 })),
302 }
303 }
304}
305
306pub(crate) struct StructDef {
308 target_ty_ident: Ident,
310
311 builder_ty_ident: Ident,
313
314 item_iter_ty_ident: Ident,
316
317 debug: bool,
319
320 inner: StructInner,
322}
323
324impl StructDef {
325 pub(crate) fn new(ident: &Ident, mut meta: XmlCompoundMeta, fields: &Fields) -> Result<Self> {
327 let builder_ty_ident = match meta.builder.take() {
328 Some(v) => v,
329 None => quote::format_ident!("{}FromXmlBuilder", ident.to_string()),
330 };
331
332 let item_iter_ty_ident = match meta.iterator.take() {
333 Some(v) => v,
334 None => quote::format_ident!("{}AsXmlIterator", ident.to_string()),
335 };
336
337 let debug = meta.debug.take();
338
339 let inner = StructInner::new(meta, fields)?;
340
341 Ok(Self {
342 inner,
343 target_ty_ident: ident.clone(),
344 builder_ty_ident,
345 item_iter_ty_ident,
346 debug: debug.is_set(),
347 })
348 }
349}
350
351impl ItemDef for StructDef {
352 fn make_from_events_builder(
353 &self,
354 vis: &Visibility,
355 name_ident: &Ident,
356 attrs_ident: &Ident,
357 ) -> Result<FromXmlParts> {
358 let target_ty_ident = &self.target_ty_ident;
359 let builder_ty_ident = &self.builder_ty_ident;
360 let state_ty_ident = quote::format_ident!("{}State", builder_ty_ident);
361
362 let defs = self
363 .inner
364 .make_from_events_statemachine(
365 &state_ty_ident,
366 &Path::from(target_ty_ident.clone()).into(),
367 "Struct",
368 )?
369 .compile()
370 .render(
371 vis,
372 builder_ty_ident,
373 &state_ty_ident,
374 &TypePath {
375 qself: None,
376 path: target_ty_ident.clone().into(),
377 }
378 .into(),
379 )?;
380
381 Ok(FromXmlParts {
382 defs,
383 from_events_body: quote! {
384 #builder_ty_ident::new(#name_ident, #attrs_ident)
385 },
386 builder_ty_ident: builder_ty_ident.clone(),
387 })
388 }
389
390 fn make_as_xml_iter(&self, vis: &Visibility) -> Result<AsXmlParts> {
391 let target_ty_ident = &self.target_ty_ident;
392 let item_iter_ty_ident = &self.item_iter_ty_ident;
393 let item_iter_ty_lifetime = Lifetime {
394 apostrophe: Span::call_site(),
395 ident: Ident::new("xso_proc_as_xml_iter_lifetime", Span::call_site()),
396 };
397 let item_iter_ty = Type::Path(TypePath {
398 qself: None,
399 path: Path {
400 leading_colon: None,
401 segments: [PathSegment {
402 ident: item_iter_ty_ident.clone(),
403 arguments: PathArguments::AngleBracketed(AngleBracketedGenericArguments {
404 colon2_token: None,
405 lt_token: token::Lt {
406 spans: [Span::call_site()],
407 },
408 args: [GenericArgument::Lifetime(item_iter_ty_lifetime.clone())]
409 .into_iter()
410 .collect(),
411 gt_token: token::Gt {
412 spans: [Span::call_site()],
413 },
414 }),
415 }]
416 .into_iter()
417 .collect(),
418 },
419 });
420 let state_ty_ident = quote::format_ident!("{}State", item_iter_ty_ident);
421
422 let defs = self
423 .inner
424 .make_as_item_iter_statemachine(
425 &Path::from(target_ty_ident.clone()).into(),
426 &state_ty_ident,
427 "Struct",
428 &item_iter_ty_lifetime,
429 )?
430 .compile()
431 .render(
432 vis,
433 &ref_ty(
434 ty_from_ident(target_ty_ident.clone()).into(),
435 item_iter_ty_lifetime.clone(),
436 ),
437 &state_ty_ident,
438 &item_iter_ty_lifetime,
439 &item_iter_ty,
440 )?;
441
442 Ok(AsXmlParts {
443 defs,
444 as_xml_iter_body: quote! {
445 #item_iter_ty_ident::new(self)
446 },
447 item_iter_ty,
448 item_iter_ty_lifetime,
449 })
450 }
451
452 fn debug(&self) -> bool {
453 self.debug
454 }
455}