difftreelog
feat(macros) #[typed(method)]
in: master
2 files changed
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -127,6 +127,7 @@
syn::custom_keyword!(flatten);
syn::custom_keyword!(add);
syn::custom_keyword!(hide);
+ syn::custom_keyword!(method);
syn::custom_keyword!(ok);
}
@@ -381,18 +382,8 @@
let name = &fun.sig.ident;
let vis = &fun.vis;
- let static_ext = if attr.fields.is_empty() {
- quote! {
- impl #name {
- pub const INST: &'static dyn StaticBuiltin = &#name {};
- }
- impl StaticBuiltin for #name {}
- }
- } else {
- quote! {}
- };
let static_derive_copy = if attr.fields.is_empty() {
- quote! {, Copy}
+ quote! {, Copy, Default}
} else {
quote! {}
};
@@ -409,7 +400,7 @@
const _: () = {
use ::jrsonnet_evaluator::{
State, Val,
- function::{builtin::{Builtin, StaticBuiltin}, FunctionSignature, ParamParse, ParamName, ParamDefault, CallLocation},
+ function::{builtin::Builtin, FunctionSignature, ParamParse, ParamName, ParamDefault, CallLocation},
Result, Context, typed::{Typed, FromUntyped, IntoUntypedResult},
parser::Span, params, Thunk,
};
@@ -417,7 +408,6 @@
#(#params_desc)*
);
- #static_ext
impl Builtin for #name
where
Self: 'static
crates/jrsonnet-macros/src/typed.rsdiffbeforeafterboth1use proc_macro2::TokenStream;2use quote::quote;3use syn::{4 DeriveInput, Error, Ident, LitStr, Result, Token, Type, parenthesized,5 parse::{Parse, ParseStream},6 spanned::Spanned as _,7 token,8};910use crate::{extract_type_from_option, kw, names::Names, parse_attr, type_is_path};1112#[derive(Default)]13#[allow(clippy::struct_excessive_bools)]14struct TypedAttr {15 rename: Option<String>,16 aliases: Vec<String>,17 flatten: bool,18 /// flatten(ok) strategy for flattened optionals19 /// field would be None in case of any parsing error (as in serde)20 flatten_ok: bool,21 // Should it be `field+:` instead of `field:`22 add: bool,23 // Should it be `field::` instead of `field:`24 hide: bool,25}26impl Parse for TypedAttr {27 fn parse(input: ParseStream) -> syn::Result<Self> {28 let mut out = Self::default();29 loop {30 let lookahead = input.lookahead1();31 if lookahead.peek(kw::rename) {32 input.parse::<kw::rename>()?;33 input.parse::<Token![=]>()?;34 let name = input.parse::<LitStr>()?;35 if out.rename.is_some() {36 return Err(Error::new(37 name.span(),38 "rename attribute may only be specified once",39 ));40 }41 out.rename = Some(name.value());42 } else if lookahead.peek(kw::alias) {43 input.parse::<kw::alias>()?;44 input.parse::<Token![=]>()?;45 let alias = input.parse::<LitStr>()?;46 out.aliases.push(alias.value());47 } else if lookahead.peek(kw::flatten) {48 input.parse::<kw::flatten>()?;49 out.flatten = true;50 if input.peek(token::Paren) {51 let content;52 parenthesized!(content in input);53 let lookahead = content.lookahead1();54 if lookahead.peek(kw::ok) {55 content.parse::<kw::ok>()?;56 out.flatten_ok = true;57 } else {58 return Err(lookahead.error());59 }60 }61 } else if lookahead.peek(kw::add) {62 input.parse::<kw::add>()?;63 out.add = true;64 } else if lookahead.peek(kw::hide) {65 input.parse::<kw::hide>()?;66 out.hide = true;67 } else if input.is_empty() {68 break;69 } else {70 return Err(lookahead.error());71 }72 if input.peek(Token![,]) {73 input.parse::<Token![,]>()?;74 } else {75 break;76 }77 }78 Ok(out)79 }80}81struct TypedField {82 attr: TypedAttr,83 ident: Ident,84 ty: Type,85 is_option: bool,86 is_lazy: bool,87}88impl TypedField {89 fn parse(field: &syn::Field) -> Result<Self> {90 let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();91 let Some(ident) = field.ident.clone() else {92 return Err(Error::new(93 field.span(),94 "this field should appear in output object, but it has no visible name",95 ));96 };97 let (is_option, ty) = extract_type_from_option(&field.ty)?98 .map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));99 if is_option && attr.flatten {100 if !attr.flatten_ok {101 return Err(Error::new(102 field.span(),103 "strategy should be set when flattening Option",104 ));105 }106 } else if attr.flatten_ok {107 return Err(Error::new(108 field.span(),109 "flatten(ok) is only useable on optional fields",110 ));111 }112113 let is_lazy = type_is_path(&ty, "Thunk").is_some();114115 Ok(Self {116 attr,117 ident,118 ty,119 is_option,120 is_lazy,121 })122 }123 /// None if this field is flattened in jsonnet output124 fn name(&self) -> Option<String> {125 if self.attr.flatten {126 return None;127 }128 Some(129 self.attr130 .rename131 .clone()132 .unwrap_or_else(|| self.ident.to_string()),133 )134 }135136 fn expand_field(&self) -> Option<TokenStream> {137 if self.is_option {138 return None;139 }140 let name = self.name()?;141 let ty = &self.ty;142 Some(quote! {143 (#name, <#ty as Typed>::TYPE)144 })145 }146147 fn expand_parse(&self, names: &mut Names) -> TokenStream {148 if self.is_option {149 self.expand_parse_optional(names)150 } else {151 self.expand_parse_mandatory(names)152 }153 }154155 fn expand_parse_optional(&self, names: &mut Names) -> TokenStream {156 let ident = &self.ident;157 let ty = &self.ty;158159 // optional flatten is handled in same way as serde160 if self.attr.flatten {161 return quote! {162 #ident: <#ty as ParseTypedObj>::parse(&obj).ok(),163 };164 }165166 let name = names.intern(self.name().unwrap());167 let aliases = self168 .attr169 .aliases170 .iter()171 .map(|name| names.intern(name))172 .collect::<Vec<_>>();173174 quote! {175 #ident: {176 let __value = if let Some(__v) = obj.get(__names[#name].clone())? {177 Some(__v)178 } #(else if let Some(__v) = obj.get(__names[#aliases].clone())? {179 Some(__v)180 })* else {181 None182 };183184 __value.map(<#ty as FromUntyped>::from_untyped).transpose()?185 },186 }187 }188189 fn expand_parse_mandatory(&self, names: &mut Names) -> TokenStream {190 let ident = &self.ident;191 let ty = &self.ty;192193 // optional flatten is handled in same way as serde194 if self.attr.flatten {195 return quote! {196 #ident: <#ty as ParseTypedObj>::parse(&obj)?,197 };198 }199200 let name = self.name().unwrap();201 let aliases = &self.attr.aliases;202203 let error_text = if aliases.is_empty() {204 // clippy does not understand name variable usage in quote! macro205 #[allow(clippy::redundant_clone)]206 name.clone()207 } else {208 format!("{name} (alias {})", aliases.join(", "))209 };210211 let error_text = names.intern(error_text);212 let name = names.intern(name);213 let aliases = aliases.iter().map(|alias| names.intern(alias));214215 quote! {216 #ident: {217 let __value = if let Some(__v) = obj.get(__names[#name].clone())? {218 __v219 } #(else if let Some(__v) = obj.get(__names[#aliases].clone())? {220 __v221 })* else {222 return Err(ErrorKind::NoSuchField(__names[#error_text].clone(), vec![]).into());223 };224225 <#ty as FromUntyped>::from_untyped(__value)?226 },227 }228 }229230 fn expand_serialize(&self, names: &mut Names) -> TokenStream {231 let ident = &self.ident;232 let ty = &self.ty;233 self.name().map_or_else(234 || {235 if self.is_option {236 quote! {237 if let Some(value) = self.#ident {238 <#ty as SerializeTypedObj>::serialize(value, out)?;239 }240 }241 } else {242 quote! {243 <#ty as SerializeTypedObj>::serialize(self.#ident, out)?;244 }245 }246 },247 |name| {248 let name = names.intern(name);249 let hide = if self.attr.hide {250 quote! {.hide()}251 } else {252 quote! {}253 };254 let add = if self.attr.add {255 quote! {.add()}256 } else {257 quote! {}258 };259 let value = if self.is_lazy {260 quote! {261 out.field(__names[#name].clone())262 #hide263 #add264 .try_thunk(<#ty as IntoUntyped>::into_lazy_untyped(value))?;265 }266 } else {267 quote! {268 out.field(__names[#name].clone())269 #hide270 #add271 .try_value(<#ty as IntoUntyped>::into_untyped(value)?)?;272 }273 };274 if self.is_option {275 quote! {276 if let Some(value) = self.#ident {277 #value278 }279 }280 } else {281 quote! {282 {283 let value = self.#ident;284 #value285 }286 }287 }288 },289 )290 }291}292293pub fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {294 let syn::Data::Struct(data) = &input.data else {295 return Err(Error::new(input.span(), "only structs supported"));296 };297298 let ident = &input.ident;299 let fields = data300 .fields301 .iter()302 .map(TypedField::parse)303 .collect::<Result<Vec<_>>>()?;304305 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();306307 let fields = fields308 .iter()309 .filter_map(TypedField::expand_field)310 .collect::<Vec<_>>();311 Ok(quote! {312 const _: () = {313 use ::jrsonnet_evaluator::typed::__typed_macro_prelude::*;314315 impl #impl_generics Typed for #ident #ty_generics #where_clause {316 const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[317 #(#fields,)*318 ]);319 }320 };321 })322}323pub fn derive_into_untyped_inner(input: DeriveInput) -> Result<TokenStream> {324 let syn::Data::Struct(data) = &input.data else {325 return Err(Error::new(input.span(), "only structs supported"));326 };327328 let ident = &input.ident;329 let fields = data330 .fields331 .iter()332 .map(TypedField::parse)333 .collect::<Result<Vec<_>>>()?;334335 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();336337 let capacity = fields.len();338339 let mut names = Names::default();340341 let fields_serialize = fields342 .iter()343 .map(|f| f.expand_serialize(&mut names))344 .collect::<Vec<_>>();345346 let names_expanded = names.expand();347 Ok(quote! {348 const _: () = {349 use ::jrsonnet_evaluator::typed::__typed_macro_prelude::*;350351 impl #impl_generics IntoUntyped for #ident #ty_generics #where_clause {352 fn into_untyped(value: Self) -> JrResult<Val> {353 let mut out = ObjValueBuilder::with_capacity(#capacity);354 value.serialize(&mut out)?;355 Ok(Val::Obj(out.build()))356 }357 }358359 #names_expanded360361 impl #impl_generics SerializeTypedObj for #ident #ty_generics #where_clause {362 fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {363 NAMES.with(|__names| {364 #(#fields_serialize)*365366 Ok(())367 })368 }369 }370 };371 })372}373pub fn derive_from_untyped_inner(input: DeriveInput) -> Result<TokenStream> {374 let syn::Data::Struct(data) = &input.data else {375 return Err(Error::new(input.span(), "only structs supported"));376 };377378 let ident = &input.ident;379 let fields = data380 .fields381 .iter()382 .map(TypedField::parse)383 .collect::<Result<Vec<_>>>()?;384385 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();386387 let mut names = Names::default();388389 let fields_parse = fields390 .iter()391 .map(|f| f.expand_parse(&mut names))392 .collect::<Vec<_>>();393394 let names_expanded = names.expand();395 Ok(quote! {396 const _: () = {397 use ::jrsonnet_evaluator::typed::__typed_macro_prelude::*;398399 impl #impl_generics FromUntyped for #ident #ty_generics #where_clause {400 fn from_untyped(value: Val) -> JrResult<Self> {401 let obj = value.as_obj().expect("shape is correct");402 Self::parse(&obj)403 }404 }405406 #names_expanded407408 impl #impl_generics ParseTypedObj for #ident #ty_generics #where_clause {409 fn parse(obj: &ObjValue) -> JrResult<Self> {410 NAMES.with(|__names| Ok(Self {411 #(#fields_parse)*412 }))413 }414 }415 };416 })417}1use proc_macro2::TokenStream;2use quote::quote;3use syn::{4 parenthesized,5 parse::{Parse, ParseStream},6 spanned::Spanned as _,7 token, DeriveInput, Error, Ident, LitStr, Result, Token, Type,8};910use crate::{extract_type_from_option, kw, names::Names, parse_attr, type_is_path};1112#[derive(Default)]13#[allow(clippy::struct_excessive_bools)]14struct TypedAttr {15 rename: Option<String>,16 aliases: Vec<String>,17 flatten: bool,18 /// flatten(ok) strategy for flattened optionals19 /// field would be None in case of any parsing error (as in serde)20 flatten_ok: bool,21 // Should it be `field+:` instead of `field:`22 add: bool,23 // Should it be `field::` instead of `field:`24 hide: bool,25 // Builtin value26 method: bool,27}28impl Parse for TypedAttr {29 fn parse(input: ParseStream) -> syn::Result<Self> {30 let mut out = Self::default();31 loop {32 let lookahead = input.lookahead1();33 if lookahead.peek(kw::rename) {34 input.parse::<kw::rename>()?;35 input.parse::<Token![=]>()?;36 let name = input.parse::<LitStr>()?;37 if out.rename.is_some() {38 return Err(Error::new(39 name.span(),40 "rename attribute may only be specified once",41 ));42 }43 out.rename = Some(name.value());44 } else if lookahead.peek(kw::alias) {45 input.parse::<kw::alias>()?;46 input.parse::<Token![=]>()?;47 let alias = input.parse::<LitStr>()?;48 out.aliases.push(alias.value());49 } else if lookahead.peek(kw::flatten) {50 input.parse::<kw::flatten>()?;51 out.flatten = true;52 if input.peek(token::Paren) {53 let content;54 parenthesized!(content in input);55 let lookahead = content.lookahead1();56 if lookahead.peek(kw::ok) {57 content.parse::<kw::ok>()?;58 out.flatten_ok = true;59 } else {60 return Err(lookahead.error());61 }62 }63 } else if lookahead.peek(kw::add) {64 input.parse::<kw::add>()?;65 out.add = true;66 } else if lookahead.peek(kw::hide) {67 input.parse::<kw::hide>()?;68 out.hide = true;69 } else if lookahead.peek(kw::method) {70 input.parse::<kw::method>()?;71 out.method = true;72 } else if input.is_empty() {73 break;74 } else {75 return Err(lookahead.error());76 }77 if input.peek(Token![,]) {78 input.parse::<Token![,]>()?;79 } else {80 break;81 }82 }83 Ok(out)84 }85}86struct TypedField {87 attr: TypedAttr,88 ident: Ident,89 ty: Type,90 is_option: bool,91 is_lazy: bool,92}93impl TypedField {94 fn parse(field: &syn::Field) -> Result<Self> {95 let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();96 let Some(ident) = field.ident.clone() else {97 return Err(Error::new(98 field.span(),99 "this field should appear in output object, but it has no visible name",100 ));101 };102 let (is_option, ty) = extract_type_from_option(&field.ty)?103 .map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));104 if is_option && attr.flatten {105 if !attr.flatten_ok {106 return Err(Error::new(107 field.span(),108 "strategy should be set when flattening Option",109 ));110 }111 } else if attr.flatten_ok {112 return Err(Error::new(113 field.span(),114 "flatten(ok) is only useable on optional fields",115 ));116 }117118 let is_lazy = type_is_path(&ty, "Thunk").is_some();119120 Ok(Self {121 attr,122 ident,123 ty,124 is_option,125 is_lazy,126 })127 }128 /// None if this field is flattened in jsonnet output129 fn name(&self) -> Option<String> {130 if self.attr.flatten {131 return None;132 }133 Some(134 self.attr135 .rename136 .clone()137 .unwrap_or_else(|| self.ident.to_string()),138 )139 }140141 fn expand_field(&self) -> Option<TokenStream> {142 if self.is_option || self.attr.method {143 return None;144 }145 let name = self.name()?;146 let ty = &self.ty;147 Some(quote! {148 (#name, <#ty as Typed>::TYPE)149 })150 }151152 fn expand_parse(&self, names: &mut Names) -> TokenStream {153 if self.is_option {154 self.expand_parse_optional(names)155 } else {156 self.expand_parse_mandatory(names)157 }158 }159160 fn expand_parse_optional(&self, names: &mut Names) -> TokenStream {161 let ident = &self.ident;162 let ty = &self.ty;163164 // optional flatten is handled in same way as serde165 if self.attr.flatten {166 return quote! {167 #ident: <#ty as ParseTypedObj>::parse(&obj).ok(),168 };169 }170171 let name = names.intern(self.name().unwrap());172 let aliases = self173 .attr174 .aliases175 .iter()176 .map(|name| names.intern(name))177 .collect::<Vec<_>>();178179 quote! {180 #ident: {181 let __value = if let Some(__v) = obj.get(__names[#name].clone())? {182 Some(__v)183 } #(else if let Some(__v) = obj.get(__names[#aliases].clone())? {184 Some(__v)185 })* else {186 None187 };188189 __value.map(<#ty as FromUntyped>::from_untyped).transpose()?190 },191 }192 }193194 fn expand_parse_mandatory(&self, names: &mut Names) -> TokenStream {195 let ident = &self.ident;196 let ty = &self.ty;197198 // optional flatten is handled in same way as serde199 if self.attr.flatten {200 return quote! {201 #ident: <#ty as ParseTypedObj>::parse(&obj)?,202 };203 }204205 let name = self.name().unwrap();206 let aliases = &self.attr.aliases;207208 let error_text = if aliases.is_empty() {209 // clippy does not understand name variable usage in quote! macro210 #[allow(clippy::redundant_clone)]211 name.clone()212 } else {213 format!("{name} (alias {})", aliases.join(", "))214 };215216 let error_text = names.intern(error_text);217 let name = names.intern(name);218 let aliases = aliases.iter().map(|alias| names.intern(alias));219220 quote! {221 #ident: {222 let __value = if let Some(__v) = obj.get(__names[#name].clone())? {223 __v224 } #(else if let Some(__v) = obj.get(__names[#aliases].clone())? {225 __v226 })* else {227 return Err(ErrorKind::NoSuchField(__names[#error_text].clone(), vec![]).into());228 };229230 <#ty as FromUntyped>::from_untyped(__value)?231 },232 }233 }234235 fn expand_serialize(&self, names: &mut Names) -> TokenStream {236 let ident = &self.ident;237 let ty = &self.ty;238 self.name().map_or_else(239 || {240 if self.is_option {241 quote! {242 if let Some(value) = self.#ident {243 <#ty as SerializeTypedObj>::serialize(value, out)?;244 }245 }246 } else {247 quote! {248 <#ty as SerializeTypedObj>::serialize(self.#ident, out)?;249 }250 }251 },252 |name| {253 let name = names.intern(name);254 let hide = if self.attr.hide {255 quote! {.hide()}256 } else {257 quote! {}258 };259 let add = if self.attr.add {260 quote! {.add()}261 } else {262 quote! {}263 };264 let value = if self.attr.method {265 quote! {266 out.method(__names[#name].clone(), value);267 }268 } else if self.is_lazy {269 quote! {270 out.field(__names[#name].clone())271 #hide272 #add273 .try_thunk(<#ty as IntoUntyped>::into_lazy_untyped(value))?;274 }275 } else {276 quote! {277 out.field(__names[#name].clone())278 #hide279 #add280 .try_value(<#ty as IntoUntyped>::into_untyped(value)?)?;281 }282 };283 if self.is_option {284 quote! {285 if let Some(value) = self.#ident {286 #value287 }288 }289 } else {290 quote! {291 {292 let value = self.#ident;293 #value294 }295 }296 }297 },298 )299 }300}301302pub fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {303 let syn::Data::Struct(data) = &input.data else {304 return Err(Error::new(input.span(), "only structs supported"));305 };306307 let ident = &input.ident;308 let fields = data309 .fields310 .iter()311 .map(TypedField::parse)312 .collect::<Result<Vec<_>>>()?;313314 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();315316 let fields = fields317 .iter()318 .filter_map(TypedField::expand_field)319 .collect::<Vec<_>>();320 Ok(quote! {321 const _: () = {322 use ::jrsonnet_evaluator::typed::__typed_macro_prelude::*;323324 impl #impl_generics Typed for #ident #ty_generics #where_clause {325 const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[326 #(#fields,)*327 ]);328 }329 };330 })331}332pub fn derive_into_untyped_inner(input: DeriveInput) -> Result<TokenStream> {333 let syn::Data::Struct(data) = &input.data else {334 return Err(Error::new(input.span(), "only structs supported"));335 };336337 let ident = &input.ident;338 let fields = data339 .fields340 .iter()341 .map(TypedField::parse)342 .collect::<Result<Vec<_>>>()?;343344 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();345346 let capacity = fields.len();347348 let mut names = Names::default();349350 let fields_serialize = fields351 .iter()352 .map(|f| f.expand_serialize(&mut names))353 .collect::<Vec<_>>();354355 let names_expanded = names.expand();356 Ok(quote! {357 const _: () = {358 use ::jrsonnet_evaluator::typed::__typed_macro_prelude::*;359360 impl #impl_generics IntoUntyped for #ident #ty_generics #where_clause {361 fn into_untyped(value: Self) -> JrResult<Val> {362 let mut out = ObjValueBuilder::with_capacity(#capacity);363 value.serialize(&mut out)?;364 Ok(Val::Obj(out.build()))365 }366 }367368 #names_expanded369370 impl #impl_generics SerializeTypedObj for #ident #ty_generics #where_clause {371 fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {372 NAMES.with(|__names| {373 #(#fields_serialize)*374375 Ok(())376 })377 }378 }379 };380 })381}382pub fn derive_from_untyped_inner(input: DeriveInput) -> Result<TokenStream> {383 let syn::Data::Struct(data) = &input.data else {384 return Err(Error::new(input.span(), "only structs supported"));385 };386387 let ident = &input.ident;388 let fields = data389 .fields390 .iter()391 .map(TypedField::parse)392 .collect::<Result<Vec<_>>>()?;393394 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();395396 let mut names = Names::default();397398 let fields_parse = fields399 .iter()400 .map(|f| f.expand_parse(&mut names))401 .collect::<Vec<_>>();402403 let names_expanded = names.expand();404 Ok(quote! {405 const _: () = {406 use ::jrsonnet_evaluator::typed::__typed_macro_prelude::*;407408 impl #impl_generics FromUntyped for #ident #ty_generics #where_clause {409 fn from_untyped(value: Val) -> JrResult<Self> {410 let obj = value.as_obj().expect("shape is correct");411 Self::parse(&obj)412 }413 }414415 #names_expanded416417 impl #impl_generics ParseTypedObj for #ident #ty_generics #where_clause {418 fn parse(obj: &ObjValue) -> JrResult<Self> {419 NAMES.with(|__names| Ok(Self {420 #(#fields_parse)*421 }))422 }423 }424 };425 })426}