Repository: CuyZ/Valinor (php)

Issue:
Unsealed array with trailing comma causes parse error
Valinor currently fails to parse an unsealed array with a trailing comma:
```php
class Foo
{
    /**
     * @param array{
     *     a: int,
     *     b: int,
     *     ...,
     * } $arr
     */
    public function __construct(private readonly array $arr) {}
}

$foo = (new \CuyZ\Valinor\MapperBuilder())
    ->allowPermissiveTypes()
    ->mapper()
    ->map(Foo::class, ["arr" => ["a" => 1, "b" => 2]])
;
```
It's debatable whether this trailing comma is really appropriate, since `...` must appear last the comma isn't useful for diffs, but since Psalm and PHPStan both support it I think it would be good for it not to cause a parsing error.

Pull request description:
misc: handle trailing comma in shaped array declaration
Fixes #566

Patch:
diff --git a/src/Type/Parser/Lexer/Token/ArrayToken.php b/src/Type/Parser/Lexer/Token/ArrayToken.php
index 57bcd85..703d31f 100644
--- a/src/Type/Parser/Lexer/Token/ArrayToken.php
+++ b/src/Type/Parser/Lexer/Token/ArrayToken.php
@@ -137,7 +137,7 @@ final class ArrayToken implements TraversingToken
 
             if ($stream->next() instanceof VacantToken) {
                 $type = new StringValueType($stream->forward()->symbol());
-            } elseif ($isUnsealed && $stream->next() instanceof ClosingCurlyBracketToken) {
+            } elseif ($isUnsealed && ($stream->next() instanceof ClosingCurlyBracketToken || $stream->next() instanceof CommaToken)) {
                 $stream->forward();
                 break;
             } else {


Test patch:
diff --git a/tests/Functional/Type/Parser/LexingParserTest.php b/tests/Functional/Type/Parser/LexingParserTest.php
index 88d9903..0a32700 100644
--- a/tests/Functional/Type/Parser/LexingParserTest.php
+++ b/tests/Functional/Type/Parser/LexingParserTest.php
@@ -738,6 +738,18 @@ final class LexingParserTest extends TestCase
             'type' => ShapedArrayType::class,
         ];
 
+        yield 'Unsealed shaped array' => [
+            'raw' => 'array{foo: string, ...}',
+            'transformed' => 'array{foo: string, ...}',
+            'type' => ShapedArrayType::class,
+        ];
+
+        yield 'Unsealed shaped array with trailing comma' => [
+            'raw' => 'array{foo: string, ...,}',
+            'transformed' => 'array{foo: string, ...}',
+            'type' => ShapedArrayType::class,
+        ];
+
         yield 'Iterable type' => [
             'raw' => 'iterable',
             'transformed' => 'iterable',