From 0f5ef44c0f7005ef970785f0c854fef2435c7e3c Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Wed, 26 Jul 2023 17:21:20 +0000 Subject: [PATCH] feat: experimental null-coaelse operator --- --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -24,6 +24,8 @@ exp-object-iteration = [] # Bigint type exp-bigint = ["num-bigint", "jrsonnet-types/exp-bigint"] +# obj?.field, obj?.['field'] +exp-null-coaelse = ["jrsonnet-parser/exp-null-coaelse"] # Improves performance, and implements some useful things using nightly-only features nightly = ["hashbrown/nightly"] --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -446,7 +446,12 @@ || format!("variable <{name}> access"), || ctx.binding(name.clone())?.evaluate(), )?, - Index(LocExpr(v, _), index) if matches!(&**v, Expr::Literal(LiteralType::Super)) => { + Index { + indexable: LocExpr(v, _), + index, + #[cfg(feature = "exp-null-coaelse")] + null_coaelse, + } if matches!(&**v, Expr::Literal(LiteralType::Super)) => { let name = evaluate(ctx.clone(), index)?; let Val::Str(name) = name else { throw!(ValueIndexMustBeTypeGot( @@ -455,17 +460,37 @@ name.value_type(), )) }; - ctx.super_obj() - .expect("no super found") - .get_for(name.into_flat(), ctx.this().expect("no this found").clone())? - .expect("value not found") + let Some(super_obj) = ctx.super_obj() else { + throw!(NoSuperFound) + }; + let this = ctx + .this() + .expect("no this found, while super present, should not happen"); + let key = name.into_flat(); + match super_obj.get_for(key.clone(), this.clone())? { + Some(v) => v, + #[cfg(feature = "exp-null-coaelse")] + None if *null_coaelse => Val::Null, + None => { + let suggestions = suggest_object_fields(super_obj, key.clone()); + + throw!(NoSuchField(key, suggestions)) + } + } } - Index(value, index) => match (evaluate(ctx.clone(), value)?, evaluate(ctx, index)?) { + Index { + indexable, + index, + #[cfg(feature = "exp-null-coaelse")] + null_coaelse, + } => match (evaluate(ctx.clone(), indexable)?, evaluate(ctx, index)?) { (Val::Obj(v), Val::Str(key)) => State::push( CallLocation::new(loc), || format!("field <{key}> access"), || match v.get(key.clone().into_flat()) { Ok(Some(v)) => Ok(v), + #[cfg(feature = "exp-null-coaelse")] + Ok(None) if *null_coaelse => Ok(Val::Null), Ok(None) => { let suggestions = suggest_object_fields(&v, key.clone().into_flat()); @@ -514,6 +539,8 @@ ValType::Num, n.value_type(), )), + #[cfg(feature = "exp-null-coaelse")] + (Val::Null, _) if *null_coaelse => Val::Null, (v, _) => throw!(CantIndexInto(v.value_type())), }, --- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs @@ -88,6 +88,10 @@ Ok(match (evaluate(ctx.clone(), a)?, op, b) { (Bool(true), Or, _o) => Val::Bool(true), (Bool(false), And, _o) => Val::Bool(false), + #[cfg(feature = "exp-null-coaelse")] + (Null, NullCoaelse, eb) => evaluate(ctx, eb)?, + #[cfg(feature = "exp-null-coaelse")] + (a, NullCoaelse, _o) => a, (a, op, eb) => evaluate_binary_op_normal(&a, op, &evaluate(ctx, eb)?)?, }) } --- a/crates/jrsonnet-parser/Cargo.toml +++ b/crates/jrsonnet-parser/Cargo.toml @@ -10,6 +10,7 @@ [features] default = [] exp-destruct = [] +exp-null-coaelse = [] # Implement serialization of AST using structdump # # Structdump generates code, which exactly replicated passed AST --- a/crates/jrsonnet-parser/src/expr.rs +++ b/crates/jrsonnet-parser/src/expr.rs @@ -122,6 +122,8 @@ And, Or, + #[cfg(feature = "exp-null-coaelse")] + NullCoaelse, // Equialent to std.objectHasEx(a, b, true) In, @@ -153,6 +155,8 @@ And => "&&", Or => "||", In => "in", + #[cfg(feature = "exp-null-coaelse")] + NullCoaelse => "??", } ) } @@ -399,8 +403,13 @@ ErrorStmt(LocExpr), /// a(b, c) Apply(LocExpr, ArgsDesc, bool), - /// a[b] - Index(LocExpr, LocExpr), + /// a[b], a.b, a?.b + Index { + indexable: LocExpr, + index: LocExpr, + #[cfg(feature = "exp-null-coaelse")] + null_coaelse: bool, + }, /// function(x) x Function(ParamsDesc, LocExpr), /// if true == false then 1 else 2 --- a/crates/jrsonnet-parser/src/lib.rs +++ b/crates/jrsonnet-parser/src/lib.rs @@ -288,7 +288,11 @@ rule unaryop(x: rule<()>) -> () = quiet!{ x() } / expected!("") - + rule ensure_null_coaelse() + = "" {? + #[cfg(not(feature = "exp-null-coaelse"))] return Err("!!!experimental null coaelscing was not enabled"); + #[cfg(feature = "exp-null-coaelse")] Ok(()) + } use BinaryOpType::*; use UnaryOpType::*; rule expr(s: &ParserSettings) -> LocExpr @@ -296,6 +300,10 @@ start:position!() v:@ end:position!() { LocExpr(Rc::new(v), ExprLocation(s.source.clone(), start as u32, end as u32)) } -- a:(@) _ binop(<"||">) _ b:@ {expr_bin!(a Or b)} + a:(@) _ binop(<"??">) _ ensure_null_coaelse() b:@ { + #[cfg(feature = "exp-null-coaelse")] return expr_bin!(a NullCoaelse b); + unreachable!("ensure_null_coaelse will fail if feature is not enabled") + } -- a:(@) _ binop(<"&&">) _ b:@ {expr_bin!(a And b)} -- @@ -329,8 +337,16 @@ unaryop(<"~">) _ b:@ {expr_un!(BitNot b)} -- a:(@) _ "[" _ e:slice_desc(s) _ "]" {Expr::Slice(a, e)} - a:(@) _ "." _ a:position!() e:id_loc(s) b:position!() {Expr::Index(a, e)} - a:(@) _ "[" _ e:expr(s) _ "]" {Expr::Index(a, e)} + indexable:(@) _ null_coaelse:("?" _ ensure_null_coaelse())? "." _ index:id_loc(s) {Expr::Index{ + indexable, index, + #[cfg(feature = "exp-null-coaelse")] + null_coaelse: null_coaelse.is_some(), + }} + indexable:(@) _ null_coaelse:("?" _ "." _ ensure_null_coaelse())? "[" _ index:expr(s) _ "]" {Expr::Index{ + indexable, index, + #[cfg(feature = "exp-null-coaelse")] + null_coaelse: null_coaelse.is_some(), + }} a:(@) _ "(" _ args:args(s) _ ")" ts:(_ keyword("tailstrict"))? {Expr::Apply(a, args, ts.is_some())} a:(@) _ "{" _ body:objinside(s) _ "}" {Expr::ObjExtend(a, body)} -- -- gitstuff