Skip to content

Commit edcdee8

Browse files
committed
Support typing extra items in unsealed array shapes
1 parent 8752839 commit edcdee8

File tree

4 files changed

+459
-4
lines changed

4 files changed

+459
-4
lines changed

src/Ast/Type/ArrayShapeNode.php

+21-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
namespace PHPStan\PhpDocParser\Ast\Type;
44

5+
use InvalidArgumentException;
56
use PHPStan\PhpDocParser\Ast\NodeAttributes;
67
use function implode;
8+
use function strlen;
9+
use function substr;
710

811
class ArrayShapeNode implements TypeNode
912
{
@@ -22,15 +25,27 @@ class ArrayShapeNode implements TypeNode
2225
/** @var self::KIND_* */
2326
public $kind;
2427

28+
/** @var GenericTypeNode|null */
29+
public $extraItemType;
30+
2531
/**
2632
* @param ArrayShapeItemNode[] $items
2733
* @param self::KIND_* $kind
2834
*/
29-
public function __construct(array $items, bool $sealed = true, string $kind = self::KIND_ARRAY)
35+
public function __construct(
36+
array $items,
37+
bool $sealed = true,
38+
string $kind = self::KIND_ARRAY,
39+
?GenericTypeNode $extraItemType = null
40+
)
3041
{
3142
$this->items = $items;
3243
$this->sealed = $sealed;
3344
$this->kind = $kind;
45+
$this->extraItemType = $extraItemType;
46+
if ($sealed && $extraItemType !== null) {
47+
throw new InvalidArgumentException('An extra item type may only be set for an unsealed array shape');
48+
}
3449
}
3550

3651

@@ -39,7 +54,11 @@ public function __toString(): string
3954
$items = $this->items;
4055

4156
if (! $this->sealed) {
42-
$items[] = '...';
57+
$item = '...';
58+
if ($this->extraItemType !== null) {
59+
$item .= substr((string) $this->extraItemType, strlen((string) $this->extraItemType->type));
60+
}
61+
$items[] = $item;
4362
}
4463

4564
return $this->kind . '{' . implode(', ', $items) . '}';

src/Parser/TypeParser.php

+15-1
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,7 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
848848

849849
$items = [];
850850
$sealed = true;
851+
$extraItemType = null;
851852

852853
do {
853854
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
@@ -858,6 +859,19 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
858859

859860
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC)) {
860861
$sealed = false;
862+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
863+
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
864+
$extraItemType = $this->parseGeneric(
865+
$tokens,
866+
$this->enrichWithAttributes(
867+
$tokens,
868+
new Ast\Type\IdentifierTypeNode($kind),
869+
$tokens->currentTokenLine(),
870+
$tokens->currentTokenIndex()
871+
)
872+
);
873+
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
874+
}
861875
$tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA);
862876
break;
863877
}
@@ -870,7 +884,7 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
870884
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
871885
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
872886

873-
return new Ast\Type\ArrayShapeNode($items, $sealed, $kind);
887+
return new Ast\Type\ArrayShapeNode($items, $sealed, $kind, $extraItemType);
874888
}
875889

876890

src/Printer/Printer.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
use function sprintf;
7575
use function strlen;
7676
use function strpos;
77+
use function substr;
7778
use function trim;
7879
use const PREG_SET_ORDER;
7980

@@ -366,7 +367,11 @@ private function printType(TypeNode $node): string
366367
}, $node->items);
367368

368369
if (! $node->sealed) {
369-
$items[] = '...';
370+
$item = '...';
371+
if ($node->extraItemType !== null) {
372+
$item .= substr($this->printType($node->extraItemType), strlen((string) $node->extraItemType->type));
373+
}
374+
$items[] = $item;
370375
}
371376

372377
return $node->kind . '{' . implode(', ', $items) . '}';

0 commit comments

Comments
 (0)