1use proc_macro2::TokenStream;2use quote::quote;3use syn::{4 parenthesized,5 parse::{Parse, ParseStream},6 parse_macro_input,7 punctuated::Punctuated,8 spanned::Spanned,9 token::Comma,10 Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,11 PathArguments, Result, ReturnType, Token, Type,12};1314fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>15where16 Ident: PartialEq<I>,17{18 let attrs = attrs19 .iter()20 .filter(|a| a.path.is_ident(&ident))21 .collect::<Vec<_>>();22 if attrs.len() > 1 {23 return Err(Error::new(24 attrs[1].span(),25 "this attribute may be specified only once",26 ));27 } else if attrs.is_empty() {28 return Ok(None);29 }30 let attr = attrs[0];31 let attr = attr.parse_args::<A>()?;3233 Ok(Some(attr))34}3536fn path_is(path: &Path, needed: &str) -> bool {37 path.leading_colon.is_none()38 && !path.segments.is_empty()39 && path.segments.iter().last().unwrap().ident == needed40}4142fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {43 match ty {44 Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {45 let args = &path.path.segments.iter().last().unwrap().arguments;46 Some(args)47 }48 _ => None,49 }50}5152fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {53 Ok(if let Some(args) = type_is_path(ty, "Option") {54 55 let generic_arg = match args {56 PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),57 _ => return Err(Error::new(args.span(), "missing option generic")),58 };59 60 match generic_arg {61 GenericArgument::Type(ty) => Some(ty),62 _ => {63 return Err(Error::new(64 generic_arg.span(),65 "option generic should be a type",66 ))67 }68 }69 } else {70 None71 })72}7374struct Field {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 name: input.parse()?,83 _colon: input.parse()?,84 ty: input.parse()?,85 })86 }87}8889mod kw {90 syn::custom_keyword!(fields);91 syn::custom_keyword!(rename);92 syn::custom_keyword!(flatten);93}9495struct EmptyAttr;96impl Parse for EmptyAttr {97 fn parse(_input: ParseStream) -> Result<Self> {98 Ok(Self)99 }100}101102struct BuiltinAttrs {103 fields: Vec<Field>,104}105impl Parse for BuiltinAttrs {106 fn parse(input: ParseStream) -> syn::Result<Self> {107 if input.is_empty() {108 return Ok(Self { fields: Vec::new() });109 }110 input.parse::<kw::fields>()?;111 let fields;112 parenthesized!(fields in input);113 let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;114 Ok(Self {115 fields: p.into_iter().collect(),116 })117 }118}119120enum ArgInfo {121 Normal {122 ty: Box<Type>,123 is_option: bool,124 name: String,125 cfg_attrs: Vec<Attribute>,126 127 },128 Lazy {129 is_option: bool,130 name: String,131 },132 Location,133 This,134}135136impl ArgInfo {137 fn parse(arg: &FnArg) -> Result<Self> {138 let arg = match arg {139 FnArg::Receiver(_) => unreachable!(),140 FnArg::Typed(a) => a,141 };142 let ident = match &arg.pat as &Pat {143 Pat::Ident(i) => i.ident.clone(),144 _ => return Err(Error::new(arg.pat.span(), "arg should be plain identifier")),145 };146 let ty = &arg.ty;147 if type_is_path(ty, "CallLocation").is_some() {148 return Ok(Self::Location);149 } else if type_is_path(ty, "Self").is_some() {150 return Ok(Self::This);151 } else if type_is_path(ty, "LazyVal").is_some() {152 return Ok(Self::Lazy {153 is_option: false,154 name: ident.to_string(),155 });156 }157158 let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {159 if type_is_path(ty, "LazyVal").is_some() {160 return Ok(Self::Lazy {161 is_option: true,162 name: ident.to_string(),163 });164 }165166 (true, Box::new(ty.clone()))167 } else {168 (false, ty.clone())169 };170171 let cfg_attrs = arg172 .attrs173 .iter()174 .filter(|a| a.path.is_ident("cfg"))175 .cloned()176 .collect();177178 Ok(Self::Normal {179 ty,180 is_option,181 name: ident.to_string(),182 cfg_attrs,183 })184 }185}186187#[proc_macro_attribute]188pub fn builtin(189 attr: proc_macro::TokenStream,190 item: proc_macro::TokenStream,191) -> proc_macro::TokenStream {192 let attr = parse_macro_input!(attr as BuiltinAttrs);193 let item: ItemFn = parse_macro_input!(item);194195 match builtin_inner(attr, item) {196 Ok(v) => v.into(),197 Err(e) => e.into_compile_error().into(),198 }199}200201fn builtin_inner(attr: BuiltinAttrs, fun: ItemFn) -> syn::Result<TokenStream> {202 let result = match fun.sig.output {203 ReturnType::Default => {204 return Err(Error::new(205 fun.sig.span(),206 "builtin should return something",207 ))208 }209 ReturnType::Type(_, ref ty) => ty.clone(),210 };211212 let args = fun213 .sig214 .inputs215 .iter()216 .map(ArgInfo::parse)217 .collect::<Result<Vec<_>>>()?;218219 let params_desc = args.iter().flat_map(|a| match a {220 ArgInfo::Normal {221 is_option,222 name,223 cfg_attrs,224 ..225 } => Some(quote! {226 #(#cfg_attrs)*227 BuiltinParam {228 name: std::borrow::Cow::Borrowed(#name),229 has_default: #is_option,230 },231 }),232 ArgInfo::Lazy { is_option, name } => Some(quote! {233 BuiltinParam {234 name: std::borrow::Cow::Borrowed(#name),235 has_default: #is_option,236 },237 }),238 ArgInfo::Location => None,239 ArgInfo::This => None,240 });241242 let pass = args.iter().map(|a| match a {243 ArgInfo::Normal {244 ty,245 is_option,246 name,247 cfg_attrs,248 } => {249 let eval = quote! {::jrsonnet_evaluator::push_description_frame(250 || format!("argument <{}> evaluation", #name),251 || <#ty>::try_from(value.evaluate()?),252 )?};253 let value = if *is_option {254 quote! {if let Some(value) = parsed.get(#name) {255 Some(#eval)256 } else {257 None258 },}259 } else {260 quote! {{261 let value = parsed.get(#name).expect("args shape is checked");262 #eval263 },}264 };265 quote! {266 #(#cfg_attrs)*267 #value268 }269 }270 ArgInfo::Lazy { is_option, name } => {271 if *is_option {272 quote! {if let Some(value) = parsed.get(#name) {273 Some(value.clone())274 } else {275 None276 }}277 } else {278 quote! {279 parsed.get(#name).expect("args shape is correct").clone(),280 }281 }282 }283 ArgInfo::Location => quote! {location,},284 ArgInfo::This => quote! {self,},285 });286287 let fields = attr.fields.iter().map(|field| {288 let name = &field.name;289 let ty = &field.ty;290 quote! {291 pub #name: #ty,292 }293 });294295 let name = &fun.sig.ident;296 let vis = &fun.vis;297 let static_ext = if attr.fields.is_empty() {298 quote! {299 impl #name {300 pub const INST: &'static dyn StaticBuiltin = &#name {};301 }302 impl StaticBuiltin for #name {}303 }304 } else {305 quote! {}306 };307 let static_derive_copy = if attr.fields.is_empty() {308 quote! {, Copy}309 } else {310 quote! {}311 };312313 Ok(quote! {314 #fun315 #[doc(hidden)]316 #[allow(non_camel_case_types)]317 #[derive(Clone, gcmodule::Trace #static_derive_copy)]318 #vis struct #name {319 #(#fields)*320 }321 const _: () = {322 use ::jrsonnet_evaluator::{323 function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},324 error::Result, Context,325 parser::ExprLocation,326 };327 const PARAMS: &'static [BuiltinParam] = &[328 #(#params_desc)*329 ];330331 #static_ext332 impl Builtin for #name333 where334 Self: 'static335 {336 fn name(&self) -> &str {337 stringify!(#name)338 }339 fn params(&self) -> &[BuiltinParam] {340 PARAMS341 }342 fn call(&self, context: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {343 let parsed = parse_builtin_call(context, &PARAMS, args, false)?;344345 let result: #result = #name(#(#pass)*);346 let result = result?;347 result.try_into()348 }349 }350 };351 })352}353354#[derive(Default)]355struct TypedAttr {356 rename: Option<String>,357 flatten: bool,358}359impl Parse for TypedAttr {360 fn parse(input: ParseStream) -> syn::Result<Self> {361 let mut out = Self::default();362 loop {363 let lookahead = input.lookahead1();364 if lookahead.peek(kw::rename) {365 input.parse::<kw::rename>()?;366 input.parse::<Token![=]>()?;367 let name = input.parse::<LitStr>()?;368 if out.rename.is_some() {369 return Err(Error::new(370 name.span(),371 "rename attribute may only be specified once",372 ));373 }374 out.rename = Some(name.value());375 } else if lookahead.peek(kw::flatten) {376 input.parse::<kw::flatten>()?;377 out.flatten = true;378 } else if input.is_empty() {379 break;380 } else {381 return Err(lookahead.error());382 }383 if input.peek(Token![,]) {384 input.parse::<Token![,]>()?;385 } else {386 break;387 }388 }389 390 391 392 Ok(out)393 }394}395396struct TypedField<'f>(&'f syn::Field, TypedAttr);397impl<'f> TypedField<'f> {398 fn try_new(field: &'f syn::Field) -> Result<Self> {399 let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();400 if field.ident.is_none() {401 return Err(Error::new(402 field.span(),403 "this field should appear in output object, but it has no visible name",404 ));405 }406 Ok(Self(field, attr))407 }408 fn ident(&self) -> Ident {409 self.0410 .ident411 .clone()412 .expect("constructor disallows fields without name")413 }414 415 fn name(&self) -> Option<String> {416 if self.1.flatten {417 return None;418 }419 Some(420 self.1421 .rename422 .clone()423 .unwrap_or_else(|| self.ident().to_string()),424 )425 }426427 fn expand_field(&self) -> Option<TokenStream> {428 if self.is_option() {429 return None;430 }431 let name = self.name()?;432 let ty = &self.0.ty;433 Some(quote! {434 (#name, <#ty>::TYPE)435 })436 }437 fn expand_parse(&self) -> TokenStream {438 let ident = self.ident();439 let ty = &self.0.ty;440 if self.1.flatten {441 442 return if self.is_option() {443 quote! {444 #ident: <#ty>::parse(&obj).ok(),445 }446 } else {447 quote! {448 #ident: <#ty>::parse(&obj)?,449 }450 };451 };452453 let name = self.name().unwrap();454 let value = if let Some(ty) = self.as_option() {455 quote! {456 if let Some(value) = obj.get(#name.into())? {457 Some(<#ty>::try_from(vakue)?)458 } else {459 None460 }461 }462 } else {463 quote! {464 <#ty>::try_from(obj.get(#name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?)?465 }466 };467468 quote! {469 #ident: #value,470 }471 }472 fn expand_serialize(&self) -> TokenStream {473 let ident = self.ident();474 if let Some(name) = self.name() {475 if self.is_option() {476 quote! {477 if let Some(value) = self.#ident {478 out.member(#name.into()).value(value.try_into()?)?;479 }480 }481 } else {482 quote! {483 out.member(#name.into()).value(self.#ident.try_into()?)?;484 }485 }486 } else if self.is_option() {487 quote! {488 if let Some(value) = self.#ident {489 value.serialize(out)?;490 }491 }492 } else {493 quote! {494 self.#ident.serialize(out)?;495 }496 }497 }498499 fn as_option(&self) -> Option<&Type> {500 extract_type_from_option(&self.0.ty).unwrap()501 }502 fn is_option(&self) -> bool {503 self.as_option().is_some()504 }505}506507#[proc_macro_derive(Typed, attributes(typed))]508pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {509 let input = parse_macro_input!(item as DeriveInput);510511 match derive_typed_inner(input) {512 Ok(v) => v.into(),513 Err(e) => e.to_compile_error().into(),514 }515}516517fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {518 let data = match &input.data {519 syn::Data::Struct(s) => s,520 _ => return Err(Error::new(input.span(), "only structs supported")),521 };522523 let ident = &input.ident;524 let fields = data525 .fields526 .iter()527 .map(TypedField::try_new)528 .collect::<Result<Vec<_>>>()?;529530 let typed = {531 let fields = fields532 .iter()533 .flat_map(TypedField::expand_field)534 .collect::<Vec<_>>();535 let len = fields.len();536 quote! {537 const ITEMS: [(&'static str, &'static ComplexValType); #len] = [538 #(#fields,)*539 ];540 impl Typed for #ident {541 const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&ITEMS);542 }543 }544 };545546 let fields_parse = fields.iter().map(TypedField::expand_parse);547 let fields_serialize = fields.iter().map(TypedField::expand_serialize);548549 Ok(quote! {550 const _: () = {551 use ::jrsonnet_evaluator::{552 typed::{ComplexValType, Typed, TypedObj, CheckType},553 Val,554 error::{LocError, Error},555 ObjValueBuilder, ObjValue,556 };557558 #typed559560 impl #ident {561 fn serialize(self, out: &mut ObjValueBuilder) -> Result<(), LocError> {562 #(#fields_serialize)*563564 Ok(())565 }566 fn parse(obj: &ObjValue) -> Result<Self, LocError> {567 Ok(Self {568 #(#fields_parse)*569 })570 }571 }572573 impl TryFrom<Val> for #ident {574 type Error = LocError;575 fn try_from(value: Val) -> Result<Self, Self::Error> {576 let obj = value.as_obj().expect("shape is correct");577 Self::parse(&obj)578 }579 }580 impl TryInto<Val> for #ident {581 type Error = LocError;582 fn try_into(self) -> Result<Val, Self::Error> {583 let mut out = ObjValueBuilder::new();584 self.serialize(&mut out)?;585 Ok(Val::Obj(out.build()))586 }587 }588 ()589 };590 })591}