1#![deny(missing_docs)]
124#![deny(unsafe_code)]
125
126use proc_macro::TokenStream;
127use proc_macro2::TokenStream as TokenStream2;
128use proc_macro_crate::{FoundCrate, crate_name};
129use quote::{format_ident, quote};
130use syn::{
131 Attribute, Data, DeriveInput, Field, Fields, GenericArgument, Ident,
132 Path, PathArguments, Type, parse_macro_input,
133};
134
135fn kv_crate_path(container_attrs: &ContainerAttrs) -> TokenStream2 {
144 if let Some(path) = &container_attrs.crate_path {
145 return quote! { #path };
146 }
147 match crate_name("pkgsrc-kv") {
148 Ok(FoundCrate::Itself) => quote! { crate },
149 Ok(FoundCrate::Name(name)) => {
150 let ident = format_ident!("{}", name);
151 quote! { ::#ident }
152 }
153 Err(_) => quote! { ::pkgsrc_kv },
154 }
155}
156
157#[proc_macro_derive(Kv, attributes(kv))]
164pub fn derive_kv(input: TokenStream) -> TokenStream {
165 let input = parse_macro_input!(input as DeriveInput);
166
167 match generate_impl(&input) {
168 Ok(tokens) => tokens.into(),
169 Err(err) => err.to_compile_error().into(),
170 }
171}
172
173fn generate_impl(input: &DeriveInput) -> syn::Result<TokenStream2> {
175 let name = &input.ident;
176 let container_attrs = ContainerAttrs::parse(&input.attrs)?;
177 let kv = kv_crate_path(&container_attrs);
178
179 let fields = extract_named_fields(input)?;
180
181 let parsed_fields: Vec<ParsedField> = fields
182 .iter()
183 .map(ParsedField::from_field)
184 .collect::<syn::Result<_>>()?;
185
186 let collect_field =
187 parsed_fields.iter().find(|f| f.kind == FieldKind::Collect);
188 let warnings_field =
189 parsed_fields.iter().find(|f| f.kind == FieldKind::Warnings);
190 let regular_fields: Vec<_> = parsed_fields
191 .iter()
192 .filter(|f| {
193 f.kind != FieldKind::Collect && f.kind != FieldKind::Warnings
194 })
195 .collect();
196
197 let field_decls = generate_field_declarations(&parsed_fields);
198 let warnings_ident = warnings_field.map(|f| &f.ident);
199 let match_arms = generate_match_arms(®ular_fields, warnings_ident, &kv);
200 let unknown_handling =
201 generate_unknown_handling(&container_attrs, collect_field, &kv);
202 let field_extracts: Vec<_> = parsed_fields
203 .iter()
204 .map(|f| f.extract_expr(&kv))
205 .collect();
206 let field_names: Vec<_> = parsed_fields.iter().map(|f| &f.ident).collect();
207
208 let serde_impl = generate_serde_impl(name, &parsed_fields);
209
210 Ok(quote! {
211 impl #name {
212 pub fn parse(input: &str) -> std::result::Result<Self, #kv::KvError> {
223 use #kv::FromKv;
224
225 #(#field_decls)*
226
227 let input_start = input.as_ptr() as usize;
228
229 for line in input.lines() {
230 if line.is_empty() {
231 continue;
232 }
233
234 let line_offset = line.as_ptr() as usize - input_start;
237
238 let eq_pos = match line.find('=') {
239 Some(p) => p,
240 None => {
241 return Err(#kv::KvError::ParseLine(#kv::Span {
242 offset: line_offset,
243 len: line.len(),
244 }));
245 }
246 };
247
248 let key = &line[..eq_pos];
249 let value = &line[eq_pos + 1..];
250 let value_offset = line_offset + eq_pos + 1;
251 let value_span = #kv::Span {
252 offset: value_offset,
253 len: value.len(),
254 };
255
256 match key {
257 #(#match_arms)*
258 #unknown_handling
259 }
260 }
261
262 Ok(#name {
263 #(#field_names: #field_extracts,)*
264 })
265 }
266 }
267
268 #serde_impl
269 })
270}
271
272fn extract_named_fields(
274 input: &DeriveInput,
275) -> syn::Result<&syn::punctuated::Punctuated<Field, syn::token::Comma>> {
276 let Data::Struct(data) = &input.data else {
277 return Err(syn::Error::new_spanned(
278 input,
279 "Kv derive only supports structs",
280 ));
281 };
282 let Fields::Named(fields) = &data.fields else {
283 return Err(syn::Error::new_spanned(
284 input,
285 "Kv derive only supports structs with named fields",
286 ));
287 };
288 Ok(&fields.named)
289}
290
291fn generate_field_declarations(fields: &[ParsedField]) -> Vec<TokenStream2> {
293 fields
294 .iter()
295 .map(|f| {
296 let ident = &f.ident;
297 let state_ty = f.state_type();
298 match f.kind {
299 FieldKind::Collect => {
300 quote! { let mut #ident: #state_ty = std::collections::HashMap::new(); }
301 }
302 FieldKind::Warnings => {
303 quote! { let mut #ident: #state_ty = Vec::new(); }
304 }
305 _ => quote! { let mut #ident: #state_ty = None; },
306 }
307 })
308 .collect()
309}
310
311fn generate_match_arms(
313 fields: &[&ParsedField],
314 warnings_ident: Option<&Ident>,
315 kv: &TokenStream2,
316) -> Vec<TokenStream2> {
317 fields
318 .iter()
319 .map(|f| {
320 let ident = &f.ident;
321 let key_name = &f.key_name;
322 if f.lenient {
323 let inner = &f.inner_type;
324 match warnings_ident {
325 Some(warnings) => quote! {
326 #key_name => {
327 match <#inner as FromKv>::from_kv(value, value_span) {
328 Ok(parsed) => #ident = Some(parsed),
329 Err(_) => {
330 #ident = None;
331 #warnings.push(#kv::KvWarning {
332 variable: key.to_string(),
333 value: value.to_string(),
334 span: value_span,
335 });
336 }
337 }
338 }
339 },
340 None => quote! {
341 #key_name => {
342 #ident = <#inner as FromKv>::from_kv(value, value_span).ok();
343 }
344 },
345 }
346 } else {
347 let merge_expr = f.merge_expr(kv);
348 quote! {
349 #key_name => {
350 #ident = Some(#merge_expr);
351 }
352 }
353 }
354 })
355 .collect()
356}
357
358fn generate_unknown_handling(
360 container_attrs: &ContainerAttrs,
361 collect_field: Option<&ParsedField>,
362 kv: &TokenStream2,
363) -> TokenStream2 {
364 match collect_field {
365 Some(field) => {
366 let ident = &field.ident;
367 quote! {
368 _ => {
369 #ident.insert(key.to_string(), value.to_string());
370 }
371 }
372 }
373 None if container_attrs.allow_unknown => {
374 quote! { _ => {} }
375 }
376 None => {
377 quote! {
378 unknown => {
379 return Err(#kv::KvError::UnknownVariable {
380 variable: unknown.to_string(),
381 span: #kv::Span {
382 offset: line_offset,
383 len: unknown.len(),
384 },
385 });
386 }
387 }
388 }
389 }
390}
391
392fn generate_serde_impl(name: &Ident, fields: &[ParsedField]) -> TokenStream2 {
396 let helper_fields: Vec<&ParsedField> = fields
400 .iter()
401 .filter(|f| f.kind != FieldKind::Warnings)
402 .collect();
403
404 let field_defs: Vec<_> = helper_fields
405 .iter()
406 .map(|f| {
407 let ident = &f.ident;
408 let ty = &f.original_type;
409 let key_name = &f.key_name;
410
411 let serde_attrs = match f.kind {
412 FieldKind::Required | FieldKind::Vec | FieldKind::MultiLine => {
413 quote! {
414 #[serde(rename = #key_name)]
415 }
416 }
417 FieldKind::Optional | FieldKind::OptionVec | FieldKind::OptionMultiLine => {
418 quote! {
419 #[serde(rename = #key_name, default, skip_serializing_if = "Option::is_none")]
420 }
421 }
422 FieldKind::Collect => {
423 quote! {
424 #[serde(flatten)]
425 }
426 }
427 FieldKind::Warnings => quote! {},
428 };
429
430 quote! {
431 #serde_attrs
432 #ident: #ty
433 }
434 })
435 .collect();
436
437 let ser_field_defs: Vec<_> = helper_fields
444 .iter()
445 .map(|f| {
446 let ident = &f.ident;
447 let key_name = &f.key_name;
448 match f.kind {
449 FieldKind::Required | FieldKind::Vec | FieldKind::MultiLine => {
450 let ty = &f.original_type;
451 quote! {
452 #[serde(rename = #key_name)]
453 #ident: &'a #ty
454 }
455 }
456 FieldKind::Optional
457 | FieldKind::OptionVec
458 | FieldKind::OptionMultiLine => {
459 let inner = extract_type_param(&f.original_type, "Option")
460 .expect("optional field always has an Option<...> type");
461 quote! {
462 #[serde(rename = #key_name, skip_serializing_if = "Option::is_none")]
463 #ident: Option<&'a #inner>
464 }
465 }
466 FieldKind::Collect => {
467 let ty = &f.original_type;
468 quote! {
469 #[serde(flatten)]
470 #ident: &'a #ty
471 }
472 }
473 FieldKind::Warnings => unreachable!(),
475 }
476 })
477 .collect();
478
479 let ser_to_fields: Vec<_> = helper_fields
480 .iter()
481 .map(|f| {
482 let ident = &f.ident;
483 match f.kind {
484 FieldKind::Optional
485 | FieldKind::OptionVec
486 | FieldKind::OptionMultiLine => {
487 quote! { #ident: self.#ident.as_ref() }
488 }
489 _ => quote! { #ident: &self.#ident },
490 }
491 })
492 .collect();
493
494 let ser_lifetime = if helper_fields.is_empty() {
499 quote! {}
500 } else {
501 quote! { <'a> }
502 };
503
504 let from_fields: Vec<_> = fields
505 .iter()
506 .map(|f| {
507 let ident = &f.ident;
508 if f.kind == FieldKind::Warnings {
509 quote! { #ident: Default::default() }
510 } else {
511 quote! { #ident: helper.#ident }
512 }
513 })
514 .collect();
515
516 quote! {
517 #[cfg(feature = "serde")]
518 impl serde::Serialize for #name {
519 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
520 where
521 S: serde::Serializer,
522 {
523 #[derive(serde::Serialize)]
524 struct Helper #ser_lifetime {
525 #(#ser_field_defs,)*
526 }
527
528 let helper = Helper {
529 #(#ser_to_fields,)*
530 };
531 helper.serialize(serializer)
532 }
533 }
534
535 #[cfg(feature = "serde")]
536 impl<'de> serde::Deserialize<'de> for #name {
537 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
538 where
539 D: serde::Deserializer<'de>,
540 {
541 #[derive(serde::Deserialize)]
542 struct Helper {
543 #(#field_defs,)*
544 }
545
546 let helper = Helper::deserialize(deserializer)?;
547 Ok(Self {
548 #(#from_fields,)*
549 })
550 }
551 }
552 }
553}
554
555#[derive(Default)]
557struct ContainerAttrs {
558 allow_unknown: bool,
560 crate_path: Option<Path>,
562}
563
564impl ContainerAttrs {
565 fn parse(attrs: &[Attribute]) -> syn::Result<Self> {
567 let mut result = Self::default();
568
569 for attr in attrs {
570 if !attr.path().is_ident("kv") {
571 continue;
572 }
573
574 attr.parse_nested_meta(|meta| {
575 if meta.path.is_ident("allow_unknown") {
576 result.allow_unknown = true;
577 Ok(())
578 } else if meta.path.is_ident("crate") {
579 let lit: syn::LitStr = meta.value()?.parse()?;
580 result.crate_path = Some(lit.parse()?);
581 Ok(())
582 } else {
583 Err(meta.error(
584 "unknown container attribute; expected `allow_unknown` or `crate`",
585 ))
586 }
587 })?;
588 }
589
590 Ok(result)
591 }
592}
593
594#[derive(Default)]
596struct FieldAttrs {
597 variable: Option<String>,
599 multiline: bool,
601 collect: bool,
603 lenient: bool,
605 warnings: bool,
607}
608
609impl FieldAttrs {
610 fn parse(attrs: &[Attribute]) -> syn::Result<Self> {
612 let mut result = Self::default();
613
614 for attr in attrs {
615 if !attr.path().is_ident("kv") {
616 continue;
617 }
618
619 attr.parse_nested_meta(|meta| {
620 if meta.path.is_ident("variable") {
621 let lit: syn::LitStr = meta.value()?.parse()?;
622 result.variable = Some(lit.value());
623 Ok(())
624 } else if meta.path.is_ident("multiline") {
625 result.multiline = true;
626 Ok(())
627 } else if meta.path.is_ident("collect") {
628 result.collect = true;
629 Ok(())
630 } else if meta.path.is_ident("lenient") {
631 result.lenient = true;
632 Ok(())
633 } else if meta.path.is_ident("warnings") {
634 result.warnings = true;
635 Ok(())
636 } else {
637 Err(meta.error(
638 "unknown field attribute; expected `variable`, `multiline`, `collect`, `lenient`, or `warnings`",
639 ))
640 }
641 })?;
642 }
643
644 Ok(result)
645 }
646}
647
648#[derive(Debug, Clone, Copy, PartialEq, Eq)]
650enum FieldKind {
651 Required,
653 Optional,
655 Vec,
657 OptionVec,
659 MultiLine,
661 OptionMultiLine,
663 Collect,
665 Warnings,
667}
668
669struct ParsedField {
671 ident: Ident,
673 key_name: String,
675 kind: FieldKind,
677 inner_type: Type,
679 original_type: Type,
681 lenient: bool,
683}
684
685impl ParsedField {
686 fn from_field(field: &Field) -> syn::Result<Self> {
688 let ident = field.ident.clone().ok_or_else(|| {
689 syn::Error::new_spanned(field, "expected named field")
690 })?;
691
692 let attrs = FieldAttrs::parse(&field.attrs)?;
693
694 if attrs.lenient
696 && (extract_type_param(&field.ty, "Option").is_none()
697 || extract_option_vec_inner(&field.ty).is_some())
698 {
699 return Err(syn::Error::new_spanned(
700 &field.ty,
701 "`lenient` attribute requires an `Option<T>` field",
702 ));
703 }
704
705 if attrs.collect {
707 validate_collect_type(&field.ty, field)?;
708 return Ok(Self {
709 ident,
710 key_name: String::new(),
711 kind: FieldKind::Collect,
712 inner_type: field.ty.clone(),
713 original_type: field.ty.clone(),
714 lenient: false,
715 });
716 }
717
718 if attrs.warnings {
720 validate_warnings_type(&field.ty, field)?;
721 return Ok(Self {
722 ident,
723 key_name: String::new(),
724 kind: FieldKind::Warnings,
725 inner_type: field.ty.clone(),
726 original_type: field.ty.clone(),
727 lenient: false,
728 });
729 }
730
731 if attrs.multiline
733 && extract_type_param(&field.ty, "Vec").is_none()
734 && extract_option_vec_inner(&field.ty).is_none()
735 {
736 return Err(syn::Error::new_spanned(
737 &field.ty,
738 "`multiline` attribute requires `Vec<T>` or `Option<Vec<T>>` type",
739 ));
740 }
741
742 let key_name = attrs
743 .variable
744 .unwrap_or_else(|| ident.to_string().to_uppercase());
745
746 let (kind, inner_type) = analyze_type(&field.ty, attrs.multiline);
747
748 Ok(Self {
749 ident,
750 key_name,
751 kind,
752 inner_type,
753 original_type: field.ty.clone(),
754 lenient: attrs.lenient,
755 })
756 }
757
758 fn state_type(&self) -> TokenStream2 {
760 let inner = &self.inner_type;
761 match self.kind {
762 FieldKind::Required | FieldKind::Optional => {
763 quote! { Option<#inner> }
764 }
765 FieldKind::Vec
766 | FieldKind::OptionVec
767 | FieldKind::MultiLine
768 | FieldKind::OptionMultiLine => {
769 quote! { Option<Vec<#inner>> }
770 }
771 FieldKind::Collect => {
772 quote! { std::collections::HashMap<String, String> }
773 }
774 FieldKind::Warnings => {
775 let ty = &self.original_type;
776 quote! { #ty }
777 }
778 }
779 }
780
781 fn merge_expr(&self, kv: &TokenStream2) -> TokenStream2 {
783 let inner = &self.inner_type;
784 let ident = &self.ident;
785
786 match self.kind {
787 FieldKind::Required | FieldKind::Optional => {
788 quote! {
789 <#inner as FromKv>::from_kv(value, value_span)?
790 }
791 }
792 FieldKind::Vec | FieldKind::OptionVec => {
793 quote! {
794 {
795 let mut items = Vec::new();
796 for (word, word_span) in #kv::words_with_spans(value, value_offset) {
797 items.push(<#inner as FromKv>::from_kv(word, word_span)?);
798 }
799 items
800 }
801 }
802 }
803 FieldKind::MultiLine | FieldKind::OptionMultiLine => {
804 quote! {
805 {
806 let mut vec = #ident.unwrap_or_default();
807 vec.push(<#inner as FromKv>::from_kv(value, value_span)?);
808 vec
809 }
810 }
811 }
812 FieldKind::Collect => {
813 quote! { unreachable!() }
815 }
816 FieldKind::Warnings => {
817 quote! { unreachable!() }
819 }
820 }
821 }
822
823 fn extract_expr(&self, kv: &TokenStream2) -> TokenStream2 {
825 let ident = &self.ident;
826 let key_name = &self.key_name;
827
828 match self.kind {
829 FieldKind::Required | FieldKind::Vec | FieldKind::MultiLine => {
830 quote! {
831 #ident.ok_or_else(|| #kv::KvError::Incomplete(#key_name.to_string()))?
832 }
833 }
834 FieldKind::Optional
835 | FieldKind::OptionVec
836 | FieldKind::OptionMultiLine
837 | FieldKind::Collect
838 | FieldKind::Warnings => {
839 quote! { #ident }
840 }
841 }
842 }
843}
844
845fn validate_collect_type(ty: &Type, field: &Field) -> syn::Result<()> {
847 let err = || {
848 syn::Error::new_spanned(
849 field,
850 "`collect` attribute requires `HashMap<String, String>` type",
851 )
852 };
853 let Type::Path(type_path) = ty else {
854 return Err(err());
855 };
856 let Some(segment) = type_path.path.segments.last() else {
857 return Err(err());
858 };
859 if segment.ident != "HashMap" {
860 return Err(err());
861 }
862 let PathArguments::AngleBracketed(args) = &segment.arguments else {
863 return Err(err());
864 };
865 let mut arg_iter = args.args.iter();
866 let is_valid = matches!(
867 (arg_iter.next(), arg_iter.next(), arg_iter.next()),
868 (
869 Some(GenericArgument::Type(Type::Path(k))),
870 Some(GenericArgument::Type(Type::Path(v))),
871 None
872 ) if k.path.is_ident("String") && v.path.is_ident("String")
873 );
874 if is_valid { Ok(()) } else { Err(err()) }
875}
876
877fn validate_warnings_type(ty: &Type, field: &Field) -> syn::Result<()> {
879 let err = || {
880 syn::Error::new_spanned(
881 field,
882 "`warnings` attribute requires a `Vec<KvWarning>` type",
883 )
884 };
885 let Some(inner) = extract_type_param(ty, "Vec") else {
886 return Err(err());
887 };
888 let Type::Path(type_path) = &inner else {
889 return Err(err());
890 };
891 match type_path.path.segments.last() {
892 Some(segment) if segment.ident == "KvWarning" => Ok(()),
893 _ => Err(err()),
894 }
895}
896
897fn analyze_type(ty: &Type, multiline: bool) -> (FieldKind, Type) {
899 if let Some(vec_inner) = extract_option_vec_inner(ty) {
901 let kind = if multiline {
902 FieldKind::OptionMultiLine
903 } else {
904 FieldKind::OptionVec
905 };
906 return (kind, vec_inner);
907 }
908
909 if let Some(inner) = extract_type_param(ty, "Option") {
911 return (FieldKind::Optional, inner);
912 }
913
914 if let Some(inner) = extract_type_param(ty, "Vec") {
916 let kind = if multiline {
917 FieldKind::MultiLine
918 } else {
919 FieldKind::Vec
920 };
921 return (kind, inner);
922 }
923
924 (FieldKind::Required, ty.clone())
926}
927
928fn extract_option_vec_inner(ty: &Type) -> Option<Type> {
930 let option_inner = extract_type_param(ty, "Option")?;
931 extract_type_param(&option_inner, "Vec")
932}
933
934fn extract_type_param(ty: &Type, wrapper: &str) -> Option<Type> {
936 let Type::Path(type_path) = ty else {
937 return None;
938 };
939 let segment = type_path.path.segments.last()?;
940 if segment.ident != wrapper {
941 return None;
942 }
943 let PathArguments::AngleBracketed(args) = &segment.arguments else {
944 return None;
945 };
946 let GenericArgument::Type(inner) = args.args.first()? else {
947 return None;
948 };
949 Some(inner.clone())
950}