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.rsdiffbeforeafterboth1use std::rc::Rc;23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_ir::ImportKind;6use jrsonnet_types::ValType;78use self::{9 compspec::{evaluate_arr_comp, evaluate_obj_comp},10 destructure::{build_b_thunk_uno, evaluate_local_expr, evaluate_locals_unbound},11 operator::evaluate_binary_op_special,12};13use crate::{14 Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Result, ResultExt as _, SupThis,15 Unbound, Val,16 analyze::{17 ClosureShape, LArgsDesc, LAssertStmt, LExpr, LFieldMember, LFieldName, LFunction,18 LIndexPart, LObjAsserts, LObjBody, LObjMembers, LSlot,19 },20 arr::ArrValue,21 bail, error,22 error::{ErrorKind::*, suggest_object_fields},23 evaluate::{destructure::fill_letrec_binds, operator::evaluate_unary_op},24 function::{CallLocation, FuncDesc, FuncVal, prepared::PreparedFuncVal},25 in_frame,26 typed::FromUntyped as _,27 val::{CachedUnbound, Thunk},28 with_state,29};3031pub mod compspec;32pub mod destructure;33pub mod operator;3435// This is the amount of bytes that need to be left on the stack before increasing the size.36// It must be at least as large as the stack required by any code that does not call37// `ensure_sufficient_stack`.38const RED_ZONE: usize = 100 * 1024;3940// Only the first stack that is pushed, grows exponentially (2^n * STACK_PER_RECURSION) from then41// on. This flag has performance relevant characteristics. Don't set it too high.42const STACK_PER_RECURSION: usize = 1024 * 1024;4344/// Grows the stack on demand to prevent stack overflow. Call this in strategic locations45/// to "break up" recursive calls. E.g. almost any call to `visit_expr` or equivalent can benefit46/// from this.47///48/// Should not be sprinkled around carelessly, as it causes a little bit of overhead.49#[inline]50pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {51 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)52}5354pub fn evaluate_trivial(expr: &LExpr) -> Option<Val> {55 // TODO: Eager trivial array56 Some(match expr {57 LExpr::Str(s) => Val::string(s.clone()),58 LExpr::Num(n) => Val::Num(*n),59 LExpr::Bool(false) => Val::Bool(false),60 LExpr::Bool(true) => Val::Bool(true),61 LExpr::Null => Val::Null,62 _ => return None,63 })64}6566pub fn evaluate_method(ctx: Context, name: IStr, func: &Rc<LFunction>) -> Val {67 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {68 name,69 body_captures: ctx.pack_captures_sup_this(&func.body_shape),70 func: func.clone(),71 })))72}7374pub fn evaluate_field_name(ctx: Context, field_name: &LFieldName) -> Result<Option<IStr>> {75 Ok(match field_name {76 LFieldName::Fixed(n) => Some(n.clone()),77 LFieldName::Dyn(expr) => in_frame(78 // TODO: Spanned<LFieldName>79 CallLocation::native(),80 || "evaluating field name".to_string(),81 || {82 let v = evaluate(ctx.clone(), expr)?;83 Ok(if matches!(v, Val::Null) {84 None85 } else {86 Some(IStr::from_untyped(v)?)87 })88 },89 )?,90 })91}9293pub fn evaluate_thunk(ctx: Context, expr: Rc<LExpr>, tailstrict: bool) -> Result<Thunk<Val>> {94 match &*expr {95 LExpr::Slot(LSlot::Local(i)) => return Ok(ctx.local(*i)),96 LExpr::Slot(LSlot::Capture(i)) => return Ok(ctx.capture(*i)),97 _ => {98 if let Some(v) = evaluate_trivial(&expr) {99 return Ok(Thunk::evaluated(v));100 }101 }102 }103 Ok(if tailstrict {104 Thunk::evaluated(evaluate(ctx, &expr)?)105 } else {106 Thunk!(move || { evaluate(ctx, &expr) })107 })108}109110mod names {111 use crate::names;112113 names! {114 anonymous: "anonymous",115 }116}117118pub fn evaluate(ctx: Context, expr: &LExpr) -> Result<Val> {119 Ok(match expr {120 LExpr::Null => Val::Null,121 LExpr::Bool(b) => Val::Bool(*b),122 LExpr::Str(s) => Val::string(s.clone()),123 LExpr::Num(n) => Val::Num(*n),124 LExpr::Slot(slot) => ctx.slot(*slot).evaluate()?,125 LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),126 LExpr::Arr { shape, items } => Val::Arr(ArrValue::expr(ctx, shape, items.clone())),127 LExpr::UnaryOp(op, value) => {128 let value = evaluate(ctx, value)?;129 evaluate_unary_op(*op, &value)?130 }131 LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,132 LExpr::LocalExpr(local_expr) => evaluate_local_expr(ctx, local_expr)?,133 LExpr::IfElse {134 cond,135 cond_then,136 cond_else,137 } => {138 let cond_val = evaluate(ctx.clone(), cond)?;139 let Val::Bool(b) = cond_val else {140 bail!(TypeMismatch(141 "if condition",142 vec![ValType::Bool],143 cond_val.value_type()144 ))145 };146 if b {147 evaluate(ctx, cond_then)?148 } else if let Some(e) = cond_else {149 evaluate(ctx, e)?150 } else {151 Val::Null152 }153 }154 LExpr::Error(s, e) => in_frame(155 CallLocation::new(s),156 || "error statement".to_owned(),157 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),158 )?,159 LExpr::AssertExpr { assert, rest } => {160 evaluate_assert(ctx.clone(), assert)?;161 evaluate(ctx, rest)?162 }163164 LExpr::Function(func) => evaluate_method(165 ctx,166 func.name.clone().unwrap_or_else(names::anonymous),167 func,168 ),169 LExpr::IdentityFunction => Val::Func(FuncVal::identity()),170 LExpr::Apply {171 applicable,172 args,173 tailstrict,174 } => evaluate_apply(175 ctx,176 applicable,177 args,178 CallLocation::new(&args.span),179 *tailstrict,180 )?,181 LExpr::Index { indexable, parts } => evaluate_index(ctx, indexable, parts)?,182 LExpr::Obj(body) => evaluate_obj_body(None, ctx, body)?,183 LExpr::ObjExtend(lhs, body) => {184 let lhs_val = evaluate(ctx.clone(), lhs)?;185 let Val::Obj(lhs_obj) = lhs_val else {186 bail!(TypeMismatch(187 "object extend lhs",188 vec![ValType::Obj],189 lhs_val.value_type(),190 ))191 };192 evaluate_obj_body(Some(lhs_obj), ctx, body)?193 }194 LExpr::ArrComp(comp) => evaluate_arr_comp(ctx, comp)?,195 LExpr::Slice(slice) => {196 use crate::typed::BoundedUsize;197 let val = evaluate(ctx.clone(), &slice.value)?;198 let indexable = val.into_indexable()?;199 let start = slice200 .start201 .as_ref()202 .map(|e| evaluate(ctx.clone(), e))203 .transpose()?204 .map(|v| -> Result<i32> {205 v.as_num()206 .ok_or_else(|| {207 TypeMismatch("slice start", vec![ValType::Num], v.value_type()).into()208 })209 .map(|n| n as i32)210 })211 .transpose()?;212 let end = slice213 .end214 .as_ref()215 .map(|e| evaluate(ctx.clone(), e))216 .transpose()?217 .map(|v| -> Result<i32> {218 v.as_num()219 .ok_or_else(|| {220 TypeMismatch("slice end", vec![ValType::Num], v.value_type()).into()221 })222 .map(|n| n as i32)223 })224 .transpose()?;225 let step = slice226 .step227 .as_ref()228 .map(|e| evaluate(ctx, e))229 .transpose()?230 .map(|v| -> Result<BoundedUsize<1, { i32::MAX as usize }>> {231 let n = v.as_num().ok_or_else(|| -> crate::Error {232 TypeMismatch("slice step", vec![ValType::Num], v.value_type()).into()233 })?;234 BoundedUsize::new(n as usize).ok_or_else(|| error!("slice step must be >= 1"))235 })236 .transpose()?;237 Val::from(indexable.slice(start, end, step)?)238 }239 LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super()?),240 LExpr::Import {241 kind,242 kind_span,243 path,244 } => with_state(|state| {245 let resolved = state.resolve_from(kind_span.0.source_path(), &path.clone())?;246 Ok::<_, Error>(match kind.value {247 ImportKind::Normal => in_frame(248 CallLocation::new(&kind.span),249 || "import".to_string(),250 || state.import_resolved(resolved),251 )?,252 ImportKind::Str => Val::string(state.import_resolved_str(resolved)?),253 ImportKind::Bin => Val::arr(state.import_resolved_bin(resolved)?),254 })255 })?,256 })257}258259fn evaluate_apply(260 ctx: Context,261 applicable: &LExpr,262 args: &LArgsDesc,263 loc: CallLocation<'_>,264 tailstrict: bool,265) -> Result<Val> {266 let func_val = evaluate(ctx.clone(), applicable)?;267 let Val::Func(func) = func_val else {268 bail!(OnlyFunctionsCanBeCalledGot(func_val.value_type()))269 };270271 if func.is_identity() && args.names.is_empty() && args.unnamed.len() == 1 {272 return evaluate_thunk(ctx, args.unnamed[0].clone(), tailstrict)?.evaluate();273 }274275 let name = func.name();276277 if args.names.is_empty() && args.unnamed.len() == 1 && func.params().len() == 1 {278 use crate::function::prepared::PreparedCall;279 let prepared_inline = PreparedCall::empty();280 let arg = evaluate_thunk(ctx, args.unnamed[0].clone(), tailstrict)?;281 let arg_slice = std::slice::from_ref(&arg);282 return in_frame(283 loc,284 || format!("function <{name}> call"),285 || {286 func.evaluate_prepared(287 &prepared_inline,288 CallLocation::native(),289 arg_slice,290 &[],291 tailstrict,292 )293 },294 );295 }296297 let unnamed = args298 .unnamed299 .iter()300 .cloned()301 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))302 .collect::<Result<Vec<_>>>()?;303304 // Fast path: positional-only multi-arg call fully covering the305 // params, no defaults.306 if args.names.is_empty() && unnamed.len() == func.params().len() {307 use crate::function::prepared::PreparedCall;308 let prepared_inline = PreparedCall::empty();309 return in_frame(310 loc,311 || format!("function <{name}> call"),312 || {313 func.evaluate_prepared(314 &prepared_inline,315 CallLocation::native(),316 &unnamed,317 &[],318 tailstrict,319 )320 },321 );322 }323324 let named = args325 .values326 .iter()327 .cloned()328 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))329 .collect::<Result<Vec<_>>>()?;330 let prepare = PreparedFuncVal::new(func, unnamed.len(), &args.names)331 .with_description_src(loc, || format!("function <{name}> preparation"))?;332 in_frame(333 loc,334 || format!("function <{name}> call"),335 || prepare.call(CallLocation::native(), &unnamed, &named),336 )337}338339fn 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 name343 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 parts357 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 };368369 for part in parts {370 value = index_val(ctx.clone(), CallLocation::new(&part.span), value, part)?;371 }372 Ok(value)373}374375fn index_val(ctx: Context, loc: CallLocation<'_>, value: Val, part: &LIndexPart) -> Result<Val> {376 let key_val = evaluate(ctx, &part.value)?;377 Ok(match (&value, &key_val) {378 (Val::Obj(obj), Val::Str(key)) => {379 let field = key.clone().into_flat();380 if let Some(v) = obj381 .get(field.clone())382 .with_description_src(loc, || format!("field <{field}> access"))?383 {384 v385 } else {386 bail!(NoSuchField(387 field.clone(),388 suggest_object_fields(obj, field)389 ))390 }391 }392 (Val::Arr(arr), Val::Num(idx)) => {393 let n = idx.get();394 if n.fract() > f64::EPSILON {395 bail!(FractionalIndex)396 }397 if n < 0.0 {398 bail!(ArrayBoundsError(399 n as isize, // truncation is fine for error display400 arr.len()401 ));402 }403 #[expect(404 clippy::cast_possible_truncation,405 clippy::cast_sign_loss,406 reason = "n is checked positive"407 )]408 let i = n as u32;409 arr.get(i)410 .with_description_src(loc, || format!("element <{i}> access"))?411 .ok_or_else(|| ArrayBoundsError(i as isize, arr.len()))?412 }413 (Val::Str(s), Val::Num(idx)) => {414 let n = idx.get();415 if n.fract() > f64::EPSILON {416 bail!(FractionalIndex)417 }418 let flat = s.clone().into_flat();419 if n < 0.0 {420 bail!(ArrayBoundsError(421 n as isize, // truncation is fine for error display422 flat.chars().count() as u32423 ));424 }425 #[expect(426 clippy::cast_possible_truncation,427 clippy::cast_sign_loss,428 reason = "n is checked positive, overflow will truncate as expected"429 )]430 let i = n as usize;431 let Some(char) = flat.chars().nth(i) else {432 bail!(StringBoundsError(i, flat.chars().count()))433 };434 Val::string(char)435 }436 _ => bail!(ValueIndexMustBeTypeGot(437 value.value_type(),438 ValType::Str,439 key_val.value_type()440 )),441 })442}443444fn evaluate_obj_body(super_obj: Option<ObjValue>, ctx: Context, body: &LObjBody) -> Result<Val> {445 match body {446 LObjBody::MemberList(members) => evaluate_obj_members(super_obj, ctx, members),447 LObjBody::ObjComp(comp) => evaluate_obj_comp(super_obj, ctx, comp),448 }449}450451pub fn evaluate_field_member_unbound<B: Unbound<Bound = Context> + Clone>(452 builder: &mut ObjValueBuilder,453 ctx: Context,454 uctx: B,455 field: &LFieldMember,456) -> Result<()> {457 #[derive(Trace)]458 struct UnboundValue<B: Trace> {459 uctx: B,460 value: Rc<(ClosureShape, LExpr)>,461 name: IStr,462 }463 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {464 type Bound = Val;465 fn bind(&self, sup_this: SupThis) -> Result<Val> {466 let a_ctx = self.uctx.bind(sup_this)?;467 let b_ctx = Context::enter_using(&a_ctx, &self.value.0);468 evaluate(b_ctx, &self.value.1)469 }470 }471472 let LFieldMember {473 name,474 plus,475 visibility,476 value,477 } = field;478 let Some(name) = evaluate_field_name(ctx, name)? else {479 return Ok(());480 };481482 builder483 .field(name.clone())484 .with_add(*plus)485 .with_visibility(*visibility)486 .bindable(UnboundValue {487 uctx,488 value: value.clone(),489 name,490 })491}492pub fn evaluate_field_member_static(493 builder: &mut ObjValueBuilder,494 field_ctx: Context,495 value_ctx: Context,496 field: &LFieldMember,497) -> Result<()> {498 let LFieldMember {499 name,500 plus,501 visibility,502 value,503 } = field;504 let Some(name) = evaluate_field_name(field_ctx, name)? else {505 return Ok(());506 };507508 let thunk = build_b_thunk_uno(&value_ctx, value.clone());509 builder510 .field(name)511 .with_add(*plus)512 .with_visibility(*visibility)513 .try_thunk(thunk)?;514 Ok(())515}516517fn evaluate_obj_members(518 super_obj: Option<ObjValue>,519 ctx: Context,520 members: &LObjMembers,521) -> Result<Val> {522 let mut builder = ObjValueBuilder::with_capacity(members.fields.len());523 if let Some(sup) = super_obj {524 builder.with_super(sup);525 }526527 let needs_unbound = members.this.is_some() || members.uses_super;528529 if needs_unbound {530 let uctx = CachedUnbound::new(evaluate_locals_unbound(531 &ctx,532 &members.frame_shape,533 members.this,534 members.locals.clone(),535 ));536 for field in &members.fields {537 evaluate_field_member_unbound(&mut builder, ctx.clone(), uctx.clone(), field)?;538 }539 if let Some(asserts_block) = &members.asserts {540 builder.assert(evaluate_object_assertions_unbound(541 uctx,542 asserts_block.clone(),543 ));544 }545 } else {546 let a_ctx = ctx547 .pack_captures_sup_this(&members.frame_shape)548 .enter(|fill, ctx| {549 fill_letrec_binds(fill, &ctx, &members.locals);550 });551 for field in &members.fields {552 evaluate_field_member_static(&mut builder, ctx.clone(), a_ctx.clone(), field)?;553 }554 if let Some(asserts_block) = &members.asserts {555 builder.assert(evaluate_object_assertions_static(556 a_ctx,557 asserts_block.clone(),558 ));559 }560 }561562 Ok(Val::Obj(builder.build()))563}564565pub fn evaluate_assert(ctx: Context, assertion: &LAssertStmt) -> Result<()> {566 let LAssertStmt { cond, message } = assertion;567 let assertion_result = in_frame(568 CallLocation::new(&cond.span),569 || "assertion condition".to_owned(),570 || bool::from_untyped(evaluate(ctx.clone(), cond)?),571 )?;572 if !assertion_result {573 in_frame(574 CallLocation::new(&cond.span),575 || "assertion failure".to_owned(),576 || {577 if let Some(msg) = message {578 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));579 }580 bail!(AssertionFailed(Val::Null.to_string()?));581 },582 )?;583 }584 Ok(())585}586587fn evaluate_object_assertions_unbound<B: Unbound<Bound = Context>>(588 uctx: B,589 asserts: Rc<LObjAsserts>,590) -> impl ObjectAssertion {591 #[derive(Trace)]592 struct ObjectAssert<B: Trace> {593 uctx: B,594 asserts: Rc<LObjAsserts>,595 }596 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {597 fn run(&self, sup_this: SupThis) -> Result<()> {598 let a_ctx = self.uctx.bind(sup_this)?;599 let assert_env = Context::enter_using(&a_ctx, &self.asserts.shape);600 for assert in &self.asserts.asserts {601 evaluate_assert(assert_env.clone(), assert)?;602 }603 Ok(())604 }605 }606 ObjectAssert { uctx, asserts }607}608fn evaluate_object_assertions_static(609 a_ctx: Context,610 asserts: Rc<LObjAsserts>,611) -> impl ObjectAssertion {612 #[derive(Trace)]613 struct ObjectAssert {614 assert_env: Context,615 asserts: Rc<LObjAsserts>,616 }617 impl ObjectAssertion for ObjectAssert {618 fn run(&self, _sup_this: SupThis) -> Result<()> {619 for assert in &self.asserts.asserts {620 evaluate_assert(self.assert_env.clone(), assert)?;621 }622 Ok(())623 }624 }625 let assert_env = Context::enter_using(&a_ctx, &asserts.shape);626 ObjectAssert {627 assert_env,628 asserts,629 }630}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.goldendiffbeforeafterboth--- a/tests/cpp_test_suite_golden_override/error.field_not_exist.jsonnet.golden
+++ b/tests/cpp_test_suite_golden_override/error.field_not_exist.jsonnet.golden
@@ -1 +1,2 @@
-no such field: y
\ No newline at end of file
+no such field: y
+ error.field_not_exist.jsonnet:17:10-10: field <y> access
\ No newline at end of file
tests/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