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 845dcbf

Browse files
Added ability to parse object shapes.
1 parent c857e8a commit 845dcbf

File tree

8 files changed

+177
-59
lines changed

8 files changed

+177
-59
lines changed

‎doc/grammars/type.abnf‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,24 @@ Array
6060
= 1*(TokenSquareBracketOpen TokenSquareBracketClose)
6161

6262
ArrayShape
63-
=TokenCurlyBracketOpenArrayShapeItem*(TokenCommaArrayShapeItem) TokenCurlyBracketClose
63+
=Shape
6464

6565
ArrayShapeItem
66+
= ShapeItem
67+
68+
Shape
69+
= TokenCurlyBracketOpen ShapeItem *(TokenComma ShapeItem) TokenCurlyBracketClose
70+
71+
ShapeItem
6672
= (ConstantString / ConstantInt / TokenIdentifier) TokenNullable TokenColon Type
6773
/ Type
6874

75+
ObjectShape
76+
= Shape
77+
78+
ObjectShapeItem
79+
= ShapeItem
80+
6981
; ---------------------------------------------------------------------------- ;
7082
; ConstantExpr ;
7183
; ---------------------------------------------------------------------------- ;

‎src/Ast/Type/ArrayShapeItemNode.php‎

Lines changed: 1 addition & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,7 @@
22

33
namespace PHPStan\PhpDocParser\Ast\Type;
44

5-
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprIntegerNode;
6-
use PHPStan\PhpDocParser\Ast\ConstExpr\ConstExprStringNode;
7-
use PHPStan\PhpDocParser\Ast\NodeAttributes;
8-
use function sprintf;
9-
10-
class ArrayShapeItemNode implements TypeNode
5+
class ArrayShapeItemNode extends ShapeItemNode implements TypeNode
116
{
127

13-
use NodeAttributes;
14-
15-
/** @var ConstExprIntegerNode|ConstExprStringNode|IdentifierTypeNode|null */
16-
public $keyName;
17-
18-
/** @var bool */
19-
public $optional;
20-
21-
/** @var TypeNode */
22-
public $valueType;
23-
24-
/**
25-
* @param ConstExprIntegerNode|ConstExprStringNode|IdentifierTypeNode|null $keyName
26-
*/
27-
public function __construct($keyName, bool $optional, TypeNode $valueType)
28-
{
29-
$this->keyName = $keyName;
30-
$this->optional = $optional;
31-
$this->valueType = $valueType;
32-
}
33-
34-
35-
public function __toString(): string
36-
{
37-
if ($this->keyName !== null) {
38-
return sprintf(
39-
'%s%s: %s',
40-
(string) $this->keyName,
41-
$this->optional ? '?' : '',
42-
(string) $this->valueType
43-
);
44-
}
45-
46-
return (string) $this->valueType;
47-
}
48-
498
}

‎src/Ast/Type/ObjectShapeItemNode.php‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\Type;
4+
5+
class ObjectShapeItemNode extends ShapeItemNode implements TypeNode
6+
{
7+
8+
}

‎src/Ast/Type/ObjectShapeNode.php‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\PhpDocParser\Ast\Type;
4+
5+
use PHPStan\PhpDocParser\Ast\NodeAttributes;
6+
use function implode;
7+
8+
class ObjectShapeNode implements TypeNode
9+
{
10+
11+
use NodeAttributes;
12+
13+
/** @var IdentifierTypeNode $identitier */
14+
public $identifier;
15+
16+
/** @var ShapeItemNode[] */
17+
public $items;
18+
19+
public function __construct(IdentifierTypeNode $identifier, array $items)
20+
{
21+
$this->identifier = $identifier;
22+
$this->items = $items;
23+
}
24+
25+
26+
public function __toString(): string
27+
{
28+
return "{$this->identifier}{" . implode(', ', $this->items) . '}';
29+
}
30+
31+
}

‎src/Ast/Type/ShapeItemNode.php‎

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

‎src/Parser/TypeParser.php‎

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ private function parseAtomic(TokenIterator $tokens): Ast\Type\TypeNode
129129
if ($tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_SQUARE_BRACKET)) {
130130
$type = $this->tryParseArrayOrOffsetAccess($tokens, $type);
131131
}
132+
} elseif ($type->name === 'object' && $tokens->isCurrentTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET) && !$tokens->isPrecededByHorizontalWhitespace()) {
133+
$type = $this->parseObjectShape($tokens, $type);
132134
}
133135

134136
return $type;
@@ -468,53 +470,76 @@ private function tryParseArrayOrOffsetAccess(TokenIterator $tokens, Ast\Type\Typ
468470
return $type;
469471
}
470472

471-
472473
/** @phpstan-impure */
473-
private function parseArrayShape(TokenIterator $tokens, Ast\Type\TypeNode $type): Ast\Type\ArrayShapeNode
474+
private function parseShape(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $type): array
474475
{
475476
$tokens->consumeTokenType(Lexer::TOKEN_OPEN_CURLY_BRACKET);
476477
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
477-
return newAst\Type\ArrayShapeNode([]);
478+
return [];
478479
}
479480

480481
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
481-
$items = [$this->parseArrayShapeItem($tokens)];
482+
$items = [$this->parseShapeItem($tokens, $type)];
482483

483484
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
484485
while ($tokens->tryConsumeTokenType(Lexer::TOKEN_COMMA)) {
485486
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
486487
if ($tokens->tryConsumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET)) {
487488
// trailing comma case
488-
return newAst\Type\ArrayShapeNode($items);
489+
return $items;
489490
}
490491

491-
$items[] = $this->parseArrayShapeItem($tokens);
492+
$items[] = $this->parseShapeItem($tokens, $type);
492493
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
493494
}
494495

495496
$tokens->tryConsumeTokenType(Lexer::TOKEN_PHPDOC_EOL);
496497
$tokens->consumeTokenType(Lexer::TOKEN_CLOSE_CURLY_BRACKET);
497498

499+
return $items;
500+
}
501+
502+
/** @phpstan-impure */
503+
private function parseArrayShape(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $type): Ast\Type\ArrayShapeNode
504+
{
505+
$items = $this->parseShape($tokens, $type);
506+
498507
return new Ast\Type\ArrayShapeNode($items);
499508
}
500509

510+
/** @phpstan-impure */
511+
private function parseObjectShape(TokenIterator $tokens, Ast\Type\IdentifierTypeNode $type): Ast\Type\ObjectShapeNode
512+
{
513+
$items = $this->parseShape($tokens, $type);
514+
515+
return new Ast\Type\ObjectShapeNode($type, $items);
516+
}
517+
501518

502519
/** @phpstan-impure */
503-
private function parseArrayShapeItem(TokenIterator $tokens): Ast\Type\ArrayShapeItemNode
520+
private function parseShapeItem(TokenIterator $tokens, Ast\Type\IdentifierTypeNode$type): Ast\Type\ShapeItemNode
504521
{
505522
try {
506523
$tokens->pushSavePoint();
507-
$key = $this->parseArrayShapeKey($tokens);
524+
$key = $this->parseShapeKey($tokens);
508525
$optional = $tokens->tryConsumeTokenType(Lexer::TOKEN_NULLABLE);
509526
$tokens->consumeTokenType(Lexer::TOKEN_COLON);
510527
$value = $this->parse($tokens);
511528
$tokens->dropSavePoint();
512529

530+
if ($type->name === 'object') {
531+
return new Ast\Type\ObjectShapeItemNode($key, $optional, $value);
532+
}
533+
513534
return new Ast\Type\ArrayShapeItemNode($key, $optional, $value);
514535
} catch (ParserException $e) {
515536
$tokens->rollback();
516537
$value = $this->parse($tokens);
517538

539+
if ($type->name === 'object') {
540+
return new Ast\Type\ObjectShapeItemNode(null, false, $value);
541+
}
542+
518543
return new Ast\Type\ArrayShapeItemNode(null, false, $value);
519544
}
520545
}
@@ -523,7 +548,7 @@ private function parseArrayShapeItem(TokenIterator $tokens): Ast\Type\ArrayShape
523548
* @phpstan-impure
524549
* @return Ast\ConstExpr\ConstExprIntegerNode|Ast\ConstExpr\ConstExprStringNode|Ast\Type\IdentifierTypeNode
525550
*/
526-
private function parseArrayShapeKey(TokenIterator $tokens)
551+
private function parseShapeKey(TokenIterator $tokens)
527552
{
528553
if ($tokens->isCurrentTokenType(Lexer::TOKEN_INTEGER)) {
529554
$key = new Ast\ConstExpr\ConstExprIntegerNode($tokens->currentTokenValue());

‎tests/PHPStan/Parser/PhpDocParserTest.php‎

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
use PHPStan\PhpDocParser\Ast\Type\ConstTypeNode;
3939
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
4040
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
41+
use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode;
42+
use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode;
4143
use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
4244
use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode;
4345
use PHPStan\PhpDocParser\Lexer\Lexer;
@@ -880,13 +882,22 @@ public function provideVarTagsData(): Iterator
880882
new PhpDocNode([
881883
new PhpDocTagNode(
882884
'@psalm-type',
883-
new InvalidTagValueNode(
884-
'Unexpected token "{", expected \'*/\' at offset 44',
885-
new ParserException(
886-
'{',
887-
Lexer::TOKEN_OPEN_CURLY_BRACKET,
888-
44,
889-
Lexer::TOKEN_CLOSE_PHPDOC
885+
new TypeAliasTagValueNode(
886+
'PARTSTRUCTURE_PARAM',
887+
new ObjectShapeNode(
888+
new IdentifierTypeNode('object'),
889+
[
890+
new ObjectShapeItemNode(
891+
new IdentifierTypeNode('attribute'),
892+
false,
893+
new IdentifierTypeNode('string')
894+
),
895+
new ObjectShapeItemNode(
896+
new IdentifierTypeNode('value'),
897+
true,
898+
new IdentifierTypeNode('string')
899+
),
900+
]
890901
)
891902
)
892903
),

‎tests/PHPStan/Parser/TypeParserTest.php‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
2020
use PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode;
2121
use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode;
22+
use PHPStan\PhpDocParser\Ast\Type\ObjectShapeItemNode;
23+
use PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode;
2224
use PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode;
2325
use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode;
2426
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
@@ -1266,6 +1268,27 @@ public function provideParseData(): array
12661268
)
12671269
),
12681270
],
1271+
1272+
[
1273+
'object{a: int, b?: ?int}',
1274+
new ObjectShapeNode(
1275+
new IdentifierTypeNode('object'),
1276+
[
1277+
new ObjectShapeItemNode(
1278+
new IdentifierTypeNode('a'),
1279+
false,
1280+
new IdentifierTypeNode('int')
1281+
),
1282+
new ObjectShapeItemNode(
1283+
new IdentifierTypeNode('b'),
1284+
true,
1285+
new NullableTypeNode(
1286+
new IdentifierTypeNode('int')
1287+
)
1288+
),
1289+
]
1290+
),
1291+
],
12691292
];
12701293
}
12711294

0 commit comments

Comments
(0)

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