From 4cf40e2af828a6917247e124bf3c5d47138530e4 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Thu, 16 Jun 2022 09:59:19 +0200 Subject: [PATCH] Improve intersecting constant array with general array --- src/Type/TypeCombinator.php | 24 +++++++++++++++++++ .../Analyser/NodeScopeResolverTest.php | 1 + .../data/constant-array-intersect.php | 17 +++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/constant-array-intersect.php diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index a594f1a8e62..1c2b2af60e0 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -6,6 +6,7 @@ use PHPStan\Type\Accessory\HasOffsetType; use PHPStan\Type\Accessory\NonEmptyArrayType; use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantFloatType; use PHPStan\Type\Constant\ConstantIntegerType; @@ -683,6 +684,13 @@ public static function intersect(Type ...$types): Type return 1; } + if ($a instanceof ConstantArrayType && !$b instanceof ConstantArrayType) { + return -1; + } + if ($b instanceof ConstantArrayType && !$a instanceof ConstantArrayType) { + return 1; + } + return 0; }); @@ -771,6 +779,22 @@ public static function intersect(Type ...$types): Type continue 2; } + if ($types[$i] instanceof ConstantArrayType && $types[$j] instanceof ArrayType && !$types[$j] instanceof ConstantArrayType) { + $newArray = ConstantArrayTypeBuilder::createEmpty(); + $valueTypes = $types[$i]->getValueTypes(); + foreach ($types[$i]->getKeyTypes() as $k => $keyType) { + $newArray->setOffsetValueType( + self::intersect($keyType, $types[$j]->getIterableKeyType()), + self::intersect($valueTypes[$k], $types[$j]->getIterableValueType()), + $types[$i]->isOptionalKey($k), + ); + } + $types[$i] = $newArray->getArray(); + array_splice($types, $j--, 1); + $typesCount--; + continue 2; + } + if ( ($types[$i] instanceof ArrayType || $types[$i] instanceof IterableType) && ($types[$j] instanceof ArrayType || $types[$j] instanceof IterableType) diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index fdf67bc2a8c..b77b86240d4 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -912,6 +912,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7387.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7353.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7031.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/constant-array-intersect.php'); } /** diff --git a/tests/PHPStan/Analyser/data/constant-array-intersect.php b/tests/PHPStan/Analyser/data/constant-array-intersect.php new file mode 100644 index 00000000000..c1633bb14ab --- /dev/null +++ b/tests/PHPStan/Analyser/data/constant-array-intersect.php @@ -0,0 +1,17 @@ + $array1 + * @param array&array{key: string|null} $array2 + */ +function test( + array $array1, + array $array2, +): void { + assertType('array{key: string}', $array1); + assertType('array{key: string}', $array2); +}