--- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -63,6 +63,8 @@ #[error("field name should be string, got {0}")] FieldMustBeStringGot(ValType), + #[error("duplicate field name: {0}")] + DuplicateFieldName(IStr), #[error("attempted to index array with string {0}")] AttemptedIndexAnArrayWithString(IStr), --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -298,7 +298,7 @@ context_creator: context_creator.clone(), value: value.clone(), name, - }))); + })))?; } Member::Field(FieldMember { name, @@ -341,7 +341,7 @@ value: value.clone(), params: params.clone(), name, - }))); + })))?; } Member::BindStmt(_) => {} Member::AssertStmt(stmt) => { @@ -424,7 +424,7 @@ .bindable(TraceBox(Box::new(ObjCompBinding { context: ctx, value: obj.value.clone(), - }))); + })))?; } v => throw!(FieldMustBeStringGot(v.value_type())), } --- a/crates/jrsonnet-evaluator/src/integrations/serde.rs +++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs @@ -68,7 +68,7 @@ Value::Object(o) => { let mut builder = ObjValueBuilder::with_capacity(o.len()); for (k, v) in o { - builder.member((k as &str).into()).value(v.try_into()?); + builder.member((k as &str).into()).value(v.try_into()?)?; } Self::Obj(builder.build()) } --- a/crates/jrsonnet-evaluator/src/obj.rs +++ b/crates/jrsonnet-evaluator/src/obj.rs @@ -1,6 +1,11 @@ +use crate::function::CallLocation; use crate::gc::{GcHashMap, GcHashSet, TraceBox}; use crate::operator::evaluate_add_op; -use crate::{cc_ptr_eq, weak_ptr_eq, weak_raw, Bindable, LazyBinding, LazyVal, Result, Val}; +use crate::push_frame; +use crate::{ + cc_ptr_eq, error::Error::*, throw, weak_ptr_eq, weak_raw, Bindable, LazyBinding, LazyVal, + Result, Val, +}; use gcmodule::{Cc, Trace, Weak}; use jrsonnet_interner::IStr; use jrsonnet_parser::{ExprLocation, Visibility}; @@ -373,22 +378,29 @@ self.location = Some(location); self } - pub fn value(self, value: Val) -> &'v mut ObjValueBuilder { + pub fn value(self, value: Val) -> Result<()> { self.binding(LazyBinding::Bound(LazyVal::new_resolved(value))) } - pub fn bindable(self, bindable: TraceBox) -> &'v mut ObjValueBuilder { + pub fn bindable(self, bindable: TraceBox) -> Result<()> { self.binding(LazyBinding::Bindable(Cc::new(bindable))) } - pub fn binding(self, binding: LazyBinding) -> &'v mut ObjValueBuilder { - self.value.map.insert( - self.name, + pub fn binding(self, binding: LazyBinding) -> Result<()> { + let old = self.value.map.insert( + self.name.clone(), ObjMember { add: self.add, visibility: self.visibility, invoke: binding, - location: self.location, + location: self.location.clone(), }, ); - self.value + if old.is_some() { + push_frame( + CallLocation(self.location.as_ref()), + || format!("field <{}> initializtion", self.name.clone()), + || throw!(DuplicateFieldName(self.name.clone())), + )? + } + Ok(()) } } --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -459,12 +459,12 @@ if self.is_option() { quote! { if let Some(value) = self.#ident { - out.member(#name.into()).value(value.try_into()?); + out.member(#name.into()).value(value.try_into()?)?; } } } else { quote! { - out.member(#name.into()).value(self.#ident.try_into()?); + out.member(#name.into()).value(self.#ident.try_into()?)?; } } } else if self.is_option() {