1use std::string::String;23use proc_macro2::TokenStream;4use quote::quote;5use syn::{6 parenthesized,7 parse::{Parse, ParseStream},8 parse_macro_input,9 punctuated::Punctuated,10 spanned::Spanned,11 token::{self, Comma},12 Attribute, DeriveInput, Error, Expr, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,13 PathArguments, Result, ReturnType, Token, Type,14};1516fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>17where18 Ident: PartialEq<I>,19{20 let attrs = attrs21 .iter()22 .filter(|a| a.path().is_ident(&ident))23 .collect::<Vec<_>>();24 if attrs.len() > 1 {25 return Err(Error::new(26 attrs[1].span(),27 "this attribute may be specified only once",28 ));29 } else if attrs.is_empty() {30 return Ok(None);31 }32 let attr = attrs[0];33 let attr = attr.parse_args::<A>()?;3435 Ok(Some(attr))36}3738fn path_is(path: &Path, needed: &str) -> bool {39 path.leading_colon.is_none()40 && !path.segments.is_empty()41 && path.segments.iter().last().unwrap().ident == needed42}4344fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {45 match ty {46 Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {47 let args = &path.path.segments.iter().last().unwrap().arguments;48 Some(args)49 }50 _ => None,51 }52}5354fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {55 let Some(args) = type_is_path(ty, "Option") else {56 return Ok(None);57 };58 59 let PathArguments::AngleBracketed(params) = args else {60 return Err(Error::new(args.span(), "missing option generic"));61 };62 let generic_arg = params.args.iter().next().unwrap();63 64 let GenericArgument::Type(ty) = generic_arg else {65 return Err(Error::new(66 generic_arg.span(),67 "option generic should be a type",68 ));69 };70 Ok(Some(ty))71}7273struct Field {74 attrs: Vec<Attribute>,75 name: Ident,76 _colon: Token![:],77 ty: Type,78}79impl Parse for Field {80 fn parse(input: ParseStream) -> syn::Result<Self> {81 Ok(Self {82 attrs: input.call(Attribute::parse_outer)?,83 name: input.parse()?,84 _colon: input.parse()?,85 ty: input.parse()?,86 })87 }88}8990mod kw {91 syn::custom_keyword!(fields);92 syn::custom_keyword!(rename);93 syn::custom_keyword!(flatten);94 syn::custom_keyword!(add);95 syn::custom_keyword!(hide);96 syn::custom_keyword!(ok);97}9899struct EmptyAttr;100impl Parse for EmptyAttr {101 fn parse(_input: ParseStream) -> Result<Self> {102 Ok(Self)103 }104}105106struct BuiltinAttrs {107 fields: Vec<Field>,108}109impl Parse for BuiltinAttrs {110 fn parse(input: ParseStream) -> syn::Result<Self> {111 if input.is_empty() {112 return Ok(Self { fields: Vec::new() });113 }114 input.parse::<kw::fields>()?;115 let fields;116 parenthesized!(fields in input);117 let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;118 Ok(Self {119 fields: p.into_iter().collect(),120 })121 }122}123124enum ArgInfo {125 Normal {126 ty: Box<Type>,127 is_option: bool,128 name: Option<String>,129 cfg_attrs: Vec<Attribute>,130 },131 Lazy {132 is_option: bool,133 name: Option<String>,134 },135 Context,136 Location,137 This,138}139140impl ArgInfo {141 fn parse(name: &str, arg: &FnArg) -> Result<Self> {142 let FnArg::Typed(arg) = arg else {143 unreachable!()144 };145 let ident = match &arg.pat as &Pat {146 Pat::Ident(i) => Some(i.ident.clone()),147 _ => None,148 };149 let ty = &arg.ty;150 if type_is_path(ty, "Context").is_some() {151 return Ok(Self::Context);152 } else if type_is_path(ty, "CallLocation").is_some() {153 return Ok(Self::Location);154 } else if type_is_path(ty, "Thunk").is_some() {155 return Ok(Self::Lazy {156 is_option: false,157 name: ident.map(|v| v.to_string()),158 });159 }160161 match ty as &Type {162 Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),163 _ => {}164 }165166 let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {167 if type_is_path(ty, "Thunk").is_some() {168 return Ok(Self::Lazy {169 is_option: true,170 name: ident.map(|v| v.to_string()),171 });172 }173174 (true, Box::new(ty.clone()))175 } else {176 (false, ty.clone())177 };178179 let cfg_attrs = arg180 .attrs181 .iter()182 .filter(|a| a.path().is_ident("cfg"))183 .cloned()184 .collect();185186 Ok(Self::Normal {187 ty,188 is_option,189 name: ident.map(|v| v.to_string()),190 cfg_attrs,191 })192 }193}194195#[proc_macro_attribute]196pub fn builtin(197 attr: proc_macro::TokenStream,198 item: proc_macro::TokenStream,199) -> proc_macro::TokenStream {200 let attr = parse_macro_input!(attr as BuiltinAttrs);201 let item_fn = item.clone();202 let item_fn: ItemFn = parse_macro_input!(item_fn);203204 match builtin_inner(attr, item_fn, item.into()) {205 Ok(v) => v.into(),206 Err(e) => e.into_compile_error().into(),207 }208}209210#[allow(clippy::too_many_lines)]211fn builtin_inner(212 attr: BuiltinAttrs,213 fun: ItemFn,214 item: proc_macro2::TokenStream,215) -> syn::Result<TokenStream> {216 let ReturnType::Type(_, result) = &fun.sig.output else {217 return Err(Error::new(218 fun.sig.span(),219 "builtin should return something",220 ));221 };222223 let name = fun.sig.ident.to_string();224 let args = fun225 .sig226 .inputs227 .iter()228 .map(|arg| ArgInfo::parse(&name, arg))229 .collect::<Result<Vec<_>>>()?;230231 let params_desc = args.iter().filter_map(|a| match a {232 ArgInfo::Normal {233 is_option,234 name,235 cfg_attrs,236 ..237 } => {238 let name = name239 .as_ref()240 .map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});241 Some(quote! {242 #(#cfg_attrs)*243 BuiltinParam::new(#name, #is_option),244 })245 }246 ArgInfo::Lazy { is_option, name } => {247 let name = name248 .as_ref()249 .map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});250 Some(quote! {251 BuiltinParam::new(#name, #is_option),252 })253 }254 ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None,255 });256257 let mut id = 0usize;258 let pass = args259 .iter()260 .map(|a| match a {261 ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {262 let cid = id;263 id += 1;264 (quote! {#cid}, a)265 }266 ArgInfo::Context | ArgInfo::Location | ArgInfo::This => {267 (quote! {compile_error!("should not use id")}, a)268 }269 })270 .map(|(id, a)| match a {271 ArgInfo::Normal {272 ty,273 is_option,274 name,275 cfg_attrs,276 } => {277 let name = name.as_ref().map_or("<unnamed>", String::as_str);278 let eval = quote! {jrsonnet_evaluator::State::push_description(279 || format!("argument <{}> evaluation", #name),280 || <#ty>::from_untyped(value.evaluate()?),281 )?};282 let value = if *is_option {283 quote! {if let Some(value) = &parsed[#id] {284 Some(#eval)285 } else {286 None287 },}288 } else {289 quote! {{290 let value = parsed[#id].as_ref().expect("args shape is checked");291 #eval292 },}293 };294 quote! {295 #(#cfg_attrs)*296 #value297 }298 }299 ArgInfo::Lazy { is_option, .. } => {300 if *is_option {301 quote! {if let Some(value) = &parsed[#id] {302 Some(value.clone())303 } else {304 None305 }}306 } else {307 quote! {308 parsed[#id].as_ref().expect("args shape is correct").clone(),309 }310 }311 }312 ArgInfo::Context => quote! {ctx.clone(),},313 ArgInfo::Location => quote! {location,},314 ArgInfo::This => quote! {self,},315 });316317 let fields = attr.fields.iter().map(|field| {318 let attrs = &field.attrs;319 let name = &field.name;320 let ty = &field.ty;321 quote! {322 #(#attrs)*323 pub #name: #ty,324 }325 });326327 let name = &fun.sig.ident;328 let vis = &fun.vis;329 let static_ext = if attr.fields.is_empty() {330 quote! {331 impl #name {332 pub const INST: &'static dyn StaticBuiltin = &#name {};333 }334 impl StaticBuiltin for #name {}335 }336 } else {337 quote! {}338 };339 let static_derive_copy = if attr.fields.is_empty() {340 quote! {, Copy}341 } else {342 quote! {}343 };344345 Ok(quote! {346 #item347348 #[doc(hidden)]349 #[allow(non_camel_case_types)]350 #[derive(Clone, jrsonnet_gcmodule::Trace #static_derive_copy)]351 #vis struct #name {352 #(#fields)*353 }354 const _: () = {355 use ::jrsonnet_evaluator::{356 State, Val,357 function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName}, CallLocation, ArgsLike, parse::parse_builtin_call},358 Result, Context, typed::Typed,359 parser::ExprLocation,360 };361 const PARAMS: &'static [BuiltinParam] = &[362 #(#params_desc)*363 ];364365 #static_ext366 impl Builtin for #name367 where368 Self: 'static369 {370 fn name(&self) -> &str {371 stringify!(#name)372 }373 fn params(&self) -> &[BuiltinParam] {374 PARAMS375 }376 #[allow(unused_variable)]377 fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {378 let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;379380 let result: #result = #name(#(#pass)*);381 <_ as Typed>::into_result(result)382 }383 fn as_any(&self) -> &dyn ::std::any::Any {384 self385 }386 }387 };388 })389}390391#[derive(Default)]392#[allow(clippy::struct_excessive_bools)]393struct TypedAttr {394 rename: Option<String>,395 flatten: bool,396 397 398 flatten_ok: bool,399 400 add: bool,401 402 hide: bool,403}404impl Parse for TypedAttr {405 fn parse(input: ParseStream) -> syn::Result<Self> {406 let mut out = Self::default();407 loop {408 let lookahead = input.lookahead1();409 if lookahead.peek(kw::rename) {410 input.parse::<kw::rename>()?;411 input.parse::<Token![=]>()?;412 let name = input.parse::<LitStr>()?;413 if out.rename.is_some() {414 return Err(Error::new(415 name.span(),416 "rename attribute may only be specified once",417 ));418 }419 out.rename = Some(name.value());420 } else if lookahead.peek(kw::flatten) {421 input.parse::<kw::flatten>()?;422 out.flatten = true;423 if input.peek(token::Paren) {424 let content;425 parenthesized!(content in input);426 let lookahead = content.lookahead1();427 if lookahead.peek(kw::ok) {428 content.parse::<kw::ok>()?;429 out.flatten_ok = true;430 } else {431 return Err(lookahead.error());432 }433 }434 } else if lookahead.peek(kw::add) {435 input.parse::<kw::add>()?;436 out.add = true;437 } else if lookahead.peek(kw::hide) {438 input.parse::<kw::hide>()?;439 out.hide = true;440 } else if input.is_empty() {441 break;442 } else {443 return Err(lookahead.error());444 }445 if input.peek(Token![,]) {446 input.parse::<Token![,]>()?;447 } else {448 break;449 }450 }451 Ok(out)452 }453}454455struct TypedField {456 attr: TypedAttr,457 ident: Ident,458 ty: Type,459 is_option: bool,460}461impl TypedField {462 fn parse(field: &syn::Field) -> Result<Self> {463 let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();464 let Some(ident) = field.ident.clone() else {465 return Err(Error::new(466 field.span(),467 "this field should appear in output object, but it has no visible name",468 ));469 };470 let (is_option, ty) = extract_type_from_option(&field.ty)?471 .map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));472 if is_option && attr.flatten {473 if !attr.flatten_ok {474 return Err(Error::new(475 field.span(),476 "strategy should be set when flattening Option",477 ));478 }479 } else if attr.flatten_ok {480 return Err(Error::new(481 field.span(),482 "flatten(ok) is only useable on optional fields",483 ));484 }485486 Ok(Self {487 attr,488 ident,489 ty,490 is_option,491 })492 }493 494 fn name(&self) -> Option<String> {495 if self.attr.flatten {496 return None;497 }498 Some(499 self.attr500 .rename501 .clone()502 .unwrap_or_else(|| self.ident.to_string()),503 )504 }505506 fn expand_field(&self) -> Option<TokenStream> {507 if self.is_option {508 return None;509 }510 let name = self.name()?;511 let ty = &self.ty;512 Some(quote! {513 (#name, <#ty as Typed>::TYPE)514 })515 }516 fn expand_parse(&self) -> TokenStream {517 let ident = &self.ident;518 let ty = &self.ty;519 if self.attr.flatten {520 521 return if self.is_option {522 quote! {523 #ident: <#ty as TypedObj>::parse(&obj).ok(),524 }525 } else {526 quote! {527 #ident: <#ty as TypedObj>::parse(&obj)?,528 }529 };530 };531532 let name = self.name().unwrap();533 let value = if self.is_option {534 quote! {535 if let Some(value) = obj.get(#name.into())? {536 Some(<#ty as Typed>::from_untyped(value)?)537 } else {538 None539 }540 }541 } else {542 quote! {543 <#ty as Typed>::from_untyped(obj.get(#name.into())?.ok_or_else(|| ErrorKind::NoSuchField(#name.into(), vec![]))?)?544 }545 };546547 quote! {548 #ident: #value,549 }550 }551 fn expand_serialize(&self) -> TokenStream {552 let ident = &self.ident;553 let ty = &self.ty;554 self.name().map_or_else(555 || {556 if self.is_option {557 quote! {558 if let Some(value) = self.#ident {559 <#ty as TypedObj>::serialize(value, out)?;560 }561 }562 } else {563 quote! {564 <#ty as TypedObj>::serialize(self.#ident, out)?;565 }566 }567 },568 |name| {569 let hide = if self.attr.hide {570 quote! {.hide()}571 } else {572 quote! {}573 };574 let add = if self.attr.add {575 quote! {.add()}576 } else {577 quote! {}578 };579 if self.is_option {580 quote! {581 if let Some(value) = self.#ident {582 out.field(#name)583 #hide584 #add585 .try_value(<#ty as Typed>::into_untyped(value)?)?;586 }587 }588 } else {589 quote! {590 out.field(#name)591 #hide592 #add593 .try_value(<#ty as Typed>::into_untyped(self.#ident)?)?;594 }595 }596 },597 )598 }599}600601#[proc_macro_derive(Typed, attributes(typed))]602pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {603 let input = parse_macro_input!(item as DeriveInput);604605 match derive_typed_inner(input) {606 Ok(v) => v.into(),607 Err(e) => e.to_compile_error().into(),608 }609}610611fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {612 let syn::Data::Struct(data) = &input.data else {613 return Err(Error::new(input.span(), "only structs supported"));614 };615616 let ident = &input.ident;617 let fields = data618 .fields619 .iter()620 .map(TypedField::parse)621 .collect::<Result<Vec<_>>>()?;622623 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();624625 let typed = {626 let fields = fields627 .iter()628 .filter_map(TypedField::expand_field)629 .collect::<Vec<_>>();630 quote! {631 impl #impl_generics Typed for #ident #ty_generics #where_clause {632 const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[633 #(#fields,)*634 ]);635636 fn from_untyped(value: Val) -> JrResult<Self> {637 let obj = value.as_obj().expect("shape is correct");638 Self::parse(&obj)639 }640641 fn into_untyped(value: Self) -> JrResult<Val> {642 let mut out = ObjValueBuilder::new();643 value.serialize(&mut out)?;644 Ok(Val::Obj(out.build()))645 }646647 }648 }649 };650651 let fields_parse = fields.iter().map(TypedField::expand_parse);652 let fields_serialize = fields653 .iter()654 .map(TypedField::expand_serialize)655 .collect::<Vec<_>>();656657 Ok(quote! {658 const _: () = {659 use ::jrsonnet_evaluator::{660 typed::{ComplexValType, Typed, TypedObj, CheckType},661 Val, State,662 error::{ErrorKind, Result as JrResult},663 ObjValueBuilder, ObjValue,664 };665666 #typed667668 impl #impl_generics TypedObj for #ident #ty_generics #where_clause {669 fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {670 #(#fields_serialize)*671672 Ok(())673 }674 fn parse(obj: &ObjValue) -> JrResult<Self> {675 Ok(Self {676 #(#fields_parse)*677 })678 }679 }680 };681 })682}683684struct FormatInput {685 formatting: LitStr,686 arguments: Vec<Expr>,687}688impl Parse for FormatInput {689 fn parse(input: ParseStream) -> Result<Self> {690 let formatting = input.parse()?;691 let mut arguments = Vec::new();692693 while input.peek(Token![,]) {694 input.parse::<Token![,]>()?;695 if input.is_empty() {696 697 break;698 }699 let expr = input.parse()?;700 arguments.push(expr);701 }702703 if !input.is_empty() {704 return Err(syn::Error::new(input.span(), "unexpected trailing input"));705 }706707 Ok(Self {708 formatting,709 arguments,710 })711 }712}713fn is_format_str(i: &str) -> bool {714 let mut is_plain = true;715 716 717 let mut is_bracket = 0i8;718 for ele in i.chars() {719 match ele {720 '{' if is_bracket == -1 => {721 is_bracket = 0;722 }723 '}' if is_bracket == -1 => {724 is_plain = false;725 break;726 }727 '}' if is_bracket == 1 => {728 is_bracket = 0;729 }730 '{' if is_bracket == 1 => {731 is_plain = false;732 break;733 }734 '{' => {735 is_bracket = -1;736 }737 '}' => {738 is_bracket = 1;739 }740 _ if is_bracket != 0 => {741 is_plain = false;742 break;743 }744 _ => {}745 }746 }747 !is_plain || is_bracket != 0748}749impl FormatInput {750 fn expand(self) -> TokenStream {751 let format = self.formatting;752 if is_format_str(&format.value()) {753 let args = self.arguments;754 quote! {755 ::jrsonnet_evaluator::IStr::from(format!(#format #(, #args)*))756 }757 } else {758 if let Some(first) = self.arguments.first() {759 return syn::Error::new(760 first.span(),761 "string has no formatting codes, it should not have the arguments",762 )763 .into_compile_error();764 }765 quote! {766 ::jrsonnet_evaluator::IStr::from(#format)767 }768 }769 }770}771772773774775776777#[proc_macro]778pub fn format_istr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {779 let input = parse_macro_input!(input as FormatInput);780 input.expand().into()781}