difftreelog
fix(macros) derive(FromUntyped) type check
in: master
1 file changed
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 // 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}1use 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 // 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 <Self as Typed>::TYPE.check(&value)?;411 let obj = value.as_obj().expect("shape is correct");412 Self::parse(&obj)413 }414 }415416 #names_expanded417418 impl #impl_generics ParseTypedObj for #ident #ty_generics #where_clause {419 fn parse(obj: &ObjValue) -> JrResult<Self> {420 NAMES.with(|__names| Ok(Self {421 #(#fields_parse)*422 }))423 }424 }425 };426 })427}