difftreelog
refactor no need to store multiple assertions for oopobject
in: master
2 files changed
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth1use std::rc::Rc;23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_parser::{6 ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, FieldMember, FieldName,7 ForSpecData, IfSpecData, ImportKind, LiteralType, ObjBody, ObjMembers, ParamsDesc, Spanned,8};9use jrsonnet_types::ValType;10use rustc_hash::FxHashMap;1112use self::destructure::destruct;13use crate::{14 arr::ArrValue,15 bail,16 destructure::evaluate_dest,17 error::{suggest_object_fields, ErrorKind::*},18 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},19 function::{CallLocation, FuncDesc, FuncVal},20 gc::WithCapacityExt as _,21 in_frame,22 typed::Typed,23 val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk},24 with_state, Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,25 ResultExt, SupThis, Unbound, Val,26};27pub mod destructure;28pub mod operator;2930// This is the amount of bytes that need to be left on the stack before increasing the size.31// It must be at least as large as the stack required by any code that does not call32// `ensure_sufficient_stack`.33const RED_ZONE: usize = 100 * 1024; // 100k3435// Only the first stack that is pushed, grows exponentially (2^n * STACK_PER_RECURSION) from then36// on. This flag has performance relevant characteristics. Don't set it too high.37const STACK_PER_RECURSION: usize = 1024 * 1024; // 1MB3839/// Grows the stack on demand to prevent stack overflow. Call this in strategic locations40/// to "break up" recursive calls. E.g. almost any call to `visit_expr` or equivalent can benefit41/// from this.42///43/// Should not be sprinkled around carelessly, as it causes a little bit of overhead.44#[inline]45pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {46 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)47}4849pub fn evaluate_trivial(expr: &Spanned<Expr>) -> Option<Val> {50 fn is_trivial(expr: &Spanned<Expr>) -> bool {51 match &**expr {52 Expr::Str(_)53 | Expr::Num(_)54 | Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true,55 Expr::Arr(a) => a.iter().all(is_trivial),56 _ => false,57 }58 }59 Some(match &**expr {60 Expr::Str(s) => Val::string(s.clone()),61 Expr::Num(n) => {62 Val::Num(NumValue::new(*n).expect("parser will not allow non-finite values"))63 }64 Expr::Literal(LiteralType::False) => Val::Bool(false),65 Expr::Literal(LiteralType::True) => Val::Bool(true),66 Expr::Literal(LiteralType::Null) => Val::Null,67 Expr::Arr(n) => {68 if n.iter().any(|e| !is_trivial(e)) {69 return None;70 }71 Val::Arr(ArrValue::eager(72 n.iter()73 .map(evaluate_trivial)74 .map(|e| e.expect("checked trivial"))75 .collect(),76 ))77 }78 _ => return None,79 })80}8182pub fn evaluate_method(83 ctx: Context,84 name: IStr,85 params: ParamsDesc,86 body: Rc<Spanned<Expr>>,87) -> Val {88 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {89 name,90 ctx,91 params,92 body,93 })))94}9596pub fn evaluate_field_name(ctx: Context, field_name: &FieldName) -> Result<Option<IStr>> {97 Ok(match field_name {98 FieldName::Fixed(n) => Some(n.clone()),99 FieldName::Dyn(expr) => in_frame(100 CallLocation::new(&expr.span()),101 || "evaluating field name".to_string(),102 || {103 let value = evaluate(ctx, expr)?;104 if matches!(value, Val::Null) {105 Ok(None)106 } else {107 Ok(Some(IStr::from_untyped(value)?))108 }109 },110 )?,111 })112}113114pub fn evaluate_comp(115 ctx: Context,116 specs: &[CompSpec],117 callback: &mut impl FnMut(Context) -> Result<()>,118) -> Result<()> {119 match specs.first() {120 None => callback(ctx)?,121 Some(CompSpec::IfSpec(IfSpecData(cond))) => {122 if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {123 evaluate_comp(ctx, &specs[1..], callback)?;124 }125 }126 Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(ctx.clone(), expr)? {127 Val::Arr(list) => {128 for item in list.iter_lazy() {129 let fctx = Pending::new();130 let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());131 destruct(var, item, fctx.clone(), &mut new_bindings)?;132 let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);133134 evaluate_comp(ctx, &specs[1..], callback)?;135 }136 }137 #[cfg(feature = "exp-object-iteration")]138 Val::Obj(obj) => {139 for field in obj.fields(140 // TODO: Should there be ability to preserve iteration order?141 #[cfg(feature = "exp-preserve-order")]142 false,143 ) {144 let fctx = Pending::new();145 let mut new_bindings = FxHashMap::with_capacity(var.capacity_hint());146 let obj = obj.clone();147 let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![148 Thunk::evaluated(Val::string(field.clone())),149 Thunk!(move || obj.get(field).transpose().expect(150 "field exists, as field name was obtained from object.fields()",151 )),152 ])));153 destruct(var, value, fctx.clone(), &mut new_bindings)?;154 let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);155156 evaluate_comp(ctx, &specs[1..], callback)?;157 }158 }159 _ => bail!(InComprehensionCanOnlyIterateOverArray),160 },161 }162 Ok(())163}164165trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}166impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}167168fn evaluate_object_locals(169 fctx: Context,170 locals: Rc<Vec<BindSpec>>,171) -> impl CloneableUnbound<Context> {172 #[derive(Trace, Clone)]173 struct UnboundLocals {174 fctx: Context,175 locals: Rc<Vec<BindSpec>>,176 }177 impl Unbound for UnboundLocals {178 type Bound = Context;179180 fn bind(&self, sup_this: SupThis) -> Result<Context> {181 let fctx = Context::new_future();182 let mut new_bindings =183 FxHashMap::with_capacity(self.locals.iter().map(BindSpec::capacity_hint).sum());184 for b in self.locals.iter() {185 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;186 }187188 let ctx = self.fctx.clone();189190 let ctx = ctx191 .extend_bindings_sup_this(new_bindings, sup_this)192 .into_future(fctx);193194 Ok(ctx)195 }196 }197198 UnboundLocals { fctx, locals }199}200201pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(202 builder: &mut ObjValueBuilder,203 ctx: Context,204 uctx: B,205 field: &FieldMember,206) -> Result<()> {207 let name = evaluate_field_name(ctx, &field.name)?;208 let Some(name) = name else {209 return Ok(());210 };211212 match field {213 FieldMember {214 plus,215 params: None,216 visibility,217 value,218 ..219 } => {220 #[derive(Trace)]221 struct UnboundValue<B: Trace> {222 uctx: B,223 value: Rc<Spanned<Expr>>,224 name: IStr,225 }226 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {227 type Bound = Val;228 fn bind(&self, sup_this: SupThis) -> Result<Val> {229 evaluate_named(self.uctx.bind(sup_this)?, &self.value, self.name.clone())230 }231 }232233 builder234 .field(name.clone())235 .with_add(*plus)236 .with_visibility(*visibility)237 .with_location(value.span())238 .bindable(UnboundValue {239 uctx,240 value: value.clone(),241 name,242 })?;243 }244 FieldMember {245 params: Some(params),246 visibility,247 value,248 ..249 } => {250 #[derive(Trace)]251 struct UnboundMethod<B: Trace> {252 uctx: B,253 value: Rc<Spanned<Expr>>,254 params: ParamsDesc,255 name: IStr,256 }257 impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {258 type Bound = Val;259 fn bind(&self, sup_this: SupThis) -> Result<Val> {260 Ok(evaluate_method(261 self.uctx.bind(sup_this)?,262 self.name.clone(),263 self.params.clone(),264 self.value.clone(),265 ))266 }267 }268269 builder270 .field(name.clone())271 .with_visibility(*visibility)272 .with_location(value.span())273 .bindable(UnboundMethod {274 uctx,275 value: value.clone(),276 params: params.clone(),277 name,278 })?;279 }280 }281 Ok(())282}283284#[allow(clippy::too_many_lines)]285pub fn evaluate_member_list_object(ctx: Context, members: &ObjMembers) -> Result<ObjValue> {286 let mut builder = ObjValueBuilder::new();287 let locals = members.locals.clone();288289 // We have single context for all fields, so we can cache binds290 let uctx = CachedUnbound::new(evaluate_object_locals(ctx.clone(), locals));291292 for field in &members.fields {293 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), &field)?;294 }295296 if !members.asserts.is_empty() {297 #[derive(Trace)]298 struct ObjectAssert<B: Trace> {299 uctx: B,300 asserts: Rc<Vec<AssertStmt>>,301 }302 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {303 fn run(&self, sup_this: SupThis) -> Result<()> {304 let ctx = self.uctx.bind(sup_this)?;305 for assert in &*self.asserts {306 evaluate_assert(ctx.clone(), &assert)?;307 }308 Ok(())309 }310 }311 builder.assert(ObjectAssert {312 uctx: uctx.clone(),313 asserts: members.asserts.clone(),314 });315 }316317 Ok(builder.build())318}319320pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result<ObjValue> {321 Ok(match object {322 ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,323 ObjBody::ObjComp(obj) => {324 let mut builder = ObjValueBuilder::new();325 let locals = obj.locals.clone();326 evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {327 let uctx = evaluate_object_locals(ctx.clone(), locals.clone());328329 evaluate_field_member(&mut builder, ctx, uctx, &obj.field)330 })?;331332 builder.build()333 }334 })335}336337pub fn evaluate_apply(338 ctx: Context,339 value: &Spanned<Expr>,340 args: &ArgsDesc,341 loc: CallLocation<'_>,342 tailstrict: bool,343) -> Result<Val> {344 let value = evaluate(ctx.clone(), value)?;345 Ok(match value {346 Val::Func(f) => {347 let body = || f.evaluate(ctx, loc, args, tailstrict);348 if tailstrict {349 body()?350 } else {351 in_frame(loc, || format!("function <{}> call", f.name()), body)?352 }353 }354 v => bail!(OnlyFunctionsCanBeCalledGot(v.value_type())),355 })356}357358pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {359 let value = &assertion.0;360 let msg = &assertion.1;361 let assertion_result = in_frame(362 CallLocation::new(&value.span()),363 || "assertion condition".to_owned(),364 || bool::from_untyped(evaluate(ctx.clone(), value)?),365 )?;366 if !assertion_result {367 in_frame(368 CallLocation::new(&value.span()),369 || "assertion failure".to_owned(),370 || {371 if let Some(msg) = msg {372 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));373 }374 bail!(AssertionFailed(Val::Null.to_string()?));375 },376 )?;377 }378 Ok(())379}380381pub fn evaluate_named(ctx: Context, expr: &Spanned<Expr>, name: IStr) -> Result<Val> {382 use Expr::*;383 Ok(match &**expr {384 Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),385 _ => evaluate(ctx, expr)?,386 })387}388389#[allow(clippy::too_many_lines)]390pub fn evaluate(ctx: Context, expr: &Spanned<Expr>) -> Result<Val> {391 use Expr::*;392393 if let Some(trivial) = evaluate_trivial(expr) {394 return Ok(trivial);395 }396 let loc = expr.span();397 Ok(match &**expr {398 Literal(LiteralType::This) => Val::Obj(ctx.try_this()?),399 Literal(LiteralType::Super) => Val::Obj(ctx.try_sup_this()?.standalone_super()?),400 Literal(LiteralType::Dollar) => Val::Obj(ctx.try_dollar()?),401 Literal(LiteralType::True) => Val::Bool(true),402 Literal(LiteralType::False) => Val::Bool(false),403 Literal(LiteralType::Null) => Val::Null,404 Str(v) => Val::string(v.clone()),405 Num(v) => Val::try_num(*v)?,406 // I have tried to remove special behavior from super by implementing standalone-super407 // expresion, but looks like this case still needs special treatment.408 //409 // Note that other jsonnet implementations will fail on `if value in (super)` expression,410 // because the standalone super literal is not supported, that is because in other411 // implementations `in super` treated differently from `in smth_else`.412 BinaryOp(bin)413 if matches!(&*bin.rhs, Expr::Literal(LiteralType::Super))414 && bin.op == BinaryOpType::In =>415 {416 let sup_this = ctx.try_sup_this()?;417 // In jsonnet, "field" in e is eager, LHS expression is always executed regardless of super existence.418 // In jrsonnet, however, this wasn't true, this was kept here for compatibility.419 if !sup_this.has_super() {420 return Ok(Val::Bool(false));421 }422 let field = evaluate(ctx, &bin.lhs)?;423 Val::Bool(sup_this.field_in_super(field.to_string()?))424 }425 BinaryOp(bin) => evaluate_binary_op_special(ctx, &bin.lhs, bin.op, &bin.rhs)?,426 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,427 Var(name) => in_frame(428 CallLocation::new(&loc),429 || format!("local <{name}> access"),430 || ctx.binding(name.clone())?.evaluate(),431 )?,432 Index { indexable, parts } => ensure_sufficient_stack(|| {433 let mut parts = parts.iter();434 let mut indexable = if matches!(&***indexable, Expr::Literal(LiteralType::Super)) {435 let part = parts.next().expect("at least part should exist");436 // sup_this existence check might also be skipped here for null-coalesce...437 // But I believe this might cause errors.438 let sup_this = ctx.try_sup_this()?;439 if !sup_this.has_super() {440 #[cfg(feature = "exp-null-coaelse")]441 if part.null_coaelse {442 return Ok(Val::Null);443 }444 bail!(NoSuperFound)445 }446 let name = evaluate(ctx.clone(), &part.value)?;447448 let Val::Str(name) = name else {449 bail!(ValueIndexMustBeTypeGot(450 ValType::Obj,451 ValType::Str,452 name.value_type(),453 ))454 };455456 let name = name.into_flat();457 match sup_this458 .get_super(name.clone())459 .with_description_src(&part.value, || format!("field <{name}> access"))?460 {461 Some(v) => v,462 #[cfg(feature = "exp-null-coaelse")]463 None if part.null_coaelse => return Ok(Val::Null),464 None => {465 let suggestions = suggest_object_fields(466 &sup_this.standalone_super().expect("super exists"),467 name.clone(),468 );469470 bail!(NoSuchField(name, suggestions))471 }472 }473 } else {474 evaluate(ctx.clone(), indexable)?475 };476477 for part in parts {478 indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {479 (Val::Obj(v), Val::Str(key)) => match v480 .get(key.clone().into_flat())481 .with_description_src(&part.value, || format!("field <{key}> access"))?482 {483 Some(v) => v,484 #[cfg(feature = "exp-null-coaelse")]485 None if part.null_coaelse => return Ok(Val::Null),486 None => {487 let suggestions = suggest_object_fields(&v, key.clone().into_flat());488489 return Err(Error::from(NoSuchField(490 key.clone().into_flat(),491 suggestions,492 )))493 .with_description_src(&part.value, || format!("field <{key}> access"));494 }495 },496 (Val::Obj(_), n) => bail!(ValueIndexMustBeTypeGot(497 ValType::Obj,498 ValType::Str,499 n.value_type(),500 )),501 (Val::Arr(v), Val::Num(n)) => {502 let n = n.get();503 if n.fract() > f64::EPSILON {504 bail!(FractionalIndex)505 }506 if n < 0.0 {507 bail!(ArrayBoundsError(n as isize, v.len()));508 }509 v.get(n as usize)?510 .ok_or_else(|| ArrayBoundsError(n as isize, v.len()))?511 }512 (Val::Arr(_), Val::Str(n)) => {513 bail!(AttemptedIndexAnArrayWithString(n.into_flat()))514 }515 (Val::Arr(_), n) => bail!(ValueIndexMustBeTypeGot(516 ValType::Arr,517 ValType::Num,518 n.value_type(),519 )),520521 (Val::Str(s), Val::Num(n)) => Val::Str({522 let n = n.get();523 if n.fract() > f64::EPSILON {524 bail!(FractionalIndex)525 }526 if n < 0.0 {527 bail!(ArrayBoundsError(n as isize, s.into_flat().chars().count()));528 }529 let v: IStr = s530 .clone()531 .into_flat()532 .chars()533 .skip(n as usize)534 .take(1)535 .collect::<String>()536 .into();537 if v.is_empty() {538 bail!(StringBoundsError(n as usize, s.into_flat().chars().count()))539 }540 StrValue::Flat(v)541 }),542 (Val::Str(_), n) => bail!(ValueIndexMustBeTypeGot(543 ValType::Str,544 ValType::Num,545 n.value_type(),546 )),547 #[cfg(feature = "exp-null-coaelse")]548 (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),549 (v, _) => bail!(CantIndexInto(v.value_type())),550 };551 }552 Ok(indexable)553 })?,554 LocalExpr(bindings, returned) => {555 let mut new_bindings: FxHashMap<IStr, Thunk<Val>> =556 FxHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum());557 let fctx = Context::new_future();558 for b in bindings {559 evaluate_dest(b, fctx.clone(), &mut new_bindings)?;560 }561 let ctx = ctx.extend_bindings(new_bindings).into_future(fctx);562 evaluate(ctx, &returned.clone())?563 }564 Arr(items) => {565 if items.is_empty() {566 Val::Arr(ArrValue::empty())567 } else {568 Val::Arr(ArrValue::expr(ctx, items.clone()))569 }570 }571 ArrComp(expr, comp_specs) => {572 let mut out = Vec::new();573 evaluate_comp(ctx, comp_specs, &mut |ctx| {574 let expr = expr.clone();575 out.push(Thunk!(move || evaluate(ctx, &expr)));576 Ok(())577 })?;578 Val::Arr(ArrValue::lazy(out))579 }580 Obj(body) => Val::Obj(evaluate_object(ctx, body)?),581 ObjExtend(a, b) => evaluate_add_op(582 &evaluate(ctx.clone(), a)?,583 &Val::Obj(evaluate_object(ctx, b)?),584 )?,585 Apply(value, args, tailstrict) => ensure_sufficient_stack(|| {586 evaluate_apply(ctx, value, args, CallLocation::new(&loc), *tailstrict)587 })?,588 Function(params, body) => {589 evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())590 }591 AssertExpr(assert) => {592 evaluate_assert(ctx.clone(), &assert.assert)?;593 evaluate(ctx, &assert.rest)?594 }595 ErrorStmt(e) => in_frame(596 CallLocation::new(&loc),597 || "error statement".to_owned(),598 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),599 )?,600 IfElse (if_else)601 // {602 // cond,603 // cond_then,604 // cond_else,605 // }606 => {607 if in_frame(608 CallLocation::new(&loc),609 || "if condition".to_owned(),610 || bool::from_untyped(evaluate(ctx.clone(), &if_else.cond.0)?),611 )? {612 evaluate(ctx, &if_else.cond_then)?613 } else {614 match &if_else.cond_else {615 Some(v) => evaluate(ctx, v)?,616 None => Val::Null,617 }618 }619 }620 Slice(slice) => {621 fn parse_idx<T: Typed>(622 loc: CallLocation<'_>,623 ctx: Context,624 expr: Option<&Spanned<Expr>>,625 desc: &'static str,626 ) -> Result<Option<T>> {627 if let Some(value) = expr {628 Ok(in_frame(629 loc,630 || format!("slice {desc}"),631 || <Option<T>>::from_untyped(evaluate(ctx, value)?),632 )?)633 } else {634 Ok(None)635 }636 }637638 let indexable = evaluate(ctx.clone(), &slice.value)?;639 let loc = CallLocation::new(&loc);640641 let start = parse_idx(loc, ctx.clone(), slice.slice.start.as_ref(), "start")?;642 let end = parse_idx(loc, ctx.clone(), slice.slice.end.as_ref(), "end")?;643 let step = parse_idx(loc, ctx, slice.slice.step.as_ref(), "step")?;644645 IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?646 }647 Import(kind, path) => {648 let Expr::Str(path) = &***path else {649 bail!("computed imports are not supported")650 };651 let tmp = loc.clone().0;652 with_state(|s| {653 let resolved_path = s.resolve_from(tmp.source_path(), path)?;654 Ok(match kind {655 ImportKind::Normal => in_frame(656 CallLocation::new(&loc),657 || format!("import {:?}", path.clone()),658 || s.import_resolved(resolved_path),659 )?,660 ImportKind::Str => Val::string(s.import_resolved_str(resolved_path)?),661 ImportKind::Bin => {662 Val::Arr(ArrValue::bytes(s.import_resolved_bin(resolved_path)?))663 }664 }) as Result<Val>665 })?666 }667 })668}crates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -151,7 +151,7 @@
#[derive(Trace, Default)]
#[trace(tracking(force))]
pub struct OopObject {
- assertions: Vec<CcObjectAssertion>,
+ assertion: Option<CcObjectAssertion>,
this_entries: FxHashMap<IStr, ObjMember>,
}
impl Debug for OopObject {
@@ -163,7 +163,7 @@
}
impl OopObject {
fn is_empty(&self) -> bool {
- self.assertions.is_empty() && self.this_entries.is_empty()
+ self.assertion.is_none() && self.this_entries.is_empty()
}
}
@@ -979,11 +979,11 @@
impl OopObject {
pub fn new(
this_entries: FxHashMap<IStr, ObjMember>,
- assertions: Vec<CcObjectAssertion>,
+ assertion: Option<CcObjectAssertion>,
) -> Self {
Self {
this_entries,
- assertions,
+ assertion,
}
}
}
@@ -1042,10 +1042,7 @@
}
fn run_assertions_core(&self, sup_this: SupThis) -> Result<()> {
- if self.assertions.is_empty() {
- return Ok(());
- }
- for assertion in self.assertions.iter() {
+ if let Some(assertion) = &self.assertion {
assertion.0.run(sup_this.clone())?;
}
Ok(())
@@ -1067,7 +1064,7 @@
Self {
sup: vec![],
new: OopObject {
- assertions: vec![],
+ assertion: None,
this_entries: FxHashMap::with_capacity(capacity),
},
next_field_index: FieldIndex::default(),
@@ -1075,10 +1072,6 @@
}
pub fn reserve_cores(&mut self, capacity: usize) -> &mut Self {
self.sup.reserve_exact(capacity);
- self
- }
- pub fn reserve_asserts(&mut self, capacity: usize) -> &mut Self {
- self.new.assertions.reserve_exact(capacity);
self
}
pub fn with_super(&mut self, super_obj: ObjValue) -> &mut Self {
@@ -1087,7 +1080,11 @@
}
pub fn assert(&mut self, assertion: impl ObjectAssertion + 'static) -> &mut Self {
- self.new.assertions.push(CcObjectAssertion::new(assertion));
+ assert!(
+ self.new.assertion.is_none(),
+ "one OopObject can only have one assertion"
+ );
+ self.new.assertion = Some(CcObjectAssertion::new(assertion));
self
}
pub fn field(&mut self, name: impl Into<IStr>) -> ObjMemberBuilder<ValueBuilder<'_>> {