Skip to content

Commit 1056f69

Browse files
authored
Add support for the key-of<...> and value-of<...> types
* Add support for the `key-of<...>` type * Add support for the `value-of` type and add bsaic tests * Add more tests
1 parent a30769f commit 1056f69

File tree

6 files changed

+159
-0
lines changed

6 files changed

+159
-0
lines changed

src/PhpDoc/TypeNodeResolver.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,18 @@ private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $na
505505

506506
return IntegerRangeType::fromInterval($min, $max);
507507
}
508+
} elseif ($mainTypeName === 'key-of') {
509+
if (count($genericTypes) === 1) { // key-of<ValueType>
510+
return $genericTypes[0]->getIterableKeyType();
511+
}
512+
513+
return new ErrorType();
514+
} elseif ($mainTypeName === 'value-of') {
515+
if (count($genericTypes) === 1) { // value-of<ValueType>
516+
return $genericTypes[0]->getIterableValueType();
517+
}
518+
519+
return new ErrorType();
508520
}
509521

510522
$mainType = $this->resolveIdentifierTypeNode($typeNode->type, $nameScope);

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ public function dataFileAsserts(): iterable
5555
yield from $this->gatherAssertTypes(__DIR__ . '/data/native-types.php');
5656
yield from $this->gatherAssertTypes(__DIR__ . '/data/type-change-after-array-access-assignment.php');
5757
yield from $this->gatherAssertTypes(__DIR__ . '/data/iterator_to_array.php');
58+
yield from $this->gatherAssertTypes(__DIR__ . '/data/key-of.php');
59+
yield from $this->gatherAssertTypes(__DIR__ . '/data/value-of.php');
5860

5961
if (self::$useStaticReflectionProvider || extension_loaded('ds')) {
6062
yield from $this->gatherAssertTypes(__DIR__ . '/data/ext-ds.php');
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace KeyOfType;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
public const JFK = 'jfk';
11+
public const LGA = 'lga';
12+
13+
private const ALL = [
14+
self::JFK => 'John F. Kennedy Airport',
15+
self::LGA => 'La Guardia Airport',
16+
];
17+
18+
/**
19+
* @param key-of<self::ALL> $code
20+
*/
21+
public static function foo(string $code): void
22+
{
23+
assertType('\'jfk\'|\'lga\'', $code);
24+
}
25+
26+
/**
27+
* @param key-of<'jfk'> $code
28+
*/
29+
public static function bar(string $code): void
30+
{
31+
assertType('string', $code);
32+
}
33+
34+
/**
35+
* @param key-of<'jfk'|'lga'> $code
36+
*/
37+
public static function baz(string $code): void
38+
{
39+
assertType('string', $code);
40+
}
41+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace ValueOfType;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
private const ALL = [
11+
'jfk',
12+
'lga',
13+
];
14+
15+
/**
16+
* @param value-of<self::ALL> $code
17+
*/
18+
public static function foo(string $code): void
19+
{
20+
assertType('\'jfk\'|\'lga\'', $code);
21+
}
22+
23+
/**
24+
* @param value-of<'jfk'> $code
25+
*/
26+
public static function bar(string $code): void
27+
{
28+
assertType('string', $code);
29+
}
30+
31+
/**
32+
* @param value-of<'jfk'|'lga'> $code
33+
*/
34+
public static function baz(string $code): void
35+
{
36+
assertType('string', $code);
37+
}
38+
}

tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,14 @@ public function testCallMethods(): void
500500
1751,
501501
'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type',
502502
],
503+
[
504+
'Parameter #1 $code of method Test\\KeyOfParam::foo() expects \'jfk\'|\'lga\', \'sfo\' given.',
505+
1777,
506+
],
507+
[
508+
'Parameter #1 $code of method Test\\ValueOfParam::foo() expects \'John F. Kennedy…\'|\'La Guardia Airport\', \'Newark Liberty…\' given.',
509+
1802,
510+
],
503511
]);
504512
}
505513

@@ -787,6 +795,14 @@ public function testCallMethodsOnThisOnly(): void
787795
1751,
788796
'See: https://phpstan.org/blog/solving-phpstan-error-unable-to-resolve-template-type',
789797
],
798+
[
799+
'Parameter #1 $code of method Test\\KeyOfParam::foo() expects \'jfk\'|\'lga\', \'sfo\' given.',
800+
1777,
801+
],
802+
[
803+
'Parameter #1 $code of method Test\\ValueOfParam::foo() expects \'John F. Kennedy…\'|\'La Guardia Airport\', \'Newark Liberty…\' given.',
804+
1802,
805+
],
790806
]);
791807
}
792808

tests/PHPStan/Rules/Methods/data/call-methods.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1752,3 +1752,53 @@ public function doBar()
17521752
}
17531753

17541754
}
1755+
1756+
class KeyOfParam
1757+
{
1758+
public const JFK = 'jfk';
1759+
public const LGA = 'lga';
1760+
1761+
private const ALL = [
1762+
self::JFK => 'John F. Kennedy Airport',
1763+
self::LGA => 'La Guardia Airport',
1764+
];
1765+
1766+
/**
1767+
* @param key-of<self::ALL> $code
1768+
*/
1769+
public function foo(string $code): void
1770+
{
1771+
}
1772+
1773+
public function test(): void
1774+
{
1775+
$this->foo(KeyOfParam::JFK);
1776+
$this->foo('jfk');
1777+
$this->foo('sfo');
1778+
}
1779+
}
1780+
1781+
class ValueOfParam
1782+
{
1783+
public const JFK = 'jfk';
1784+
public const LGA = 'lga';
1785+
1786+
public const ALL = [
1787+
self::JFK => 'John F. Kennedy Airport',
1788+
self::LGA => 'La Guardia Airport',
1789+
];
1790+
1791+
/**
1792+
* @param value-of<self::ALL> $code
1793+
*/
1794+
public function foo(string $code): void
1795+
{
1796+
}
1797+
1798+
public function test(): void
1799+
{
1800+
$this->foo(ValueOfParam::ALL[ValueOfParam::JFK]);
1801+
$this->foo('John F. Kennedy Airport');
1802+
$this->foo('Newark Liberty International');
1803+
}
1804+
}

0 commit comments

Comments
 (0)