1use proc_macro::TokenStream;
7use proc_macro2::{Span, TokenStream as TokenStream2};
8use quote::{quote, quote_spanned};
9use std::collections::BTreeMap;
10use syn::parse::ParseStream;
11use syn::spanned::Spanned;
12use syn::visit_mut::VisitMut;
13use syn::{Data, DeriveInput, Error, GenericParam, Ident, Lifetime, LitStr, Token, Type};
14
15#[proc_macro_derive(FieldSection, attributes(field, fields))]
19pub fn derive_field_section(input: TokenStream) -> TokenStream {
20 syn::parse::<DeriveInput>(input)
22 .and_then(|input| expand(&input))
23 .unwrap_or_else(Error::into_compile_error)
24 .into()
25}
26
27struct Section {
29 unknown: Option<(bool, LitStr)>,
31 forbidden: Vec<LitStr>,
32}
33
34#[derive(PartialEq)]
36enum Container {
37 Optional,
38 Required,
39 Many,
40}
41
42struct Declared {
43 member: Ident,
44 ty: Type,
45 name: LitStr,
46 invalid: Option<Ident>,
47 container: Container,
48}
49
50struct Flattened {
51 member: Ident,
52 ty: Type,
53 identity: Type,
55}
56
57enum Member {
59 Declared(LitStr, Option<Ident>),
60 Flattened,
61}
62
63impl Declared {
64 fn accumulator(&self) -> TokenStream2 {
66 let ty = &self.ty;
67 match self.container {
68 Container::Required => quote_spanned!(ty.span() => ::core::option::Option<#ty>),
69 Container::Optional | Container::Many => quote!(#ty),
70 }
71 }
72
73 fn add(&self, lifetime: &Lifetime) -> TokenStream2 {
75 let (member, name, accumulator) = (&self.member, &self.name, self.accumulator());
76 let invalid = match (&self.invalid, &self.container) {
77 (_, Container::Required) => quote!(::base_message::InvalidValue::Error),
78 (Some(invalid), _) => quote!(::base_message::InvalidValue::#invalid),
79 (None, _) => {
80 quote!(<#accumulator as ::base_message::__private::Accumulate<#lifetime>>::INVALID)
81 }
82 };
83 quote_spanned!(self.ty.span() => <#accumulator as ::base_message::__private::Accumulate<#lifetime>>::add(&mut __builder.#member, __value, __position, #name, #invalid)?)
84 }
85}
86
87struct StaticLifetimes;
89
90impl VisitMut for StaticLifetimes {
91 fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {
92 *lifetime = Lifetime::new("'static", lifetime.span());
93 }
94}
95
96fn expand(input: &DeriveInput) -> syn::Result<TokenStream2> {
97 let Data::Struct(data) = &input.data else {
98 return Err(Error::new_spanned(
99 input,
100 "FieldSection can only be derived for a struct with named fields",
101 ));
102 };
103 let own_lifetime = section_lifetime(input)?;
104 let lifetime = own_lifetime
105 .clone()
106 .unwrap_or_else(|| Lifetime::new("'__field", Span::call_site()));
107 let to_static = |ty: &Type| {
108 let mut ty = ty.clone();
109 StaticLifetimes.visit_type_mut(&mut ty);
110 ty
111 };
112 let section = struct_section(input)?;
113 let mut declared: Vec<Declared> = Vec::new();
114 let mut flattened: Vec<Flattened> = Vec::new();
115 for field in &data.fields {
116 let member = field.ident.clone().ok_or_else(|| {
117 Error::new_spanned(
118 field,
119 "FieldSection can only be derived for a struct with named fields",
120 )
121 })?;
122 let mut kind = None;
123 for attribute in &field.attrs {
124 let is_field = attribute.path().is_ident("field");
125 if !is_field && !attribute.path().is_ident("fields") {
126 continue;
127 }
128 if kind.is_some() {
129 return Err(Error::new_spanned(
130 attribute,
131 "a member takes one `#[field(\"name\")]` or `#[fields(flatten)]` declaration",
132 ));
133 }
134 kind = Some(if is_field {
135 let (name, invalid) = attribute.parse_args_with(parse_field)?;
136 check_name(&name)?;
137 if declared
138 .iter()
139 .any(|existing| existing.name.value() == name.value())
140 {
141 return Err(Error::new_spanned(
142 &name,
143 "this field name is already declared",
144 ));
145 }
146 Member::Declared(name, invalid)
147 } else {
148 let word: Ident = attribute.parse_args()?;
149 if word == "other" {
150 return Err(Error::new_spanned(
151 word,
152 "sections do not keep unknown fields; use `partition` to collect the fields a section does not take",
153 ));
154 }
155 if word != "flatten" {
156 return Err(Error::new_spanned(word, "expected `flatten`"));
157 }
158 Member::Flattened
159 });
160 }
161 match kind {
162 Some(Member::Declared(name, invalid)) => {
163 let container = container(&field.ty);
164 if let (Container::Required, Some(invalid)) = (&container, &invalid) {
165 if invalid == "Ignore" {
166 return Err(Error::new(
167 invalid.span(),
168 "a required field always treats an invalid value as an error; declare it as `Option<T>` to make it optional",
169 ));
170 }
171 }
172 declared.push(Declared {
173 container,
174 member,
175 ty: field.ty.clone(),
176 name,
177 invalid,
178 });
179 }
180 Some(Member::Flattened) => flattened.push(Flattened {
181 member,
182 identity: to_static(&field.ty),
183 ty: field.ty.clone(),
184 }),
185 None => {
186 return Err(Error::new_spanned(
187 field,
188 "declare this member with `#[field(\"name\")]` or `#[fields(flatten)]`",
189 ));
190 }
191 }
192 }
193 for forbidden in §ion.forbidden {
194 if declared
195 .iter()
196 .any(|declared| declared.name.value() == forbidden.value())
197 {
198 return Err(Error::new_spanned(
199 forbidden,
200 "this field name is declared, so it cannot also be forbidden",
201 ));
202 }
203 }
204
205 let ident = &input.ident;
206 let (section_type, identity) = match &own_lifetime {
207 Some(own) => (quote!(#ident<#own>), quote!(#ident<'static>)),
208 None => (quote!(#ident), quote!(#ident)),
209 };
210
211 let builder_fields = declared.iter().map(|declared| {
212 let (member, accumulator) = (&declared.member, declared.accumulator());
213 quote!(#member: ::base_message::__private::Slot<#accumulator>)
214 });
215 let flattened_builders = flattened.iter().map(|Flattened { member, ty, .. }| quote!(#member: <#ty as ::base_message::FieldSection<#lifetime>>::Builder));
216 let builder_members = declared.iter().map(|declared| &declared.member);
217 let flattened_members = flattened.iter().map(|Flattened { member, ty, .. }| quote!(#member: <#ty as ::base_message::FieldSection<#lifetime>>::builder()));
218
219 let mut groups: BTreeMap<(usize, u8), Vec<TokenStream2>> = BTreeMap::new();
221 for declared in &declared {
222 let (name, add) = (&declared.name, declared.add(&lifetime));
223 groups.entry(group(name)).or_default().push(quote! {
224 if __name.eq_ignore_ascii_case(#name) {
225 #add;
226 return ::core::result::Result::Ok(true);
227 }
228 });
229 }
230 for forbidden in §ion.forbidden {
231 groups.entry(group(forbidden)).or_default().push(quote! {
232 if __name.eq_ignore_ascii_case(#forbidden) {
233 return ::core::result::Result::Err(::base_message::__private::forbidden(#forbidden, __position));
234 }
235 });
236 }
237 let arms = groups
238 .iter()
239 .map(|((length, first), checks)| quote!((#length, #first) => { #(#checks)* }));
240 let flattened_accepts = flattened.iter().map(|Flattened { member, ty, .. }| {
241 quote! {
242 if <#ty as ::base_message::FieldSection<#lifetime>>::accept(&mut __builder.#member, __name, __value, __position)? {
243 return ::core::result::Result::Ok(true);
244 }
245 }
246 });
247
248 let accept_declaration = accept_declaration(&declared, &flattened, &lifetime);
249
250 let finishes = declared.iter().map(|declared| {
251 let (member, name, accumulator) = (&declared.member, &declared.name, declared.accumulator());
252 let finish = quote_spanned!(declared.ty.span() => <#accumulator as ::base_message::__private::Accumulate<#lifetime>>::finish(__builder.#member));
253 match declared.container {
254 Container::Required => quote!(#member: #finish.ok_or_else(|| ::base_message::__private::missing(#name))?),
255 Container::Optional | Container::Many => quote!(#member: #finish),
256 }
257 });
258 let flattened_finishes = flattened.iter().map(|Flattened { member, ty, .. }| quote!(#member: <#ty as ::base_message::FieldSection<#lifetime>>::finish(__builder.#member)?));
259 let names = declared.iter().map(|declared| &declared.name);
260 let forbidden = §ion.forbidden;
261 let flattened_names = flattened.iter().map(|Flattened { identity, .. }| quote!(&<#identity as ::base_message::FieldSection<'static>>::NAMES));
262 let rejects_unknown = matches!(section.unknown, Some((true, _)));
263 let checks = checks(&declared, &flattened, §ion);
264
265 Ok(quote! {
266 const _: () = {
267 #[doc(hidden)]
268 pub struct __FieldSectionBuilder<#lifetime> {
269 #(#builder_fields,)*
270 #(#flattened_builders,)*
271 __lifetime: ::core::marker::PhantomData<&#lifetime ()>,
272 }
273
274 #[automatically_derived]
275 impl<#lifetime> ::base_message::FieldSection<#lifetime> for #section_type {
276 const NAMES: ::base_message::FieldNames = ::base_message::FieldNames::new(&[#(#names),*], &[#(#forbidden),*], &[#(#flattened_names),*], #rejects_unknown);
277
278 type Identity = #identity;
279
280 type Builder = __FieldSectionBuilder<#lifetime>;
281
282 fn builder() -> Self::Builder {
284 __FieldSectionBuilder {
285 #(#builder_members: ::core::default::Default::default(),)*
286 #(#flattened_members,)*
287 __lifetime: ::core::marker::PhantomData,
288 }
289 }
290
291 #[inline]
292 fn accept(__builder: &mut Self::Builder, __name: &str, __value: &#lifetime [u8], __position: usize) -> ::core::result::Result<bool, ::base_message::FieldError> {
293 if let ::core::option::Option::Some(&__first) = __name.as_bytes().first() {
294 match (__name.len(), __first.to_ascii_lowercase()) {
295 #(#arms)*
296 _ => {}
297 }
298 }
299 #(#flattened_accepts)*
300 ::core::result::Result::Ok(false)
301 }
302
303 #[inline]
304 fn accept_declaration(__builder: &mut Self::Builder, __declaration: usize, __value: &#lifetime [u8], __position: usize) -> ::core::result::Result<(), ::base_message::FieldError> {
305 #accept_declaration
306 }
307
308 fn finish(__builder: Self::Builder) -> ::core::result::Result<Self, ::base_message::FieldError> {
309 ::core::result::Result::Ok(Self {
310 #(#finishes,)*
311 #(#flattened_finishes,)*
312 })
313 }
314 }
315
316 #checks
317 };
318 })
319}
320
321fn accept_declaration(
325 declared: &[Declared],
326 flattened: &[Flattened],
327 lifetime: &Lifetime,
328) -> TokenStream2 {
329 let own_count = declared.len();
330 let arms = declared.iter().enumerate().map(|(id, declared)| {
331 let add = declared.add(lifetime);
332 quote!(#id => { #add; ::core::result::Result::Ok(()) })
333 });
334 let routes = flattened.iter().map(|Flattened { member, ty, identity }| {
335 quote! {
336 let __count = const { <#identity as ::base_message::FieldSection<'static>>::NAMES.count() };
337 if __declaration < __count {
338 return <#ty as ::base_message::FieldSection<#lifetime>>::accept_declaration(&mut __builder.#member, __declaration, __value, __position);
339 }
340 let __declaration = __declaration - __count;
341 }
342 });
343 quote! {
344 match __declaration {
345 #(#arms)*
346 _ => {
347 let __declaration = __declaration - #own_count;
348 #(#routes)*
349 ::core::unreachable!("a static table classified a declaration id past this section's declarations")
350 }
351 }
352 }
353}
354
355fn checks(declared: &[Declared], flattened: &[Flattened], section: &Section) -> TokenStream2 {
357 let mut checks = Vec::new();
358 let names = declared
359 .iter()
360 .map(|declared| &declared.name)
361 .chain(§ion.forbidden);
362 for name in names {
363 for Flattened {
364 member, identity, ..
365 } in flattened
366 {
367 let message = format!(
368 "the field name {:?} is also declared or forbidden by the section flattened as `{member}`, so the two would disagree about it",
369 name.value()
370 );
371 checks.push(quote_spanned! {name.span() =>
372 const _: () = ::core::assert!(!<#identity as ::base_message::FieldSection<'static>>::NAMES.names(#name), #message);
373 });
374 }
375 }
376 for (index, second) in flattened.iter().enumerate() {
377 for first in &flattened[..index] {
378 let message = format!(
379 "the sections flattened as `{}` and `{}` both declare or forbid a field name, so they would disagree about it",
380 first.member, second.member
381 );
382 let (left, right) = (&first.identity, &second.identity);
383 checks.push(quote_spanned! {second.member.span() =>
384 const _: () = ::core::assert!(!<#left as ::base_message::FieldSection<'static>>::NAMES.shares_names_with(&<#right as ::base_message::FieldSection<'static>>::NAMES), #message);
385 });
386 }
387 }
388 if let Some((false, setting)) = §ion.unknown {
389 for Flattened {
390 member, identity, ..
391 } in flattened
392 {
393 let message = format!(
394 "the section flattened as `{member}` rejects unknown fields, so this section cannot ignore them"
395 );
396 checks.push(quote_spanned! {setting.span() =>
397 const _: () = ::core::assert!(!<#identity as ::base_message::FieldSection<'static>>::NAMES.rejects_unknown(), #message);
398 });
399 }
400 }
401 quote!(#(#checks)*)
402}
403
404fn group(name: &LitStr) -> (usize, u8) {
406 let text = name.value();
407 (text.len(), text.as_bytes()[0])
408}
409
410fn section_lifetime(input: &DeriveInput) -> syn::Result<Option<Lifetime>> {
412 let generics = &input.generics;
413 let message = "a field section may have one lifetime parameter, for values it borrows from the input, and no other generic parameters, bounds, or where clause";
414 if generics.params.len() > 1 || generics.where_clause.is_some() {
415 return Err(Error::new_spanned(generics, message));
416 }
417 match generics.params.first() {
418 None => Ok(None),
419 Some(GenericParam::Lifetime(parameter))
420 if parameter.bounds.is_empty() && parameter.attrs.is_empty() =>
421 {
422 Ok(Some(parameter.lifetime.clone()))
423 }
424 Some(parameter) => Err(Error::new_spanned(parameter, message)),
425 }
426}
427
428fn parse_field(input: ParseStream) -> syn::Result<(LitStr, Option<Ident>)> {
429 let name: LitStr = input.parse()?;
430 let mut invalid = None;
431 while !input.is_empty() {
432 input.parse::<Token![,]>()?;
433 if input.is_empty() {
434 break;
435 }
436 let key: Ident = input.parse()?;
437 if key != "invalid" {
438 return Err(Error::new_spanned(
439 key,
440 "expected `invalid = \"error\"` or `invalid = \"ignore\"`",
441 ));
442 }
443 if invalid.is_some() {
444 return Err(Error::new_spanned(key, "`invalid` is already given"));
445 }
446 input.parse::<Token![=]>()?;
447 let value: LitStr = input.parse()?;
448 invalid = Some(match value.value().as_str() {
449 "error" => Ident::new("Error", value.span()),
450 "ignore" => Ident::new("Ignore", value.span()),
451 _ => {
452 return Err(Error::new_spanned(
453 value,
454 "`invalid` must be \"error\" or \"ignore\"",
455 ));
456 }
457 });
458 }
459 Ok((name, invalid))
460}
461
462fn struct_section(input: &DeriveInput) -> syn::Result<Section> {
464 let mut section = Section {
465 unknown: None,
466 forbidden: Vec::new(),
467 };
468 for attribute in &input.attrs {
469 if attribute.path().is_ident("field") {
470 return Err(Error::new_spanned(
471 attribute,
472 "`#[field(...)]` declares a member; on the struct, use `#[fields(...)]`",
473 ));
474 }
475 if !attribute.path().is_ident("fields") {
476 continue;
477 }
478 attribute.parse_args_with(|stream: ParseStream| {
479 while !stream.is_empty() {
480 let key: Ident = stream.parse()?;
481 if key == "unknown" {
482 if section.unknown.is_some() {
483 return Err(Error::new_spanned(key, "`unknown` is already given"));
484 }
485 stream.parse::<Token![=]>()?;
486 let value: LitStr = stream.parse()?;
487 let rejects = match value.value().as_str() {
488 "reject" => true,
489 "ignore" => false,
490 _ => return Err(Error::new_spanned(value, "`unknown` must be \"reject\" or \"ignore\"; use `partition` to collect the fields a section does not take")),
491 };
492 section.unknown = Some((rejects, value));
493 } else if key == "forbid" {
494 let content;
495 syn::parenthesized!(content in stream);
496 for name in content.parse_terminated(|input: ParseStream| input.parse::<LitStr>(), Token![,])? {
497 check_name(&name)?;
498 if section.forbidden.iter().any(|existing| existing.value() == name.value()) {
499 return Err(Error::new_spanned(&name, "this field name is already forbidden"));
500 }
501 section.forbidden.push(name);
502 }
503 } else {
504 return Err(Error::new_spanned(key, "expected `unknown = \"...\"` or `forbid(\"name\", ...)`"));
505 }
506 if !stream.is_empty() {
507 stream.parse::<Token![,]>()?;
508 }
509 }
510 Ok(())
511 })?;
512 }
513 Ok(section)
514}
515
516fn check_name(name: &LitStr) -> syn::Result<()> {
518 let text = name.value();
519 let valid = !text.is_empty()
520 && text.bytes().all(|byte| {
521 byte.is_ascii_lowercase() || byte.is_ascii_digit() || b"!#$%&'*+-.^_`|~".contains(&byte)
522 });
523 if valid {
524 Ok(())
525 } else {
526 Err(Error::new_spanned(
527 name,
528 "a field name must be a lowercase token, such as \"content-type\"",
529 ))
530 }
531}
532
533fn container(ty: &Type) -> Container {
535 let Type::Path(path) = ty else {
536 return Container::Required;
537 };
538 match path
539 .path
540 .segments
541 .last()
542 .map(|segment| segment.ident.to_string())
543 .as_deref()
544 {
545 Some("Option") => Container::Optional,
546 Some("Vec") => Container::Many,
547 _ => Container::Required,
548 }
549}