difftreelog
test basic interop checks
in: master
7 files changed
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -1,4 +1,5 @@
use std::{
+ fmt::Debug,
path::{Path, PathBuf},
rc::Rc,
};
@@ -166,7 +167,7 @@
#[derive(Debug, Clone, Trace)]
pub struct StackTrace(pub Vec<StackTraceElement>);
-#[derive(Debug, Clone, Trace)]
+#[derive(Clone, Trace)]
pub struct LocError(Box<(Error, StackTrace)>);
impl LocError {
pub fn new(e: Error) -> Self {
@@ -186,6 +187,15 @@
&mut (self.0).1
}
}
+impl Debug for LocError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ writeln!(f, "{}", self.0 .0)?;
+ for el in self.0 .1 .0.iter() {
+ writeln!(f, "\t{:?}", el)?;
+ }
+ Ok(())
+ }
+}
pub type Result<V, E = LocError> = std::result::Result<V, E>;
crates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function.rs
+++ b/crates/jrsonnet-evaluator/src/function.rs
@@ -45,7 +45,6 @@
evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)
}
}
-
pub trait ArgLike {
fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal>;
}
@@ -169,6 +168,34 @@
}
}
+impl ArgsLike for [(); 0] {
+ fn unnamed_len(&self) -> usize {
+ 0
+ }
+
+ fn unnamed_iter(
+ &self,
+ _s: State,
+ _ctx: Context,
+ _tailstrict: bool,
+ _handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ Ok(())
+ }
+
+ fn named_iter(
+ &self,
+ _s: State,
+ _ctx: Context,
+ _tailstrict: bool,
+ _handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ Ok(())
+ }
+
+ fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}
+}
+
impl<A: ArgLike> ArgsLike for [(IStr, A)] {
fn unnamed_len(&self) -> usize {
0
crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -14,11 +14,11 @@
};
pub trait TypedObj: Typed {
- fn serialize(self, out: &mut ObjValueBuilder) -> Result<()>;
- fn parse(obj: &ObjValue) -> Result<Self>;
- fn into_object(self) -> Result<ObjValue> {
+ fn serialize(self, s: State, out: &mut ObjValueBuilder) -> Result<()>;
+ fn parse(obj: &ObjValue, s: State) -> Result<Self>;
+ fn into_object(self, s: State) -> Result<ObjValue> {
let mut builder = ObjValueBuilder::new();
- self.serialize(&mut builder)?;
+ self.serialize(s, &mut builder)?;
Ok(builder.build())
}
}
crates/jrsonnet-evaluator/tests/builtin.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/tests/builtin.rs
@@ -0,0 +1,105 @@
+mod common;
+
+use std::path::PathBuf;
+
+use gcmodule::Cc;
+use jrsonnet_evaluator::{
+ error::Result,
+ function::{builtin, Builtin, CallLocation},
+ gc::TraceBox,
+ typed::Typed,
+ val::FuncVal,
+ State, Val,
+};
+
+#[builtin]
+fn a() -> Result<u32> {
+ Ok(1)
+}
+
+#[test]
+fn basic_function() -> Result<()> {
+ let s = State::default();
+ let a: a = a {};
+ let v = u32::from_untyped(
+ a.call(
+ s.clone(),
+ s.create_default_context(),
+ CallLocation::native(),
+ &[],
+ )?,
+ s.clone(),
+ )?;
+
+ ensure_eq!(v, 1);
+ Ok(())
+}
+
+#[builtin]
+fn native_add(a: u32, b: u32) -> Result<u32> {
+ Ok(a + b)
+}
+
+#[test]
+fn call_from_code() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ s.settings_mut().globals.insert(
+ "nativeAdd".into(),
+ Val::Func(FuncVal::StaticBuiltin(native_add::INST)),
+ );
+
+ let v = s.evaluate_snippet_raw(
+ PathBuf::new().into(),
+ "
+ assert nativeAdd(1, 2) == 3;
+ assert nativeAdd(100, 200) == 300;
+ null
+ "
+ .into(),
+ )?;
+ ensure_val_eq!(s.clone(), v, Val::Null);
+ Ok(())
+}
+
+#[builtin(fields(
+ a: u32
+))]
+fn curried_add(this: &curried_add, b: u32) -> Result<u32> {
+ Ok(this.a + b)
+}
+
+#[builtin]
+fn curry_add(a: u32) -> Result<FuncVal> {
+ Ok(FuncVal::Builtin(Cc::new(TraceBox(Box::new(curried_add {
+ a,
+ })))))
+}
+
+#[test]
+fn nonstatic_builtin() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ s.settings_mut().globals.insert(
+ "curryAdd".into(),
+ Val::Func(FuncVal::StaticBuiltin(curry_add::INST)),
+ );
+
+ let v = s.evaluate_snippet_raw(
+ PathBuf::new().into(),
+ "
+ local a = curryAdd(1);
+ local b = curryAdd(4);
+
+ assert a(2) == 3;
+ assert a(200) == 201;
+
+ assert b(2) == 6;
+ assert b(200) == 204;
+ null
+ "
+ .into(),
+ )?;
+ ensure_val_eq!(s.clone(), v, Val::Null);
+ Ok(())
+}
crates/jrsonnet-evaluator/tests/common.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/tests/common.rs
@@ -0,0 +1,25 @@
+#[macro_export]
+macro_rules! ensure_eq {
+ ($a:expr, $b:expr $(,)?) => {{
+ if $a != $b {
+ ::jrsonnet_evaluator::throw_runtime!(
+ "assertion failed: a != b\na={:#?}\nb={:#?}",
+ $a,
+ $b,
+ )
+ }
+ }};
+}
+
+#[macro_export]
+macro_rules! ensure_val_eq {
+ ($s:expr, $a:expr, $b:expr) => {{
+ if !::jrsonnet_evaluator::val::equals($s.clone(), &$a.clone(), &$b.clone())? {
+ ::jrsonnet_evaluator::throw_runtime!(
+ "assertion failed: a != b\na={:#?}\nb={:#?}",
+ $a.to_json($s.clone(), 2)?,
+ $b.to_json($s.clone(), 2)?,
+ )
+ }
+ }};
+}
crates/jrsonnet-evaluator/tests/typed_obj.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/tests/typed_obj.rs
@@ -0,0 +1,194 @@
+mod common;
+
+use std::{fmt::Debug, path::PathBuf};
+
+use jrsonnet_evaluator::{error::Result, typed::Typed, State};
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct A {
+ a: u32,
+ b: u16,
+}
+
+fn test_roundtrip<T: Typed + PartialEq + Debug + Clone>(value: T, s: State) -> Result<()> {
+ let untyped = T::into_untyped(value.clone(), s.clone())?;
+ let value2 = T::from_untyped(untyped.clone(), s.clone())?;
+ ensure_eq!(value, value2);
+ let untyped2 = T::into_untyped(value2, s.clone())?;
+ ensure_val_eq!(s, untyped, untyped2);
+
+ Ok(())
+}
+
+#[test]
+fn simple_object() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let a = A::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, b: 2}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(a, A { a: 1, b: 2 });
+ test_roundtrip(a.clone(), s.clone())?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct B {
+ a: u32,
+ #[typed(rename = "c")]
+ b: u16,
+}
+
+#[test]
+fn renamed_field() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let b = B::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, c: 2}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(b, B { a: 1, b: 2 });
+ ensure_eq!(
+ &B::into_untyped(b.clone(), s.clone())?.to_string(s.clone())? as &str,
+ "{a: 1, c: 2}",
+ );
+ test_roundtrip(b.clone(), s.clone())?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct ObjectKind {
+ #[typed(rename = "apiVersion")]
+ api_version: String,
+ #[typed(rename = "kind")]
+ kind: String,
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct Object {
+ #[typed(flatten)]
+ kind: ObjectKind,
+ b: u16,
+}
+
+#[test]
+fn flattened_object() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let obj = Object::from_untyped(
+ s.evaluate_snippet_raw(
+ PathBuf::new().into(),
+ "{apiVersion: 'ver', kind: 'kind', b: 2}".into(),
+ )?,
+ s.clone(),
+ )?;
+ ensure_eq!(
+ obj,
+ Object {
+ kind: ObjectKind {
+ api_version: "ver".into(),
+ kind: "kind".into(),
+ },
+ b: 2
+ }
+ );
+ ensure_eq!(
+ &Object::into_untyped(obj.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"apiVersion": "ver", "b": 2, "kind": "kind"}"#,
+ );
+ test_roundtrip(obj.clone(), s.clone())?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct C {
+ a: Option<u32>,
+ b: u16,
+}
+
+#[test]
+fn optional_field_some() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let c = C::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, b: 2}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(c, C { a: Some(1), b: 2 });
+ ensure_eq!(
+ &C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"a": 1, "b": 2}"#,
+ );
+ test_roundtrip(c.clone(), s.clone())?;
+ Ok(())
+}
+
+#[test]
+fn optional_field_none() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let c = C::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(c, C { a: None, b: 2 });
+ ensure_eq!(
+ &C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"b": 2}"#,
+ );
+ test_roundtrip(c.clone(), s.clone())?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct D {
+ #[typed(flatten(ok))]
+ e: Option<E>,
+ b: u16,
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct E {
+ v: u32,
+}
+
+#[test]
+fn flatten_optional_some() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let d = D::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2, v:1}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(
+ d,
+ D {
+ e: Some(E { v: 1 }),
+ b: 2
+ }
+ );
+ ensure_eq!(
+ &D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"b": 2, "v": 1}"#,
+ );
+ test_roundtrip(d.clone(), s.clone())?;
+ Ok(())
+}
+
+#[test]
+fn flatten_optional_none() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let d = D::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2, v: '1'}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(d, D { e: None, b: 2 });
+ ensure_eq!(
+ &D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"b": 2}"#,
+ );
+ test_roundtrip(d.clone(), s.clone())?;
+ Ok(())
+}
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth1use 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 // It should have only on angle-bracketed param ("<String>"):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 // This argument must be a type: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 // ident: Ident,127 },128 Lazy {129 is_option: bool,130 name: String,131 },132 State,133 Location,134 This,135}136137impl ArgInfo {138 fn parse(arg: &FnArg) -> Result<Self> {139 let arg = match arg {140 FnArg::Receiver(_) => unreachable!(),141 FnArg::Typed(a) => a,142 };143 let ident = match &arg.pat as &Pat {144 Pat::Ident(i) => i.ident.clone(),145 _ => return Err(Error::new(arg.pat.span(), "arg should be plain identifier")),146 };147 let ty = &arg.ty;148 if type_is_path(ty, "State").is_some() {149 return Ok(Self::State);150 } else if type_is_path(ty, "CallLocation").is_some() {151 return Ok(Self::Location);152 } else if type_is_path(ty, "Self").is_some() {153 return Ok(Self::This);154 } else if type_is_path(ty, "LazyVal").is_some() {155 return Ok(Self::Lazy {156 is_option: false,157 name: ident.to_string(),158 });159 }160161 let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {162 if type_is_path(ty, "LazyVal").is_some() {163 return Ok(Self::Lazy {164 is_option: true,165 name: ident.to_string(),166 });167 }168169 (true, Box::new(ty.clone()))170 } else {171 (false, ty.clone())172 };173174 let cfg_attrs = arg175 .attrs176 .iter()177 .filter(|a| a.path.is_ident("cfg"))178 .cloned()179 .collect();180181 Ok(Self::Normal {182 ty,183 is_option,184 name: ident.to_string(),185 cfg_attrs,186 })187 }188}189190#[proc_macro_attribute]191pub fn builtin(192 attr: proc_macro::TokenStream,193 item: proc_macro::TokenStream,194) -> proc_macro::TokenStream {195 let attr = parse_macro_input!(attr as BuiltinAttrs);196 let item: ItemFn = parse_macro_input!(item);197198 match builtin_inner(attr, item) {199 Ok(v) => v.into(),200 Err(e) => e.into_compile_error().into(),201 }202}203204fn builtin_inner(attr: BuiltinAttrs, fun: ItemFn) -> syn::Result<TokenStream> {205 let result = match fun.sig.output {206 ReturnType::Default => {207 return Err(Error::new(208 fun.sig.span(),209 "builtin should return something",210 ))211 }212 ReturnType::Type(_, ref ty) => ty.clone(),213 };214 let result_inner = if let Some(args) = type_is_path(&result, "Result") {215 let generic_arg = match args {216 PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),217 _ => return Err(Error::new(args.span(), "missing result generic")),218 };219 // This argument must be a type:220 match generic_arg {221 GenericArgument::Type(ty) => ty,222 _ => {223 return Err(Error::new(224 generic_arg.span(),225 "option generic should be a type",226 ))227 }228 }229 } else {230 return Err(Error::new(result.span(), "return value should be result"));231 };232233 let args = fun234 .sig235 .inputs236 .iter()237 .map(ArgInfo::parse)238 .collect::<Result<Vec<_>>>()?;239240 let params_desc = args.iter().flat_map(|a| match a {241 ArgInfo::Normal {242 is_option,243 name,244 cfg_attrs,245 ..246 } => Some(quote! {247 #(#cfg_attrs)*248 BuiltinParam {249 name: std::borrow::Cow::Borrowed(#name),250 has_default: #is_option,251 },252 }),253 ArgInfo::Lazy { is_option, name } => Some(quote! {254 BuiltinParam {255 name: std::borrow::Cow::Borrowed(#name),256 has_default: #is_option,257 },258 }),259 ArgInfo::State => None,260 ArgInfo::Location => None,261 ArgInfo::This => None,262 });263264 let pass = args.iter().map(|a| match a {265 ArgInfo::Normal {266 ty,267 is_option,268 name,269 cfg_attrs,270 } => {271 let eval = quote! {s.push_description(272 || format!("argument <{}> evaluation", #name),273 || <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()),274 )?};275 let value = if *is_option {276 quote! {if let Some(value) = parsed.get(#name) {277 Some(#eval)278 } else {279 None280 },}281 } else {282 quote! {{283 let value = parsed.get(#name).expect("args shape is checked");284 #eval285 },}286 };287 quote! {288 #(#cfg_attrs)*289 #value290 }291 }292 ArgInfo::Lazy { is_option, name } => {293 if *is_option {294 quote! {if let Some(value) = parsed.get(#name) {295 Some(value.clone())296 } else {297 None298 }}299 } else {300 quote! {301 parsed.get(#name).expect("args shape is correct").clone(),302 }303 }304 }305 ArgInfo::State => quote! {s.clone(),},306 ArgInfo::Location => quote! {location,},307 ArgInfo::This => quote! {self,},308 });309310 let fields = attr.fields.iter().map(|field| {311 let name = &field.name;312 let ty = &field.ty;313 quote! {314 pub #name: #ty,315 }316 });317318 let name = &fun.sig.ident;319 let vis = &fun.vis;320 let static_ext = if attr.fields.is_empty() {321 quote! {322 impl #name {323 pub const INST: &'static dyn StaticBuiltin = &#name {};324 }325 impl StaticBuiltin for #name {}326 }327 } else {328 quote! {}329 };330 let static_derive_copy = if attr.fields.is_empty() {331 quote! {, Copy}332 } else {333 quote! {}334 };335336 Ok(quote! {337 #fun338 #[doc(hidden)]339 #[allow(non_camel_case_types)]340 #[derive(Clone, gcmodule::Trace #static_derive_copy)]341 #vis struct #name {342 #(#fields)*343 }344 const _: () = {345 use ::jrsonnet_evaluator::{346 State,347 function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},348 error::Result, Context,349 parser::ExprLocation,350 };351 const PARAMS: &'static [BuiltinParam] = &[352 #(#params_desc)*353 ];354355 #static_ext356 impl Builtin for #name357 where358 Self: 'static359 {360 fn name(&self) -> &str {361 stringify!(#name)362 }363 fn params(&self) -> &[BuiltinParam] {364 PARAMS365 }366 fn call(&self, s: State, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {367 let parsed = parse_builtin_call(s.clone(), ctx, &PARAMS, args, false)?;368369 let result: #result = #name(#(#pass)*);370 let result = result?;371 <#result_inner>::into_untyped(result, s)372 }373 }374 };375 })376}377378#[derive(Default)]379struct TypedAttr {380 rename: Option<String>,381 flatten: bool,382}383impl Parse for TypedAttr {384 fn parse(input: ParseStream) -> syn::Result<Self> {385 let mut out = Self::default();386 loop {387 let lookahead = input.lookahead1();388 if lookahead.peek(kw::rename) {389 input.parse::<kw::rename>()?;390 input.parse::<Token![=]>()?;391 let name = input.parse::<LitStr>()?;392 if out.rename.is_some() {393 return Err(Error::new(394 name.span(),395 "rename attribute may only be specified once",396 ));397 }398 out.rename = Some(name.value());399 } else if lookahead.peek(kw::flatten) {400 input.parse::<kw::flatten>()?;401 out.flatten = true;402 } else if input.is_empty() {403 break;404 } else {405 return Err(lookahead.error());406 }407 if input.peek(Token![,]) {408 input.parse::<Token![,]>()?;409 } else {410 break;411 }412 }413 // input.parse::<kw::rename>()?;414 // input.parse::<Token![=]>()?;415 // let rename = input.parse::<LitStr>()?.value();416 Ok(out)417 }418}419420struct TypedField<'f>(&'f syn::Field, TypedAttr);421impl<'f> TypedField<'f> {422 fn try_new(field: &'f syn::Field) -> Result<Self> {423 let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();424 if field.ident.is_none() {425 return Err(Error::new(426 field.span(),427 "this field should appear in output object, but it has no visible name",428 ));429 }430 Ok(Self(field, attr))431 }432 fn ident(&self) -> Ident {433 self.0434 .ident435 .clone()436 .expect("constructor disallows fields without name")437 }438 /// None if this field is flattened in jsonnet output439 fn name(&self) -> Option<String> {440 if self.1.flatten {441 return None;442 }443 Some(444 self.1445 .rename446 .clone()447 .unwrap_or_else(|| self.ident().to_string()),448 )449 }450451 fn expand_field(&self) -> Option<TokenStream> {452 if self.is_option() {453 return None;454 }455 let name = self.name()?;456 let ty = &self.0.ty;457 Some(quote! {458 (#name, <#ty>::TYPE)459 })460 }461 fn expand_parse(&self) -> TokenStream {462 let ident = self.ident();463 let ty = &self.0.ty;464 if self.1.flatten {465 // optional flatten is handled in same way as serde466 return if self.is_option() {467 quote! {468 #ident: <#ty>::parse(&obj).ok(),469 }470 } else {471 quote! {472 #ident: <#ty>::parse(&obj)?,473 }474 };475 };476477 let name = self.name().unwrap();478 let value = if let Some(ty) = self.as_option() {479 quote! {480 if let Some(value) = obj.get(#name.into())? {481 Some(<#ty>::try_from(vakue)?)482 } else {483 None484 }485 }486 } else {487 quote! {488 <#ty>::try_from(obj.get(#name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?)?489 }490 };491492 quote! {493 #ident: #value,494 }495 }496 fn expand_serialize(&self) -> TokenStream {497 let ident = self.ident();498 if let Some(name) = self.name() {499 if self.is_option() {500 quote! {501 if let Some(value) = self.#ident {502 out.member(#name.into()).value(value.try_into()?)?;503 }504 }505 } else {506 quote! {507 out.member(#name.into()).value(self.#ident.try_into()?)?;508 }509 }510 } else if self.is_option() {511 quote! {512 if let Some(value) = self.#ident {513 value.serialize(out)?;514 }515 }516 } else {517 quote! {518 self.#ident.serialize(out)?;519 }520 }521 }522523 fn as_option(&self) -> Option<&Type> {524 extract_type_from_option(&self.0.ty).unwrap()525 }526 fn is_option(&self) -> bool {527 self.as_option().is_some()528 }529}530531#[proc_macro_derive(Typed, attributes(typed))]532pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {533 let input = parse_macro_input!(item as DeriveInput);534535 match derive_typed_inner(input) {536 Ok(v) => v.into(),537 Err(e) => e.to_compile_error().into(),538 }539}540541fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {542 let data = match &input.data {543 syn::Data::Struct(s) => s,544 _ => return Err(Error::new(input.span(), "only structs supported")),545 };546547 let ident = &input.ident;548 let fields = data549 .fields550 .iter()551 .map(TypedField::try_new)552 .collect::<Result<Vec<_>>>()?;553554 let typed = {555 let fields = fields556 .iter()557 .flat_map(TypedField::expand_field)558 .collect::<Vec<_>>();559 let len = fields.len();560 quote! {561 const ITEMS: [(&'static str, &'static ComplexValType); #len] = [562 #(#fields,)*563 ];564 impl Typed for #ident {565 const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&ITEMS);566567 fn from_untyped(value: Val, s: State) -> Result<Self> {568 let obj = value.as_obj().expect("shape is correct");569 Self::parse(&obj)570 }571572 fn into_untyped(value: Self, s: State) -> Result<Val> {573 let mut out = ObjValueBuilder::new();574 value.serialize(&mut out)?;575 Ok(Val::Obj(out.build()))576 }577578 }579 }580 };581582 let fields_parse = fields.iter().map(TypedField::expand_parse);583 let fields_serialize = fields.iter().map(TypedField::expand_serialize);584585 Ok(quote! {586 const _: () = {587 use ::jrsonnet_evaluator::{588 typed::{ComplexValType, Typed, TypedObj, CheckType},589 Val,590 error::{LocError, Error},591 ObjValueBuilder, ObjValue,592 };593594 #typed595596 impl #ident {597 fn serialize(self, out: &mut ObjValueBuilder) -> Result<(), LocError> {598 #(#fields_serialize)*599600 Ok(())601 }602 fn parse(obj: &ObjValue) -> Result<Self, LocError> {603 Ok(Self {604 #(#fields_parse)*605 })606 }607 }608 };609 })610}