diff --git a/sjsonnet/src/sjsonnet/Evaluator.scala b/sjsonnet/src/sjsonnet/Evaluator.scala index 5855e0f5..2b6bde7f 100644 --- a/sjsonnet/src/sjsonnet/Evaluator.scala +++ b/sjsonnet/src/sjsonnet/Evaluator.scala @@ -1,7 +1,7 @@ package sjsonnet import sjsonnet.Expr.Member.Visibility -import sjsonnet.Expr.{Error => _, _} +import sjsonnet.Expr.{Error as _, *} import ujson.Value import scala.annotation.{switch, tailrec} @@ -29,7 +29,6 @@ class Evaluator( def materialize(v: Val): Value = Materializer.apply(v) val cachedImports: collection.mutable.HashMap[Path, Val] = collection.mutable.HashMap.empty[Path, Val] - var tailstrict: Boolean = false override def visitExpr(e: Expr)(implicit scope: ValScope): Val = try { e match { @@ -202,49 +201,31 @@ class Evaluator( } } - protected def visitApply(e: Apply)(implicit scope: ValScope) = { + protected def visitApply(e: Apply)(implicit scope: ValScope): Val = { val lhs = visitExpr(e.value) + implicit val tailstrictMode: TailstrictMode = + if (e.tailstrict) TailstrictModeEnabled else TailstrictModeDisabled - if (tailstrict) { + if (e.tailstrict) { lhs.cast[Val.Func].apply(e.args.map(visitExpr(_)), e.namedNames, e.pos) - } else if (e.tailstrict) { - tailstrict = true - val res = lhs.cast[Val.Func].apply(e.args.map(visitExpr(_)), e.namedNames, e.pos) - tailstrict = false - res } else { - val args = e.args - val argsL = new Array[Lazy](args.length) - var idx = 0 - while (idx < args.length) { - argsL(idx) = visitAsLazy(args(idx)) - idx += 1 - } - lhs.cast[Val.Func].apply(argsL, e.namedNames, e.pos) + lhs.cast[Val.Func].apply(e.args.map(visitAsLazy(_)), e.namedNames, e.pos) } } protected def visitApply0(e: Apply0)(implicit scope: ValScope): Val = { val lhs = visitExpr(e.value) - if (e.tailstrict) { - tailstrict = true - val res = lhs.cast[Val.Func].apply0(e.pos) - tailstrict = false - res - } else { - lhs.cast[Val.Func].apply0(e.pos) - } + implicit val tailstrictMode: TailstrictMode = + if (e.tailstrict) TailstrictModeEnabled else TailstrictModeDisabled + lhs.cast[Val.Func].apply0(e.pos) } protected def visitApply1(e: Apply1)(implicit scope: ValScope): Val = { val lhs = visitExpr(e.value) - if (tailstrict) { + implicit val tailstrictMode: TailstrictMode = + if (e.tailstrict) TailstrictModeEnabled else TailstrictModeDisabled + if (e.tailstrict) { lhs.cast[Val.Func].apply1(visitExpr(e.a1), e.pos) - } else if (e.tailstrict) { - tailstrict = true - val res = lhs.cast[Val.Func].apply1(visitExpr(e.a1), e.pos) - tailstrict = false - res } else { val l1 = visitAsLazy(e.a1) lhs.cast[Val.Func].apply1(l1, e.pos) @@ -253,13 +234,11 @@ class Evaluator( protected def visitApply2(e: Apply2)(implicit scope: ValScope): Val = { val lhs = visitExpr(e.value) - if (tailstrict) { + implicit val tailstrictMode: TailstrictMode = + if (e.tailstrict) TailstrictModeEnabled else TailstrictModeDisabled + + if (e.tailstrict) { lhs.cast[Val.Func].apply2(visitExpr(e.a1), visitExpr(e.a2), e.pos) - } else if (e.tailstrict) { - tailstrict = true - val res = lhs.cast[Val.Func].apply2(visitExpr(e.a1), visitExpr(e.a2), e.pos) - tailstrict = false - res } else { val l1 = visitAsLazy(e.a1) val l2 = visitAsLazy(e.a2) @@ -269,13 +248,11 @@ class Evaluator( protected def visitApply3(e: Apply3)(implicit scope: ValScope): Val = { val lhs = visitExpr(e.value) - if (tailstrict) { + implicit val tailstrictMode: TailstrictMode = + if (e.tailstrict) TailstrictModeEnabled else TailstrictModeDisabled + + if (e.tailstrict) { lhs.cast[Val.Func].apply3(visitExpr(e.a1), visitExpr(e.a2), visitExpr(e.a3), e.pos) - } else if (e.tailstrict) { - tailstrict = true - val res = lhs.cast[Val.Func].apply3(visitExpr(e.a1), visitExpr(e.a2), visitExpr(e.a3), e.pos) - tailstrict = false - res } else { val l1 = visitAsLazy(e.a1) val l2 = visitAsLazy(e.a2) @@ -284,60 +261,34 @@ class Evaluator( } } - protected def visitApplyBuiltin0(e: ApplyBuiltin0) = { - if (tailstrict) { - e.func.evalRhs(this, e.pos) - } else if (e.tailstrict) { - tailstrict = true - val res = e.func.evalRhs(this, e.pos) - tailstrict = false - res - } else { - e.func.evalRhs(this, e.pos) - } - } + protected def visitApplyBuiltin0(e: ApplyBuiltin0): Val = e.func.evalRhs(this, e.pos) - protected def visitApplyBuiltin1(e: ApplyBuiltin1)(implicit scope: ValScope) = { - if (tailstrict) { + protected def visitApplyBuiltin1(e: ApplyBuiltin1)(implicit scope: ValScope): Val = { + if (e.tailstrict) { e.func.evalRhs(visitExpr(e.a1), this, e.pos) - } else if (e.tailstrict) { - tailstrict = true - val res = e.func.evalRhs(visitExpr(e.a1), this, e.pos) - tailstrict = false - res } else { e.func.evalRhs(visitAsLazy(e.a1), this, e.pos) } } - protected def visitApplyBuiltin2(e: ApplyBuiltin2)(implicit scope: ValScope) = { - if (tailstrict) { + protected def visitApplyBuiltin2(e: ApplyBuiltin2)(implicit scope: ValScope): Val = { + if (e.tailstrict) { e.func.evalRhs(visitExpr(e.a1), visitExpr(e.a2), this, e.pos) - } else if (e.tailstrict) { - tailstrict = true - val res = e.func.evalRhs(visitExpr(e.a1), visitExpr(e.a2), this, e.pos) - tailstrict = false - res } else { e.func.evalRhs(visitAsLazy(e.a1), visitAsLazy(e.a2), this, e.pos) } } - protected def visitApplyBuiltin3(e: ApplyBuiltin3)(implicit scope: ValScope) = { - if (tailstrict) { + protected def visitApplyBuiltin3(e: ApplyBuiltin3)(implicit scope: ValScope): Val = { + if (e.tailstrict) { e.func.evalRhs(visitExpr(e.a1), visitExpr(e.a2), visitExpr(e.a3), this, e.pos) - } else if (e.tailstrict) { - tailstrict = true - val res = e.func.evalRhs(visitExpr(e.a1), visitExpr(e.a2), visitExpr(e.a3), this, e.pos) - tailstrict = false - res } else { e.func.evalRhs(visitAsLazy(e.a1), visitAsLazy(e.a2), visitAsLazy(e.a3), this, e.pos) } } - protected def visitApplyBuiltin4(e: ApplyBuiltin4)(implicit scope: ValScope) = { - if (tailstrict) { + protected def visitApplyBuiltin4(e: ApplyBuiltin4)(implicit scope: ValScope): Val = { + if (e.tailstrict) { e.func.evalRhs( visitExpr(e.a1), visitExpr(e.a2), @@ -346,18 +297,6 @@ class Evaluator( this, e.pos ) - } else if (e.tailstrict) { - tailstrict = true - val res = e.func.evalRhs( - visitExpr(e.a1), - visitExpr(e.a2), - visitExpr(e.a3), - visitExpr(e.a4), - this, - e.pos - ) - tailstrict = false - res } else { e.func.evalRhs( visitAsLazy(e.a1), @@ -370,27 +309,17 @@ class Evaluator( } } - protected def visitApplyBuiltin(e: ApplyBuiltin)(implicit scope: ValScope) = { + protected def visitApplyBuiltin(e: ApplyBuiltin)(implicit scope: ValScope): Val = { val arr = new Array[Lazy](e.argExprs.length) var idx = 0 - if (tailstrict) { + if (e.tailstrict) { while (idx < e.argExprs.length) { arr(idx) = visitExpr(e.argExprs(idx)) idx += 1 } e.func.evalRhs(arr, this, e.pos) - } else if (e.tailstrict) { - tailstrict = true - while (idx < e.argExprs.length) { - arr(idx) = visitExpr(e.argExprs(idx)) - idx += 1 - } - val res = e.func.evalRhs(arr, this, e.pos) - tailstrict = false - res } else { - while (idx < e.argExprs.length) { val boundIdx = idx arr(idx) = visitAsLazy(e.argExprs(boundIdx)) diff --git a/sjsonnet/src/sjsonnet/Interpreter.scala b/sjsonnet/src/sjsonnet/Interpreter.scala index 94dd9ea4..7acc8c60 100644 --- a/sjsonnet/src/sjsonnet/Interpreter.scala +++ b/sjsonnet/src/sjsonnet/Interpreter.scala @@ -197,7 +197,7 @@ class Interpreter( if (tlaExpressions.exists(_ eq expr)) ValScope.empty else vs ) } - }.apply0(f.pos)(evaluator) + }.apply0(f.pos)(evaluator, TailstrictModeDisabled) case x => x } } yield res diff --git a/sjsonnet/src/sjsonnet/StaticOptimizer.scala b/sjsonnet/src/sjsonnet/StaticOptimizer.scala index 3b415bbe..b62b3dca 100644 --- a/sjsonnet/src/sjsonnet/StaticOptimizer.scala +++ b/sjsonnet/src/sjsonnet/StaticOptimizer.scala @@ -153,7 +153,8 @@ class StaticOptimizer( tailstrict: Boolean): Expr = { if (f.staticSafe && args.forall(_.isInstanceOf[Val])) { val vargs = args.map(_.asInstanceOf[Val]) - try f.apply(vargs, null, pos)(ev).asInstanceOf[Expr] + val tailstrictMode = if (tailstrict) TailstrictModeEnabled else TailstrictModeDisabled + try f.apply(vargs, null, pos)(ev, tailstrictMode).asInstanceOf[Expr] catch { case _: Exception => null } } else null } diff --git a/sjsonnet/src/sjsonnet/Std.scala b/sjsonnet/src/sjsonnet/Std.scala index 8a4ac7a2..462f13eb 100644 --- a/sjsonnet/src/sjsonnet/Std.scala +++ b/sjsonnet/src/sjsonnet/Std.scala @@ -109,7 +109,7 @@ class Std( var res = 0 if (func.isInstanceOf[Val.Builtin] || func.params.names.length != 1) { while (i < a.length) { - if (func.apply1(a(i), p)(ev).isInstanceOf[Val.True]) res += 1 + if (func.apply1(a(i), p)(ev, TailstrictModeDisabled).isInstanceOf[Val.True]) res += 1 i += 1 } } else { @@ -231,7 +231,11 @@ class Std( arr.asStrictArray.min(ev) } else { val minTuple = arr.asStrictArray - .map(v => keyF.asInstanceOf[Val.Func].apply1(v, pos.fileScope.noOffsetPos)(ev)) + .map(v => + keyF + .asInstanceOf[Val.Func] + .apply1(v, pos.fileScope.noOffsetPos)(ev, TailstrictModeDisabled) + ) .zipWithIndex .min((x: (Val, Int), y: (Val, Int)) => ev.compare(x._1, y._1)) arr.force(minTuple._2) @@ -259,7 +263,11 @@ class Std( arr.asStrictArray.max(ev) } else { val maxTuple = arr.asStrictArray - .map(v => keyF.asInstanceOf[Val.Func].apply1(v, pos.fileScope.noOffsetPos)(ev)) + .map(v => + keyF + .asInstanceOf[Val.Func] + .apply1(v, pos.fileScope.noOffsetPos)(ev, TailstrictModeDisabled) + ) .zipWithIndex .max((x: (Val, Int), y: (Val, Int)) => ev.compare(x._1, y._1)) arr.force(maxTuple._2) @@ -296,7 +304,7 @@ class Std( var current = init.force for (item <- arr.asLazyArray) { val c = current - current = func.apply2(c, item, pos.noOffset)(ev) + current = func.apply2(c, item, pos.noOffset)(ev, TailstrictModeDisabled) } current @@ -304,7 +312,10 @@ class Std( var current = init.force for (char <- s.value) { val c = current - current = func.apply2(c, Val.Str(pos, new String(Array(char))), pos.noOffset)(ev) + current = func.apply2(c, Val.Str(pos, new String(Array(char))), pos.noOffset)( + ev, + TailstrictModeDisabled + ) } current @@ -322,14 +333,17 @@ class Std( var current = init.force for (item <- arr.asLazyArray.reverse) { val c = current - current = func.apply2(item, c, pos.noOffset)(ev) + current = func.apply2(item, c, pos.noOffset)(ev, TailstrictModeDisabled) } current case s: Val.Str => var current = init.force for (char <- s.value.reverse) { val c = current - current = func.apply2(Val.Str(pos, new String(Array(char))), c, pos.noOffset)(ev) + current = func.apply2(Val.Str(pos, new String(Array(char))), c, pos.noOffset)( + ev, + TailstrictModeDisabled + ) } current case arr => Error.fail("Cannot call foldr on " + arr.prettyName) @@ -383,12 +397,12 @@ class Std( val func = _func.force.asFunc if (func.isInstanceOf[Val.Builtin] || func.params.names.length != 1) { while (i < a.length) { - if (!func.apply1(a(i), p)(ev).isInstanceOf[Val.True]) { + if (!func.apply1(a(i), p)(ev, TailstrictModeDisabled).isInstanceOf[Val.True]) { var b = new Array[Lazy](a.length - 1) System.arraycopy(a, 0, b, 0, i) var j = i + 1 while (j < a.length) { - if (func.apply1(a(j), p)(ev).isInstanceOf[Val.True]) { + if (func.apply1(a(j), p)(ev, TailstrictModeDisabled).isInstanceOf[Val.True]) { b(i) = a(j) i += 1 } @@ -437,7 +451,9 @@ class Std( val func = _func.force.asFunc Val.Arr( pos, - arr.force.asArr.asLazyArray.map(v => (() => func.apply1(v, pos.noOffset)(ev)): Lazy) + arr.force.asArr.asLazyArray.map(v => + (() => func.apply1(v, pos.noOffset)(ev, TailstrictModeDisabled)): Lazy + ) ) } } @@ -453,7 +469,10 @@ class Std( val k = allKeys(i) val v = new Val.Obj.Member(false, Visibility.Normal) { def invoke(self: Val.Obj, sup: Val.Obj, fs: FileScope, ev: EvalScope): Val = - func.apply2(Val.Str(pos, k), () => obj.value(k, pos.noOffset)(ev), pos.noOffset)(ev) + func.apply2(Val.Str(pos, k), () => obj.value(k, pos.noOffset)(ev), pos.noOffset)( + ev, + TailstrictModeDisabled + ) } m.put(k, v) i += 1 @@ -472,7 +491,7 @@ class Std( while (i < a.length) { val x = arr(i) val idx = Val.Num(pos, i) - a(i) = () => func.apply2(idx, x, pos.noOffset)(ev) + a(i) = () => func.apply2(idx, x, pos.noOffset)(ev, TailstrictModeDisabled) i += 1 } Val.Arr(pos, a) @@ -1244,7 +1263,8 @@ class Std( var i = 0 while (i < sz) { val forcedI = i - a(i) = () => func.apply1(Val.Num(pos, forcedI), pos.noOffset)(ev) + a(i) = + () => func.apply1(Val.Num(pos, forcedI), pos.noOffset)(ev, TailstrictModeDisabled) i += 1 } a @@ -1349,7 +1369,7 @@ class Std( case a: Val.Arr => val arrResults = a.asLazyArray.flatMap { v => { - val fres = func.apply1(v, pos.noOffset)(ev) + val fres = func.apply1(v, pos.noOffset)(ev, TailstrictModeDisabled) fres match { case va: Val.Arr => va.asLazyArray case unknown => Error.fail("flatMap func must return an array, not " + unknown) @@ -1361,7 +1381,8 @@ class Std( case s: Val.Str => val builder = new StringBuilder() for (c: Char <- s.value) { - val fres = func.apply1(Val.Str(pos, c.toString), pos.noOffset)(ev) + val fres = + func.apply1(Val.Str(pos, c.toString), pos.noOffset)(ev, TailstrictModeDisabled) builder.append( fres match { case fstr: Val.Str => fstr.value @@ -1387,8 +1408,12 @@ class Std( pos, arr.asLazyArray.flatMap { i => i.force - if (!filter_func.apply1(i, pos.noOffset)(ev).isInstanceOf[Val.True]) None - else Some[Lazy](() => map_func.apply1(i, pos.noOffset)(ev)) + if ( + !filter_func + .apply1(i, pos.noOffset)(ev, TailstrictModeDisabled) + .isInstanceOf[Val.True] + ) None + else Some[Lazy](() => map_func.apply1(i, pos.noOffset)(ev, TailstrictModeDisabled)) } ) }, @@ -1914,15 +1939,16 @@ class Std( arr: mutable.IndexedSeq[? <: Lazy], toFind: Val): Boolean = { val appliedX = keyF match { - case keyFFunc: Val.Func => keyFFunc.apply1(toFind, pos.noOffset)(ev) + case keyFFunc: Val.Func => keyFFunc.apply1(toFind, pos.noOffset)(ev, TailstrictModeDisabled) case _ => toFind } if (ev.settings.strictSetOperations) { arr .search(appliedX.force)((toFind: Lazy, value: Lazy) => { val appliedValue = keyF match { - case keyFFunc: Val.Func => keyFFunc.apply1(value, pos.noOffset)(ev) - case _ => value + case keyFFunc: Val.Func => + keyFFunc.apply1(value, pos.noOffset)(ev, TailstrictModeDisabled) + case _ => value } ev.compare(toFind.force, appliedValue.force) }) @@ -1930,8 +1956,9 @@ class Std( } else { arr.exists(value => { val appliedValue = keyF match { - case keyFFunc: Val.Func => keyFFunc.apply1(value, pos.noOffset)(ev) - case _ => value + case keyFFunc: Val.Func => + keyFFunc.apply1(value, pos.noOffset)(ev, TailstrictModeDisabled) + case _ => value } ev.equal(appliedValue.force, appliedX.force) }) @@ -1980,8 +2007,8 @@ class Std( } } else if (!keyF.isInstanceOf[Val.False]) { val keyFFunc = keyF.asInstanceOf[Val.Func] - val o1Key = keyFFunc.apply1(v, pos.noOffset)(ev) - val o2Key = keyFFunc.apply1(out.last, pos.noOffset)(ev) + val o1Key = keyFFunc.apply1(v, pos.noOffset)(ev, TailstrictModeDisabled) + val o2Key = keyFFunc.apply1(out.last, pos.noOffset)(ev, TailstrictModeDisabled) if (!ev.equal(o1Key, o2Key)) { out.append(v) } @@ -2001,7 +2028,9 @@ class Std( Val.Arr( pos, if (keyFFunc != null) { - val keys: Array[Val] = vs.map(v => keyFFunc(Array(v.force), null, pos.noOffset)(ev).force) + val keys: Array[Val] = vs.map(v => + keyFFunc(Array(v.force), null, pos.noOffset)(ev, TailstrictModeDisabled).force + ) val keyType = keys(0).getClass if (classOf[Val.Bool].isAssignableFrom(keyType)) { Error.fail("Cannot sort with key values that are booleans") diff --git a/sjsonnet/src/sjsonnet/Val.scala b/sjsonnet/src/sjsonnet/Val.scala index 3385bd02..acc6b573 100644 --- a/sjsonnet/src/sjsonnet/Val.scala +++ b/sjsonnet/src/sjsonnet/Val.scala @@ -1,7 +1,6 @@ package sjsonnet import java.util -import java.util.Arrays import sjsonnet.Expr.Member.Visibility import sjsonnet.Expr.Params @@ -16,7 +15,7 @@ import scala.reflect.ClassTag * on-demand */ abstract class Lazy { - protected var cached: Val = null + protected var cached: Val = _ def compute(): Val final def force: Val = { if (cached == null) cached = compute() @@ -231,7 +230,7 @@ object Val { with Expr.ObjBody { var asserting: Boolean = false - def getSuper = `super` + def getSuper: Obj = `super` private def getValue0: util.LinkedHashMap[String, Obj.Member] = { // value0 is always defined for non-static objects, so if we're computing it here @@ -354,27 +353,23 @@ object Val { * Merge two values for "nested field inheritance"; see * https://jsonnet.org/ref/language.html#nested-field-inheritance for background. */ - def mergeMember(l: Val, r: Val, pos: Position)(implicit evaluator: EvalScope): Literal = { - val lStr = l.isInstanceOf[Val.Str] - val rStr = r.isInstanceOf[Val.Str] - if (lStr || rStr) { - val ll = if (lStr) l.asInstanceOf[Val.Str].value else renderString(l) - val rr = if (rStr) r.asInstanceOf[Val.Str].value else renderString(r) - Val.Str(pos, ll ++ rr) - } else if (l.isInstanceOf[Val.Num] && r.isInstanceOf[Val.Num]) { - val ll = l.asInstanceOf[Val.Num].value - val rr = r.asInstanceOf[Val.Num].value - Val.Num(pos, ll + rr) - } else if (l.isInstanceOf[Val.Arr] && r.isInstanceOf[Val.Arr]) { - val ll = l.asInstanceOf[Val.Arr].asLazyArray - val rr = r.asInstanceOf[Val.Arr].asLazyArray - Val.Arr(pos, ll ++ rr) - } else if (l.isInstanceOf[Val.Obj] && r.isInstanceOf[Val.Obj]) { - val ll = l.asInstanceOf[Val.Obj] - val rr = r.asInstanceOf[Val.Obj] - rr.addSuper(pos, ll) - } else throw new MatchError((l, r)) - } + private def mergeMember(l: Val, r: Val, pos: Position)(implicit evaluator: EvalScope): Literal = + (l, r) match { + case (lStr: Val.Str, rStr: Val.Str) => + Val.Str(pos, lStr.value ++ rStr.value) + case (lStr: Val.Str, _) => + Val.Str(pos, lStr.value ++ renderString(r)) + case (_, rStr: Val.Str) => + Val.Str(pos, renderString(l) ++ rStr.value) + case (lNum: Val.Num, rNum: Val.Num) => + Val.Num(pos, lNum.value + rNum.value) + case (lArr: Val.Arr, rArr: Val.Arr) => + Val.Arr(pos, lArr.asLazyArray ++ rArr.asLazyArray) + case (lObj: Val.Obj, rObj: Val.Obj) => + rObj.addSuper(pos, lObj) + case _ => + throw new MatchError((l, r)) + } def valueRaw( k: String, @@ -418,7 +413,7 @@ object Val { final class StaticObjectFieldSet(protected val keys: Array[String]) { override def hashCode(): Int = { - Arrays.hashCode(keys.asInstanceOf[Array[Object]]) + util.Arrays.hashCode(keys.asInstanceOf[Array[Object]]) } override def equals(obj: scala.Any): Boolean = { @@ -451,7 +446,7 @@ object Val { idx += 1 case other => throw new Error( - s"Unexpected non-literal field in static object: ${other} of class ${other.getClass}" + s"Unexpected non-literal field in static object: $other of class ${other.getClass}" ) } val fieldSet = new StaticObjectFieldSet(keys) @@ -482,15 +477,16 @@ object Val { override def asFunc: Func = this def apply(argsL: Array[? <: Lazy], namedNames: Array[String], outerPos: Position)(implicit - ev: EvalScope): Val = { + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = { val simple = namedNames == null && params.names.length == argsL.length val funDefFileScope: FileScope = pos match { case null => outerPos.fileScope case p => p.fileScope } // println(s"apply: argsL: ${argsL.length}, namedNames: $namedNames, paramNames: ${params.names.mkString(",")}") - if (simple || ev.tailstrict) { - if (ev.tailstrict) { + if (simple || tailstrictMode == TailstrictModeEnabled) { + if (tailstrictMode == TailstrictModeEnabled) { argsL.foreach(_.force) } val newScope = defSiteValScope.extendSimple(argsL) @@ -547,13 +543,12 @@ object Val { outerPos ) } - // println(argVals.mkString(s"params: ${params.names.length}, argsL: ${argsL.length}, argVals: [", ", ", "]")) } evalRhs(newScope, ev, funDefFileScope, outerPos) } } - def apply0(outerPos: Position)(implicit ev: EvalScope): Val = { + def apply0(outerPos: Position)(implicit ev: EvalScope, tailstrictMode: TailstrictMode): Val = { if (params.names.length != 0) apply(Evaluator.emptyLazyArray, null, outerPos) else { val funDefFileScope: FileScope = pos match { @@ -564,14 +559,16 @@ object Val { } } - def apply1(argVal: Lazy, outerPos: Position)(implicit ev: EvalScope): Val = { + def apply1(argVal: Lazy, outerPos: Position)(implicit + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = { if (params.names.length != 1) apply(Array(argVal), null, outerPos) else { val funDefFileScope: FileScope = pos match { case null => outerPos.fileScope case p => p.fileScope } - if (ev.tailstrict) { + if (tailstrictMode == TailstrictModeEnabled) { argVal.force } val newScope: ValScope = defSiteValScope.extendSimple(argVal) @@ -579,14 +576,16 @@ object Val { } } - def apply2(argVal1: Lazy, argVal2: Lazy, outerPos: Position)(implicit ev: EvalScope): Val = { + def apply2(argVal1: Lazy, argVal2: Lazy, outerPos: Position)(implicit + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = { if (params.names.length != 2) apply(Array(argVal1, argVal2), null, outerPos) else { val funDefFileScope: FileScope = pos match { case null => outerPos.fileScope case p => p.fileScope } - if (ev.tailstrict) { + if (tailstrictMode == TailstrictModeEnabled) { argVal1.force argVal2.force } @@ -596,14 +595,15 @@ object Val { } def apply3(argVal1: Lazy, argVal2: Lazy, argVal3: Lazy, outerPos: Position)(implicit - ev: EvalScope): Val = { + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = { if (params.names.length != 3) apply(Array(argVal1, argVal2, argVal3), null, outerPos) else { val funDefFileScope: FileScope = pos match { case null => outerPos.fileScope case p => p.fileScope } - if (ev.tailstrict) { + if (tailstrictMode == TailstrictModeEnabled) { argVal1.force argVal2.force argVal3.force @@ -638,20 +638,23 @@ object Val { def evalRhs(args: Array[? <: Lazy], ev: EvalScope, pos: Position): Val - override def apply1(argVal: Lazy, outerPos: Position)(implicit ev: EvalScope): Val = + override def apply1(argVal: Lazy, outerPos: Position)(implicit + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = if (params.names.length != 1) apply(Array(argVal), null, outerPos) else { - if (ev.tailstrict) { + if (tailstrictMode == TailstrictModeEnabled) { argVal.force } evalRhs(Array(argVal), ev, outerPos) } override def apply2(argVal1: Lazy, argVal2: Lazy, outerPos: Position)(implicit - ev: EvalScope): Val = + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = if (params.names.length != 2) apply(Array(argVal1, argVal2), null, outerPos) else { - if (ev.tailstrict) { + if (tailstrictMode == TailstrictModeEnabled) { argVal1.force argVal2.force } @@ -659,10 +662,11 @@ object Val { } override def apply3(argVal1: Lazy, argVal2: Lazy, argVal3: Lazy, outerPos: Position)(implicit - ev: EvalScope): Val = + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = if (params.names.length != 3) apply(Array(argVal1, argVal2, argVal3), null, outerPos) else { - if (ev.tailstrict) { + if (tailstrictMode == TailstrictModeEnabled) { argVal1.force argVal2.force argVal3.force @@ -692,7 +696,9 @@ object Val { def evalRhs(ev: EvalScope, pos: Position): Val override def apply(argVals: Array[? <: Lazy], namedNames: Array[String], outerPos: Position)( - implicit ev: EvalScope): Val = + implicit + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = if (namedNames == null && argVals.length == 0) evalRhs(ev, outerPos) else super.apply(argVals, namedNames, outerPos) @@ -706,7 +712,9 @@ object Val { def evalRhs(arg1: Lazy, ev: EvalScope, pos: Position): Val override def apply(argVals: Array[? <: Lazy], namedNames: Array[String], outerPos: Position)( - implicit ev: EvalScope): Val = + implicit + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = if (namedNames == null && argVals.length == 1) evalRhs(argVals(0).force, ev, outerPos) else super.apply(argVals, namedNames, outerPos) } @@ -719,13 +727,16 @@ object Val { def evalRhs(arg1: Lazy, arg2: Lazy, ev: EvalScope, pos: Position): Val override def apply(argVals: Array[? <: Lazy], namedNames: Array[String], outerPos: Position)( - implicit ev: EvalScope): Val = + implicit + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = if (namedNames == null && argVals.length == 2) evalRhs(argVals(0).force, argVals(1).force, ev, outerPos) else super.apply(argVals, namedNames, outerPos) override def apply2(argVal1: Lazy, argVal2: Lazy, outerPos: Position)(implicit - ev: EvalScope): Val = + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = if (params.names.length == 2) evalRhs(argVal1.force, argVal2.force, ev, outerPos) else super.apply(Array(argVal1, argVal2), null, outerPos) } @@ -743,7 +754,9 @@ object Val { def evalRhs(arg1: Lazy, arg2: Lazy, arg3: Lazy, ev: EvalScope, pos: Position): Val override def apply(argVals: Array[? <: Lazy], namedNames: Array[String], outerPos: Position)( - implicit ev: EvalScope): Val = + implicit + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = if (namedNames == null && argVals.length == 3) evalRhs(argVals(0).force, argVals(1).force, argVals(2).force, ev, outerPos) else super.apply(argVals, namedNames, outerPos) @@ -763,7 +776,9 @@ object Val { def evalRhs(arg1: Lazy, arg2: Lazy, arg3: Lazy, arg4: Lazy, ev: EvalScope, pos: Position): Val override def apply(argVals: Array[? <: Lazy], namedNames: Array[String], outerPos: Position)( - implicit ev: EvalScope): Val = + implicit + ev: EvalScope, + tailstrictMode: TailstrictMode): Val = if (namedNames == null && argVals.length == 4) evalRhs( argVals(0).force, @@ -777,13 +792,15 @@ object Val { } } +sealed trait TailstrictMode +case object TailstrictModeEnabled extends TailstrictMode +case object TailstrictModeDisabled extends TailstrictMode + /** * [[EvalScope]] models the per-evaluator context that is propagated throughout the Jsonnet * evaluation. */ abstract class EvalScope extends EvalErrorScope with Ordering[Val] { - def tailstrict: Boolean - def visitExpr(expr: Expr)(implicit scope: ValScope): Val def materialize(v: Val): ujson.Value @@ -792,7 +809,7 @@ abstract class EvalScope extends EvalErrorScope with Ordering[Val] { def compare(x: Val, y: Val): Int - val emptyMaterializeFileScope = new FileScope(wd / "(materialize)") + private val emptyMaterializeFileScope = new FileScope(wd / "(materialize)") val emptyMaterializeFileScopePos = new Position(emptyMaterializeFileScope, -1) def settings: Settings diff --git a/sjsonnet/test/resources/test_suite/error.tailstrict_stack.jsonnet b/sjsonnet/test/resources/test_suite/error.tailstrict_stack.jsonnet new file mode 100644 index 00000000..e201b958 --- /dev/null +++ b/sjsonnet/test/resources/test_suite/error.tailstrict_stack.jsonnet @@ -0,0 +1,20 @@ +local helper = { + local this = self, + + bar(param, auxone=0, auxtwo=0):: + param + auxone + auxtwo, + + foo(param):: + local sum_from_one_to_n(counter, n) = + if n == 0 then + error "n is 0" + else + sum_from_one_to_n(this.bar(counter + n, auxtwo=1), n - 1) tailstrict; + + local result = sum_from_one_to_n(0, param); + result, +}; + +{ + bar: helper.foo(10), +} diff --git a/sjsonnet/test/resources/test_suite/tailstrict_eager_params.jsonnet b/sjsonnet/test/resources/test_suite/tailstrict_eager_params.jsonnet new file mode 100644 index 00000000..d0f19079 --- /dev/null +++ b/sjsonnet/test/resources/test_suite/tailstrict_eager_params.jsonnet @@ -0,0 +1,20 @@ +local helper = { + local this = self, + + bar(param, unusedParam):: + param, + + foo(param):: + local sum_from_one_to_n(counter, n) = + if n == 0 then + counter + else + sum_from_one_to_n(this.bar(counter + n, error 'Kaboom!'), n - 1) tailstrict; + + local result = sum_from_one_to_n(0, param); + result, +}; + +{ + bar: helper.foo(10), +} diff --git a/sjsonnet/test/resources/test_suite/tailstrict_eager_params.jsonnet.golden b/sjsonnet/test/resources/test_suite/tailstrict_eager_params.jsonnet.golden new file mode 100644 index 00000000..01e2a814 --- /dev/null +++ b/sjsonnet/test/resources/test_suite/tailstrict_eager_params.jsonnet.golden @@ -0,0 +1,3 @@ +{ + "bar": 55 +} \ No newline at end of file diff --git a/sjsonnet/test/resources/test_suite/tailstrict_missing_bindings.jsonnet b/sjsonnet/test/resources/test_suite/tailstrict_missing_bindings.jsonnet new file mode 100644 index 00000000..86744134 --- /dev/null +++ b/sjsonnet/test/resources/test_suite/tailstrict_missing_bindings.jsonnet @@ -0,0 +1,20 @@ +local helper = { + local this = self, + + bar(param, auxone=0, auxtwo=0):: + param + auxone + auxtwo, + + foo(param):: + local sum_from_one_to_n(counter, n) = + if n == 0 then + counter + else + sum_from_one_to_n(this.bar(counter + n, auxtwo=1), n - 1) tailstrict; + + local result = sum_from_one_to_n(0, param); + result, +}; + +{ + bar: helper.foo(10), +} diff --git a/sjsonnet/test/resources/test_suite/tailstrict_missing_bindings.jsonnet.golden b/sjsonnet/test/resources/test_suite/tailstrict_missing_bindings.jsonnet.golden new file mode 100644 index 00000000..2c420752 --- /dev/null +++ b/sjsonnet/test/resources/test_suite/tailstrict_missing_bindings.jsonnet.golden @@ -0,0 +1,3 @@ +{ + "bar": 65 +} \ No newline at end of file diff --git a/sjsonnet/test/src-js/sjsonnet/ErrorTests.scala b/sjsonnet/test/src-js/sjsonnet/ErrorTests.scala index 71e727ad..24bd4ea0 100644 --- a/sjsonnet/test/src-js/sjsonnet/ErrorTests.scala +++ b/sjsonnet/test/src-js/sjsonnet/ErrorTests.scala @@ -312,6 +312,16 @@ object ErrorTests extends TestSuite { suite = "imports" ) + test("tailstrict_stack") - check( + """|sjsonnet.Error: n is 0 + | at [Error].(sjsonnet/test/resources/test_suite/error.tailstrict_stack.jsonnet:10:9) + | at [Apply2].(sjsonnet/test/resources/test_suite/error.tailstrict_stack.jsonnet:12:26) + | at [Apply2].(sjsonnet/test/resources/test_suite/error.tailstrict_stack.jsonnet:14:37) + | at [ValidId result].(sjsonnet/test/resources/test_suite/error.tailstrict_stack.jsonnet:15:5) + | at [Apply1].(sjsonnet/test/resources/test_suite/error.tailstrict_stack.jsonnet:19:18) + |""".stripMargin + ) + test("too_many_arg") - check( """|sjsonnet.Error: Too many args, function has 2 parameter(s) | at [Apply].(sjsonnet/test/resources/imports/error.too_many_arg.jsonnet:3:6) diff --git a/sjsonnet/test/src-js/sjsonnet/FileTests.scala b/sjsonnet/test/src-js/sjsonnet/FileTests.scala index 4cc3bce7..a2dad413 100644 --- a/sjsonnet/test/src-js/sjsonnet/FileTests.scala +++ b/sjsonnet/test/src-js/sjsonnet/FileTests.scala @@ -107,6 +107,8 @@ object FileTests extends TestSuite { test("slice.sugar") - check() test("std_all_hidden") - check() test("stdlib_native") - check() + test("tailstrict_eager_params") - checkGolden() + test("tailstrict_missing_bindings") - checkGolden() test("text_block") - check() test("tla.simple") - check() test("unicode") - check() diff --git a/sjsonnet/test/src-jvm-native/ErrorTests.scala b/sjsonnet/test/src-jvm-native/ErrorTests.scala index 37e3a6fd..dc7fe4ed 100644 --- a/sjsonnet/test/src-jvm-native/ErrorTests.scala +++ b/sjsonnet/test/src-jvm-native/ErrorTests.scala @@ -299,6 +299,16 @@ object ErrorTests extends TestSuite { suite = "imports" ) + test("tailstrict_stack") - check( + """|sjsonnet.Error: n is 0 + | at [Error].(sjsonnet/test/resources/test_suite/error.tailstrict_stack.jsonnet:10:9) + | at [Apply2].(sjsonnet/test/resources/test_suite/error.tailstrict_stack.jsonnet:12:26) + | at [Apply2].(sjsonnet/test/resources/test_suite/error.tailstrict_stack.jsonnet:14:37) + | at [ValidId result].(sjsonnet/test/resources/test_suite/error.tailstrict_stack.jsonnet:15:5) + | at [Apply1].(sjsonnet/test/resources/test_suite/error.tailstrict_stack.jsonnet:19:18) + |""".stripMargin + ) + test("too_many_arg") - check( """|sjsonnet.Error: Too many args, function has 2 parameter(s) | at [Apply].(sjsonnet/test/resources/imports/error.too_many_arg.jsonnet:3:6) diff --git a/sjsonnet/test/src-jvm/sjsonnet/FileTests.scala b/sjsonnet/test/src-jvm/sjsonnet/FileTests.scala index 720b7004..8f2ba7ce 100644 --- a/sjsonnet/test/src-jvm/sjsonnet/FileTests.scala +++ b/sjsonnet/test/src-jvm/sjsonnet/FileTests.scala @@ -74,6 +74,8 @@ object FileTests extends TestSuite { "slice.sugar" - check() test("std_all_hidden") - check() test("stdlib") - check() + test("tailstrict_eager_params") - checkGolden() + test("tailstrict_missing_bindings") - checkGolden() test("text_block") - check() "tla.simple" - check() test("unicode") - check() diff --git a/sjsonnet/test/src-native/sjsonnet/FileTests.scala b/sjsonnet/test/src-native/sjsonnet/FileTests.scala index 54870978..61d35054 100644 --- a/sjsonnet/test/src-native/sjsonnet/FileTests.scala +++ b/sjsonnet/test/src-native/sjsonnet/FileTests.scala @@ -61,7 +61,7 @@ object FileTests extends TestSuite { test("oop_extra") - check() test("parsing_edge_cases") - check() test("precedence") - check() - test("recursive_function_native") - check() + test("recursive_function") - check() test("recursive_import_ok") - check() test("recursive_object") - check() test("regex") - check() @@ -72,6 +72,8 @@ object FileTests extends TestSuite { "slice.sugar" - check() test("std_all_hidden") - check() test("stdlib_native") - check() + test("tailstrict_eager_params") - checkGolden() + test("tailstrict_missing_bindings") - checkGolden() test("text_block") - check() "tla.simple" - check() test("unicode") - check() diff --git a/sjsonnet/test/src/sjsonnet/FormatTests.scala b/sjsonnet/test/src/sjsonnet/FormatTests.scala index 3238b0d4..6b227901 100644 --- a/sjsonnet/test/src/sjsonnet/FormatTests.scala +++ b/sjsonnet/test/src/sjsonnet/FormatTests.scala @@ -10,7 +10,6 @@ object FormatTests extends TestSuite { val json = ujson.read(jsonStr) val formatted = Format.format(fmt, Materializer.reverse(null, json), dummyPos)( new EvalScope { - def tailstrict: Boolean = false def extVars: String => Option[sjsonnet.Expr] = _ => None def wd: Path = DummyPath() def visitExpr(expr: Expr)(implicit scope: ValScope): Val = ???