From 245488da9119fbaef78b2a27318db890b4371ab0 Mon Sep 17 00:00:00 2001 From: He-Pin Date: Sat, 4 Jul 2026 02:12:07 +0800 Subject: [PATCH] fix: align std.lines with official semantics Motivation: go-jsonnet issue #885 exposed confusion around std.lines: official Jsonnet treats it as array-to-text, not string-to-array. sjsonnet already rejected string input, but the native implementation materialized arrays unnecessarily and lacked a focused regression for this edge. Modification: Rewrite std.lines to build the newline-terminated string directly from Val.Arr while preserving null-skipping behavior. Add compatibility coverage and a golden error case for string input. Result: std.lines keeps the official array-to-text behavior, rejects string input, and avoids an unnecessary ujson materialization pass. References: https://github.com/google/go-jsonnet/issues/885 --- .../src/sjsonnet/stdlib/ManifestModule.scala | 21 +++++-------------- .../error.std_lines_string.jsonnet | 1 + .../error.std_lines_string.jsonnet.golden | 2 ++ .../StdLibOfficialCompatibilityTests.scala | 9 ++++++++ 4 files changed, 17 insertions(+), 16 deletions(-) create mode 100644 sjsonnet/test/resources/new_test_suite/error.std_lines_string.jsonnet create mode 100644 sjsonnet/test/resources/new_test_suite/error.std_lines_string.jsonnet.golden diff --git a/sjsonnet/src/sjsonnet/stdlib/ManifestModule.scala b/sjsonnet/src/sjsonnet/stdlib/ManifestModule.scala index b7591e8a..6b25918f 100644 --- a/sjsonnet/src/sjsonnet/stdlib/ManifestModule.scala +++ b/sjsonnet/src/sjsonnet/stdlib/ManifestModule.scala @@ -347,25 +347,14 @@ object ManifestModule extends AbstractFunctionModule { */ private object Lines extends Val.Builtin1("lines", "arr") { def evalRhs(v1: Eval, ev: EvalScope, pos: Position): Val = { + val out = new StringBuilder v1.value.asArr.foreach { - case _: Val.Str | _: Val.Null => // donothing - case x => + case s: Val.Str => out.append(s.str).append('\n') + case _: Val.Null => // skip nulls like std.join + case x => Error.fail("std.lines: expected string or null element, got " + x.value.prettyName) } - Val.Str( - pos, - Materializer - .apply(v1.value)(ev) - .asInstanceOf[ujson.Arr] - .value - .filter(_ != ujson.Null) - .map { - case ujson.Str(s) => s + "\n" - case _ => - throw new RuntimeException("Unexpected") /* we ensure it's all strings above */ - } - .mkString - ) + Val.Str(pos, out.toString) } } diff --git a/sjsonnet/test/resources/new_test_suite/error.std_lines_string.jsonnet b/sjsonnet/test/resources/new_test_suite/error.std_lines_string.jsonnet new file mode 100644 index 00000000..5ca2e7d7 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.std_lines_string.jsonnet @@ -0,0 +1 @@ +std.lines("a\nb\n") diff --git a/sjsonnet/test/resources/new_test_suite/error.std_lines_string.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/error.std_lines_string.jsonnet.golden new file mode 100644 index 00000000..7a4159ae --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/error.std_lines_string.jsonnet.golden @@ -0,0 +1,2 @@ +sjsonnet.Error: [std.lines] Wrong parameter type: expected Array, got string + at [].(error.std_lines_string.jsonnet:1:10) diff --git a/sjsonnet/test/src/sjsonnet/StdLibOfficialCompatibilityTests.scala b/sjsonnet/test/src/sjsonnet/StdLibOfficialCompatibilityTests.scala index 6c51814a..6053e0a3 100644 --- a/sjsonnet/test/src/sjsonnet/StdLibOfficialCompatibilityTests.scala +++ b/sjsonnet/test/src/sjsonnet/StdLibOfficialCompatibilityTests.scala @@ -15,6 +15,15 @@ object StdLibOfficialCompatibilityTests extends TestSuite { assert(evalErr("""std.flattenArrays([[1], null, [2]])""").contains("array and null")) } + test("lines follows official array-to-text semantics") { + eval("""std.lines(["a", "b"])""") ==> ujson.Str("a\nb\n") + eval("""std.lines(["a", null, "b"])""") ==> ujson.Str("a\nb\n") + + val stringInputError = evalErr("""std.lines("a\nb\n")""") + assert(stringInputError.contains("expected Array")) + assert(stringInputError.contains("got string")) + } + test("flattenDeepArray wraps scalar values") { eval("""std.flattenDeepArray(1)""") ==> ujson.Arr(1) eval("""std.flattenDeepArray(null)""") ==> ujson.Arr(ujson.Null)