Skip to content

Commit 9f92f4b

Browse files
authored
Merge branch refs/heads/1.12.x into 2.0.x
2 parents 75e1aad + b76fecc commit 9f92f4b

File tree

3 files changed

+89
-46
lines changed

3 files changed

+89
-46
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
use PHPStan\Reflection\ParametersAcceptorWithPhpDocs;
3030
use PHPStan\Reflection\ReflectionProvider;
3131
use PHPStan\Reflection\ResolvedFunctionVariant;
32+
use PHPStan\Rules\Arrays\AllowedArrayKeysTypes;
3233
use PHPStan\ShouldNotHappenException;
3334
use PHPStan\TrinaryLogic;
3435
use PHPStan\Type\Accessory\AccessoryArrayListType;
@@ -788,47 +789,8 @@ public function specifyTypesInCondition(
788789
);
789790
} else {
790791
$varType = $scope->getType($var->var);
791-
if ($varType->isArray()->yes() && !$varType->isIterableAtLeastOnce()->no()) {
792-
$varIterableKeyType = $varType->getIterableKeyType();
793-
794-
if ($varIterableKeyType->isConstantScalarValue()->yes()) {
795-
$narrowedKey = TypeCombinator::union(
796-
$varIterableKeyType,
797-
TypeCombinator::remove($varIterableKeyType->toString(), new ConstantStringType('')),
798-
);
799-
800-
if (!$varType->hasOffsetValueType(new ConstantIntegerType(0))->no()) {
801-
$narrowedKey = TypeCombinator::union(
802-
$narrowedKey,
803-
new ConstantBooleanType(false),
804-
);
805-
}
806-
807-
if (!$varType->hasOffsetValueType(new ConstantIntegerType(1))->no()) {
808-
$narrowedKey = TypeCombinator::union(
809-
$narrowedKey,
810-
new ConstantBooleanType(true),
811-
);
812-
}
813-
814-
if (!$varType->hasOffsetValueType(new ConstantStringType(''))->no()) {
815-
$narrowedKey = TypeCombinator::addNull($narrowedKey);
816-
}
817-
818-
if (!$varIterableKeyType->isNumericString()->no() || !$varIterableKeyType->isInteger()->no()) {
819-
$narrowedKey = TypeCombinator::union($narrowedKey, new FloatType());
820-
}
821-
} else {
822-
$narrowedKey = new MixedType(
823-
false,
824-
new UnionType([
825-
new ArrayType(new MixedType(), new MixedType()),
826-
new ObjectWithoutClassType(),
827-
new ResourceType(),
828-
]),
829-
);
830-
}
831-
792+
$narrowedKey = AllowedArrayKeysTypes::narrowOffsetKeyType($varType, $dimType);
793+
if ($narrowedKey !== null) {
832794
$types = $types->unionWith(
833795
$this->create(
834796
$var->dim,

src/Rules/Arrays/AllowedArrayKeysTypes.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@
22

33
namespace PHPStan\Rules\Arrays;
44

5+
use PHPStan\Type\ArrayType;
56
use PHPStan\Type\BooleanType;
7+
use PHPStan\Type\Constant\ConstantBooleanType;
8+
use PHPStan\Type\Constant\ConstantIntegerType;
9+
use PHPStan\Type\Constant\ConstantStringType;
610
use PHPStan\Type\FloatType;
711
use PHPStan\Type\IntegerType;
12+
use PHPStan\Type\MixedType;
813
use PHPStan\Type\NullType;
14+
use PHPStan\Type\ObjectWithoutClassType;
15+
use PHPStan\Type\ResourceType;
916
use PHPStan\Type\StringType;
1017
use PHPStan\Type\Type;
18+
use PHPStan\Type\TypeCombinator;
1119
use PHPStan\Type\UnionType;
1220

1321
final class AllowedArrayKeysTypes
@@ -24,4 +32,55 @@ public static function getType(): Type
2432
]);
2533
}
2634

35+
public static function narrowOffsetKeyType(Type $varType, Type $keyType): ?Type
36+
{
37+
if (!$varType->isArray()->yes() || $varType->isIterableAtLeastOnce()->no()) {
38+
return null;
39+
}
40+
41+
$varIterableKeyType = $varType->getIterableKeyType();
42+
43+
if ($varIterableKeyType->isConstantScalarValue()->yes()) {
44+
$narrowedKey = TypeCombinator::union(
45+
$varIterableKeyType,
46+
TypeCombinator::remove($varIterableKeyType->toString(), new ConstantStringType('')),
47+
);
48+
49+
if (!$varType->hasOffsetValueType(new ConstantIntegerType(0))->no()) {
50+
$narrowedKey = TypeCombinator::union(
51+
$narrowedKey,
52+
new ConstantBooleanType(false),
53+
);
54+
}
55+
56+
if (!$varType->hasOffsetValueType(new ConstantIntegerType(1))->no()) {
57+
$narrowedKey = TypeCombinator::union(
58+
$narrowedKey,
59+
new ConstantBooleanType(true),
60+
);
61+
}
62+
63+
if (!$varType->hasOffsetValueType(new ConstantStringType(''))->no()) {
64+
$narrowedKey = TypeCombinator::addNull($narrowedKey);
65+
}
66+
67+
if (!$varIterableKeyType->isNumericString()->no() || !$varIterableKeyType->isInteger()->no()) {
68+
$narrowedKey = TypeCombinator::union($narrowedKey, new FloatType());
69+
}
70+
71+
return $narrowedKey;
72+
} elseif ($varIterableKeyType->isInteger()->yes() && $keyType->isString()->yes()) {
73+
return TypeCombinator::intersect($varIterableKeyType->toString(), $keyType);
74+
}
75+
76+
return new MixedType(
77+
false,
78+
new UnionType([
79+
new ArrayType(new MixedType(), new MixedType()),
80+
new ObjectWithoutClassType(),
81+
new ResourceType(),
82+
]),
83+
);
84+
}
85+
2786
}

tests/PHPStan/Analyser/nsrt/bug-11716.php

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ public function parse(string $glue): string
3535
}
3636

3737
/**
38-
* @param array<int, string> $arr
38+
* @param array<int, string> $intKeyedArr
39+
* @param array<string, string> $stringKeyedArr
3940
*/
40-
function narrowKey($mixed, string $s, int $i, array $generalArr, array $arr): void {
41+
function narrowKey($mixed, string $s, int $i, array $generalArr, array $intKeyedArr, array $stringKeyedArr): void {
4142
if (isset($generalArr[$mixed])) {
4243
assertType('mixed~(array|object|resource)', $mixed);
4344
} else {
@@ -59,21 +60,42 @@ function narrowKey($mixed, string $s, int $i, array $generalArr, array $arr): vo
5960
}
6061
assertType('string', $s);
6162

62-
if (isset($arr[$mixed])) {
63+
if (isset($intKeyedArr[$mixed])) {
6364
assertType('mixed~(array|object|resource)', $mixed);
6465
} else {
6566
assertType('mixed', $mixed);
6667
}
6768
assertType('mixed', $mixed);
6869

69-
if (isset($arr[$i])) {
70+
if (isset($intKeyedArr[$i])) {
7071
assertType('int', $i);
7172
} else {
7273
assertType('int', $i);
7374
}
7475
assertType('int', $i);
7576

76-
if (isset($arr[$s])) {
77+
if (isset($intKeyedArr[$s])) {
78+
assertType("numeric-string", $s);
79+
} else {
80+
assertType('string', $s);
81+
}
82+
assertType('string', $s);
83+
84+
if (isset($stringKeyedArr[$mixed])) {
85+
assertType('mixed~(array|object|resource)', $mixed);
86+
} else {
87+
assertType('mixed', $mixed);
88+
}
89+
assertType('mixed', $mixed);
90+
91+
if (isset($stringKeyedArr[$i])) {
92+
assertType('int', $i);
93+
} else {
94+
assertType('int', $i);
95+
}
96+
assertType('int', $i);
97+
98+
if (isset($stringKeyedArr[$s])) {
7799
assertType('string', $s);
78100
} else {
79101
assertType('string', $s);

0 commit comments

Comments
 (0)