Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit f93ce08

Browse files
Array shapes support
1 parent ab518a5 commit f93ce08

File tree

6 files changed

+314
-0
lines changed

6 files changed

+314
-0
lines changed

‎doc/grammars/type.abnf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ CallableReturnType
5252
Array
5353
= 1*(TokenSquareBracketOpen TokenSquareBracketClose)
5454

55+
ArrayShape
56+
= TokenCurlyBracketOpen ArrayItem *(TokenComma ArrayItem) TokenCurlyBracketClose
57+
58+
ArrayItem
59+
= (ConstantString / ConstantInt / TokenIdentifier) TokenNullable TokenColon Type
60+
/ Type
5561

5662
; ---------------------------------------------------------------------------- ;
5763
; ConstantExpr ;
@@ -139,6 +145,12 @@ TokenSquareBracketOpen
139145
TokenSquareBracketClose
140146
= "]" *ByteHorizontalWs
141147

148+
TokenCurlyBracketOpen
149+
= "{" *ByteHorizontalWs
150+
151+
TokenCurlyBracketClose
152+
= "}" *ByteHorizontalWs
153+
142154
TokenComma
143155
= "," *ByteHorizontalWs
144156

‎src/Ast/Type/ArrayItemNode.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\Type;
4+
5+
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
6+
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprNode;
7+
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
8+
9+
class ArrayItemNode implements TypeNode
10+
{
11+
12+
/** @var ConstExprStringNode|ConstExprIntegerNode|IdentifierTypeNode|null */
13+
public $keyName;
14+
15+
/** @var bool */
16+
public $keyOptional;
17+
18+
/** @var TypeNode */
19+
public $valueType;
20+
21+
/**
22+
* @param ConstExprStringNode|ConstExprIntegerNode|IdentifierTypeNode|null $keyName
23+
*/
24+
public function __construct($keyName, bool $keyOptional, TypeNode $valueType)
25+
{
26+
$this->keyName = $keyName;
27+
$this->keyOptional = $keyOptional;
28+
$this->valueType = $valueType;
29+
}
30+
31+
32+
public function __toString(): string
33+
{
34+
if ($this->keyName !== null) {
35+
return sprintf(
36+
'%s%s: %s',
37+
(string) $this->keyName,
38+
$this->keyOptional ? '?' : '',
39+
(string) $this->valueType
40+
);
41+
} else {
42+
return (string) $this->valueType;
43+
}
44+
}
45+
46+
}

‎src/Ast/Type/ArrayShapeNode.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\Type;
4+
5+
class ArrayShapeNode implements TypeNode
6+
{
7+
8+
/** @var ArrayItemNode[] */
9+
public $items;
10+
11+
public function __construct(array $items)
12+
{
13+
$this->items = $items;
14+
}
15+
16+
17+
public function __toString(): string
18+
{
19+
return 'array{' . implode(', ', array_map(static function (ArrayItemNode $node): string {
20+
return (string) $node;
21+
}, $this->items)) . '}';
22+
}
23+
24+
}

‎src/Lexer/Lexer.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ class Lexer
1818
public const TOKEN_CLOSE_ANGLE_BRACKET = 7;
1919
public const TOKEN_OPEN_SQUARE_BRACKET = 8;
2020
public const TOKEN_CLOSE_SQUARE_BRACKET = 9;
21+
public const TOKEN_OPEN_CURLY_BRACKET = 30;
22+
public const TOKEN_CLOSE_CURLY_BRACKET = 31;
2123
public const TOKEN_COMMA = 10;
2224
public const TOKEN_COLON = 29;
2325
public const TOKEN_VARIADIC = 11;
@@ -50,6 +52,8 @@ class Lexer
5052
self::TOKEN_CLOSE_ANGLE_BRACKET => '\'>\'',
5153
self::TOKEN_OPEN_SQUARE_BRACKET => '\'[\'',
5254
self::TOKEN_CLOSE_SQUARE_BRACKET => '\']\'',
55+
self::TOKEN_OPEN_CURLY_BRACKET => '\'{\'',
56+
self::TOKEN_CLOSE_CURLY_BRACKET => '\'}\'',
5357
self::TOKEN_COMMA => '\',\'',
5458
self::TOKEN_COLON => '\':\'',
5559
self::TOKEN_VARIADIC => '\'...\'',
@@ -123,6 +127,8 @@ private function initialize(): void
123127
self::TOKEN_CLOSE_ANGLE_BRACKET => '>',
124128
self::TOKEN_OPEN_SQUARE_BRACKET => '\\[',
125129
self::TOKEN_CLOSE_SQUARE_BRACKET => '\\]',
130+
self::TOKEN_OPEN_CURLY_BRACKET => '\\{',
131+
self::TOKEN_CLOSE_CURLY_BRACKET => '\\}',
126132

127133
self::TOKEN_COMMA => ',',
128134
self::TOKEN_VARIADIC => '\\.\\.\\.',

‎src/Parser/TypeParser.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace PHPStan\PhpDocParser\Parser;
44

55
use PHPStan\PhpDocParser\Ast;
6+
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
7+
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
68
use PHPStan\PhpDocParser\Lexer\Lexer;
79

810
class TypeParser
@@ -53,6 +55,9 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
5355

5456
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
5557
$type = $this->tryParseArray($tokens, $type);
58+
59+
} elseif ($type->name === 'array' && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET)) {
60+
$type = $this->parseArrayShape($tokens, $type);
5661
}
5762
}
5863

@@ -93,6 +98,9 @@ private function parseNullable(TokenIterator $tokens): Ast\Type\TypeNode
9398

9499
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
95100
$type = $this->parseGeneric($tokens, $type);
101+
102+
} elseif ($type->name === 'array' && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET)) {
103+
$type = $this->parseArrayShape($tokens, $type);
96104
}
97105

98106
return new Ast\Type\NullableTypeNode($type);
@@ -167,6 +175,9 @@ private function parseCallableReturnType(TokenIterator $tokens): Ast\Type\TypeNo
167175

168176
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_ANGLE_BRACKET)) {
169177
$type = $this->parseGeneric($tokens, $type);
178+
179+
} elseif ($type->name === 'array' && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET)) {
180+
$type = $this->parseArrayShape($tokens, $type);
170181
}
171182
}
172183

@@ -208,4 +219,65 @@ private function tryParseArray(TokenIterator $tokens, Ast\Type\TypeNode $type):
208219
return $type;
209220
}
210221

222+
223+
private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\TypeNode
224+
{
225+
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET);
226+
$items = [$this->parseArrayShapeItem($tokens)];
227+
228+
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
229+
$items[] = $this->parseArrayShapeItem($tokens);
230+
}
231+
232+
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
233+
234+
return new Ast\Type\ArrayShapeNode($items);
235+
}
236+
237+
238+
private function parseArrayShapeItem(TokenIterator $tokens): Ast\Type\ArrayItemNode
239+
{
240+
try {
241+
$tokens->pushSavePoint();
242+
$key = $this->parseArrayShapeKey($tokens);
243+
$optional = $tokens->tryConsumeTokenType(Lexer::TOKEN_NULLABLE);
244+
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
245+
$value = $this->parse($tokens);
246+
$tokens->dropSavePoint();
247+
248+
return new Ast\Type\ArrayItemNode($key, $optional, $value);
249+
} catch (\PHPStan\PhpDocParser\Parser\ParserException $e) {
250+
$tokens->rollback();
251+
$value = $this->parse($tokens);
252+
253+
return new Ast\Type\ArrayItemNode(null, $optional, $value);
254+
}
255+
}
256+
257+
/**
258+
* @return ConstExprStringNode|ConstExprIntegerNode|IdentifierTypeNode
259+
*/
260+
private function parseArrayShapeKey(TokenIterator $tokens)
261+
{
262+
if ($tokens->isCurrentTokenType(Lexer::TOKEN_SINGLE_QUOTED_STRING)) {
263+
$key = new ConstExprStringNode($tokens->currentTokenValue());
264+
$tokens->next();
265+
266+
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_DOUBLE_QUOTED_STRING)) {
267+
$key = new ConstExprStringNode($tokens->currentTokenValue());
268+
$tokens->next();
269+
270+
} elseif ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) {
271+
$key = new ConstExprIntegerNode($tokens->currentTokenValue());
272+
$tokens->next();
273+
274+
} else {
275+
$key = new Ast\Type\IdentifierTypeNode($tokens->currentTokenValue());
276+
$tokens->consumeTokenType(Lexer::TOKEN_IDENTIFIER);
277+
}
278+
279+
return $key;
280+
}
281+
282+
211283
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /