Skip to content

Commit

Permalink
isset() narrows string-key in int-keyed-array to numeric-string
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm authored Sep 26, 2024
1 parent 31362eb commit b76fecc
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 46 deletions.
44 changes: 3 additions & 41 deletions src/Analyser/TypeSpecifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use PHPStan\Reflection\ParametersAcceptorWithPhpDocs;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Reflection\ResolvedFunctionVariant;
use PHPStan\Rules\Arrays\AllowedArrayKeysTypes;
use PHPStan\ShouldNotHappenException;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Accessory\AccessoryArrayListType;
Expand Down Expand Up @@ -820,47 +821,8 @@ public function specifyTypesInCondition(
);
} else {
$varType = $scope->getType($var->var);
if ($varType->isArray()->yes() && !$varType->isIterableAtLeastOnce()->no()) {
$varIterableKeyType = $varType->getIterableKeyType();

if ($varIterableKeyType->isConstantScalarValue()->yes()) {
$narrowedKey = TypeCombinator::union(
$varIterableKeyType,
TypeCombinator::remove($varIterableKeyType->toString(), new ConstantStringType('')),
);

if (!$varType->hasOffsetValueType(new ConstantIntegerType(0))->no()) {
$narrowedKey = TypeCombinator::union(
$narrowedKey,
new ConstantBooleanType(false),
);
}

if (!$varType->hasOffsetValueType(new ConstantIntegerType(1))->no()) {
$narrowedKey = TypeCombinator::union(
$narrowedKey,
new ConstantBooleanType(true),
);
}

if (!$varType->hasOffsetValueType(new ConstantStringType(''))->no()) {
$narrowedKey = TypeCombinator::addNull($narrowedKey);
}

if (!$varIterableKeyType->isNumericString()->no() || !$varIterableKeyType->isInteger()->no()) {
$narrowedKey = TypeCombinator::union($narrowedKey, new FloatType());
}
} else {
$narrowedKey = new MixedType(
false,
new UnionType([
new ArrayType(new MixedType(), new MixedType()),
new ObjectWithoutClassType(),
new ResourceType(),
]),
);
}

$narrowedKey = AllowedArrayKeysTypes::narrowOffsetKeyType($varType, $dimType);
if ($narrowedKey !== null) {
$types = $types->unionWith(
$this->create(
$var->dim,
Expand Down
59 changes: 59 additions & 0 deletions src/Rules/Arrays/AllowedArrayKeysTypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@

namespace PHPStan\Rules\Arrays;

use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\FloatType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\NullType;
use PHPStan\Type\ObjectWithoutClassType;
use PHPStan\Type\ResourceType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\UnionType;

final class AllowedArrayKeysTypes
Expand All @@ -24,4 +32,55 @@ public static function getType(): Type
]);
}

public static function narrowOffsetKeyType(Type $varType, Type $keyType): ?Type
{
if (!$varType->isArray()->yes() || $varType->isIterableAtLeastOnce()->no()) {
return null;
}

$varIterableKeyType = $varType->getIterableKeyType();

if ($varIterableKeyType->isConstantScalarValue()->yes()) {
$narrowedKey = TypeCombinator::union(
$varIterableKeyType,
TypeCombinator::remove($varIterableKeyType->toString(), new ConstantStringType('')),
);

if (!$varType->hasOffsetValueType(new ConstantIntegerType(0))->no()) {
$narrowedKey = TypeCombinator::union(
$narrowedKey,
new ConstantBooleanType(false),
);
}

if (!$varType->hasOffsetValueType(new ConstantIntegerType(1))->no()) {
$narrowedKey = TypeCombinator::union(
$narrowedKey,
new ConstantBooleanType(true),
);
}

if (!$varType->hasOffsetValueType(new ConstantStringType(''))->no()) {
$narrowedKey = TypeCombinator::addNull($narrowedKey);
}

if (!$varIterableKeyType->isNumericString()->no() || !$varIterableKeyType->isInteger()->no()) {
$narrowedKey = TypeCombinator::union($narrowedKey, new FloatType());
}

return $narrowedKey;
} elseif ($varIterableKeyType->isInteger()->yes() && $keyType->isString()->yes()) {
return TypeCombinator::intersect($varIterableKeyType->toString(), $keyType);
}

return new MixedType(
false,
new UnionType([
new ArrayType(new MixedType(), new MixedType()),
new ObjectWithoutClassType(),
new ResourceType(),
]),
);
}

}
32 changes: 27 additions & 5 deletions tests/PHPStan/Analyser/nsrt/bug-11716.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ public function parse(string $glue): string
}

/**
* @param array<int, string> $arr
* @param array<int, string> $intKeyedArr
* @param array<string, string> $stringKeyedArr
*/
function narrowKey($mixed, string $s, int $i, array $generalArr, array $arr): void {
function narrowKey($mixed, string $s, int $i, array $generalArr, array $intKeyedArr, array $stringKeyedArr): void {
if (isset($generalArr[$mixed])) {
assertType('mixed~(array|object|resource)', $mixed);
} else {
Expand All @@ -59,21 +60,42 @@ function narrowKey($mixed, string $s, int $i, array $generalArr, array $arr): vo
}
assertType('string', $s);

if (isset($arr[$mixed])) {
if (isset($intKeyedArr[$mixed])) {
assertType('mixed~(array|object|resource)', $mixed);
} else {
assertType('mixed', $mixed);
}
assertType('mixed', $mixed);

if (isset($arr[$i])) {
if (isset($intKeyedArr[$i])) {
assertType('int', $i);
} else {
assertType('int', $i);
}
assertType('int', $i);

if (isset($arr[$s])) {
if (isset($intKeyedArr[$s])) {
assertType("numeric-string", $s);
} else {
assertType('string', $s);
}
assertType('string', $s);

if (isset($stringKeyedArr[$mixed])) {
assertType('mixed~(array|object|resource)', $mixed);
} else {
assertType('mixed', $mixed);
}
assertType('mixed', $mixed);

if (isset($stringKeyedArr[$i])) {
assertType('int', $i);
} else {
assertType('int', $i);
}
assertType('int', $i);

if (isset($stringKeyedArr[$s])) {
assertType('string', $s);
} else {
assertType('string', $s);
Expand Down

0 comments on commit b76fecc

Please sign in to comment.