From ed2b6d42c6e13d2f42c6bfa403b593ecba8f0a6a Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 10 Jun 2026 22:29:47 +0100 Subject: [PATCH] Fixed multi-select hashes to end projections, so following tokens apply to the projected list --- CHANGELOG.md | 1 + src/CompilerRuntime.php | 2 +- src/Parser.php | 4 ++++ tests/CompilerRuntimeTest.php | 37 ++++++++++++++++++++++++++++++++++- 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4d54dc..6476f17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Fixed reverse() and string slicing to operate on UTF-8 characters rather than bytes. * Fixed slicing of array-like (ArrayAccess + Countable) values. * Fixed equality and contains() to use JSON semantics, e.g. 1 == 1.0 is now true. +* Fixed multi-select hashes to end projections, so following tokens apply to the projected list. * Fixed sort() and sort_by() to compare numbers numerically. * Changed sort(), sort_by(), max(), min(), max_by() and min_by() to order strings by code point. * Fixed max_by() and min_by() to error on mixed-type keys instead of returning arbitrary elements. diff --git a/src/CompilerRuntime.php b/src/CompilerRuntime.php index e6498e5..a03ee25 100644 --- a/src/CompilerRuntime.php +++ b/src/CompilerRuntime.php @@ -14,7 +14,7 @@ */ class CompilerRuntime { - const CACHE_VERSION = 3; + const CACHE_VERSION = 4; private $parser; private $compiler; diff --git a/src/Parser.php b/src/Parser.php index 6668598..a47d3cd 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -353,6 +353,10 @@ private function parseDot($bp) if ($this->token['type'] == T::T_LBRACKET) { $this->next(); return $this->parseMultiSelectList(); + } elseif ($this->token['type'] == T::T_LBRACE) { + // Like the multi-select list above, a multi-select hash ends any + // projection: tokens that follow apply to the projected list. + return $this->nud_lbrace(); } return $this->expr($bp); diff --git a/tests/CompilerRuntimeTest.php b/tests/CompilerRuntimeTest.php index fda9304..62d9f15 100644 --- a/tests/CompilerRuntimeTest.php +++ b/tests/CompilerRuntimeTest.php @@ -29,7 +29,7 @@ public function testFunctionNameUsesVersionedSalt(): void { $this->assertSame( 'jmespath_' . md5( - 'jmespath:' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . ':3:foo.bar' + 'jmespath:' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . ':4:foo.bar' ), CompilerRuntime::functionName('foo.bar') ); @@ -69,6 +69,41 @@ public function testRuntimesUseJsonSemanticEqualityForNumbers(): void } } + public function testRuntimesStopProjectionsAtMultiSelectHashes(): void + { + $dir = $this->createTempDir(); + $data = [ + 'people' => [ + ['age' => 20, 'name' => 'Bob'], + ['age' => 25, 'name' => 'Fred'], + ['age' => 30, 'name' => 'George'], + ], + ]; + + try { + foreach ([new AstRuntime(), new CompilerRuntime($dir)] as $runtime) { + $this->assertSame( + [['name' => 'Fred'], ['name' => 'George']], + $runtime('people[?age >= `25`].{name: name}', $data) + ); + $this->assertSame( + ['name' => 'Fred'], + $runtime('people[?age >= `25`].{name: name}[0]', $data) + ); + $this->assertSame( + 'Fred', + $runtime('people[?age >= `25`].{name: name}[0].name', $data) + ); + $this->assertNull($runtime('people[?age >= `25`].{name: name}.name', $data)); + + // Multi-select lists already ended projections; pin the symmetry. + $this->assertSame(['Bob'], $runtime('people[*].[name][0]', $data)); + } + } finally { + $this->removeTempDir($dir); + } + } + public function testCompiledRuntimeSortsNumerically(): void { $dir = $this->createTempDir();