From e202928864800bf9a18ae4fefae9c920a4035192 Mon Sep 17 00:00:00 2001 From: He-Pin Date: Thu, 18 Jun 2026 13:03:04 +0800 Subject: [PATCH] fix: equal() detects functions in nested containers during equality comparison Motivation: The `equal()` function in Evaluator only rejected function comparison at the top level (`(function() 3) == (function() 3)`) but not when functions appeared inside arrays or objects. This diverges from go-jsonnet and jsonnet-cpp which both raise "cannot test equality of functions" when functions are encountered at any nesting depth. Modification: - Array comparison loop: detect Val.Func in either element before recursing, including the shared-thunk fast path (xe eq ye) - Object comparison loop: detect Val.Func in field values before recursing via equal() - Top-level catch-all: reject when both sides are Val.Func Added golden tests covering functions in arrays, objects, and mixed function-vs-non-function container comparisons. Result: `[function() 3] == [function() 4]` and `{a: function() 3} == {a: function() 4}` now correctly raise "cannot test equality of functions", aligning with go-jsonnet and jsonnet-cpp behavior. References: - go-jsonnet: builtins.go equal() checks for function type in all paths - jsonnet-cpp: vm.cpp equality check rejects function operands --- sjsonnet/src/sjsonnet/Evaluator.scala | 15 +++++++++++++-- .../equality_function_mixed_container.jsonnet | 9 +++++++++ ...uality_function_mixed_container.jsonnet.golden | 8 ++++++++ .../error.equality_function_in_array.jsonnet | 3 +++ ...rror.equality_function_in_array.jsonnet.golden | 1 + .../error.equality_function_in_object.jsonnet | 3 +++ ...ror.equality_function_in_object.jsonnet.golden | 1 + 7 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 sjsonnet/test/resources/new_test_suite/equality_function_mixed_container.jsonnet create mode 100644 sjsonnet/test/resources/new_test_suite/equality_function_mixed_container.jsonnet.golden create mode 100644 sjsonnet/test/resources/new_test_suite/error.equality_function_in_array.jsonnet create mode 100644 sjsonnet/test/resources/new_test_suite/error.equality_function_in_array.jsonnet.golden create mode 100644 sjsonnet/test/resources/new_test_suite/error.equality_function_in_object.jsonnet create mode 100644 sjsonnet/test/resources/new_test_suite/error.equality_function_in_object.jsonnet.golden diff --git a/sjsonnet/src/sjsonnet/Evaluator.scala b/sjsonnet/src/sjsonnet/Evaluator.scala index e41deda5d..db94376a3 100644 --- a/sjsonnet/src/sjsonnet/Evaluator.scala +++ b/sjsonnet/src/sjsonnet/Evaluator.scala @@ -2149,10 +2149,16 @@ class Evaluator( val ye = y.eval(i) // Skip forcing when Eval references match (shared backing elements) if (!(xe eq ye)) { - if (!equal(xe.value, ye.value)) return false + val xv = xe.value + val yv = ye.value + if (xv.isInstanceOf[Val.Func] && yv.isInstanceOf[Val.Func]) + Error.fail("cannot test equality of functions") + if (!equal(xv, yv)) return false } else if (!xe.isInstanceOf[Val]) { // Invariant: Eval = Val | Lazy. Val is pure; Lazy may error on force. - xe.value // Force shared Lazy thunks for error semantics + val xv = xe.value // Force shared Lazy thunks for error semantics + if (xv.isInstanceOf[Val.Func]) + Error.fail("cannot test equality of functions") } i += 1 } @@ -2174,12 +2180,17 @@ class Evaluator( if (!y.containsVisibleKey(k)) return false val v1 = x.value(k, emptyMaterializeFileScopePos) val v2 = y.value(k, emptyMaterializeFileScopePos) + if (v1.isInstanceOf[Val.Func] && v2.isInstanceOf[Val.Func]) + Error.fail("cannot test equality of functions") if (!equal(v1, v2)) return false i += 1 } true case _ => false } + case _: Val.Func => + if (y.isInstanceOf[Val.Func]) Error.fail("cannot test equality of functions") + false case _ => false }) } diff --git a/sjsonnet/test/resources/new_test_suite/equality_function_mixed_container.jsonnet b/sjsonnet/test/resources/new_test_suite/equality_function_mixed_container.jsonnet new file mode 100644 index 000000000..63d7d189a --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/equality_function_mixed_container.jsonnet @@ -0,0 +1,9 @@ +local f = function(x) x; +[ + [f] == [1], + [f] != [1], + {a: f} == {a: 1}, + {a: f} != {a: 1}, + [1, f] == [1, 2], + [f] == [], +] diff --git a/sjsonnet/test/resources/new_test_suite/equality_function_mixed_container.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/equality_function_mixed_container.jsonnet.golden new file mode 100644 index 000000000..055d01e30 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/equality_function_mixed_container.jsonnet.golden @@ -0,0 +1,8 @@ +[ + false, + true, + false, + true, + false, + false +] \ No newline at end of file diff --git a/sjsonnet/test/resources/new_test_suite/error.equality_function_in_array.jsonnet b/sjsonnet/test/resources/new_test_suite/error.equality_function_in_array.jsonnet new file mode 100644 index 000000000..47b43c1ad --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.equality_function_in_array.jsonnet @@ -0,0 +1,3 @@ +local f = function(x) x; +local g = function(y) y; +[f] == [g] diff --git a/sjsonnet/test/resources/new_test_suite/error.equality_function_in_array.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/error.equality_function_in_array.jsonnet.golden new file mode 100644 index 000000000..09d8b3335 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.equality_function_in_array.jsonnet.golden @@ -0,0 +1 @@ +sjsonnet.Error: cannot test equality of functions \ No newline at end of file diff --git a/sjsonnet/test/resources/new_test_suite/error.equality_function_in_object.jsonnet b/sjsonnet/test/resources/new_test_suite/error.equality_function_in_object.jsonnet new file mode 100644 index 000000000..e7fb5d6af --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.equality_function_in_object.jsonnet @@ -0,0 +1,3 @@ +local f = function(x) x; +local g = function(y) y; +{a: f} == {a: g} diff --git a/sjsonnet/test/resources/new_test_suite/error.equality_function_in_object.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/error.equality_function_in_object.jsonnet.golden new file mode 100644 index 000000000..09d8b3335 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.equality_function_in_object.jsonnet.golden @@ -0,0 +1 @@ +sjsonnet.Error: cannot test equality of functions \ No newline at end of file