difftreelog
feat lazy values in builtin
in: master
8 files changed
crates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth--- 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<String> {
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<Any> {
+fn builtin_trace(loc: CallLocation, str: IStr, rest: Any) -> Result<Any> {
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!(
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- 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<Val> {
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)),
)?
crates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth--- 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<Val>;
+ fn call(&self, context: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;
}
pub trait StaticBuiltin: Builtin + Send + Sync
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- 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<Option<EvaluationState>> = RefCell::new(None)
}
+
pub(crate) fn with_state<T>(f: impl FnOnce(&EvaluationState) -> T) -> T {
EVAL_STATE.with(|s| {
f(s.borrow().as_ref().expect(
@@ -181,7 +182,7 @@
})
}
pub fn push_frame<T>(
- e: Option<&ExprLocation>,
+ e: CallLocation,
frame_desc: impl FnOnce() -> String,
f: impl FnOnce() -> Result<T>,
) -> Result<T> {
@@ -345,7 +346,7 @@
/// Executes code creating a new stack frame
pub fn push<T>(
&self,
- e: Option<&ExprLocation>,
+ e: CallLocation,
frame_desc: impl FnOnce() -> String,
f: impl FnOnce() -> Result<T>,
) -> Result<T> {
@@ -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,
)
crates/jrsonnet-evaluator/src/native.rsdiffbeforeafterboth--- 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<Val> {
+ fn call(&self, context: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val> {
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)
}
}
crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth--- 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<FuncDesc> {
+ const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);
+}
+impl TryFrom<Val> for Cc<FuncDesc> {
+ type Error = LocError;
+
+ fn try_from(value: Val) -> Result<Self, Self::Error> {
+ <Self as Typed>::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<Cc<FuncDesc>> for Val {
+ type Error = LocError;
+
+ fn try_from(value: Cc<FuncDesc>) -> Result<Self, Self::Error> {
+ Ok(Self::Func(FuncVal::Normal(value)))
+ }
+}
+
impl Typed for ObjValue {
const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Obj);
}
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- 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<Val> {
@@ -166,7 +167,7 @@
}
}
pub fn evaluate_simple(&self, args: &dyn ArgsLike) -> Result<Val> {
- self.evaluate(Context::default(), None, args, true)
+ self.evaluate(Context::default(), CallLocation::native(), args, true)
}
}
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth1use proc_macro2::TokenStream;2use quote::{quote, quote_spanned};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, PatType,11 Path, PathArguments, Result, 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 is_location_arg(t: &PatType) -> bool {37 t.attrs.iter().any(|a| a.path.is_ident("location"))38}39fn is_self_arg(t: &PatType) -> bool {40 t.attrs.iter().any(|a| a.path.is_ident("self"))41}4243trait RetainHad<T> {44 fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool;45}46impl<T> RetainHad<T> for Vec<T> {47 fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool {48 let before = self.len();49 self.retain(h);50 let after = self.len();51 before != after52 }53}5455fn extract_type_from_option(ty: &Type) -> Option<&Type> {56 fn path_is_option(path: &Path) -> bool {57 path.leading_colon.is_none()58 && path.segments.len() == 159 && path.segments.iter().next().unwrap().ident == "Option"60 }6162 match ty {63 Type::Path(typepath) if typepath.qself.is_none() && path_is_option(&typepath.path) => {64 // Get the first segment of the path (there is only one, in fact: "Option"):65 let type_params = &typepath.path.segments.iter().next().unwrap().arguments;66 // It should have only on angle-bracketed param ("<String>"):67 let generic_arg = match type_params {68 PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),69 _ => panic!("missing option generic"),70 };71 // This argument must be a type:72 match generic_arg {73 GenericArgument::Type(ty) => Some(ty),74 _ => panic!("option generic should be a type"),75 }76 }77 _ => None,78 }79}8081struct Field {82 name: Ident,83 _colon: Token![:],84 ty: Type,85}86impl Parse for Field {87 fn parse(input: ParseStream) -> syn::Result<Self> {88 Ok(Self {89 name: input.parse()?,90 _colon: input.parse()?,91 ty: input.parse()?,92 })93 }94}9596mod kw {97 syn::custom_keyword!(fields);98 syn::custom_keyword!(rename);99 syn::custom_keyword!(flatten);100}101102struct EmptyAttr;103impl Parse for EmptyAttr {104 fn parse(input: ParseStream) -> Result<Self> {105 Ok(Self)106 }107}108109struct BuiltinAttrs {110 fields: Vec<Field>,111}112impl Parse for BuiltinAttrs {113 fn parse(input: ParseStream) -> syn::Result<Self> {114 if input.is_empty() {115 return Ok(Self { fields: Vec::new() });116 }117 input.parse::<kw::fields>()?;118 let fields;119 parenthesized!(fields in input);120 let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;121 Ok(Self {122 fields: p.into_iter().collect(),123 })124 }125}126127#[proc_macro_attribute]128pub fn builtin(129 attr: proc_macro::TokenStream,130 item: proc_macro::TokenStream,131) -> proc_macro::TokenStream {132 let attrs = parse_macro_input!(attr as BuiltinAttrs);133 let mut fun: ItemFn = parse_macro_input!(item);134135 let result = match fun.sig.output {136 syn::ReturnType::Default => {137 return quote_spanned! { fun.sig.span() =>138 compile_error!("builtins should return something");139 }140 .into()141 }142 syn::ReturnType::Type(_, ref ty) => ty.clone(),143 };144145 let params = fun146 .sig147 .inputs148 .iter()149 .map(|i| match i {150 FnArg::Receiver(_) => unreachable!(),151 FnArg::Typed(t) => t,152 })153 .filter(|a| !is_location_arg(a) && !is_self_arg(a))154 .map(|t| {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 optional = extract_type_from_option(&t.ty).is_some();165 quote! {166 BuiltinParam {167 name: std::borrow::Cow::Borrowed(#ident),168 has_default: #optional,169 }170 }171 })172 .collect::<Vec<_>>();173174 let args = fun175 .sig176 .inputs177 .iter_mut()178 .map(|i| match i {179 FnArg::Receiver(_) => unreachable!(),180 FnArg::Typed(t) => t,181 })182 .map(|t| {183 if t.attrs.retain_had(|a| !a.path.is_ident("location")) {184 quote! {{185 loc186 }}187 } else if t.attrs.retain_had(|a| !a.path.is_ident("self")) {188 quote! {{189 self190 }}191 } else {192 let ident = match &t.pat as &Pat {193 Pat::Ident(i) => i.ident.to_string(),194 _ => {195 return quote_spanned! { t.pat.span() =>196 compile_error!("args should be plain identifiers")197 }198 .into()199 }200 };201 let ty = &t.ty;202 if let Some(opt_ty) = extract_type_from_option(&t.ty) {203 quote! {{204 if let Some(value) = parsed.get(#ident) {205 Some(::jrsonnet_evaluator::push_description_frame(206 || format!("argument <{}> evaluation", #ident),207 || <#opt_ty>::try_from(value.evaluate()?),208 )?)209 } else {210 None211 }212 }}213 } else {214 quote! {{215 let value = parsed.get(#ident).unwrap();216217 ::jrsonnet_evaluator::push_description_frame(218 || format!("argument <{}> evaluation", #ident),219 || <#ty>::try_from(value.evaluate()?),220 )?221 }}222 }223 }224 })225 .collect::<Vec<_>>();226227 let fields = attrs.fields.iter().map(|field| {228 let name = &field.name;229 let ty = &field.ty;230 quote! {231 pub #name: #ty,232 }233 });234235 let name = &fun.sig.ident;236 let vis = &fun.vis;237 let static_ext = if attrs.fields.is_empty() {238 quote! {239 impl #name {240 pub const INST: &'static dyn StaticBuiltin = &#name {};241 }242 impl StaticBuiltin for #name {}243 }244 } else {245 quote! {}246 };247 let static_derive_copy = if attrs.fields.is_empty() {248 quote! {, Copy}249 } else {250 quote! {}251 };252253 (quote! {254 #fun255 #[doc(hidden)]256 #[allow(non_camel_case_types)]257 #[derive(Clone, gcmodule::Trace #static_derive_copy)]258 #vis struct #name {259 #(#fields)*260 }261 const _: () = {262 use ::jrsonnet_evaluator::{263 function::{Builtin, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},264 error::Result, Context,265 parser::ExprLocation,266 };267 const PARAMS: &'static [BuiltinParam] = &[268 #(#params),*269 ];270271 #static_ext272 impl Builtin for #name273 where274 Self: 'static275 {276 fn name(&self) -> &str {277 stringify!(#name)278 }279 fn params(&self) -> &[BuiltinParam] {280 PARAMS281 }282 fn call(&self, context: Context, loc: Option<&ExprLocation>, args: &dyn ArgsLike) -> Result<Val> {283 let parsed = parse_builtin_call(context, &PARAMS, args, false)?;284285 let result: #result = #name(#(#args),*);286 let result = result?;287 result.try_into()288 }289 }290 };291 })292 .into()293}294295#[derive(Default)]296struct TypedAttr {297 rename: Option<String>,298 flatten: bool,299}300impl Parse for TypedAttr {301 fn parse(input: ParseStream) -> syn::Result<Self> {302 let mut out = Self::default();303 loop {304 let lookahead = input.lookahead1();305 if lookahead.peek(kw::rename) {306 input.parse::<kw::rename>()?;307 input.parse::<Token![=]>()?;308 let name = input.parse::<LitStr>()?;309 if out.rename.is_some() {310 return Err(Error::new(311 name.span(),312 "rename attribute may only be specified once",313 ));314 }315 out.rename = Some(name.value());316 } else if lookahead.peek(kw::flatten) {317 input.parse::<kw::flatten>()?;318 out.flatten = true;319 } else if input.is_empty() {320 break;321 } else {322 return Err(lookahead.error());323 }324 if input.peek(Token![,]) {325 input.parse::<Token![,]>()?;326 } else {327 break;328 }329 }330 // input.parse::<kw::rename>()?;331 // input.parse::<Token![=]>()?;332 // let rename = input.parse::<LitStr>()?.value();333 Ok(out)334 }335}336337struct TypedField<'f>(&'f syn::Field, TypedAttr);338impl<'f> TypedField<'f> {339 fn try_new(field: &'f syn::Field) -> Result<Self> {340 let attr =341 parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_else(Default::default);342 if field.ident.is_none() {343 return Err(Error::new(344 field.span(),345 "this field should appear in output object, but it has no visible name",346 ));347 }348 Ok(Self(field, attr))349 }350 fn ident(&self) -> Ident {351 self.0352 .ident353 .clone()354 .expect("constructor disallows fields without name")355 }356 /// None if this field is flattened in jsonnet output357 fn name(&self) -> Option<String> {358 if self.1.flatten {359 return None;360 }361 Some(362 self.1363 .rename364 .clone()365 .unwrap_or_else(|| self.ident().to_string()),366 )367 }368369 fn expand_shallow_field(&self) -> Option<TokenStream> {370 if self.is_option() {371 return None;372 }373 let name = self.name()?;374 Some(quote! {375 (#name, ComplexValType::Any)376 })377 }378 fn expand_field(&self) -> Option<TokenStream> {379 if self.is_option() {380 return None;381 }382 let name = self.name()?;383 let ty = &self.0.ty;384 Some(quote! {385 (#name, <#ty>::TYPE)386 })387 }388 fn expand_parse(&self) -> TokenStream {389 let ident = self.ident();390 let ty = &self.0.ty;391 if self.1.flatten {392 // optional flatten is handled in same way as serde393 return if self.is_option() {394 quote! {395 #ident: <#ty>::parse(&obj).ok(),396 }397 } else {398 quote! {399 #ident: <#ty>::parse(&obj)?,400 }401 };402 };403404 let name = self.name().unwrap();405 let value = if let Some(ty) = self.as_option() {406 quote! {407 if let Some(value) = obj.get(#name.into())? {408 Some(<#ty>::try_from(vakue)?)409 } else {410 None411 }412 }413 } else {414 quote! {415 <#ty>::try_from(obj.get(#name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?)?416 }417 };418419 quote! {420 #ident: #value,421 }422 }423 fn expand_serialize(&self) -> TokenStream {424 let ident = self.ident();425 if let Some(name) = self.name() {426 if self.is_option() {427 quote! {428 if let Some(value) = self.#ident {429 out.member(#name.into()).value(value.try_into()?);430 }431 }432 } else {433 quote! {434 out.member(#name.into()).value(self.#ident.try_into()?);435 }436 }437 } else {438 if self.is_option() {439 quote! {440 if let Some(value) = self.#ident {441 value.serialize(out)?;442 }443 }444 } else {445 quote! {446 self.#ident.serialize(out)?;447 }448 }449 }450 }451452 fn as_option(&self) -> Option<&Type> {453 extract_type_from_option(&self.0.ty)454 }455 fn is_option(&self) -> bool {456 self.as_option().is_some()457 }458}459460#[proc_macro_derive(Typed, attributes(typed))]461pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {462 let input = parse_macro_input!(item as DeriveInput);463 let data = match &input.data {464 syn::Data::Struct(s) => s,465 _ => {466 return syn::Error::new(input.span(), "only structs supported")467 .to_compile_error()468 .into()469 }470 };471472 let ident = &input.ident;473 let fields = match data474 .fields475 .iter()476 .map(TypedField::try_new)477 .collect::<Result<Vec<_>>>()478 {479 Ok(v) => v,480 Err(e) => return e.to_compile_error().into(),481 };482483 let typed = {484 let fields = fields485 .iter()486 .flat_map(TypedField::expand_field)487 .collect::<Vec<_>>();488 let len = fields.len();489 quote! {490 const ITEMS: [(&'static str, &'static ComplexValType); #len] = [491 #(#fields,)*492 ];493 impl Typed for #ident {494 const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&ITEMS);495 }496 }497 };498499 let fields_parse = fields.iter().map(TypedField::expand_parse);500 let fields_serialize = fields.iter().map(TypedField::expand_serialize);501502 quote! {503 const _: () = {504 use ::jrsonnet_evaluator::{505 typed::{ComplexValType, Typed, TypedObj, CheckType},506 Val,507 error::{LocError, Error},508 ObjValueBuilder, ObjValue,509 };510511 #typed512513 impl #ident {514 fn serialize(self, out: &mut ObjValueBuilder) -> Result<(), LocError> {515 #(#fields_serialize)*516517 Ok(())518 }519 fn parse(obj: &ObjValue) -> Result<Self, LocError> {520 Ok(Self {521 #(#fields_parse)*522 })523 }524 }525526 impl TryFrom<Val> for #ident {527 type Error = LocError;528 fn try_from(value: Val) -> Result<Self, Self::Error> {529 let obj = value.as_obj().expect("shape is correct");530 Self::parse(&obj)531 }532 }533 impl TryInto<Val> for #ident {534 type Error = LocError;535 fn try_into(self) -> Result<Val, Self::Error> {536 let mut out = ObjValueBuilder::new();537 self.serialize(&mut out)?;538 Ok(Val::Obj(out.build()))539 }540 }541 ()542 };543 }544 .into()545}