Skip to content

Commit

Permalink
Support typing extra items in unsealed array shapes
Browse files Browse the repository at this point in the history
  • Loading branch information
tscni authored Aug 29, 2024
1 parent cc2b26c commit 5ceb0e3
Show file tree
Hide file tree
Showing 6 changed files with 645 additions and 4 deletions.
13 changes: 11 additions & 2 deletions src/Ast/Type/ArrayShapeNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,24 @@ class ArrayShapeNode implements TypeNode
/** @var self::KIND_* */
public $kind;

/** @var ArrayShapeUnsealedTypeNode|null */
public $unsealedType;

/**
* @param ArrayShapeItemNode[] $items
* @param self::KIND_* $kind
*/
public function __construct(array $items, bool $sealed = true, string $kind = self::KIND_ARRAY)
public function __construct(
array $items,
bool $sealed = true,
string $kind = self::KIND_ARRAY,
?ArrayShapeUnsealedTypeNode $unsealedType = null
)
{
$this->items = $items;
$this->sealed = $sealed;
$this->kind = $kind;
$this->unsealedType = $unsealedType;
}


Expand All @@ -39,7 +48,7 @@ public function __toString(): string
$items = $this->items;

if (! $this->sealed) {
$items[] = '...';
$items[] = '...' . $this->unsealedType;
}

return $this->kind . '{' . implode(', ', $items) . '}';
Expand Down
34 changes: 34 additions & 0 deletions src/Ast/Type/ArrayShapeUnsealedTypeNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types = 1);

namespace PHPStan\PhpDocParser\Ast\Type;

use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\NodeAttributes;
use function sprintf;

class ArrayShapeUnsealedTypeNode implements Node
{

use NodeAttributes;

/** @var TypeNode */
public $valueType;

/** @var TypeNode|null */
public $keyType;

public function __construct(TypeNode $valueType, ?TypeNode $keyType)
{
$this->valueType = $valueType;
$this->keyType = $keyType;
}

public function __toString(): string
{
if ($this->keyType !== null) {
return sprintf('<%s, %s>', $this->keyType, $this->valueType);
}
return sprintf('<%s>', $this->valueType);
}

}
71 changes: 70 additions & 1 deletion src/Parser/TypeParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,7 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,

$items = [];
$sealed = true;
$unsealedType = null;

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

if ($tokens->tryConsumeTokenType(Lexer::TOKEN_VARIADIC)) {
$sealed = false;

$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
if ($kind === Ast\Type\ArrayShapeNode::KIND_ARRAY) {
$unsealedType = $this->parseArrayShapeUnsealedType($tokens);
} else {
$unsealedType = $this->parseListShapeUnsealedType($tokens);
}
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
}

$tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA);
break;
}
Expand All @@ -870,7 +882,7 @@ private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type,
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);

return new Ast\Type\ArrayShapeNode($items, $sealed, $kind);
return new Ast\Type\ArrayShapeNode($items, $sealed, $kind, $unsealedType);
}


Expand Down Expand Up @@ -949,6 +961,63 @@ private function parseArrayShapeKey(TokenIterator $tokens)
);
}

/**
* @phpstan-impure
*/
private function parseArrayShapeUnsealedType(TokenIterator $tokens): Ast\Type\ArrayShapeUnsealedTypeNode
{
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();

$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);

$valueType = $this->parse($tokens);
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);

$keyType = null;
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);

$keyType = $valueType;
$valueType = $this->parse($tokens);
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
}

$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);

return $this->enrichWithAttributes(
$tokens,
new Ast\Type\ArrayShapeUnsealedTypeNode($valueType, $keyType),
$startLine,
$startIndex
);
}

/**
* @phpstan-impure
*/
private function parseListShapeUnsealedType(TokenIterator $tokens): Ast\Type\ArrayShapeUnsealedTypeNode
{
$startLine = $tokens->currentTokenLine();
$startIndex = $tokens->currentTokenIndex();

$tokens->consumeTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET);
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);

$valueType = $this->parse($tokens);
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);

$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_ANGLE_BRACKET);

return $this->enrichWithAttributes(
$tokens,
new Ast\Type\ArrayShapeUnsealedTypeNode($valueType, null),
$startLine,
$startIndex
);
}

/**
* @phpstan-impure
*/
Expand Down
9 changes: 8 additions & 1 deletion src/Printer/Printer.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayShapeUnsealedTypeNode;
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode;
use PHPStan\PhpDocParser\Ast\Type\CallableTypeParameterNode;
Expand Down Expand Up @@ -229,6 +230,12 @@ function (PhpDocChildNode $child): string {
$isOptional = $node->isOptional ? '=' : '';
return trim("{$type}{$isReference}{$isVariadic}{$node->parameterName}") . $isOptional;
}
if ($node instanceof ArrayShapeUnsealedTypeNode) {
if ($node->keyType !== null) {
return sprintf('<%s, %s>', $this->printType($node->keyType), $this->printType($node->valueType));
}
return sprintf('<%s>', $this->printType($node->valueType));
}
if ($node instanceof DoctrineAnnotation) {
return (string) $node;
}
Expand Down Expand Up @@ -366,7 +373,7 @@ private function printType(TypeNode $node): string
}, $node->items);

if (! $node->sealed) {
$items[] = '...';
$items[] = '...' . ($node->unsealedType === null ? '' : $this->print($node->unsealedType));
}

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

0 comments on commit 5ceb0e3

Please sign in to comment.