diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3b45e07613..37501d1395 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1549,7 +1549,7 @@ parameters: - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantArrayType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantArrays\\(\\) instead\\.$#" - count: 12 + count: 10 path: src/Type/TypeCombinator.php - diff --git a/src/Type/TypeCombinator.php b/src/Type/TypeCombinator.php index 8d641cbfe5..491b9eb302 100644 --- a/src/Type/TypeCombinator.php +++ b/src/Type/TypeCombinator.php @@ -354,7 +354,7 @@ private static function compareTypesInUnion(Type $a, Type $b): ?array return [new HasOffsetValueType($a->getOffsetType(), self::union($a->getValueType(), $b->getValueType())), null]; } } - if ($a instanceof ConstantArrayType && $b instanceof ConstantArrayType) { + if ($a->isConstantArray()->yes() && $b->isConstantArray()->yes()) { return null; } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 9435bbeaa8..054f64348b 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -226,6 +226,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-3915.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2378.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9985.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-6294.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2580.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-9753.php'); diff --git a/tests/PHPStan/Analyser/data/bug-9985.php b/tests/PHPStan/Analyser/data/bug-9985.php new file mode 100644 index 0000000000..edbfebffc5 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-9985.php @@ -0,0 +1,25 @@ += 1) { + $warnings['a'] = true; + } + + if (rand(0, 100) >= 2) { + $warnings['b'] = true; + } elseif (rand(0, 100) >= 3) { + $warnings['c'] = true; + } + + assertType('array{}|array{a?: true, b: true}|array{a?: true, c?: true}', $warnings); + + if (!empty($warnings)) { + assertType('array{a?: true, b: true}|(array{a?: true, c?: true}&non-empty-array)', $warnings); + } +}; diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index ff1b961f90..b679b777fb 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -2450,6 +2450,50 @@ public function dataUnion(): iterable NeverType::class, '*NEVER*', ]; + yield [ + [ + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new ConstantBooleanType(true), + new ConstantBooleanType(true), + ], [0], [0]), + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('c'), + ], [ + new ConstantBooleanType(true), + new ConstantBooleanType(true), + ], [0], [0, 1]), + ], + UnionType::class, + 'array{a?: true, b: true}|array{a?: true, c?: true}', + ]; + + yield [ + [ + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new ConstantBooleanType(true), + new ConstantBooleanType(true), + ], [0], [0]), + new IntersectionType([ + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('c'), + ], [ + new ConstantBooleanType(true), + new ConstantBooleanType(true), + ], [0], [0, 1]), + new NonEmptyArrayType(), + ]), + ], + UnionType::class, + 'array{a?: true, b: true}|(array{a?: true, c?: true}&non-empty-array)', + ]; } /** @@ -4030,6 +4074,66 @@ public function dataIntersect(): iterable NonAcceptingNeverType::class, 'never=explicit', ]; + yield [ + [ + new UnionType([ + new ConstantArrayType([], []), + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new ConstantBooleanType(true), + new ConstantBooleanType(true), + ], [0], [0]), + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('c'), + ], [ + new ConstantBooleanType(true), + new ConstantBooleanType(true), + ], [0], [0, 1]), + ]), + new NonEmptyArrayType(), + ], + UnionType::class, + 'array{a?: true, b: true}|(array{a?: true, c?: true}&non-empty-array)', + ]; + yield [ + [ + new ConstantArrayType([], []), + new NonEmptyArrayType(), + ], + NeverType::class, + '*NEVER*=implicit', + ]; + yield [ + [ + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('b'), + ], [ + new ConstantBooleanType(true), + new ConstantBooleanType(true), + ], [0], [0]), + new NonEmptyArrayType(), + ], + ConstantArrayType::class, + 'array{a?: true, b: true}', + ]; + yield [ + [ + new ConstantArrayType([ + new ConstantStringType('a'), + new ConstantStringType('c'), + ], [ + new ConstantBooleanType(true), + new ConstantBooleanType(true), + ], [0], [0, 1]), + new NonEmptyArrayType(), + ], + IntersectionType::class, + 'array{a?: true, c?: true}&non-empty-array', + ]; } /**