difftreelog
feature: non-static builtins
in: master
1 file changed
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth1use quote::quote;2use syn::{3 parse_macro_input, FnArg, GenericArgument, ItemFn, Pat, PatType, Path, PathArguments, Type,4};56fn is_location_arg(t: &PatType) -> bool {7 t.attrs.iter().any(|a| a.path.is_ident("location"))8}910trait RetainHad<T> {11 fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool;12}13impl<T> RetainHad<T> for Vec<T> {14 fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool {15 let before = self.len();16 self.retain(h);17 let after = self.len();18 before != after19 }20}2122fn extract_type_from_option(ty: &Type) -> Option<&Type> {23 fn path_is_option(path: &Path) -> bool {24 path.leading_colon.is_none()25 && path.segments.len() == 126 && path.segments.iter().next().unwrap().ident == "Option"27 }2829 match ty {30 Type::Path(typepath) if typepath.qself.is_none() && path_is_option(&typepath.path) => {31 // Get the first segment of the path (there is only one, in fact: "Option"):32 let type_params = &typepath.path.segments.iter().next().unwrap().arguments;33 // It should have only on angle-bracketed param ("<String>"):34 let generic_arg = match type_params {35 PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),36 _ => panic!("missing option generic"),37 };38 // This argument must be a type:39 match generic_arg {40 GenericArgument::Type(ty) => Some(ty),41 _ => panic!("option generic should be a type"),42 }43 }44 _ => None,45 }46}4748#[proc_macro_attribute]49pub fn builtin(50 _attr: proc_macro::TokenStream,51 item: proc_macro::TokenStream,52) -> proc_macro::TokenStream {53 // syn::ItemFn::parse(input)54 let mut fun: ItemFn = parse_macro_input!(item);5556 let result = match fun.sig.output {57 syn::ReturnType::Default => panic!("builtin should return something"),58 syn::ReturnType::Type(_, ref ty) => ty.clone(),59 };6061 let params = fun62 .sig63 .inputs64 .iter()65 .map(|i| match i {66 FnArg::Receiver(_) => unreachable!(),67 FnArg::Typed(t) => t,68 })69 .filter(|a| !is_location_arg(a))70 .map(|t| {71 let ident = match &t.pat as &Pat {72 Pat::Ident(i) => i.ident.to_string(),73 _ => panic!("only idents supported yet"),74 };75 let optional = extract_type_from_option(&t.ty).is_some();76 quote! {77 BuiltinParam {78 name: std::borrow::Cow::Borrowed(#ident),79 has_default: #optional,80 }81 }82 })83 .collect::<Vec<_>>();8485 let args = fun86 .sig87 .inputs88 .iter_mut()89 .map(|i| match i {90 FnArg::Receiver(_) => unreachable!(),91 FnArg::Typed(t) => t,92 })93 .map(|t| {94 let is_location = t.attrs.retain_had(|a| !a.path.is_ident("location"));95 if is_location {96 quote! {{97 loc98 }}99 } else {100 let ident = match &t.pat as &Pat {101 Pat::Ident(i) => i.ident.to_string(),102 _ => panic!("only idents supported yet"),103 };104 let ty = &t.ty;105 if let Some(opt_ty) = extract_type_from_option(&t.ty) {106 quote! {{107 if let Some(value) = parsed.get(#ident) {108 Some(::jrsonnet_evaluator::push_description_frame(109 || format!("argument <{}> evaluation", #ident),110 || <#opt_ty>::try_from(value.evaluate()?),111 )?)112 } else {113 None114 }115 }}116 } else {117 quote! {{118 let value = parsed.get(#ident).unwrap();119120 ::jrsonnet_evaluator::push_description_frame(121 || format!("argument <{}> evaluation", #ident),122 || <#ty>::try_from(value.evaluate()?),123 )?124 }}125 }126 }127 })128 .collect::<Vec<_>>();129130 let name = &fun.sig.ident;131 let vis = &fun.vis;132 (quote! {133 #fun134 #[doc(hidden)]135 #[allow(non_camel_case_types)]136 #[derive(Clone, Copy, gcmodule::Trace)]137 #vis struct #name {}138 const _: () = {139 use ::jrsonnet_evaluator::{140 function::{Builtin, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},141 error::Result, Context,142 parser::ExprLocation,143 };144 const PARAMS: &'static [BuiltinParam] = &[145 #(#params),*146 ];147148 impl #name {149 pub const INST: &'static dyn StaticBuiltin = &#name {};150 }151 impl StaticBuiltin for #name {}152 impl Builtin for #name153 where154 Self: 'static155 {156 fn name(&self) -> &str {157 stringify!(#name)158 }159 fn params(&self) -> &[BuiltinParam] {160 PARAMS161 }162 fn call(&self, context: Context, loc: Option<&ExprLocation>, args: &dyn ArgsLike) -> Result<Val> {163 let parsed = parse_builtin_call(context, &PARAMS, args, false)?;164165 let result: #result = #name(#(#args),*);166 let result = result?;167 result.try_into()168 }169 }170 };171 })172 .into()173}1use quote::{quote, quote_spanned};2use syn::{3 parenthesized, parse::Parse, parse_macro_input, punctuated::Punctuated, spanned::Spanned,4 token::Comma, FnArg, GenericArgument, Ident, ItemFn, Pat, PatType, Path, PathArguments, Token,5 Type,6};78fn is_location_arg(t: &PatType) -> bool {9 t.attrs.iter().any(|a| a.path.is_ident("location"))10}11fn is_self_arg(t: &PatType) -> bool {12 t.attrs.iter().any(|a| a.path.is_ident("self"))13}1415trait RetainHad<T> {16 fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool;17}18impl<T> RetainHad<T> for Vec<T> {19 fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool {20 let before = self.len();21 self.retain(h);22 let after = self.len();23 before != after24 }25}2627fn extract_type_from_option(ty: &Type) -> Option<&Type> {28 fn path_is_option(path: &Path) -> bool {29 path.leading_colon.is_none()30 && path.segments.len() == 131 && path.segments.iter().next().unwrap().ident == "Option"32 }3334 match ty {35 Type::Path(typepath) if typepath.qself.is_none() && path_is_option(&typepath.path) => {36 // Get the first segment of the path (there is only one, in fact: "Option"):37 let type_params = &typepath.path.segments.iter().next().unwrap().arguments;38 // It should have only on angle-bracketed param ("<String>"):39 let generic_arg = match type_params {40 PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),41 _ => panic!("missing option generic"),42 };43 // This argument must be a type:44 match generic_arg {45 GenericArgument::Type(ty) => Some(ty),46 _ => panic!("option generic should be a type"),47 }48 }49 _ => None,50 }51}5253struct Field {54 name: Ident,55 _colon: Token![:],56 ty: Type,57}58impl Parse for Field {59 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {60 Ok(Self {61 name: input.parse()?,62 _colon: input.parse()?,63 ty: input.parse()?,64 })65 }66}6768mod kw {69 syn::custom_keyword!(fields);70}7172struct BuiltinAttrs {73 fields: Vec<Field>,74}75impl Parse for BuiltinAttrs {76 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {77 if input.is_empty() {78 return Ok(Self { fields: Vec::new() });79 }80 input.parse::<kw::fields>()?;81 let fields;82 parenthesized!(fields in input);83 let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;84 Ok(Self {85 fields: p.into_iter().collect(),86 })87 }88}8990#[proc_macro_attribute]91pub fn builtin(92 attr: proc_macro::TokenStream,93 item: proc_macro::TokenStream,94) -> proc_macro::TokenStream {95 let attrs = parse_macro_input!(attr as BuiltinAttrs);96 let mut fun: ItemFn = parse_macro_input!(item);9798 let result = match fun.sig.output {99 syn::ReturnType::Default => {100 return quote_spanned! { fun.sig.span() =>101 compile_error!("builtins should return something");102 }103 .into()104 }105 syn::ReturnType::Type(_, ref ty) => ty.clone(),106 };107108 let params = fun109 .sig110 .inputs111 .iter()112 .map(|i| match i {113 FnArg::Receiver(_) => unreachable!(),114 FnArg::Typed(t) => t,115 })116 .filter(|a| !is_location_arg(a) && !is_self_arg(a))117 .map(|t| {118 let ident = match &t.pat as &Pat {119 Pat::Ident(i) => i.ident.to_string(),120 _ => {121 return quote_spanned! { t.pat.span() =>122 compile_error!("args should be plain identifiers")123 }124 .into()125 }126 };127 let optional = extract_type_from_option(&t.ty).is_some();128 quote! {129 BuiltinParam {130 name: std::borrow::Cow::Borrowed(#ident),131 has_default: #optional,132 }133 }134 })135 .collect::<Vec<_>>();136137 let args = fun138 .sig139 .inputs140 .iter_mut()141 .map(|i| match i {142 FnArg::Receiver(_) => unreachable!(),143 FnArg::Typed(t) => t,144 })145 .map(|t| {146 if t.attrs.retain_had(|a| !a.path.is_ident("location")) {147 quote! {{148 loc149 }}150 } else if t.attrs.retain_had(|a| !a.path.is_ident("self")) {151 quote! {{152 self153 }}154 } else {155 let ident = match &t.pat as &Pat {156 Pat::Ident(i) => i.ident.to_string(),157 _ => {158 return quote_spanned! { t.pat.span() =>159 compile_error!("args should be plain identifiers")160 }161 .into()162 }163 };164 let ty = &t.ty;165 if let Some(opt_ty) = extract_type_from_option(&t.ty) {166 quote! {{167 if let Some(value) = parsed.get(#ident) {168 Some(::jrsonnet_evaluator::push_description_frame(169 || format!("argument <{}> evaluation", #ident),170 || <#opt_ty>::try_from(value.evaluate()?),171 )?)172 } else {173 None174 }175 }}176 } else {177 quote! {{178 let value = parsed.get(#ident).unwrap();179180 ::jrsonnet_evaluator::push_description_frame(181 || format!("argument <{}> evaluation", #ident),182 || <#ty>::try_from(value.evaluate()?),183 )?184 }}185 }186 }187 })188 .collect::<Vec<_>>();189190 let fields = attrs.fields.iter().map(|field| {191 let name = &field.name;192 let ty = &field.ty;193 quote! {194 pub #name: #ty,195 }196 });197198 let name = &fun.sig.ident;199 let vis = &fun.vis;200 let static_ext = if attrs.fields.is_empty() {201 quote! {202 impl #name {203 pub const INST: &'static dyn StaticBuiltin = &#name {};204 }205 impl StaticBuiltin for #name {}206 }207 } else {208 quote! {}209 };210 let static_derive_copy = if attrs.fields.is_empty() {211 quote! {, Copy}212 } else {213 quote! {}214 };215216 (quote! {217 #fun218 #[doc(hidden)]219 #[allow(non_camel_case_types)]220 #[derive(Clone, gcmodule::Trace #static_derive_copy)]221 #vis struct #name {222 #(#fields)*223 }224 const _: () = {225 use ::jrsonnet_evaluator::{226 function::{Builtin, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},227 error::Result, Context,228 parser::ExprLocation,229 };230 const PARAMS: &'static [BuiltinParam] = &[231 #(#params),*232 ];233234 #static_ext235 impl Builtin for #name236 where237 Self: 'static238 {239 fn name(&self) -> &str {240 stringify!(#name)241 }242 fn params(&self) -> &[BuiltinParam] {243 PARAMS244 }245 fn call(&self, context: Context, loc: Option<&ExprLocation>, args: &dyn ArgsLike) -> Result<Val> {246 let parsed = parse_builtin_call(context, &PARAMS, args, false)?;247248 let result: #result = #name(#(#args),*);249 let result = result?;250 result.try_into()251 }252 }253 };254 })255 .into()256}