Created
December 28, 2025 10:15
-
-
Save thekid/5f85c8ec6e14644b06cb9b100644529e to your computer and use it in GitHub Desktop.
Is operator with binding rest
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| diff --git a/src/main/php/lang/ast/syntax/php/IsOperator.class.php b/src/main/php/lang/ast/syntax/php/IsOperator.class.php | |
| index 7e34fc1..99c40b3 100755 | |
| --- a/src/main/php/lang/ast/syntax/php/IsOperator.class.php | |
| +++ b/src/main/php/lang/ast/syntax/php/IsOperator.class.php | |
| @@ -14,6 +14,7 @@ use lang\ast\nodes\{ | |
| MatchExpression, | |
| OffsetExpression, | |
| ScopeExpression, | |
| + UnpackExpression, | |
| Variable | |
| }; | |
| use lang\ast\syntax\Extension; | |
| @@ -80,9 +81,17 @@ class IsOperator implements Extension { | |
| $r= new IsArrayStructure(); | |
| $parse->forward(); | |
| if (']' !== $parse->token->value) do { | |
| + | |
| + // Bind rest of array via `... $rest` vs. discarding it | |
| if ('...' === $parse->token->value) { | |
| - $r->rest= true; | |
| $parse->forward(); | |
| + if ('variable' === $parse->token->kind) { | |
| + $parse->forward(); | |
| + $r->rest= new Variable($parse->token->value); | |
| + $parse->forward(); | |
| + } else { | |
| + $r->rest= true; | |
| + } | |
| break; | |
| } | |
| @@ -251,14 +260,34 @@ class IsOperator implements Extension { | |
| new Literal((string)sizeof($pattern->patterns)) | |
| ) | |
| ); | |
| + $matched= new ArrayLiteral([]); | |
| + $null= new Literal('null'); | |
| foreach ($pattern->patterns as $key => $p) { | |
| $offset= new Literal((string)$key); | |
| + $matched->values[]= [$offset, $null]; | |
| $compound= new BinaryExpression($compound, '&&', new BinaryExpression( | |
| new InvokeExpression(new Literal('array_key_exists'), [$offset, $use]), | |
| '&&', | |
| $match($codegen, new OffsetExpression($use, $offset), $p), | |
| )); | |
| } | |
| + | |
| + // array_diff_key() removes entries for keys in the second argument, | |
| + // unpacking re-keys lists but keeps map key-value pairs intact. | |
| + if ($pattern->rest instanceof Variable) { | |
| + $compound= new BinaryExpression($compound, '&&', new Braced(new BinaryExpression( | |
| + new Braced(new Assignment($pattern->rest, '=', $pattern->patterns | |
| + ? new ArrayLiteral([[ | |
| + null, | |
| + new UnpackExpression(new InvokeExpression(new Literal('array_diff_key'), [$use, $matched])) | |
| + ]]) | |
| + : $use | |
| + )), | |
| + '||', | |
| + new Literal('true') | |
| + ))); | |
| + } | |
| + | |
| return $compound; | |
| } | |
| diff --git a/src/test/php/lang/ast/syntax/php/unittest/VariableBindingTest.class.php b/src/test/php/lang/ast/syntax/php/unittest/VariableBindingTest.class.php | |
| index 3c02f5e..8ec2077 100755 | |
| --- a/src/test/php/lang/ast/syntax/php/unittest/VariableBindingTest.class.php | |
| +++ b/src/test/php/lang/ast/syntax/php/unittest/VariableBindingTest.class.php | |
| @@ -31,6 +31,13 @@ class VariableBindingTest extends EmittingTest { | |
| yield ['[1, [1]] is [1, $rest] ? $rest : null', [1]]; | |
| yield ['[1, [2], "three"] is [1, mixed, $rest] ? $rest : null', 'three']; | |
| + yield ['[] is [...$rest] ? $rest : null', []]; | |
| + yield ['[1] is [int, ...$rest] ? $rest : null', []]; | |
| + yield ['[1, 2, 3] is [...$rest] ? $rest : null', [1, 2, 3]]; | |
| + yield ['[1, 2, 3] is [1, ...$rest] ? $rest : null', [2, 3]]; | |
| + yield ['[1, [1]] is [int, ...$rest] ? $rest : null', [[1]]]; | |
| + yield ['["one" => 1] is ["one" => 1, ...$rest] ? $rest : null', []]; | |
| + yield ['["one" => 1, "two" => 2] is ["one" => 1, ...$rest] ? $rest : null', ['two' => 2]]; | |
| yield ['["one" => 1, "two" => 2] is ["one" => 1, "two" => $t] ? $t : null', 2]; | |
| yield ['["one" => 0, "two" => 2] is ["one" => 1, "two" => $t] ? $t : null', null]; | |
| @@ -57,12 +64,13 @@ class VariableBindingTest extends EmittingTest { | |
| }', explode(' ', $input))); | |
| } | |
| - #[Test, Values([['go north', 'north'], ['go south', 'south'], ['go home', null]])] | |
| + #[Test, Values([['go north', 'north'], ['go south', 'south'], ['go home', null], ['drop it now', ['it', 'now']]])] | |
| public function bind_with_subpattern($input, $expected) { | |
| Assert::equals($expected, $this->run('class %T { | |
| public function run($command) { | |
| return match ($command) is { | |
| ["go", $direction & ("north"|"south"|"east"|"west")] => $direction, | |
| + ["drop", ...$objects] => $objects, | |
| default => null, | |
| }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment