Skip to content

Conversation

@thekid
Copy link
Collaborator

@thekid thekid commented Dec 21, 2025

This PR extends the basic type matching capabilities and implements all of https://wiki.php.net/rfc/pattern-matching

Implementation status

Notes

  • The PHP PR currently uses a different match syntax as the one described in the RFC, see here
  • The PHP PR currently includes the range syntax listed under Future scope in the RFC

See also

@thekid thekid marked this pull request as draft December 21, 2025 15:48
@thekid thekid added the enhancement New feature or request label Dec 21, 2025
@thekid thekid marked this pull request as ready for review December 21, 2025 19:19
@thekid
Copy link
Collaborator Author

thekid commented Dec 21, 2025

In Python, the following works:

match command.split():
    case ["drop", *objects]:
        for obj in objects:
            character.drop(obj, current_room)
    # The rest of your commands go here

Maybe we should be able to bind to variables when using the spread operator, too?


Example:

if ([1, 2, 3] is [1, ...$rest]) {
  var_dump($rest); // [2, 3]
}

POC:

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 af55634..1d3b4f8 100755
--- a/src/main/php/lang/ast/syntax/php/IsOperator.class.php
+++ b/src/main/php/lang/ast/syntax/php/IsOperator.class.php
@@ -74,8 +74,16 @@ class IsOperator implements Extension {
         $parse->forward();
         if (']' !== $parse->token->value) do {
           if ('...' === $parse->token->value) {
-            $r->rest= true;
             $parse->forward();
+
+            // Bind rest of array via `... $rest`
+            if ('variable' === $parse->token->kind) {
+              $parse->forward();
+              $r->rest= new Variable($parse->token->value);
+              $parse->forward();
+            } else {
+              $r->rest= true;
+            }
             break;
           }
 
@@ -233,6 +241,21 @@ class IsOperator implements Extension {
             $match($codegen, new OffsetExpression($temp, $offset), $p),
           ));
         }
+
+        if ($pattern->rest instanceof Variable) {
+          $true= new Literal('true');
+          return new BinaryExpression($compound, '&&', new Braced(
+            new TernaryExpression(
+              new Braced(new Assignment($pattern->rest, '=', isset($key)
+                ? new InvokeExpression(new Literal('array_slice'), [$temp, new Literal((string)($key + 1))])
+                : $temp
+              )),
+              $true,
+              $true
+            ))
+          );
+        }
+
         return $compound;
       } else if ($pattern instanceof IsObjectStructure) {
         $temp= new Variable($codegen->symbol());
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 57645d0..57b82e7 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
@@ -29,6 +29,9 @@ class VariableBindingTest extends EmittingTest {
     yield ['[0, 1, 2] is [$x, $y, $z] ? [$x, $y, $z] : null', [0, 1, 2]];
 
     yield ['[1, [1]] is [1, $rest] ? $rest : null', [1]];
+    yield ['[1, [1]] is [1, ...$rest] ? $rest : null', [[1]]];
+    yield ['[1, 2, 3] is [...$rest] ? $rest : null', [1, 2, 3]];
+    yield ['[1, 2, 3] is [1, ...$rest] ? $rest : null', [2, 3]];
 
     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];

@thekid
Copy link
Collaborator Author

thekid commented Dec 23, 2025

Variable bindings can replace destructuring assignments in most cases:

list() is Note
[$a, $b]= $list $list is [$a, $b]
[$a, , $b]= $list $list is [$a, mixed, $b]
[[$a, $b], $c]= $nested $nested is [[$a, $b], $c]
['key' => $key]= $map $map is ['key' => $key]
[$a[]]= $list *Can only bind variables *
[$a['key']]= $list *Can only bind variables *
[$this->member]= $list *Can only bind variables *

The is operator also has the benefit of not raising errors when $list does not contain the number of elements necessary for destructuring, and instead returns false:

if ($list is [$a, $b, ...]) {
  var_dump($a, $b);
}

// Equivalent of, guarded by type and length checks to prevent errors
if (is_array($list) && sizeof($list) >= 2) {
  [$a, $b]= $list;
  var_dump($a, $b);
}

@thekid
Copy link
Collaborator Author

thekid commented Dec 24, 2025

Following the idea of replacing destructuring assignments, I wanted to see whether I would be able to parse Composer files. Here's what I came up with:

use lang\FormatException;
use util\cmd\Console;

class ComposerFile {

  private function __construct(
    public private(set) string $name,
    public private(set) ?string $type,
    public private(set) array $dependencies,
    public private(set) array $development= [],
    public private(set) array $scripts= [],
  ) { }

  private static function parse(string $input): self {
    if (json_decode($input, true) is [
      'name'        => $name & string,
      'type'        => $type & ?string ?? null,
      'require'     => $dependencies & array,
      'require-dev' => $development & array ?? [],
      'scripts'     => $scripts & array ?? [],
      ...
    ]) return new self($name, $type, $dependencies, $development, $scripts);

    throw new FormatException('Cannnot parse given input');
  }

  public static function main(array $args): void {
    file_get_contents($args[0])
      |> self::parse(...)
      |> Console::writeLine(...)
    ;
  }
}

The missing piece is having a way to make certain keys optional, which I implemented using the null-coalescing operator, inspired by https://wiki.php.net/rfc/destructuring_coalesce

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants