Created
January 31, 2026 11:01
-
-
Save thekid/56ccd4057a3aa7c2e32447d076d52fb5 to your computer and use it in GitHub Desktop.
Match pattern matching inside branches
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 30832b5..9c3f126 100755 | |
| --- a/src/main/php/lang/ast/syntax/php/IsOperator.class.php | |
| +++ b/src/main/php/lang/ast/syntax/php/IsOperator.class.php | |
| @@ -125,6 +125,8 @@ class IsOperator implements Extension { | |
| }); | |
| $language->prefix('match', 0, function($parse, $token) use($pattern) { | |
| + static $id= 0; | |
| + | |
| $patterns= null; | |
| $condition= null; | |
| @@ -132,22 +134,9 @@ class IsOperator implements Extension { | |
| $parse->forward(); | |
| $condition= $this->expression($parse, 0); | |
| $parse->expecting(')', 'match'); | |
| - | |
| - // See https://wiki.php.net/rfc/pattern-matching#match_is_placement | |
| - if ('is' === $parse->token->value) { | |
| - $parse->forward(); | |
| - | |
| - $true= new Literal('true'); | |
| - if ($condition instanceof Variable) { | |
| - $patterns= $condition; | |
| - $condition= $true; | |
| - } else { | |
| - $patterns= new Variable('Ṁ'); | |
| - $condition= new BinaryExpression(new Braced(new Assignment($patterns, '=', $condition)), '||', $true); | |
| - } | |
| - } | |
| } | |
| + $is= false; | |
| $default= null; | |
| $cases= []; | |
| $parse->expecting('{', 'match'); | |
| @@ -156,8 +145,10 @@ class IsOperator implements Extension { | |
| $parse->forward(); | |
| $parse->expecting('=>', 'match'); | |
| $default= $this->expression($parse, 0); | |
| - } else if ($patterns) { | |
| - $match= [new PatternMatch($patterns, $pattern($parse, $this), $parse->token->line)]; | |
| + } else if ('is' === $parse->token->value) { | |
| + $is= true; | |
| + $parse->forward(); | |
| + $match= [new PatternMatch(null, $pattern($parse, $this), $parse->token->line)]; | |
| $parse->expecting('=>', 'match'); | |
| $cases[]= new MatchCondition($match, $this->expression($parse, 0), $parse->token->line); | |
| } else { | |
| @@ -175,6 +166,26 @@ class IsOperator implements Extension { | |
| } | |
| $parse->expecting('}', 'match'); | |
| + // If one of the branches contains an `is` match, rewrite the statement | |
| + if ($is) { | |
| + $patterns= new Variable('Ṁ'.($id++)); | |
| + $condition= new BinaryExpression( | |
| + new Braced(new Assignment($patterns, '=', $condition)), | |
| + '||', | |
| + new Literal('true') | |
| + ); | |
| + | |
| + foreach ($cases as $case) { | |
| + foreach ($case->expressions as &$match) { | |
| + if ($match instanceof PatternMatch) { | |
| + $match->expression= $patterns; | |
| + } else { | |
| + $match= new BinaryExpression($match, '===', $patterns); | |
| + } | |
| + } | |
| + } | |
| + } | |
| + | |
| return new MatchExpression($condition, $cases, $default, $token->line); | |
| }); | |
| diff --git a/src/test/php/lang/ast/syntax/php/unittest/IsOperatorTest.class.php b/src/test/php/lang/ast/syntax/php/unittest/IsOperatorTest.class.php | |
| index e6b3709..93fb797 100755 | |
| --- a/src/test/php/lang/ast/syntax/php/unittest/IsOperatorTest.class.php | |
| +++ b/src/test/php/lang/ast/syntax/php/unittest/IsOperatorTest.class.php | |
| @@ -36,13 +36,14 @@ class IsOperatorTest extends EmittingTest { | |
| }', $arg)); | |
| } | |
| - #[Test, Values([[1, 'int'], ['test', 'string']])] | |
| + #[Test, Values([[1, 'int'], ['', 'string'], ['test', 'string'], [null, 'undefined']])] | |
| public function match_is_variant($arg, $expected) { | |
| Assert::equals($expected, $this->run('class %T { | |
| - public function run(string|int $arg) { | |
| - return match ($arg) is { | |
| - string => "string", | |
| - int => "int", | |
| + public function run(?string|int $arg) { | |
| + return match ($arg) { | |
| + is string => "string", | |
| + is int => "int", | |
| + null => "undefined", | |
| }; | |
| } | |
| }', $arg)); | |
| @@ -57,9 +58,9 @@ class IsOperatorTest extends EmittingTest { | |
| $invoked++; | |
| return 1; | |
| }; | |
| - return match ($arg()) is { | |
| - 0 => ["zero", $invoked], | |
| - 1 => ["one", $invoked], | |
| + return match ($arg()) { | |
| + is 0 => ["zero", $invoked], | |
| + is 1 => ["one", $invoked], | |
| }; | |
| } | |
| }')); | |
| 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..80fcd3d 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 | |
| @@ -49,8 +49,8 @@ class VariableBindingTest extends EmittingTest { | |
| public function compound_binding($input) { | |
| Assert::equals('apple', $this->run('class %T { | |
| public function run($command) { | |
| - return match ($command) is { | |
| - ["get", $object]|["pick", "up", $object]|["pick", $object, "up"] => $object, | |
| + return match ($command) { | |
| + is ["get", $object]|["pick", "up", $object]|["pick", $object, "up"] => $object, | |
| default => null, | |
| }; | |
| } | |
| @@ -61,8 +61,8 @@ class VariableBindingTest extends EmittingTest { | |
| 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, | |
| + return match ($command) { | |
| + is ["go", $direction & ("north"|"south"|"east"|"west")] => $direction, | |
| default => null, | |
| }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment