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