1#![no_std]
6#![forbid(unsafe_code, missing_docs, rustdoc::all)]
7
8extern crate alloc;
9
10use core::any;
11
12use alloc::vec::Vec;
13
14use proc_macro2::Span;
15
16use syn::{Data, DataEnum, DataStruct, DeriveInput, Variant, spanned::Spanned};
17
18use common::{FieldRef, Format, ImportRoot, InlineOptions, Param, ParamKind, Transparent};
19
20use structure::{Structure, StructureOptions};
21
22pub mod enumerate;
23
24pub mod structure;
25
26pub mod common;
27
28pub mod expand;
29
30#[derive(Debug, Clone)]
35struct ErrorList(Option<syn::Error>);
36
37impl ErrorList {
38 #[inline]
40 pub const fn empty() -> Self {
41 Self(None)
42 }
43
44 #[inline]
46 pub fn append(&mut self, error: syn::Error) {
47 let Self(target_container) = self;
48
49 match target_container.as_mut() {
50 Some(target_list) => target_list.combine(error),
51 None => *target_container = Some(error),
52 }
53 }
54
55 #[inline]
59 pub fn compose<T>(self, value: T) -> syn::Result<T> {
60 match self {
61 Self(Some(target_error)) => Err(target_error),
62 Self(None) => Ok(value),
63 }
64 }
65}
66
67#[derive(Clone, Debug, PartialEq, Eq, Hash)]
71#[allow(clippy::large_enum_variant)]
72pub enum Target {
73 Enum(enumerate::Enumeration),
75
76 Struct(structure::Structure),
78}
79
80impl Target {
81 pub fn input(input: &DeriveInput) -> syn::Result<Self> {
83 struct ParamBucket<P> {
84 span_subject: Span,
86
87 first_found: Option<(Span, P)>,
89
90 extra_found: Vec<(Span, P)>,
92 }
93
94 impl<P> ParamBucket<P> {
95 #[inline]
97 pub fn empty() -> Self {
98 Self {
99 span_subject: Span::call_site(),
100 first_found: None,
101 extra_found: Vec::new(),
102 }
103 }
104
105 #[inline]
108 pub fn spanned<S>(span_subject: &S) -> Self
109 where
110 S: Spanned,
111 {
112 Self {
113 first_found: None,
114 extra_found: Vec::new(),
115 span_subject: span_subject.span(),
116 }
117 }
118
119 #[inline]
121 pub fn push<T>(&mut self, (spanned, param): (T, P))
122 where
123 T: Spanned,
124 {
125 let &mut Self {
126 ref mut first_found,
127 ref mut extra_found,
128 ..
129 } = self;
130
131 let span = spanned.span();
132
133 let value = (span, param);
134
135 match first_found {
136 Some(_) => extra_found.push(value),
137 None => *first_found = Some(value),
138 }
139 }
140
141 #[inline]
143 pub fn one_or_nothing(self) -> syn::Result<Option<P>> {
144 let Self {
145 first_found, extra_found, ..
146 } = self;
147
148 match first_found {
149 Some((span, param)) => {
150 if extra_found.is_empty() {
151 Ok(Some(param))
152 } else {
153 let mut error = syn::Error::new(span, "multiple parameters found");
154
155 for (span, _) in extra_found {
156 error.combine(syn::Error::new(span, "extraneous parameter"));
157 }
158
159 Err(error)
160 }
161 }
162 None => Ok(None),
163 }
164 }
165
166 #[inline]
168 pub fn only_one(self) -> syn::Result<P> {
169 let Self {
170 span_subject,
171 first_found,
172 extra_found,
173 } = self;
174
175 match first_found {
176 Some((span, param)) => {
177 if extra_found.is_empty() {
178 Ok(param)
179 } else {
180 let mut error = syn::Error::new(span, "multiple parameters found");
181
182 for (span, _) in extra_found {
183 error.combine(syn::Error::new(span, "extraneous parameter"));
184 }
185
186 Err(error)
187 }
188 }
189 None => Err(syn::Error::new(
190 span_subject,
191 format_args!("{}: no parameters found, but one was expected", any::type_name::<P>()),
192 )),
193 }
194 }
195 }
196
197 let DeriveInput {
198 attrs,
199 ident: name_ident,
200 generics,
201 data,
202 ..
203 } = input;
204
205 let mut error_list = ErrorList::empty();
206
207 match data {
208 Data::Struct(DataStruct { fields, .. }) => {
209 let (param_list, ..) = common::Param::classify(attrs)?;
210
211 let mut source_bucket = ParamBucket::<FieldRef>::empty();
212 let mut format_bucket = ParamBucket::<Format>::empty();
213 let mut inline_bucket = ParamBucket::<InlineOptions>::empty();
214 let mut root_bucket = ParamBucket::<ImportRoot>::empty();
215
216 let mut transparent_bucket = ParamBucket::<Transparent>::empty();
217 let mut from_bucket = ParamBucket::<()>::empty();
218
219 for Param { name, kind } in param_list {
220 match kind {
221 ParamKind::Source(source) => source_bucket.push((name, source)),
222 ParamKind::Format(format) => format_bucket.push((name, format)),
223 ParamKind::Inline(inline) => inline_bucket.push((name, inline)),
224 ParamKind::Import(root) => root_bucket.push((name, root)),
225 ParamKind::Transparent(transparent) => transparent_bucket.push((name, transparent)),
226 ParamKind::From => from_bucket.push((name, ())),
227 }
228 }
229
230 let source_field = source_bucket.one_or_nothing()?;
231 let inline_opts = inline_bucket.one_or_nothing()?;
232 let root_import = root_bucket.one_or_nothing()?;
233 let transparent = transparent_bucket.one_or_nothing()?;
234
235 let from = from_bucket.one_or_nothing()?;
236
237 let options = match (transparent, from) {
238 (Some(transparent), None) => StructureOptions::Transparent(transparent),
239 (None, Some(..)) => {
240 let format_args = format_bucket.only_one()?;
241
242 let (field_ref, field_type) = structure::FieldList::exactly_one(fields)?;
243
244 StructureOptions::Forward {
245 source_field,
246 field_ref,
247 field_type,
248 format_args,
249 }
250 }
251 (None, None) => {
252 let format_args = format_bucket.only_one()?;
253
254 StructureOptions::Standalone { source_field, format_args }
255 }
256 (Some(_), Some(())) => {
257 return Err(syn::Error::new_spanned(
258 name_ident,
259 "structure can't be both transparent and from-forwarded",
260 ));
261 }
262 };
263
264 let field_list = structure::FieldList::raw(fields)?;
265
266 let generics = generics.clone();
267
268 let name_ident = name_ident.clone();
269
270 Ok(Target::Struct(Structure {
271 inline_opts,
272 root_import,
273 name_ident,
274 generics,
275 field_list,
276 options,
277 }))
278 }
279 Data::Enum(DataEnum { variants, .. }) => {
280 let (param_list, ..) = common::Param::classify(attrs)?;
281
282 let mut inline_bucket = ParamBucket::<InlineOptions>::empty();
283 let mut root_bucket = ParamBucket::<ImportRoot>::empty();
284
285 for Param { name, kind } in param_list {
286 match kind {
287 ParamKind::Inline(inline) => inline_bucket.push((name, inline)),
288 ParamKind::Import(root) => root_bucket.push((name, root)),
289 _ => error_list.append(syn::Error::new_spanned(name, "extraneous parameter")),
290 }
291 }
292
293 let inline_opts = inline_bucket.one_or_nothing()?;
294 let root_import = root_bucket.one_or_nothing()?;
295
296 let mut variant_list = Vec::with_capacity(variants.len());
297
298 for Variant { ident, attrs, fields, .. } in variants {
299 let (param_list, ..) = common::Param::classify(attrs)?;
300
301 let mut format_bucket = ParamBucket::<Format>::spanned(ident);
302 let mut source_bucket = ParamBucket::<FieldRef>::empty();
303 let mut transparent_bucket = ParamBucket::<Transparent>::empty();
304 let mut from_bucket = ParamBucket::<()>::empty();
305
306 for Param { name, kind } in param_list {
307 match kind {
308 ParamKind::Format(format) => format_bucket.push((name, format)),
309 ParamKind::Source(source_field) => source_bucket.push((name, source_field)),
310 ParamKind::Transparent(transparent) => transparent_bucket.push((name, transparent)),
311 ParamKind::From => from_bucket.push((name, ())),
312 _ => error_list.append(syn::Error::new_spanned(name, "extraneous parameter")),
313 }
314 }
315
316 let transparent = transparent_bucket.one_or_nothing()?;
317 let from = from_bucket.one_or_nothing()?;
318 let source_field = source_bucket.one_or_nothing()?;
319
320 let variant_name = ident.clone();
321
322 let variant = match (transparent, from) {
323 (Some(_), Some(())) => {
324 error_list.append(syn::Error::new_spanned(
325 ident,
326 "variant can't be both transparent and from-forwarded",
327 ));
328
329 continue;
330 }
331 (None, Some(..)) => {
332 let format = format_bucket.only_one()?;
333
334 let (field_ref, field_type) = structure::FieldList::exactly_one(fields)?;
335
336 enumerate::Variant::Forward {
337 format,
338 variant_name,
339 field_ref,
340 field_type,
341 }
342 }
343 (Some(transparent), None) => enumerate::Variant::Transparent {
344 transparent,
345 variant_name,
346 field_list: structure::FieldList::raw(fields)?,
347 },
348 (None, None) => {
349 let format = format_bucket.only_one()?;
350
351 enumerate::Variant::Struct {
352 format,
353 source_field,
354 variant_name,
355 field_list: structure::FieldList::raw(fields)?,
356 }
357 }
358 };
359
360 variant_list.push(variant);
361 }
362
363 let generics = generics.clone();
364
365 let name_ident = name_ident.clone();
366
367 let enumeration = enumerate::Enumeration {
368 inline_opts,
369 root_import,
370 name_ident,
371 generics,
372 variant_list,
373 };
374
375 error_list.compose(enumeration).map(Target::Enum)
376 }
377 Data::Union(..) => Err(syn::Error::new_spanned(name_ident, "unions can't be errors")),
378 }
379 }
380}