Skip to content

Commit 9d318e1

Browse files
committed
Support array<K, V>
1 parent 8c4a5ab commit 9d318e1

File tree

3 files changed

+35
-25
lines changed

3 files changed

+35
-25
lines changed

composer.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Internal/ContextualTypeParser.php

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Typhoon\PHPStanTypeParser\TypeContext;
2323
use Typhoon\Type\Type;
2424
use function Typhoon\Type\andT;
25+
use function Typhoon\Type\arrayT;
2526
use function Typhoon\Type\floatRangeT;
2627
use function Typhoon\Type\floatT;
2728
use function Typhoon\Type\intRangeT;
@@ -30,6 +31,7 @@
3031
use function Typhoon\Type\orT;
3132
use function Typhoon\Type\stringT;
3233
use const Typhoon\Type\arrayKeyT;
34+
use const Typhoon\Type\arrayT;
3335
use const Typhoon\Type\boolT;
3436
use const Typhoon\Type\falseT;
3537
use const Typhoon\Type\floatT;
@@ -58,11 +60,6 @@
5860
*/
5961
final class ContextualTypeParser
6062
{
61-
/**
62-
* @var ?non-empty-array<non-empty-string, Type|\Closure(list<TypeNode>): Type>
63-
*/
64-
private static ?array $identifierMap = null;
65-
6663
public function __construct(
6764
private readonly CustomTypeParser $customTypeParser,
6865
private readonly TypeContext $context,
@@ -106,22 +103,18 @@ private static function parseConstExpr(ConstExprNode $node): Type
106103
*/
107104
private function parseIdentifier(string $name, array $genericNodes = []): Type
108105
{
109-
self::$identifierMap ??= [
106+
$atomic = match ($name) {
110107
'never' => neverT,
111108
'void' => voidT,
112109
'null' => nullT,
113110
'false' => falseT,
114111
'true' => trueT,
115-
'bool' => boolT,
116-
'boolean' => boolT,
117-
'int' => self::parseInt(...),
118-
'integer' => self::parseInt(...),
112+
'bool', 'boolean' => boolT,
119113
'positive-int' => positiveIntT,
120114
'negative-int' => negativeIntT,
121115
'non-negative-int' => nonNegativeIntT,
122116
'non-positive-int' => nonPositiveIntT,
123117
'non-zero-int' => nonZeroIntT,
124-
'float' => self::parseFloat(...),
125118
'non-empty-string' => nonEmptyStringT,
126119
'lowercase-string' => lowercaseStringT,
127120
'numeric-string' => numericStringT,
@@ -131,32 +124,44 @@ private function parseIdentifier(string $name, array $genericNodes = []): Type
131124
'numeric' => numericT,
132125
'scalar' => scalarT,
133126
'mixed' => mixedT,
134-
];
135-
136-
$type = self::$identifierMap[$name] ?? null;
127+
default => null,
128+
};
137129

138-
if ($type instanceof Type) {
130+
if ($atomic !== null) {
139131
if ($genericNodes !== []) {
140132
throw new \LogicException();
141133
}
142134

143-
return $type;
135+
return $atomic;
144136
}
145137

146-
if ($type instanceof \Closure) {
147-
return $type($genericNodes);
138+
if ($name === 'int' || $name === 'integer') {
139+
return $this->parseInt($genericNodes);
140+
}
141+
142+
if ($name === 'float') {
143+
return $this->parseFloat($genericNodes);
148144
}
149145

150146
$templateArguments = array_map($this->parseTypeNode(...), $genericNodes);
151147

148+
if ($name === 'array') {
149+
return match ($number = \count($templateArguments)) {
150+
0 => arrayT,
151+
1 => arrayT(valueType: $templateArguments[0]),
152+
2 => arrayT($templateArguments[0], $templateArguments[1]),
153+
default => throw new \LogicException(\sprintf('array type should have at most 2 type arguments, got %d', $number)),
154+
};
155+
}
156+
152157
return $this->customTypeParser->parseCustomType($name, $templateArguments, $this->context)
153158
?? $this->context->resolveNameAsType($name, $templateArguments);
154159
}
155160

156161
/**
157162
* @param list<TypeNode> $genericNodes
158163
*/
159-
private static function parseInt(array $genericNodes): Type
164+
private function parseInt(array $genericNodes): Type
160165
{
161166
return match (\count($genericNodes)) {
162167
0 => intT,
@@ -174,7 +179,7 @@ private static function parseInt(array $genericNodes): Type
174179
/**
175180
* @param list<TypeNode> $genericNodes
176181
*/
177-
private static function parseFloat(array $genericNodes): Type
182+
private function parseFloat(array $genericNodes): Type
178183
{
179184
return match (\count($genericNodes)) {
180185
0 => floatT,
@@ -193,7 +198,7 @@ private static function parseFloat(array $genericNodes): Type
193198
* @param 'min'|'max' $name
194199
* @return ?numeric-string
195200
*/
196-
private static function parseRangeLimit(TypeNode $type, string $name, bool $float): ?string
201+
private function parseRangeLimit(TypeNode $type, string $name, bool $float): ?string
197202
{
198203
if ($type instanceof IdentifierTypeNode) {
199204
if ($type->name === $name) {

tests/PHPStanTypeParserTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Typhoon\PHPStanTypeParser\Internal\ContextualTypeParser;
1111
use Typhoon\Type\Type;
1212
use function Typhoon\Type\andT;
13+
use function Typhoon\Type\arrayT;
1314
use function Typhoon\Type\floatRangeT;
1415
use function Typhoon\Type\floatT;
1516
use function Typhoon\Type\intRangeT;
@@ -19,6 +20,7 @@
1920
use function Typhoon\Type\orT;
2021
use function Typhoon\Type\stringT;
2122
use const Typhoon\Type\arrayKeyT;
23+
use const Typhoon\Type\arrayT;
2224
use const Typhoon\Type\boolT;
2325
use const Typhoon\Type\falseT;
2426
use const Typhoon\Type\floatT;
@@ -96,6 +98,9 @@ private static function cases(): \Generator
9698
yield '(int|string)|float' => orT(orT(intT, stringT), floatT);
9799
yield 'int&string' => andT(intT, stringT);
98100
yield '(int&string)&float' => andT(andT(intT, stringT), floatT);
101+
yield 'array' => arrayT;
102+
yield 'array<string>' => arrayT(valueType: stringT);
103+
yield 'array<int, string>' => arrayT(intT, stringT);
99104
yield 'mixed' => mixedT;
100105
yield \stdClass::class => objectT(\stdClass::class);
101106
yield \Stringable::class => objectT(\Stringable::class);

0 commit comments

Comments
 (0)