diff --git a/sjsonnet/src/sjsonnet/Parser.scala b/sjsonnet/src/sjsonnet/Parser.scala index a1589bd3f..8318e8314 100644 --- a/sjsonnet/src/sjsonnet/Parser.scala +++ b/sjsonnet/src/sjsonnet/Parser.scala @@ -951,7 +951,7 @@ class Parser( def field[$: P](currentDepth: Int): P[Expr.Member.Field] = { P( - (Pos ~~ fieldname(currentDepth + 1) ~/ "+".!.? ~ ("(" ~ params( + (Pos ~~ fieldname(currentDepth + 1) ~/ "+".!.? ~ ("(" ~/ params( currentDepth + 1 ) ~ ")").? ~ fieldKeySep ~/ expr(currentDepth + 1)).map { case (pos, name, plus, p, h2, e) => Expr.Member.Field(pos, name, plus.nonEmpty, p.orNull, h2, e) @@ -1045,18 +1045,27 @@ class Parser( def params[$: P]: P[Expr.Params] = params(0) def params[$: P](currentDepth: Int): P[Expr.Params] = { - P((id ~ ("=" ~ expr(currentDepth + 1)).?).rep(sep = ",") ~ ",".?).flatMapX { x => + val ctx = implicitly[P[?]] + P( + (Pos ~~ id ~ ("=" ~ expr(currentDepth + 1)).?).rep(sep = ",") ~ ",".? + ).flatMapX { x => val seen = collection.mutable.Set.empty[String] - var overlap: String = null - for ((k, _) <- x) { - if (seen(k)) overlap = k - else seen.add(k) + var overlap: (Position, String) = null + for ((pos, k, _) <- x) { + if (overlap == null) { + if (seen(k)) overlap = (pos, k) + else seen.add(k) + } } if (overlap == null) { - val names = x.map(_._1).toArray[String] - val exprs = x.map(_._2.orNull).toArray[Expr] + val names = x.map(_._2).toArray[String] + val exprs = x.map(_._3.orNull).toArray[Expr] Pass(Expr.Params(names, exprs)) - } else Fail.opaque("no duplicate parameter: " + overlap) + } else { + ctx.cut = true + ctx.index = overlap._1.offset + Fail.opaque("no duplicate parameter: " + overlap._2) + } } } diff --git a/sjsonnet/test/resources/test_suite/error.function_duplicate_param.jsonnet.golden b/sjsonnet/test/resources/test_suite/error.function_duplicate_param.jsonnet.golden index 43ee6cd64..78396d562 100644 --- a/sjsonnet/test/resources/test_suite/error.function_duplicate_param.jsonnet.golden +++ b/sjsonnet/test/resources/test_suite/error.function_duplicate_param.jsonnet.golden @@ -1,3 +1,3 @@ -sjsonnet.ParseError: Expected no duplicate parameter: x:17:14, found ") x\n" - at [].(error.function_duplicate_param.jsonnet:17:14) +sjsonnet.ParseError: Expected no duplicate parameter: x:17:13, found "x) x\n" + at [].(error.function_duplicate_param.jsonnet:17:13) diff --git a/sjsonnet/test/src/sjsonnet/ParserTests.scala b/sjsonnet/test/src/sjsonnet/ParserTests.scala index 81c8b5432..39aa4f44e 100644 --- a/sjsonnet/test/src/sjsonnet/ParserTests.scala +++ b/sjsonnet/test/src/sjsonnet/ParserTests.scala @@ -57,6 +57,16 @@ object ParserTests extends TestSuite { test("duplicateFields") { parseErr("{ a: 1, a: 2 }") ==> """Expected no duplicate field: a:1:14, found "}"""" } + test("duplicateParams") { + parseErr("(function(x, x, x) x)(null, 3, 2)") ==> + """Expected no duplicate parameter: x:1:14, found "x, x) x)(n"""" + parseErr("local f(x, x) = x; f(1, 2)") ==> + "Expected no duplicate parameter: x:1:12, found \"x) = x; f(\"" + parseErr("{ f(x, x): x }.f(1, 2)") ==> + "Expected no duplicate parameter: x:1:8, found \"x): x }.f(\"" + parseErr("{ local f(x, x) = x, a: f(1, 2) }") ==> + "Expected no duplicate parameter: x:1:14, found \"x) = x, a:\"" + } test("localInObj") { parse("""{ |local x = 1,