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 bab0af7

Browse files
authored
!array_key_exists() should imply array for PHP8+
1 parent 300b7b2 commit bab0af7

14 files changed

+291
-10
lines changed

‎src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use PHPStan\Analyser\TypeSpecifierAwareExtension;
1414
use PHPStan\Analyser\TypeSpecifierContext;
1515
use PHPStan\DependencyInjection\AutowiredService;
16+
use PHPStan\Php\PhpVersion;
1617
use PHPStan\Reflection\FunctionReflection;
1718
use PHPStan\Type\Accessory\HasOffsetType;
1819
use PHPStan\Type\Accessory\NonEmptyArrayType;
@@ -31,6 +32,12 @@ final class ArrayKeyExistsFunctionTypeSpecifyingExtension implements FunctionTyp
3132

3233
private TypeSpecifier $typeSpecifier;
3334

35+
public function __construct(
36+
private PhpVersion $phpVersion,
37+
)
38+
{
39+
}
40+
3441
public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
3542
{
3643
$this->typeSpecifier = $typeSpecifier;
@@ -116,6 +123,22 @@ public function specifyTypes(
116123
new ArrayType(new MixedType(), new MixedType()),
117124
new HasOffsetType($keyType),
118125
);
126+
} elseif ($this->phpVersion->throwsValueErrorForInternalFunctions()) {
127+
$specifiedTypes = $this->typeSpecifier->create(
128+
$array,
129+
new HasOffsetType($keyType),
130+
$context,
131+
$scope,
132+
);
133+
134+
$type = new ArrayType(new MixedType(), new MixedType());
135+
$type = $type->unsetOffset($keyType);
136+
return $specifiedTypes->unionWith($this->typeSpecifier->create(
137+
$array,
138+
$type,
139+
$context->negate(),
140+
$scope,
141+
));
119142
} else {
120143
$type = new HasOffsetType($keyType);
121144
}

‎tests/PHPStan/Analyser/TypeSpecifierTest.php‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,7 +1040,7 @@ public static function dataCondition(): iterable
10401040
'$array' => 'non-empty-array',
10411041
],
10421042
[
1043-
'$array' => '~hasOffset(\'bar\')|hasOffset(\'foo\')',
1043+
'$array' => PHP_VERSION_ID < 80000 ? '~hasOffset(\'bar\')|hasOffset(\'foo\')' : "array<mixed~'foo', mixed> & ~hasOffset('bar')|hasOffset('foo')",
10441044
],
10451045
],
10461046
[
@@ -1055,7 +1055,7 @@ public static function dataCondition(): iterable
10551055
]),
10561056
)),
10571057
[
1058-
'$array' => '~hasOffset(\'bar\')|hasOffset(\'foo\')',
1058+
'$array' => PHP_VERSION_ID < 80000 ? '~hasOffset(\'bar\')|hasOffset(\'foo\')' : "array<mixed~'foo', mixed> & ~hasOffset('bar')|hasOffset('foo')",
10591059
],
10601060
[
10611061
'$array' => 'non-empty-array',
@@ -1070,7 +1070,7 @@ public static function dataCondition(): iterable
10701070
'$array' => 'non-empty-array&hasOffset(\'foo\')',
10711071
],
10721072
[
1073-
'$array' => '~hasOffset(\'foo\')',
1073+
'$array' => PHP_VERSION_ID < 80000 ? '~hasOffset(\'foo\')' : "array<mixed~'foo', mixed> & ~hasOffset('foo')",
10741074
],
10751075
],
10761076
[
@@ -1088,7 +1088,7 @@ public static function dataCondition(): iterable
10881088
'$array' => 'non-empty-array',
10891089
],
10901090
[
1091-
'$array' => '~hasOffset(\'bar\')|hasOffset(\'foo\')',
1091+
'$array' => PHP_VERSION_ID < 80000 ? '~hasOffset(\'bar\')|hasOffset(\'foo\')' : "array<mixed~'foo', mixed> & ~hasOffset('bar')|hasOffset('foo')",
10921092
],
10931093
],
10941094
[
@@ -1103,7 +1103,7 @@ public static function dataCondition(): iterable
11031103
]),
11041104
)),
11051105
[
1106-
'$array' => '~hasOffset(\'bar\')|hasOffset(\'foo\')',
1106+
'$array' => PHP_VERSION_ID < 80000 ? '~hasOffset(\'bar\')|hasOffset(\'foo\')' : "array<mixed~'foo', mixed> & ~hasOffset('bar')|hasOffset('foo')",
11071107
],
11081108
[
11091109
'$array' => 'non-empty-array',
@@ -1118,7 +1118,7 @@ public static function dataCondition(): iterable
11181118
'$array' => 'non-empty-array&hasOffset(\'foo\')',
11191119
],
11201120
[
1121-
'$array' => '~hasOffset(\'foo\')',
1121+
'$array' => PHP_VERSION_ID < 80000 ? '~hasOffset(\'foo\')' : "array<mixed~'foo', mixed> & ~hasOffset('foo')",
11221122
],
11231123
],
11241124
[
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types=1);
4+
5+
namespace Bug13270bPhp8;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
class Test
10+
{
11+
/**
12+
* @param mixed[] $data
13+
* @return mixed[]
14+
*/
15+
public function parseData(array $data): array
16+
{
17+
if (isset($data['price'])) {
18+
assertType('mixed~null', $data['price']);
19+
if (!array_key_exists('priceWithVat', $data['price'])) {
20+
$data['price']['priceWithVat'] = null;
21+
}
22+
assertType("non-empty-array&hasOffsetValue('priceWithVat', mixed)", $data['price']);
23+
if (!array_key_exists('priceWithoutVat', $data['price'])) {
24+
$data['price']['priceWithoutVat'] = null;
25+
}
26+
assertType("non-empty-array&hasOffsetValue('priceWithoutVat', mixed)&hasOffsetValue('priceWithVat', mixed)", $data['price']);
27+
}
28+
return $data;
29+
}
30+
}

‎tests/PHPStan/Analyser/nsrt/bug-13270b.php‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
<?php declare(strict_types=1);
1+
<?php // lint < 8.0
2+
3+
declare(strict_types=1);
24

35
namespace Bug13270b;
46

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug13301Php8;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function doFoo($mixed) {
8+
if (array_key_exists('a', $mixed)) {
9+
assertType("non-empty-array&hasOffset('a')", $mixed);
10+
echo "has-a";
11+
} else {
12+
assertType("array<mixed~'a', mixed>", $mixed);
13+
echo "NO-a";
14+
}
15+
assertType('array', $mixed);
16+
}
17+
18+
function doFooTrue($mixed) {
19+
if (array_key_exists('a', $mixed) === true) {
20+
assertType("non-empty-array&hasOffset('a')", $mixed);
21+
} else {
22+
assertType("array<mixed~'a', mixed>", $mixed);
23+
}
24+
assertType('array', $mixed);
25+
}
26+
27+
function doFooTruethy($mixed) {
28+
if (array_key_exists('a', $mixed) == true) {
29+
assertType("non-empty-array&hasOffset('a')", $mixed);
30+
} else {
31+
assertType("array<mixed~'a', mixed>", $mixed);
32+
}
33+
assertType('array', $mixed);
34+
}
35+
36+
function doFooFalsey($mixed) {
37+
if (array_key_exists('a', $mixed) == 0) {
38+
assertType("array<mixed~'a', mixed>", $mixed);
39+
} else {
40+
assertType("non-empty-array&hasOffset('a')", $mixed);
41+
}
42+
assertType('array', $mixed);
43+
}
44+
45+
function doArray(array $arr) {
46+
if (array_key_exists('a', $arr)) {
47+
assertType("non-empty-array&hasOffset('a')", $arr);
48+
} else {
49+
assertType("array<mixed~'a', mixed>", $arr);
50+
}
51+
assertType('array', $arr);
52+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php // lint < 8.0
2+
3+
namespace Bug13301Php7;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
function doFoo($mixed) {
8+
if (array_key_exists('a', $mixed)) {
9+
assertType("non-empty-array&hasOffset('a')", $mixed);
10+
echo "has-a";
11+
} else {
12+
assertType("mixed~hasOffset('a')", $mixed);
13+
echo "NO-a";
14+
}
15+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug2001Php8;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class HelloWorld
8+
{
9+
public function parseUrl(string $url): string
10+
{
11+
$parsedUrl = parse_url(urldecode($url));
12+
assertType('array{scheme?: string, host?: string, port?: int<0, 65535>, user?: string, pass?: string, path?: string, query?: string, fragment?: string}|false', $parsedUrl);
13+
14+
if (array_key_exists('host', $parsedUrl)) {
15+
assertType('array{scheme?: string, host: string, port?: int<0, 65535>, user?: string, pass?: string, path?: string, query?: string, fragment?: string}', $parsedUrl);
16+
throw new \RuntimeException('Absolute URLs are prohibited for the redirectTo parameter.');
17+
}
18+
19+
assertType('array{scheme?: string, port?: int<0, 65535>, user?: string, pass?: string, path?: string, query?: string, fragment?: string}', $parsedUrl);
20+
21+
$redirectUrl = $parsedUrl['path'];
22+
23+
if (array_key_exists('query', $parsedUrl)) {
24+
assertType('array{scheme?: string, port?: int<0, 65535>, user?: string, pass?: string, path?: string, query: string, fragment?: string}', $parsedUrl);
25+
$redirectUrl .= '?' . $parsedUrl['query'];
26+
}
27+
28+
if (array_key_exists('fragment', $parsedUrl)) {
29+
assertType('array{scheme?: string, port?: int<0, 65535>, user?: string, pass?: string, path?: string, query?: string, fragment: string}', $parsedUrl);
30+
$redirectUrl .= '#' . $parsedUrl['query'];
31+
}
32+
33+
assertType('array{scheme?: string, port?: int<0, 65535>, user?: string, pass?: string, path?: string, query?: string, fragment?: string}', $parsedUrl);
34+
35+
return $redirectUrl;
36+
}
37+
38+
public function doFoo(int $i)
39+
{
40+
$a = ['a' => $i];
41+
if (rand(0, 1)) {
42+
$a['b'] = $i;
43+
}
44+
45+
if (rand(0,1)) {
46+
$a = ['d' => $i];
47+
}
48+
49+
assertType('array{a: int, b?: int}|array{d: int}', $a);
50+
}
51+
}

‎tests/PHPStan/Analyser/nsrt/bug-2001.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?php
1+
<?php// lint < 8.0
22

33
namespace Bug2001;
44

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php // lint >= 8.0
2+
3+
namespace Bug4099Php8;
4+
5+
use function PHPStan\Testing\assertNativeType;
6+
use function PHPStan\Testing\assertType;
7+
8+
class Foo
9+
{
10+
11+
/**
12+
* @param array{key: array{inner: mixed}} $arr
13+
*/
14+
function arrayHint(array $arr): void
15+
{
16+
assertType('array{key: array{inner: mixed}}', $arr);
17+
assertNativeType('array', $arr);
18+
19+
if (!array_key_exists('key', $arr)) {
20+
assertType('*NEVER*', $arr);
21+
assertNativeType("array<mixed~'key', mixed>", $arr);
22+
throw new \Exception('no key "key" found.');
23+
}
24+
assertType('array{key: array{inner: mixed}}', $arr);
25+
assertNativeType('non-empty-array&hasOffset(\'key\')', $arr);
26+
assertType('array{inner: mixed}', $arr['key']);
27+
assertNativeType('mixed', $arr['key']);
28+
29+
if (!array_key_exists('inner', $arr['key'])) {
30+
assertType('*NEVER*', $arr);
31+
assertNativeType('non-empty-array&hasOffset(\'key\')', $arr);
32+
assertType('*NEVER*', $arr['key']);
33+
assertNativeType("array<mixed~'inner', mixed>", $arr['key']);
34+
throw new \Exception('need key.inner');
35+
}
36+
37+
assertType('array{key: array{inner: mixed}}', $arr);
38+
assertNativeType('non-empty-array&hasOffset(\'key\')', $arr);
39+
}
40+
41+
}

‎tests/PHPStan/Analyser/nsrt/bug-4099.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?php
1+
<?php// lint < 8.0
22

33
namespace Bug4099;
44

0 commit comments

Comments
(0)

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