git.delta.rocks / jrsonnet / refs/commits / 86671948a995

difftreelog

feat macro name interning

vtqzklkwYaroslav Bolyukin2026-03-22parent: #795a53d.patch.diff
in: master

9 files changed

modifiedcmds/jrsonnet/Cargo.tomldiffbeforeafterboth
11workspace = true11workspace = true
1212
13[features]13[features]
14default = [
15 "exp-regex",
16]
17
14experimental = [18experimental = [
15 "exp-preserve-order",19 "exp-preserve-order",
18 "exp-object-iteration",22 "exp-object-iteration",
19 "exp-bigint",23 "exp-bigint",
20 "exp-apply",24 "exp-apply",
21 "exp-regex",
22]25]
23# Use mimalloc as allocator26# Use mimalloc as allocator
24mimalloc = ["mimallocator"]27mimalloc = ["mimallocator"]
modifiedcrates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth
5 rc::Rc,5 rc::Rc,
6};6};
77
8use jrsonnet_gcmodule::{cc_dyn, Cc};8use jrsonnet_gcmodule::{cc_dyn, Cc, Trace};
9use jrsonnet_interner::IBytes;9use jrsonnet_interner::IBytes;
10use jrsonnet_parser::{Expr, Spanned};10use jrsonnet_parser::{Expr, Spanned};
1111
12use crate::{function::NativeFn, Context, Result, Thunk, Val};12use crate::{function::NativeFn, typed::Typed, Context, Result, Thunk, Val};
1313
14mod spec;14mod spec;
15pub use spec::{ArrayLike, *};15pub use spec::{ArrayLike, *};
modifiedcrates/jrsonnet-evaluator/src/obj/mod.rsdiffbeforeafterboth
12use educe::Educe;12use educe::Educe;
13use jrsonnet_gcmodule::{cc_dyn, Acyclic, Cc, Trace, Weak};13use jrsonnet_gcmodule::{cc_dyn, Acyclic, Cc, Trace, Weak};
14use jrsonnet_interner::IStr;14use jrsonnet_interner::IStr;
15use jrsonnet_parser::{Span, Visibility};15use jrsonnet_parser::Span;
16use rustc_hash::{FxHashMap, FxHashSet};16use rustc_hash::{FxHashMap, FxHashSet};
1717
18mod oop;18mod oop;
1919
20pub use jrsonnet_parser::Visibility;
20pub use oop::ObjValueBuilder;21pub use oop::ObjValueBuilder;
2122
22use crate::{23use crate::{
30};31};
3132
32#[cfg(not(feature = "exp-preserve-order"))]33#[cfg(not(feature = "exp-preserve-order"))]
33mod ordering {34pub mod ordering {
34 #![allow(35 #![allow(
35 // This module works as stub for preserve-order feature36 // This module works as stub for preserve-order feature
36 clippy::unused_self,37 clippy::unused_self,
41 #[derive(Clone, Copy, Default, Debug, Trace)]42 #[derive(Clone, Copy, Default, Debug, Trace)]
42 pub struct FieldIndex(());43 pub struct FieldIndex(());
43 impl FieldIndex {44 impl FieldIndex {
45 pub fn absolute(_v: u32) -> Self {
46 Self(())
47 }
44 pub const fn next(self) -> Self {48 pub const fn next(self) -> Self {
45 Self(())49 Self(())
46 }50 }
54}58}
5559
56#[cfg(feature = "exp-preserve-order")]60#[cfg(feature = "exp-preserve-order")]
57mod ordering {61pub mod ordering {
58 use std::cmp::Reverse;62 use std::cmp::Reverse;
5963
60 use jrsonnet_gcmodule::Trace;64 use jrsonnet_gcmodule::Trace;
6165
62 #[derive(Clone, Copy, Default, Debug, Trace, PartialEq, Eq, PartialOrd, Ord)]66 #[derive(Clone, Copy, Default, Debug, Trace, PartialEq, Eq, PartialOrd, Ord)]
63 pub struct FieldIndex(u32);67 pub struct FieldIndex(u32);
64 impl FieldIndex {68 impl FieldIndex {
69 pub fn absolute(v: u32) -> Self {
70 Self(v)
71 }
65 pub fn next(self) -> Self {72 pub fn next(self) -> Self {
66 Self(self.0 + 1)73 Self(self.0 + 1)
67 }74 }
149 Pending,156 Pending,
150}157}
151158
152type EnumFieldsHandler<'a> =159pub type EnumFieldsHandler<'a> =
153 dyn FnMut(SuperDepth, FieldIndex, IStr, EnumFields) -> ControlFlow<()> + 'a;160 dyn FnMut(SuperDepth, FieldIndex, IStr, EnumFields) -> ControlFlow<()> + 'a;
154161
155pub enum EnumFields {162pub enum EnumFields {
modifiedcrates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth
21mod inner;21mod inner;
22use inner::Inner;22use inner::Inner;
23
24mod names;
2325
24/// Interned string26/// Interned string
25///27///
addedcrates/jrsonnet-interner/src/names.rsdiffbeforeafterboth

no syntactic changes

modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
13 LitStr, Meta, Pat, Path, PathArguments, Result, ReturnType, Token, Type,13 LitStr, Meta, Pat, Path, PathArguments, Result, ReturnType, Token, Type,
14};14};
15
16use self::typed::derive_typed_inner;
17
18mod typed;
19mod names;
1520
16fn try_parse_attr_noargs<I>(attrs: &[Attribute], ident: I) -> Result<bool>21fn try_parse_attr_noargs<I>(attrs: &[Attribute], ident: I) -> Result<bool>
17where22where
437 })442 })
438}443}
439
440#[derive(Default)]
441#[allow(clippy::struct_excessive_bools)]
442struct TypedAttr {
443 rename: Option<String>,
444 aliases: Vec<String>,
445 flatten: bool,
446 /// flatten(ok) strategy for flattened optionals
447 /// field would be None in case of any parsing error (as in serde)
448 flatten_ok: bool,
449 // Should it be `field+:` instead of `field:`
450 add: bool,
451 // Should it be `field::` instead of `field:`
452 hide: bool,
453}
454impl Parse for TypedAttr {
455 fn parse(input: ParseStream) -> syn::Result<Self> {
456 let mut out = Self::default();
457 loop {
458 let lookahead = input.lookahead1();
459 if lookahead.peek(kw::rename) {
460 input.parse::<kw::rename>()?;
461 input.parse::<Token![=]>()?;
462 let name = input.parse::<LitStr>()?;
463 if out.rename.is_some() {
464 return Err(Error::new(
465 name.span(),
466 "rename attribute may only be specified once",
467 ));
468 }
469 out.rename = Some(name.value());
470 } else if lookahead.peek(kw::alias) {
471 input.parse::<kw::alias>()?;
472 input.parse::<Token![=]>()?;
473 let alias = input.parse::<LitStr>()?;
474 out.aliases.push(alias.value());
475 } else if lookahead.peek(kw::flatten) {
476 input.parse::<kw::flatten>()?;
477 out.flatten = true;
478 if input.peek(token::Paren) {
479 let content;
480 parenthesized!(content in input);
481 let lookahead = content.lookahead1();
482 if lookahead.peek(kw::ok) {
483 content.parse::<kw::ok>()?;
484 out.flatten_ok = true;
485 } else {
486 return Err(lookahead.error());
487 }
488 }
489 } else if lookahead.peek(kw::add) {
490 input.parse::<kw::add>()?;
491 out.add = true;
492 } else if lookahead.peek(kw::hide) {
493 input.parse::<kw::hide>()?;
494 out.hide = true;
495 } else if input.is_empty() {
496 break;
497 } else {
498 return Err(lookahead.error());
499 }
500 if input.peek(Token![,]) {
501 input.parse::<Token![,]>()?;
502 } else {
503 break;
504 }
505 }
506 Ok(out)
507 }
508}
509
510struct TypedField {
511 attr: TypedAttr,
512 ident: Ident,
513 ty: Type,
514 is_option: bool,
515 is_lazy: bool,
516}
517impl TypedField {
518 fn parse(field: &syn::Field) -> Result<Self> {
519 let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();
520 let Some(ident) = field.ident.clone() else {
521 return Err(Error::new(
522 field.span(),
523 "this field should appear in output object, but it has no visible name",
524 ));
525 };
526 let (is_option, ty) = extract_type_from_option(&field.ty)?
527 .map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));
528 if is_option && attr.flatten {
529 if !attr.flatten_ok {
530 return Err(Error::new(
531 field.span(),
532 "strategy should be set when flattening Option",
533 ));
534 }
535 } else if attr.flatten_ok {
536 return Err(Error::new(
537 field.span(),
538 "flatten(ok) is only useable on optional fields",
539 ));
540 }
541
542 let is_lazy = type_is_path(&ty, "Thunk").is_some();
543
544 Ok(Self {
545 attr,
546 ident,
547 ty,
548 is_option,
549 is_lazy,
550 })
551 }
552 /// None if this field is flattened in jsonnet output
553 fn name(&self) -> Option<String> {
554 if self.attr.flatten {
555 return None;
556 }
557 Some(
558 self.attr
559 .rename
560 .clone()
561 .unwrap_or_else(|| self.ident.to_string()),
562 )
563 }
564
565 fn expand_field(&self) -> Option<TokenStream> {
566 if self.is_option {
567 return None;
568 }
569 let name = self.name()?;
570 let ty = &self.ty;
571 Some(quote! {
572 (#name, <#ty as Typed>::TYPE)
573 })
574 }
575
576 fn expand_parse(&self) -> TokenStream {
577 if self.is_option {
578 self.expand_parse_optional()
579 } else {
580 self.expand_parse_mandatory()
581 }
582 }
583
584 fn expand_parse_optional(&self) -> TokenStream {
585 let ident = &self.ident;
586 let ty = &self.ty;
587
588 // optional flatten is handled in same way as serde
589 if self.attr.flatten {
590 return quote! {
591 #ident: <#ty as TypedObj>::parse(&obj).ok(),
592 };
593 }
594
595 let name = self.name().unwrap();
596 let aliases = &self.attr.aliases;
597
598 quote! {
599 #ident: {
600 let __value = if let Some(__v) = obj.get(#name.into())? {
601 Some(__v)
602 } #(else if let Some(__v) = obj.get(#aliases.into())? {
603 Some(__v)
604 })* else {
605 None
606 };
607
608 __value.map(<#ty as Typed>::from_untyped).transpose()?
609 },
610 }
611 }
612
613 fn expand_parse_mandatory(&self) -> TokenStream {
614 let ident = &self.ident;
615 let ty = &self.ty;
616
617 // optional flatten is handled in same way as serde
618 if self.attr.flatten {
619 return quote! {
620 #ident: <#ty as TypedObj>::parse(&obj)?,
621 };
622 }
623
624 let name = self.name().unwrap();
625 let aliases = &self.attr.aliases;
626
627 let error_text = if aliases.is_empty() {
628 // clippy does not understand name variable usage in quote! macro
629 #[allow(clippy::redundant_clone)]
630 name.clone()
631 } else {
632 format!("{name} (alias {})", aliases.join(", "))
633 };
634
635 quote! {
636 #ident: {
637 let __value = if let Some(__v) = obj.get(#name.into())? {
638 __v
639 } #(else if let Some(__v) = obj.get(#aliases.into())? {
640 __v
641 })* else {
642 return Err(ErrorKind::NoSuchField(#error_text.into(), vec![]).into());
643 };
644
645 <#ty as Typed>::from_untyped(__value)?
646 },
647 }
648 }
649
650 fn expand_serialize(&self) -> TokenStream {
651 let ident = &self.ident;
652 let ty = &self.ty;
653 self.name().map_or_else(
654 || {
655 if self.is_option {
656 quote! {
657 if let Some(value) = self.#ident {
658 <#ty as TypedObj>::serialize(value, out)?;
659 }
660 }
661 } else {
662 quote! {
663 <#ty as TypedObj>::serialize(self.#ident, out)?;
664 }
665 }
666 },
667 |name| {
668 let hide = if self.attr.hide {
669 quote! {.hide()}
670 } else {
671 quote! {}
672 };
673 let add = if self.attr.add {
674 quote! {.add()}
675 } else {
676 quote! {}
677 };
678 let value = if self.is_lazy {
679 quote! {
680 out.field(#name)
681 #hide
682 #add
683 .try_thunk(<#ty as Typed>::into_lazy_untyped(value))?;
684 }
685 } else {
686 quote! {
687 out.field(#name)
688 #hide
689 #add
690 .try_value(<#ty as Typed>::into_untyped(value)?)?;
691 }
692 };
693 if self.is_option {
694 quote! {
695 if let Some(value) = self.#ident {
696 #value
697 }
698 }
699 } else {
700 quote! {
701 {
702 let value = self.#ident;
703 #value
704 }
705 }
706 }
707 },
708 )
709 }
710}
711444
712#[proc_macro_derive(Typed, attributes(typed))]445#[proc_macro_derive(Typed, attributes(typed))]
713pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {446pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
719 }452 }
720}453}
721
722fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {
723 let syn::Data::Struct(data) = &input.data else {
724 return Err(Error::new(input.span(), "only structs supported"));
725 };
726
727 let ident = &input.ident;
728 let fields = data
729 .fields
730 .iter()
731 .map(TypedField::parse)
732 .collect::<Result<Vec<_>>>()?;
733
734 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
735
736 let typed = {
737 let fields = fields
738 .iter()
739 .filter_map(TypedField::expand_field)
740 .collect::<Vec<_>>();
741 quote! {
742 impl #impl_generics Typed for #ident #ty_generics #where_clause {
743 const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[
744 #(#fields,)*
745 ]);
746
747 fn from_untyped(value: Val) -> JrResult<Self> {
748 let obj = value.as_obj().expect("shape is correct");
749 Self::parse(&obj)
750 }
751
752 fn into_untyped(value: Self) -> JrResult<Val> {
753 let mut out = ObjValueBuilder::new();
754 value.serialize(&mut out)?;
755 Ok(Val::Obj(out.build()))
756 }
757
758 }
759 }
760 };
761
762 let fields_parse = fields.iter().map(TypedField::expand_parse);
763 let fields_serialize = fields
764 .iter()
765 .map(TypedField::expand_serialize)
766 .collect::<Vec<_>>();
767
768 Ok(quote! {
769 const _: () = {
770 use ::jrsonnet_evaluator::{
771 typed::{ComplexValType, Typed, TypedObj, CheckType},
772 Val, State,
773 error::{ErrorKind, Result as JrResult},
774 ObjValueBuilder, ObjValue,
775 };
776
777 #typed
778
779 impl #impl_generics TypedObj for #ident #ty_generics #where_clause {
780 fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {
781 #(#fields_serialize)*
782
783 Ok(())
784 }
785 fn parse(obj: &ObjValue) -> JrResult<Self> {
786 Ok(Self {
787 #(#fields_parse)*
788 })
789 }
790 }
791 };
792 })
793}
794454
795struct FormatInput {455struct FormatInput {
796 formatting: LitStr,456 formatting: LitStr,
addedcrates/jrsonnet-macros/src/names.rsdiffbeforeafterboth

no changes

addedcrates/jrsonnet-macros/src/typed.rsdiffbeforeafterboth

no changes

modifiedcrates/jrsonnet-stdlib/src/regex.rsdiffbeforeafterboth
4use jrsonnet_evaluator::{4use jrsonnet_evaluator::{
5 error::{ErrorKind::*, Result},5 error::{ErrorKind::*, Result},
6 rustc_hash::FxBuildHasher,6 rustc_hash::FxBuildHasher,
7 typed::Typed,
7 val::StrValue,8 val::StrValue,
8 IStr, ObjValueBuilder, Val,9 IStr, ObjValue, ObjValueBuilder,
9};10};
10use jrsonnet_gcmodule::Acyclic;11use jrsonnet_gcmodule::Acyclic;
11use jrsonnet_macros::builtin;12use jrsonnet_macros::builtin;
20 Self {21 Self {
21 cache: RefCell::new(LruCache::with_hasher(22 cache: RefCell::new(LruCache::with_hasher(
22 NonZeroUsize::new(20).unwrap(),23 NonZeroUsize::new(20).unwrap(),
23 FxBuildHasher::default(),24 FxBuildHasher,
24 )),25 )),
25 }26 }
26 }27 }
40 }41 }
41}42}
4243
44#[derive(Typed)]
43pub fn regex_match_inner(regex: &Regex, str: String) -> Result<Val> {45pub struct RegexMatch {
44 let mut out = ObjValueBuilder::with_capacity(3);46 string: IStr,
4547 captures: Vec<IStr>,
48 #[typed(rename = "namedCaptures")]
49 named_captures: ObjValue,
50}
51
52fn regex_match_inner(regex: &Regex, str: String) -> Result<Option<RegexMatch>> {
46 let mut captures = Vec::with_capacity(regex.captures_len());53 let mut captures = Vec::with_capacity(regex.captures_len());
47 let mut named_captures = ObjValueBuilder::with_capacity(regex.capture_names().len());54 let mut named_captures = ObjValueBuilder::with_capacity(regex.capture_names().len());
4855
49 let Some(captured) = regex.captures(&str) else {56 let Some(captured) = regex.captures(&str) else {
50 return Ok(Val::Null);57 return Ok(None);
51 };58 };
5259
53 for ele in captured.iter().skip(1) {60 for ele in captured.iter().skip(1) {
54 if let Some(ele) = ele {61 if let Some(ele) = ele {
55 captures.push(Val::Str(StrValue::Flat(ele.as_str().into())));62 captures.push(ele.as_str().into());
56 } else {63 } else {
57 captures.push(Val::Str(StrValue::Flat(IStr::empty())));64 captures.push(IStr::empty());
58 }65 }
59 }66 }
60 for (i, name) in regex67 for (i, name) in regex
67 named_captures.field(name).try_value(capture)?;74 named_captures.field(name).try_value(capture)?;
68 }75 }
6976
70 out.field("string")
71 .value(Val::Str(captured.get(0).unwrap().as_str().into()));77 Ok(Some(RegexMatch {
72 out.field("captures").value(Val::Arr(captures.into()));78 string: captured.get(0).expect("regex matched").as_str().into(),
73 out.field("namedCaptures")79 named_captures: named_captures.build(),
74 .value(Val::Obj(named_captures.build()));80 captures,
7581 }))
76 Ok(Val::Obj(out.build()))
77}82}
7883
79#[builtin(fields(84#[builtin(fields(
83 this: &builtin_regex_partial_match,88 this: &builtin_regex_partial_match,
84 pattern: IStr,89 pattern: IStr,
85 str: String,90 str: String,
86) -> Result<Val> {91) -> Result<Option<RegexMatch>> {
87 let regex = this.cache.parse(pattern)?;92 let regex = this.cache.parse(pattern)?;
88 regex_match_inner(&regex, str)93 regex_match_inner(&regex, str)
89}94}
95 this: &builtin_regex_full_match,100 this: &builtin_regex_full_match,
96 pattern: StrValue,101 pattern: StrValue,
97 str: String,102 str: String,
98) -> Result<Val> {103) -> Result<Option<RegexMatch>> {
99 let pattern = format!("^{pattern}$").into();104 let pattern = format!("^{pattern}$").into();
100 let regex = this.cache.parse(pattern)?;105 let regex = this.cache.parse(pattern)?;
101 regex_match_inner(&regex, str)106 regex_match_inner(&regex, str)