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