git.delta.rocks / jrsonnet / refs/commits / 6b79e9c7b1b0

difftreelog

feat bring back null coaelse

xprpsquvYaroslav Bolyukin2026-05-06parent: #a2182e0.patch.diff
in: master

5 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/compspec.rsdiffbeforeafterboth
1use std::rc::Rc;1use std::rc::Rc;
22
3#[cfg(feature = "exp-object-iteration")]
4use jrsonnet_interner::IStr;
3use jrsonnet_types::ValType;5use jrsonnet_types::ValType;
46
5use super::{7use super::{
6 destructure::{destruct, evaluate_locals_unbound, fill_letrec_binds},8 destructure::{destruct, evaluate_locals_unbound, fill_letrec_binds},
7 evaluate_field_member_static, evaluate_field_member_unbound,9 evaluate_field_member_static, evaluate_field_member_unbound,
8};10};
9#[cfg(feature = "exp-object-iteration")]
10use jrsonnet_interner::IStr;
11
12use crate::{11use crate::{
13 Context, ObjValue, ObjValueBuilder, Result, Thunk, Val,12 Context, ObjValue, ObjValueBuilder, Result, Thunk, Val,
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
236 .transpose()?;236 .transpose()?;
237 Val::from(indexable.slice(start, end, step)?)237 Val::from(indexable.slice(start, end, step)?)
238 }238 }
239 LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super()?),239 LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super().ok_or(NoSuperFound)?),
240 LExpr::Import {240 LExpr::Import {
241 kind,241 kind,
242 kind_span,242 kind_span,
336 )336 )
337}337}
338
339fn evaluate_index(ctx: Context, indexable: &LExpr, parts: &[LIndexPart]) -> Result<Val> {
340 let mut value = if matches!(indexable, LExpr::Super) {
341 let sup_this = ctx.try_sup_this()?;
342 // First part must be evaluated to get the super field name
343 if parts.is_empty() {
344 bail!(RuntimeError("super requires an index".into()))
345 }
346 let key_val = evaluate(ctx.clone(), &parts[0].value)?;
347 let Val::Str(key) = &key_val else {
348 bail!(ValueIndexMustBeTypeGot(
349 ValType::Obj,
350 ValType::Str,
351 key_val.value_type(),
352 ))
353 };
354 let field = key.clone().into_flat();
355 if let Some(v) = sup_this.get_super(field.clone())? {
356 // Continue with remaining parts
357 let mut value = v;
358 for part in &parts[1..] {
359 value = index_val(ctx.clone(), CallLocation::new(&part.span), value, part)?;
360 }
361 return Ok(value);
362 }
363 let suggestions = suggest_object_fields(sup_this.this(), field.clone());
364 bail!(NoSuchField(field, suggestions))
365 } else {
366 evaluate(ctx.clone(), indexable)?
367 };
368
369 for part in parts {
370 value = index_val(ctx.clone(), CallLocation::new(&part.span), value, part)?;
371 }
372 Ok(value)
373}
374338
375fn index_val(ctx: Context, loc: CallLocation<'_>, value: Val, part: &LIndexPart) -> Result<Val> {339fn evaluate_index(ctx: Context, indexable: &LExpr, parts: &[LIndexPart]) -> Result<Val> {
340 let mut parts = parts.iter();
341 let mut indexable = if matches!(indexable, LExpr::Super) {
342 let part = parts.next().expect("at least part should exist");
343 // sup_this existence check might also be skipped here for null-coalesce...
344 // But I believe this might cause errors.
345 let sup_this = ctx.try_sup_this()?;
346
347 if !sup_this.has_super() {
348 #[cfg(feature = "exp-null-coaelse")]
349 if part.null_coaelse {
350 return Ok(Val::Null);
351 }
352 bail!(NoSuperFound);
353 }
354 let name = evaluate(ctx.clone(), &part.value)?;
355
356 let Val::Str(name) = name else {
357 bail!(ValueIndexMustBeTypeGot(
358 ValType::Obj,
359 ValType::Str,
360 name.value_type(),
361 ))
362 };
363
364 let name = name.into_flat();
365 match sup_this
366 .get_super(name.clone())
367 .with_description_src(&part.span, || format!("super field <{name}> access"))?
368 {
369 Some(v) => v,
370 #[cfg(feature = "exp-null-coaelse")]
371 None if part.null_coaelse => return Ok(Val::Null),
372 None => {
373 let suggestions = suggest_object_fields(
374 &sup_this.standalone_super().expect("super exists"),
375 name.clone(),
376 );
377 bail!(NoSuchField(name, suggestions))
378 }
379 }
380 } else {
381 evaluate(ctx.clone(), indexable)?
382 };
383
384 for part in parts {
385 let ctx = ctx.clone();
386 let loc = CallLocation::new(&part.span);
387 let value = indexable;
376 let key_val = evaluate(ctx, &part.value)?;388 let key_val = evaluate(ctx, &part.value)?;
377 Ok(match (&value, &key_val) {389 indexable = match (&value, &key_val) {
378 (Val::Obj(obj), Val::Str(key)) => {390 (Val::Obj(obj), Val::Str(key)) => {
379 let field = key.clone().into_flat();391 let key = key.clone().into_flat();
380 if let Some(v) = obj392 match obj
381 .get(field.clone())393 .get(key.clone())
382 .with_description_src(loc, || format!("field <{field}> access"))?394 .with_description_src(loc, || format!("field <{key}> access"))?
383 {395 {
384 v396 Some(v) => v,
385 } else {397 #[cfg(feature = "exp-null-coaelse")]
386 bail!(NoSuchField(398 None if part.null_coaelse => return Ok(Val::Null),
387 field.clone(),399 None => {
388 suggest_object_fields(obj, field)400 return Err(Error::from(NoSuchField(
389 ))401 key.clone(),
390 }402 suggest_object_fields(obj, key.clone()),
403 )))
404 .with_description_src(loc, || format!("field <{key}> access"));
405 }
406 }
391 }407 }
392 (Val::Arr(arr), Val::Num(idx)) => {408 (Val::Arr(arr), Val::Num(idx)) => {
393 let n = idx.get();409 let n = idx.get();
433 };449 };
434 Val::string(char)450 Val::string(char)
435 }451 }
452 #[cfg(feature = "exp-null-coaelse")]
453 (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),
436 _ => bail!(ValueIndexMustBeTypeGot(454 _ => bail!(ValueIndexMustBeTypeGot(
437 value.value_type(),455 value.value_type(),
438 ValType::Str,456 ValType::Str,
439 key_val.value_type()457 key_val.value_type()
440 )),458 )),
441 })459 };
460 }
461 Ok(indexable)
442}462}
443463
444fn evaluate_obj_body(super_obj: Option<ObjValue>, ctx: Context, body: &LObjBody) -> Result<Val> {464fn evaluate_obj_body(super_obj: Option<ObjValue>, ctx: Context, body: &LObjBody) -> Result<Val> {
modifiedcrates/jrsonnet-evaluator/src/obj/mod.rsdiffbeforeafterboth
430 /// Exists when super appears outside of `super.field`/`"field" in super` expressions430 /// Exists when super appears outside of `super.field`/`"field" in super` expressions
431 /// Exclusive to jrsonnet.431 /// Exclusive to jrsonnet.
432 ///432 ///
433 /// Might return `NoSuperFound` error.433 /// Returns None if no `super` found
434 pub fn standalone_super(&self) -> Result<ObjValue> {434 pub fn standalone_super(&self) -> Option<ObjValue> {
435 if !self.sup.super_exists() {435 if !self.sup.super_exists() {
436 bail!(NoSuperFound)436 return None;
437 }437 }
438 let mut out = ObjValue::builder();438 let mut out = ObjValue::builder();
439 out.extend_with_core(StandaloneSuperCore {439 out.extend_with_core(StandaloneSuperCore {
440 sup: self.sup,440 sup: self.sup,
441 this: self.this.clone(),441 this: self.this.clone(),
442 });442 });
443 Ok(out.build())443 Some(out.build())
444 }444 }
445 pub fn this(&self) -> &ObjValue {445 pub fn this(&self) -> &ObjValue {
446 &self.this446 &self.this
modifiedtests/cpp_test_suite_golden_override/error.field_not_exist.jsonnet.goldendiffbeforeafterboth
1no such field: y1no such field: y
2 error.field_not_exist.jsonnet:17:10-10: field <y> access
modifiedtests/go_testdata_golden_override/builtinObjectRemoveKey_super_assert.jsonnet.goldendiffbeforeafterboth
1no such field: x1no such field: x
2 builtinObjectRemoveKey_super_assert.jsonnet:2:15-15: field <x> access
2 builtinObjectRemoveKey_super_assert.jsonnet:2:10-15: assertion condition3 builtinObjectRemoveKey_super_assert.jsonnet:2:10-15: assertion condition