From 349c41065b93e948a41324bad0d236ea5cf9baf6 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Wed, 20 Apr 2022 06:39:06 +0000 Subject: [PATCH] feat: lazy values in builtin --- --- a/crates/jrsonnet-evaluator/src/builtin/mod.rs +++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs @@ -1,4 +1,4 @@ -use crate::function::StaticBuiltin; +use crate::function::{CallLocation, StaticBuiltin}; use crate::typed::{Any, PositiveF64, VecVal, M1}; use crate::{ builtin::manifest::{manifest_yaml_ex, ManifestYamlOptions}, @@ -12,7 +12,6 @@ use crate::{Either, ObjValue}; use format::{format_arr, format_obj}; use jrsonnet_interner::IStr; -use jrsonnet_parser::ExprLocation; use serde::Deserialize; use serde_yaml::DeserializingQuirks; use std::collections::HashMap; @@ -29,7 +28,7 @@ pub fn std_format(str: IStr, vals: Val) -> Result { push_frame( - None, + CallLocation::native(), || format!("std.format of {}", str), || { Ok(match vals { @@ -467,9 +466,9 @@ } #[jrsonnet_macros::builtin] -fn builtin_trace(#[location] loc: Option<&ExprLocation>, str: IStr, rest: Any) -> Result { +fn builtin_trace(loc: CallLocation, str: IStr, rest: Any) -> Result { eprint!("TRACE:"); - if let Some(loc) = loc { + if let Some(loc) = loc.0 { with_state(|s| { let locs = s.map_source_locations(&loc.0, &[loc.1]); eprint!( --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -4,6 +4,7 @@ builtin::{std_slice, BUILTINS}, error::Error::*, evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op}, + function::CallLocation, gc::TraceBox, push_frame, throw, with_state, ArrValue, Bindable, Context, ContextCreator, FuncDesc, FuncVal, FutureWrapper, GcHashMap, LazyBinding, LazyVal, LazyValValue, ObjValue, ObjValueBuilder, @@ -12,8 +13,8 @@ use gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; use jrsonnet_parser::{ - ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, ExprLocation, FieldMember, ForSpecData, - IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc, + ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, FieldMember, ForSpecData, IfSpecData, + LiteralType, LocExpr, Member, ObjBody, ParamsDesc, }; use jrsonnet_types::ValType; pub mod operator; @@ -192,7 +193,7 @@ Ok(match field_name { jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()), jrsonnet_parser::FieldName::Dyn(expr) => push_frame( - Some(&expr.1), + CallLocation::new(&expr.1), || "evaluating field name".to_string(), || { let value = evaluate(context, expr)?; @@ -442,7 +443,7 @@ context: Context, value: &LocExpr, args: &ArgsDesc, - loc: Option<&ExprLocation>, + loc: CallLocation, tailstrict: bool, ) -> Result { let value = evaluate(context.clone(), value)?; @@ -463,13 +464,13 @@ let value = &assertion.0; let msg = &assertion.1; let assertion_result = push_frame( - Some(&value.1), + CallLocation::new(&value.1), || "assertion condition".to_owned(), || bool::try_from(evaluate(context.clone(), value)?), )?; if !assertion_result { push_frame( - Some(&value.1), + CallLocation::new(&value.1), || "assertion failure".to_owned(), || { if let Some(msg) = msg { @@ -519,7 +520,7 @@ BinaryOp(v1, o, v2) => evaluate_binary_op_special(context, v1, *o, v2)?, UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)?)?, Var(name) => push_frame( - Some(loc), + CallLocation::new(loc), || format!("variable <{}> access", name), || context.binding(name.clone())?.evaluate(), )?, @@ -528,7 +529,7 @@ (Val::Obj(v), Val::Str(s)) => { let sn = s.clone(); push_frame( - Some(loc), + CallLocation::new(loc), || format!("field <{}> access", sn), || { if let Some(v) = v.get(s.clone())? { @@ -625,7 +626,7 @@ &Val::Obj(evaluate_object(context, t)?), )?, Apply(value, args, tailstrict) => { - evaluate_apply(context, value, args, Some(loc), *tailstrict)? + evaluate_apply(context, value, args, CallLocation::new(loc), *tailstrict)? } Function(params, body) => { evaluate_method(context, "anonymous".into(), params.clone(), body.clone()) @@ -640,7 +641,7 @@ evaluate(context, returned)? } ErrorStmt(e) => push_frame( - Some(loc), + CallLocation::new(loc), || "error statement".to_owned(), || throw!(RuntimeError(IStr::try_from(evaluate(context, e)?)?,)), )?, @@ -650,7 +651,7 @@ cond_else, } => { if push_frame( - Some(loc), + CallLocation::new(loc), || "if condition".to_owned(), || bool::try_from(evaluate(context.clone(), &cond.0)?), )? { @@ -689,7 +690,7 @@ let mut import_location = tmp.to_path_buf(); import_location.pop(); push_frame( - Some(loc), + CallLocation::new(loc), || format!("import {:?}", path), || with_state(|s| s.import_file(&import_location, path)), )? --- a/crates/jrsonnet-evaluator/src/function.rs +++ b/crates/jrsonnet-evaluator/src/function.rs @@ -12,6 +12,19 @@ use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc}; use std::{borrow::Cow, collections::HashMap, convert::TryFrom}; +#[derive(Clone, Copy)] +pub struct CallLocation<'l>(pub Option<&'l ExprLocation>); +impl<'l> CallLocation<'l> { + pub fn new(loc: &'l ExprLocation) -> Self { + Self(Some(loc)) + } +} +impl CallLocation<'static> { + pub fn native() -> Self { + Self(None) + } +} + #[derive(Trace)] struct EvaluateLazyVal { context: Context, @@ -383,12 +396,7 @@ pub trait Builtin: Trace { fn name(&self) -> &str; fn params(&self) -> &[BuiltinParam]; - fn call( - &self, - context: Context, - loc: Option<&ExprLocation>, - args: &dyn ArgsLike, - ) -> Result; + fn call(&self, context: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result; } pub trait StaticBuiltin: Builtin + Send + Sync --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -28,7 +28,7 @@ pub use dynamic::*; use error::{Error::*, LocError, Result, StackTraceElement}; pub use evaluate::*; -use function::{Builtin, TlaArg}; +use function::{Builtin, CallLocation, TlaArg}; use gc::{GcHashMap, TraceBox}; use gcmodule::{Cc, Trace, Weak}; pub use import::*; @@ -173,6 +173,7 @@ /// Global state is fine here. pub(crate) static EVAL_STATE: RefCell> = RefCell::new(None) } + pub(crate) fn with_state(f: impl FnOnce(&EvaluationState) -> T) -> T { EVAL_STATE.with(|s| { f(s.borrow().as_ref().expect( @@ -181,7 +182,7 @@ }) } pub fn push_frame( - e: Option<&ExprLocation>, + e: CallLocation, frame_desc: impl FnOnce() -> String, f: impl FnOnce() -> Result, ) -> Result { @@ -345,7 +346,7 @@ /// Executes code creating a new stack frame pub fn push( &self, - e: Option<&ExprLocation>, + e: CallLocation, frame_desc: impl FnOnce() -> String, f: impl FnOnce() -> Result, ) -> Result { @@ -368,7 +369,7 @@ } if let Err(mut err) = result { err.trace_mut().0.push(StackTraceElement { - location: e.cloned(), + location: e.0.cloned(), desc: frame_desc(), }); return Err(err); @@ -512,7 +513,7 @@ || { func.evaluate( self.create_default_context(), - None, + CallLocation::native(), &self.settings().tla_vars, true, ) --- a/crates/jrsonnet-evaluator/src/native.rs +++ b/crates/jrsonnet-evaluator/src/native.rs @@ -1,11 +1,10 @@ #![allow(clippy::type_complexity)] -use crate::function::{parse_builtin_call, ArgsLike, Builtin, BuiltinParam}; +use crate::function::{parse_builtin_call, ArgsLike, Builtin, BuiltinParam, CallLocation}; use crate::gc::TraceBox; use crate::Context; use crate::{error::Result, Val}; use gcmodule::Trace; -use jrsonnet_parser::ExprLocation; use std::path::Path; use std::rc::Rc; @@ -32,18 +31,13 @@ &self.params } - fn call( - &self, - context: Context, - loc: Option<&ExprLocation>, - args: &dyn ArgsLike, - ) -> Result { + fn call(&self, context: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result { let args = parse_builtin_call(context, &self.params, args, true)?; let mut out_args = Vec::with_capacity(self.params.len()); for p in self.params.iter() { out_args.push(args[&p.name].evaluate()?); } - self.handler.call(loc.map(|l| l.0.clone()), &out_args) + self.handler.call(loc.0.map(|l| l.0.clone()), &out_args) } } --- a/crates/jrsonnet-evaluator/src/typed/conversions.rs +++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs @@ -1,5 +1,6 @@ use std::convert::{TryFrom, TryInto}; +use gcmodule::Cc; use jrsonnet_interner::IStr; pub use jrsonnet_macros::Typed; use jrsonnet_types::{ComplexValType, ValType}; @@ -8,7 +9,7 @@ error::{Error::*, LocError, Result}, throw, typed::CheckType, - ArrValue, FuncVal, IndexableVal, ObjValue, ObjValueBuilder, Val, + ArrValue, FuncDesc, FuncVal, IndexableVal, ObjValue, ObjValueBuilder, Val, }; pub trait TypedObj: Typed { @@ -431,6 +432,30 @@ Ok(Self::Func(value)) } } + +impl Typed for Cc { + const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func); +} +impl TryFrom for Cc { + type Error = LocError; + + fn try_from(value: Val) -> Result { + ::TYPE.check(&value)?; + match value { + Val::Func(FuncVal::Normal(desc)) => Ok(desc.clone()), + Val::Func(_) => throw!(RuntimeError("expected normal function, not builtin".into())), + _ => unreachable!(), + } + } +} +impl TryFrom> for Val { + type Error = LocError; + + fn try_from(value: Cc) -> Result { + Ok(Self::Func(FuncVal::Normal(value))) + } +} + impl Typed for ObjValue { const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Obj); } --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -6,14 +6,15 @@ error::{Error::*, LocError}, evaluate, function::{ - parse_default_function_call, parse_function_call, ArgsLike, Builtin, StaticBuiltin, + parse_default_function_call, parse_function_call, ArgsLike, Builtin, CallLocation, + StaticBuiltin, }, gc::TraceBox, throw, Context, ObjValue, Result, }; use gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; -use jrsonnet_parser::{ExprLocation, LocExpr, ParamsDesc}; +use jrsonnet_parser::{LocExpr, ParamsDesc}; use jrsonnet_types::ValType; use std::{cell::RefCell, fmt::Debug, rc::Rc}; @@ -152,7 +153,7 @@ pub fn evaluate( &self, call_ctx: Context, - loc: Option<&ExprLocation>, + loc: CallLocation, args: &dyn ArgsLike, tailstrict: bool, ) -> Result { @@ -166,7 +167,7 @@ } } pub fn evaluate_simple(&self, args: &dyn ArgsLike) -> Result { - self.evaluate(Context::default(), None, args, true) + self.evaluate(Context::default(), CallLocation::native(), args, true) } } --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -1,5 +1,5 @@ use proc_macro2::TokenStream; -use quote::{quote, quote_spanned}; +use quote::quote; use syn::{ parenthesized, parse::{Parse, ParseStream}, @@ -7,8 +7,8 @@ punctuated::Punctuated, spanned::Spanned, token::Comma, - Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, PatType, - Path, PathArguments, Result, Token, Type, + Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path, + PathArguments, Result, ReturnType, Token, Type, }; fn parse_attr(attrs: &[Attribute], ident: I) -> Result> @@ -33,49 +33,42 @@ Ok(Some(attr)) } -fn is_location_arg(t: &PatType) -> bool { - t.attrs.iter().any(|a| a.path.is_ident("location")) -} -fn is_self_arg(t: &PatType) -> bool { - t.attrs.iter().any(|a| a.path.is_ident("self")) +fn path_is(path: &Path, needed: &str) -> bool { + path.leading_colon.is_none() + && path.segments.len() >= 1 + && path.segments.iter().last().unwrap().ident == needed } -trait RetainHad { - fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool; -} -impl RetainHad for Vec { - fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool { - let before = self.len(); - self.retain(h); - let after = self.len(); - before != after +fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> { + match ty { + Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => { + let args = &path.path.segments.iter().last().unwrap().arguments; + Some(args) + } + _ => None, } } -fn extract_type_from_option(ty: &Type) -> Option<&Type> { - fn path_is_option(path: &Path) -> bool { - path.leading_colon.is_none() - && path.segments.len() == 1 - && path.segments.iter().next().unwrap().ident == "Option" - } - - match ty { - Type::Path(typepath) if typepath.qself.is_none() && path_is_option(&typepath.path) => { - // Get the first segment of the path (there is only one, in fact: "Option"): - let type_params = &typepath.path.segments.iter().next().unwrap().arguments; - // It should have only on angle-bracketed param (""): - let generic_arg = match type_params { - PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(), - _ => panic!("missing option generic"), - }; - // This argument must be a type: - match generic_arg { - GenericArgument::Type(ty) => Some(ty), - _ => panic!("option generic should be a type"), +fn extract_type_from_option(ty: &Type) -> Result> { + Ok(if let Some(args) = type_is_path(ty, "Option") { + // It should have only on angle-bracketed param (""): + let generic_arg = match args { + PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(), + _ => return Err(Error::new(args.span(), "missing option generic")), + }; + // This argument must be a type: + match generic_arg { + GenericArgument::Type(ty) => Some(ty), + _ => { + return Err(Error::new( + generic_arg.span(), + "option generic should be a type", + )) } } - _ => None, - } + } else { + None + }) } struct Field { @@ -101,7 +94,7 @@ struct EmptyAttr; impl Parse for EmptyAttr { - fn parse(input: ParseStream) -> Result { + fn parse(_input: ParseStream) -> Result { Ok(Self) } } @@ -124,107 +117,158 @@ } } +enum ArgInfo { + Normal { + ty: Type, + is_option: bool, + name: String, + // ident: Ident, + }, + Lazy { + is_option: bool, + name: String, + }, + Location, + This, +} + +impl ArgInfo { + fn parse(arg: &FnArg) -> Result { + let typed = match arg { + FnArg::Receiver(_) => unreachable!(), + FnArg::Typed(a) => a, + }; + let ident = match &typed.pat as &Pat { + Pat::Ident(i) => i.ident.clone(), + _ => { + return Err(Error::new( + typed.pat.span(), + "arg should be plain identifier", + )) + } + }; + let ty = &typed.ty as &Type; + if type_is_path(&ty, "CallLocation").is_some() { + return Ok(Self::Location); + } else if type_is_path(&ty, "Self").is_some() { + return Ok(Self::This); + } else if type_is_path(&ty, "LazyVal").is_some() { + return Ok(Self::Lazy { + is_option: false, + name: ident.to_string(), + }); + } + + let (is_option, ty) = if let Some(ty) = extract_type_from_option(&ty)? { + if type_is_path(&ty, "LazyVal").is_some() { + return Ok(Self::Lazy { + is_option: true, + name: ident.to_string(), + }); + } + + (true, ty.clone()) + } else { + (false, ty.clone()) + }; + + Ok(Self::Normal { + ty, + is_option, + name: ident.to_string(), + // ident, + }) + } +} + #[proc_macro_attribute] pub fn builtin( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let attrs = parse_macro_input!(attr as BuiltinAttrs); - let mut fun: ItemFn = parse_macro_input!(item); + let attr = parse_macro_input!(attr as BuiltinAttrs); + let item: ItemFn = parse_macro_input!(item); + + match builtin_inner(attr, item) { + Ok(v) => v.into(), + Err(e) => e.into_compile_error().into(), + } +} +fn builtin_inner(attr: BuiltinAttrs, fun: ItemFn) -> syn::Result { let result = match fun.sig.output { - syn::ReturnType::Default => { - return quote_spanned! { fun.sig.span() => - compile_error!("builtins should return something"); - } - .into() + ReturnType::Default => { + return Err(Error::new( + fun.sig.span(), + "builtin should return something", + )) } - syn::ReturnType::Type(_, ref ty) => ty.clone(), + ReturnType::Type(_, ref ty) => ty.clone(), }; - let params = fun + let args = fun .sig .inputs .iter() - .map(|i| match i { - FnArg::Receiver(_) => unreachable!(), - FnArg::Typed(t) => t, - }) - .filter(|a| !is_location_arg(a) && !is_self_arg(a)) - .map(|t| { - let ident = match &t.pat as &Pat { - Pat::Ident(i) => i.ident.to_string(), - _ => { - return quote_spanned! { t.pat.span() => - compile_error!("args should be plain identifiers") - } - .into() - } - }; - let optional = extract_type_from_option(&t.ty).is_some(); - quote! { - BuiltinParam { - name: std::borrow::Cow::Borrowed(#ident), - has_default: #optional, - } + .map(|a| ArgInfo::parse(a)) + .collect::>>()?; + + let params_desc = args.iter().flat_map(|a| match a { + ArgInfo::Normal { + is_option, name, .. + } + | ArgInfo::Lazy { is_option, name } => Some(quote! { + BuiltinParam { + name: std::borrow::Cow::Borrowed(#name), + has_default: #is_option, } - }) - .collect::>(); + }), + ArgInfo::Location => None, + ArgInfo::This => None, + }); - let args = fun - .sig - .inputs - .iter_mut() - .map(|i| match i { - FnArg::Receiver(_) => unreachable!(), - FnArg::Typed(t) => t, - }) - .map(|t| { - if t.attrs.retain_had(|a| !a.path.is_ident("location")) { - quote! {{ - loc + let pass = args.iter().map(|a| match a { + ArgInfo::Normal { + ty, + is_option, + name, + // ident, + } => { + let eval = quote! {::jrsonnet_evaluator::push_description_frame( + || format!("argument <{}> evaluation", #name), + || <#ty>::try_from(value.evaluate()?), + )?}; + if *is_option { + quote! {if let Some(value) = parsed.get(#name) { + Some(#eval) + } else { + None }} - } else if t.attrs.retain_had(|a| !a.path.is_ident("self")) { + } else { quote! {{ - self + let value = parsed.get(#name).expect("args shape is checked"); + #eval }} - } else { - let ident = match &t.pat as &Pat { - Pat::Ident(i) => i.ident.to_string(), - _ => { - return quote_spanned! { t.pat.span() => - compile_error!("args should be plain identifiers") - } - .into() - } - }; - let ty = &t.ty; - if let Some(opt_ty) = extract_type_from_option(&t.ty) { - quote! {{ - if let Some(value) = parsed.get(#ident) { - Some(::jrsonnet_evaluator::push_description_frame( - || format!("argument <{}> evaluation", #ident), - || <#opt_ty>::try_from(value.evaluate()?), - )?) - } else { - None - } - }} + } + } + ArgInfo::Lazy { is_option, name } => { + if *is_option { + quote! {if let Some(value) = parsed.get(#name) { + Some(value.clone()) } else { - quote! {{ - let value = parsed.get(#ident).unwrap(); - - ::jrsonnet_evaluator::push_description_frame( - || format!("argument <{}> evaluation", #ident), - || <#ty>::try_from(value.evaluate()?), - )? - }} + None + }} + } else { + quote! { + parsed.get(#name).expect("args shape is correct").clone() } } - }) - .collect::>(); + } + ArgInfo::Location => quote! {location}, + ArgInfo::This => quote! {self}, + }); - let fields = attrs.fields.iter().map(|field| { + let fields = attr.fields.iter().map(|field| { let name = &field.name; let ty = &field.ty; quote! { @@ -234,7 +278,7 @@ let name = &fun.sig.ident; let vis = &fun.vis; - let static_ext = if attrs.fields.is_empty() { + let static_ext = if attr.fields.is_empty() { quote! { impl #name { pub const INST: &'static dyn StaticBuiltin = &#name {}; @@ -244,13 +288,13 @@ } else { quote! {} }; - let static_derive_copy = if attrs.fields.is_empty() { + let static_derive_copy = if attr.fields.is_empty() { quote! {, Copy} } else { quote! {} }; - (quote! { + Ok(quote! { #fun #[doc(hidden)] #[allow(non_camel_case_types)] @@ -260,12 +304,12 @@ } const _: () = { use ::jrsonnet_evaluator::{ - function::{Builtin, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call}, + function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call}, error::Result, Context, parser::ExprLocation, }; const PARAMS: &'static [BuiltinParam] = &[ - #(#params),* + #(#params_desc),* ]; #static_ext @@ -279,17 +323,16 @@ fn params(&self) -> &[BuiltinParam] { PARAMS } - fn call(&self, context: Context, loc: Option<&ExprLocation>, args: &dyn ArgsLike) -> Result { + fn call(&self, context: Context, location: CallLocation, args: &dyn ArgsLike) -> Result { let parsed = parse_builtin_call(context, &PARAMS, args, false)?; - let result: #result = #name(#(#args),*); + let result: #result = #name(#(#pass),*); let result = result?; result.try_into() } } }; }) - .into() } #[derive(Default)] @@ -366,15 +409,6 @@ ) } - fn expand_shallow_field(&self) -> Option { - if self.is_option() { - return None; - } - let name = self.name()?; - Some(quote! { - (#name, ComplexValType::Any) - }) - } fn expand_field(&self) -> Option { if self.is_option() { return None; @@ -450,7 +484,7 @@ } fn as_option(&self) -> Option<&Type> { - extract_type_from_option(&self.0.ty) + extract_type_from_option(&self.0.ty).unwrap() } fn is_option(&self) -> bool { self.as_option().is_some() @@ -460,25 +494,25 @@ #[proc_macro_derive(Typed, attributes(typed))] pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(item as DeriveInput); + + match derive_typed_inner(input) { + Ok(v) => v.into(), + Err(e) => e.to_compile_error().into(), + } +} + +fn derive_typed_inner(input: DeriveInput) -> Result { let data = match &input.data { syn::Data::Struct(s) => s, - _ => { - return syn::Error::new(input.span(), "only structs supported") - .to_compile_error() - .into() - } + _ => return Err(Error::new(input.span(), "only structs supported")), }; let ident = &input.ident; - let fields = match data + let fields = data .fields .iter() .map(TypedField::try_new) - .collect::>>() - { - Ok(v) => v, - Err(e) => return e.to_compile_error().into(), - }; + .collect::>>()?; let typed = { let fields = fields @@ -499,7 +533,7 @@ let fields_parse = fields.iter().map(TypedField::expand_parse); let fields_serialize = fields.iter().map(TypedField::expand_serialize); - quote! { + Ok(quote! { const _: () = { use ::jrsonnet_evaluator::{ typed::{ComplexValType, Typed, TypedObj, CheckType}, @@ -540,6 +574,5 @@ } () }; - } - .into() + }) } -- gitstuff