Skip to content

Commit bc0bf26

Browse files
committed
!array_key_exists() should imply array for PHP8+
1 parent 6bdce5d commit bc0bf26

File tree

5 files changed

+78
-1
lines changed

5 files changed

+78
-1
lines changed

src/Type/Php/ArrayKeyExistsFunctionTypeSpecifyingExtension.php

Lines changed: 15 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;
@@ -110,6 +117,14 @@ public function specifyTypes(
110117
new ArrayType(new MixedType(), new MixedType()),
111118
new HasOffsetType($keyType),
112119
);
120+
} elseif (
121+
$this->phpVersion->throwsValueErrorForInternalFunctions()
122+
&& $arrayType instanceof MixedType
123+
) {
124+
$type = TypeCombinator::intersect(
125+
new ArrayType(new MixedType(), new MixedType()),
126+
);
127+
$context = $context->negate();
113128
} else {
114129
$type = new HasOffsetType($keyType);
115130
}
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 Bug13270b;
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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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); // could be array~hasOffset('a') after arrays got subtractable
13+
echo "NO-a";
14+
}
15+
}
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+
}

0 commit comments

Comments
 (0)