difftreelog
feat bring back null coaelse
in: master
5 files changed
crates/jrsonnet-evaluator/src/evaluate/compspec.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/compspec.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/compspec.rs
@@ -1,14 +1,13 @@
use std::rc::Rc;
+#[cfg(feature = "exp-object-iteration")]
+use jrsonnet_interner::IStr;
use jrsonnet_types::ValType;
use super::{
destructure::{destruct, evaluate_locals_unbound, fill_letrec_binds},
evaluate_field_member_static, evaluate_field_member_unbound,
};
-#[cfg(feature = "exp-object-iteration")]
-use jrsonnet_interner::IStr;
-
use crate::{
Context, ObjValue, ObjValueBuilder, Result, Thunk, Val,
analyze::{
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -236,7 +236,7 @@
.transpose()?;
Val::from(indexable.slice(start, end, step)?)
}
- LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super()?),
+ LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super().ok_or(NoSuperFound)?),
LExpr::Import {
kind,
kind_span,
@@ -337,108 +337,128 @@
}
fn evaluate_index(ctx: Context, indexable: &LExpr, parts: &[LIndexPart]) -> Result<Val> {
- let mut value = if matches!(indexable, LExpr::Super) {
+ let mut parts = parts.iter();
+ let mut indexable = if matches!(indexable, LExpr::Super) {
+ let part = parts.next().expect("at least part should exist");
+ // sup_this existence check might also be skipped here for null-coalesce...
+ // But I believe this might cause errors.
let sup_this = ctx.try_sup_this()?;
- // First part must be evaluated to get the super field name
- if parts.is_empty() {
- bail!(RuntimeError("super requires an index".into()))
+
+ if !sup_this.has_super() {
+ #[cfg(feature = "exp-null-coaelse")]
+ if part.null_coaelse {
+ return Ok(Val::Null);
+ }
+ bail!(NoSuperFound);
}
- let key_val = evaluate(ctx.clone(), &parts[0].value)?;
- let Val::Str(key) = &key_val else {
+ let name = evaluate(ctx.clone(), &part.value)?;
+
+ let Val::Str(name) = name else {
bail!(ValueIndexMustBeTypeGot(
ValType::Obj,
ValType::Str,
- key_val.value_type(),
+ name.value_type(),
))
};
- let field = key.clone().into_flat();
- if let Some(v) = sup_this.get_super(field.clone())? {
- // Continue with remaining parts
- let mut value = v;
- for part in &parts[1..] {
- value = index_val(ctx.clone(), CallLocation::new(&part.span), value, part)?;
+
+ let name = name.into_flat();
+ match sup_this
+ .get_super(name.clone())
+ .with_description_src(&part.span, || format!("super field <{name}> access"))?
+ {
+ Some(v) => v,
+ #[cfg(feature = "exp-null-coaelse")]
+ None if part.null_coaelse => return Ok(Val::Null),
+ None => {
+ let suggestions = suggest_object_fields(
+ &sup_this.standalone_super().expect("super exists"),
+ name.clone(),
+ );
+ bail!(NoSuchField(name, suggestions))
}
- return Ok(value);
}
- let suggestions = suggest_object_fields(sup_this.this(), field.clone());
- bail!(NoSuchField(field, suggestions))
} else {
evaluate(ctx.clone(), indexable)?
};
for part in parts {
- value = index_val(ctx.clone(), CallLocation::new(&part.span), value, part)?;
- }
- Ok(value)
-}
-
-fn index_val(ctx: Context, loc: CallLocation<'_>, value: Val, part: &LIndexPart) -> Result<Val> {
- let key_val = evaluate(ctx, &part.value)?;
- Ok(match (&value, &key_val) {
- (Val::Obj(obj), Val::Str(key)) => {
- let field = key.clone().into_flat();
- if let Some(v) = obj
- .get(field.clone())
- .with_description_src(loc, || format!("field <{field}> access"))?
- {
- v
- } else {
- bail!(NoSuchField(
- field.clone(),
- suggest_object_fields(obj, field)
- ))
+ let ctx = ctx.clone();
+ let loc = CallLocation::new(&part.span);
+ let value = indexable;
+ let key_val = evaluate(ctx, &part.value)?;
+ indexable = match (&value, &key_val) {
+ (Val::Obj(obj), Val::Str(key)) => {
+ let key = key.clone().into_flat();
+ match obj
+ .get(key.clone())
+ .with_description_src(loc, || format!("field <{key}> access"))?
+ {
+ Some(v) => v,
+ #[cfg(feature = "exp-null-coaelse")]
+ None if part.null_coaelse => return Ok(Val::Null),
+ None => {
+ return Err(Error::from(NoSuchField(
+ key.clone(),
+ suggest_object_fields(obj, key.clone()),
+ )))
+ .with_description_src(loc, || format!("field <{key}> access"));
+ }
+ }
}
- }
- (Val::Arr(arr), Val::Num(idx)) => {
- let n = idx.get();
- if n.fract() > f64::EPSILON {
- bail!(FractionalIndex)
+ (Val::Arr(arr), Val::Num(idx)) => {
+ let n = idx.get();
+ if n.fract() > f64::EPSILON {
+ bail!(FractionalIndex)
+ }
+ if n < 0.0 {
+ bail!(ArrayBoundsError(
+ n as isize, // truncation is fine for error display
+ arr.len()
+ ));
+ }
+ #[expect(
+ clippy::cast_possible_truncation,
+ clippy::cast_sign_loss,
+ reason = "n is checked positive"
+ )]
+ let i = n as u32;
+ arr.get(i)
+ .with_description_src(loc, || format!("element <{i}> access"))?
+ .ok_or_else(|| ArrayBoundsError(i as isize, arr.len()))?
}
- if n < 0.0 {
- bail!(ArrayBoundsError(
- n as isize, // truncation is fine for error display
- arr.len()
- ));
+ (Val::Str(s), Val::Num(idx)) => {
+ let n = idx.get();
+ if n.fract() > f64::EPSILON {
+ bail!(FractionalIndex)
+ }
+ let flat = s.clone().into_flat();
+ if n < 0.0 {
+ bail!(ArrayBoundsError(
+ n as isize, // truncation is fine for error display
+ flat.chars().count() as u32
+ ));
+ }
+ #[expect(
+ clippy::cast_possible_truncation,
+ clippy::cast_sign_loss,
+ reason = "n is checked positive, overflow will truncate as expected"
+ )]
+ let i = n as usize;
+ let Some(char) = flat.chars().nth(i) else {
+ bail!(StringBoundsError(i, flat.chars().count()))
+ };
+ Val::string(char)
}
- #[expect(
- clippy::cast_possible_truncation,
- clippy::cast_sign_loss,
- reason = "n is checked positive"
- )]
- let i = n as u32;
- arr.get(i)
- .with_description_src(loc, || format!("element <{i}> access"))?
- .ok_or_else(|| ArrayBoundsError(i as isize, arr.len()))?
- }
- (Val::Str(s), Val::Num(idx)) => {
- let n = idx.get();
- if n.fract() > f64::EPSILON {
- bail!(FractionalIndex)
- }
- let flat = s.clone().into_flat();
- if n < 0.0 {
- bail!(ArrayBoundsError(
- n as isize, // truncation is fine for error display
- flat.chars().count() as u32
- ));
- }
- #[expect(
- clippy::cast_possible_truncation,
- clippy::cast_sign_loss,
- reason = "n is checked positive, overflow will truncate as expected"
- )]
- let i = n as usize;
- let Some(char) = flat.chars().nth(i) else {
- bail!(StringBoundsError(i, flat.chars().count()))
- };
- Val::string(char)
- }
- _ => bail!(ValueIndexMustBeTypeGot(
- value.value_type(),
- ValType::Str,
- key_val.value_type()
- )),
- })
+ #[cfg(feature = "exp-null-coaelse")]
+ (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),
+ _ => bail!(ValueIndexMustBeTypeGot(
+ value.value_type(),
+ ValType::Str,
+ key_val.value_type()
+ )),
+ };
+ }
+ Ok(indexable)
}
fn evaluate_obj_body(super_obj: Option<ObjValue>, ctx: Context, body: &LObjBody) -> Result<Val> {
crates/jrsonnet-evaluator/src/obj/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/obj/mod.rs
+++ b/crates/jrsonnet-evaluator/src/obj/mod.rs
@@ -430,17 +430,17 @@
/// Exists when super appears outside of `super.field`/`"field" in super` expressions
/// Exclusive to jrsonnet.
///
- /// Might return `NoSuperFound` error.
- pub fn standalone_super(&self) -> Result<ObjValue> {
+ /// Returns None if no `super` found
+ pub fn standalone_super(&self) -> Option<ObjValue> {
if !self.sup.super_exists() {
- bail!(NoSuperFound)
+ return None;
}
let mut out = ObjValue::builder();
out.extend_with_core(StandaloneSuperCore {
sup: self.sup,
this: self.this.clone(),
});
- Ok(out.build())
+ Some(out.build())
}
pub fn this(&self) -> &ObjValue {
&self.this
tests/cpp_test_suite_golden_override/error.field_not_exist.jsonnet.goldendiffbeforeafterboth1no such field: y1no such field: y2 error.field_not_exist.jsonnet:17:10-10: field <y> accesstests/go_testdata_golden_override/builtinObjectRemoveKey_super_assert.jsonnet.goldendiffbeforeafterboth--- a/tests/go_testdata_golden_override/builtinObjectRemoveKey_super_assert.jsonnet.golden
+++ b/tests/go_testdata_golden_override/builtinObjectRemoveKey_super_assert.jsonnet.golden
@@ -1,2 +1,3 @@
no such field: x
+ builtinObjectRemoveKey_super_assert.jsonnet:2:15-15: field <x> access
builtinObjectRemoveKey_super_assert.jsonnet:2:10-15: assertion condition
\ No newline at end of file