From a0abe91deeef43ae6df84cc5dd9b3af3cb87e09b Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Sun, 24 Jul 2022 17:54:50 +0200 Subject: [PATCH] Tagged unions Closes https://github.com/phpstan/phpstan/issues/6469 --- src/Type/Constant/ConstantArrayType.php | 8 +- .../Analyser/LegacyNodeScopeResolverTest.php | 24 +-- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/array-merge2.php | 4 +- tests/PHPStan/Analyser/data/array-replace.php | 4 +- tests/PHPStan/Analyser/data/bug-3269.php | 4 +- tests/PHPStan/Analyser/data/bug-6383.php | 2 +- .../PHPStan/Analyser/data/bug-6936-limit.php | 6 +- tests/PHPStan/Analyser/data/tagged-unions.php | 184 ++++++++++++++++++ ...nexistentOffsetInArrayDimFetchRuleTest.php | 2 +- tests/PHPStan/Rules/Arrays/data/bug-6364.php | 8 +- .../Rules/Methods/ReturnTypeRuleTest.php | 11 ++ .../Rules/Methods/data/tagged-unions.php | 14 ++ .../TypesAssignedToPropertiesRuleTest.php | 2 +- tests/PHPStan/Type/TypeCombinatorTest.php | 12 +- tests/PHPStan/Type/UnionTypeTest.php | 4 +- 16 files changed, 253 insertions(+), 37 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/tagged-unions.php create mode 100644 tests/PHPStan/Rules/Methods/data/tagged-unions.php diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 53b14f6a61..f56f29e383 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -1126,12 +1126,18 @@ public function isKeysSupersetOf(self $otherArray): bool } $otherKeys = $otherArray->keyTypes; - foreach ($this->keyTypes as $keyType) { + foreach ($this->keyTypes as $i => $keyType) { foreach ($otherArray->keyTypes as $j => $otherKeyType) { if (!$keyType->equals($otherKeyType)) { continue; } + $valueType = $this->valueTypes[$i]; + $otherValueType = $otherArray->valueTypes[$j]; + if ($valueType->isSuperTypeOf($otherValueType)->no()) { + continue; + } + unset($otherKeys[$j]); continue 2; } diff --git a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php index 218bbe6fe8..03b1ba4810 100644 --- a/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php @@ -3056,7 +3056,7 @@ public function dataBinaryOperations(): array "sprintf('%s %s', 'foo', 'bar')", ], [ - 'array{}|array{0: \'password\'|\'username\', 1?: \'password\'}', + 'array{}|array{\'password\'}|array{0: \'username\', 1?: \'password\'}', '$coalesceArray', ], [ @@ -4571,7 +4571,7 @@ public function dataArrayFunctions(): array 'array_intersect_key($integers, [])', ], [ - 'array{1|4, 2|5, 3|6}', + 'array{1, 2, 3}|array{4, 5, 6}', 'array_intersect_key(...[$integers, [4, 5, 6]])', ], [ @@ -5389,7 +5389,7 @@ public function dataFunctions(): array '$strSplitConstantStringWithInvalidSplitLengthType', ], [ - 'array{\'a\'|\'g\', \'b\'|\'h\', \'c\'|\'i\', \'d\'|\'j\', \'e\'|\'k\', \'f\'|\'l\'}', + "array{'a', 'b', 'c', 'd', 'e', 'f'}|array{'g', 'h', 'i', 'j', 'k', 'l'}", '$strSplitConstantStringWithVariableStringAndConstantSplitLength', ], [ @@ -5632,7 +5632,7 @@ public function dataRangeFunction(): array 'range(1, doFoo() ? 1 : 2)', ], [ - 'array{0: -1|1, 1?: 0|2, 2?: 1, 3?: 2}', + 'array{0: -1, 1: 0, 2: 1, 3?: 2}|array{0: 1, 1?: 2}', 'range(doFoo() ? -1 : 1, doFoo() ? 1 : 2)', ], [ @@ -8347,19 +8347,19 @@ public function dataIsset(): array '$array[\'b\']', ], [ - 'array{a: 1|2|3, b: 2|3, c?: 4}', + 'array{a: 1, b: 2}|array{a: 3, b: 3, c: 4}', '$array', ], [ - 'array{a: 1|2|3, b: 2|3|null, c?: 4}', + 'array{a: 1, b: 2}|array{a: 3, b: 3, c: 4}|array{a: 3, b: null}', '$arrayCopy', ], [ - 'array{a: 1|2|3, c?: 4}', + 'array{a: 2}', '$anotherArrayCopy', ], [ - 'array{a: 1|2|3, b?: 2|3|null, c?: 4}', + 'array{a: 1, b: 2}|array{a: 2}|array{a: 3, b: 3, c: 4}|array{a: 3, b: null}', '$yetAnotherArrayCopy', ], [ @@ -8391,7 +8391,7 @@ public function dataIsset(): array '$lookup[$a] ?? false', ], [ - '\'foo\'|false', + '\'foo\'', '$nullableArray[\'a\'] ?? false', ], [ @@ -8399,7 +8399,7 @@ public function dataIsset(): array '$nullableArray[\'b\'] ?? false', ], [ - '\'baz\'|false', + '\'baz\'', '$nullableArray[\'c\'] ?? false', ], ]; @@ -8777,7 +8777,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithInvalidSplitLengthType', ], [ - 'array{\'a\'|\'g\', \'b\'|\'h\', \'c\'|\'i\', \'d\'|\'j\', \'e\'|\'k\', \'f\'|\'l\'}', + "array{'a', 'b', 'c', 'd', 'e', 'f'}|array{'g', 'h', 'i', 'j', 'k', 'l'}", '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLength', ], [ @@ -8833,7 +8833,7 @@ public function dataPhp74Functions(): array '$mbStrSplitConstantStringWithInvalidSplitLengthTypeAndVariableEncoding', ], [ - 'array{\'a\'|\'g\', \'b\'|\'h\', \'c\'|\'i\', \'d\'|\'j\', \'e\'|\'k\', \'f\'|\'l\'}', + "array{'a', 'b', 'c', 'd', 'e', 'f'}|array{'g', 'h', 'i', 'j', 'k', 'l'}", '$mbStrSplitConstantStringWithVariableStringAndConstantSplitLengthAndValidEncoding', ], [ diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index 94471f9f39..a892709cb0 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -999,6 +999,7 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__ . '/data/array-filter-constant.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/array-intersect-key-constant.php'); yield from $this->gatherAssertTypes(__DIR__ . '/data/composer-array-bug.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/tagged-unions.php'); } /** diff --git a/tests/PHPStan/Analyser/data/array-merge2.php b/tests/PHPStan/Analyser/data/array-merge2.php index ce7ff06056..db51127a6f 100644 --- a/tests/PHPStan/Analyser/data/array-merge2.php +++ b/tests/PHPStan/Analyser/data/array-merge2.php @@ -52,7 +52,7 @@ public function arrayMergeUnionType($array1, $array2): void public function arrayMergeUnionTypeArrayShapes($array1, $array2): void { assertType("array", array_merge($array1, $array1)); - assertType("array", array_merge($array1, $array2)); - assertType("array", array_merge($array2, $array1)); + assertType("array", array_merge($array1, $array2)); + assertType("array", array_merge($array2, $array1)); } } diff --git a/tests/PHPStan/Analyser/data/array-replace.php b/tests/PHPStan/Analyser/data/array-replace.php index 2a43254cbf..436ce232fa 100644 --- a/tests/PHPStan/Analyser/data/array-replace.php +++ b/tests/PHPStan/Analyser/data/array-replace.php @@ -65,7 +65,7 @@ public function arrayReplaceUnionType($array1, $array2): void public function arrayReplaceUnionTypeArrayShapes($array1, $array2): void { assertType("array", array_replace($array1, $array1)); - assertType("array", array_replace($array1, $array2)); - assertType("array", array_replace($array2, $array1)); + assertType("array", array_replace($array1, $array2)); + assertType("array", array_replace($array2, $array1)); } } diff --git a/tests/PHPStan/Analyser/data/bug-3269.php b/tests/PHPStan/Analyser/data/bug-3269.php index f7f5a2bce6..daac80710a 100644 --- a/tests/PHPStan/Analyser/data/bug-3269.php +++ b/tests/PHPStan/Analyser/data/bug-3269.php @@ -20,10 +20,10 @@ public static function bar(array $intervalGroups): void } } - assertType('array', $borders); + assertType("array", $borders); foreach ($borders as $border) { - assertType('array{version: string, operator: string, side: \'end\'|\'start\'}', $border); + assertType("array{version: string, operator: string, side: 'end'}|array{version: string, operator: string, side: 'start'}", $border); assertType('\'end\'|\'start\'', $border['side']); } } diff --git a/tests/PHPStan/Analyser/data/bug-6383.php b/tests/PHPStan/Analyser/data/bug-6383.php index 4ea1f0f5d5..c2c06faf56 100644 --- a/tests/PHPStan/Analyser/data/bug-6383.php +++ b/tests/PHPStan/Analyser/data/bug-6383.php @@ -27,7 +27,7 @@ function doFoo(string $country): void { foreach ($options as $key => $option) { if (isset($option['only_in_country'])) { - assertType("array{value: 'a'|'b'|'c', checked: false, only_in_country: array{0: 'BE'|'DE', 1?: 'CH', 2?: 'DE', 3?: 'DK', 4?: 'FR', 5?: 'NL', 6?: 'SE'}}", $option); + assertType("array{value: 'a', checked: false, only_in_country: array{'DE'}}|array{value: 'b', checked: false, only_in_country: array{'BE', 'CH', 'DE', 'DK', 'FR', 'NL', 'SE'}}", $option); continue; } } diff --git a/tests/PHPStan/Analyser/data/bug-6936-limit.php b/tests/PHPStan/Analyser/data/bug-6936-limit.php index 421d3d2ffe..1d3f0e9435 100644 --- a/tests/PHPStan/Analyser/data/bug-6936-limit.php +++ b/tests/PHPStan/Analyser/data/bug-6936-limit.php @@ -36,16 +36,16 @@ public function testLimits():void $arr[] = 'g'; } - assertType("array{0: 1|'a'|'b'|'c'|'d'|'e'|'f'|'g', 1: 2|'b'|'c'|'d'|'e'|'f'|'g', 2: 3|'c'|'d'|'e'|'f'|'g', 3?: 'd'|'e'|'f'|'g', 4?: 'e'|'f'|'g', 5?: 'f'|'g', 6?: 'g'}", $arr + $arr2); + assertType("array{'e', 2|'f'|'g', 3|'g'}|array{'f', 2|'g', 3}|array{'g', 2, 3}|array{0: 'a', 1: 2|'b'|'c'|'d'|'e'|'f'|'g', 2: 3|'c'|'d'|'e'|'f'|'g', 3?: 'd'|'e'|'f'|'g', 4?: 'e'|'f'|'g', 5?: 'f'|'g', 6?: 'g'}|array{0: 'b', 1: 2|'c'|'d'|'e'|'f'|'g', 2: 3|'d'|'e'|'f'|'g', 3?: 'e'|'f'|'g', 4?: 'f'|'g', 5?: 'g'}|array{0: 'c', 1: 2|'d'|'e'|'f'|'g', 2: 3|'e'|'f'|'g', 3?: 'f'|'g', 4?: 'g'}|array{0: 'd', 1: 2|'e'|'f'|'g', 2: 3|'f'|'g', 3?: 'g'}|array{1, 2, 3}", $arr + $arr2); if (rand(0,1)) { $arr[] = 'h'; } - assertType("array{0: 1|'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h', 1: 2|'b'|'c'|'d'|'e'|'f'|'g'|'h', 2: 3|'c'|'d'|'e'|'f'|'g'|'h', 3?: 'd'|'e'|'f'|'g'|'h', 4?: 'e'|'f'|'g'|'h', 5?: 'f'|'g'|'h', 6?: 'g'|'h', 7?: 'h'}", $arr + $arr2); + assertType("array{'f', 2|'g'|'h', 3|'h'}|array{'g', 2|'h', 3}|array{'h', 2, 3}|array{0: 'a', 1: 2|'b'|'c'|'d'|'e'|'f'|'g'|'h', 2: 3|'c'|'d'|'e'|'f'|'g'|'h', 3?: 'd'|'e'|'f'|'g'|'h', 4?: 'e'|'f'|'g'|'h', 5?: 'f'|'g'|'h', 6?: 'g'|'h', 7?: 'h'}|array{0: 'b', 1: 2|'c'|'d'|'e'|'f'|'g'|'h', 2: 3|'d'|'e'|'f'|'g'|'h', 3?: 'e'|'f'|'g'|'h', 4?: 'f'|'g'|'h', 5?: 'g'|'h', 6?: 'h'}|array{0: 'c', 1: 2|'d'|'e'|'f'|'g'|'h', 2: 3|'e'|'f'|'g'|'h', 3?: 'f'|'g'|'h', 4?: 'g'|'h', 5?: 'h'}|array{0: 'd', 1: 2|'e'|'f'|'g'|'h', 2: 3|'f'|'g'|'h', 3?: 'g'|'h', 4?: 'h'}|array{0: 'e', 1: 2|'f'|'g'|'h', 2: 3|'g'|'h', 3?: 'h'}|array{1, 2, 3}", $arr + $arr2); if (rand(0,1)) { $arr[] = 'i'; } - assertType("array{0: 1|'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i', 1: 2|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i', 2: 3|'c'|'d'|'e'|'f'|'g'|'h'|'i', 3?: 'd'|'e'|'f'|'g'|'h'|'i', 4?: 'e'|'f'|'g'|'h'|'i', 5?: 'f'|'g'|'h'|'i', 6?: 'g'|'h'|'i', 7?: 'h'|'i', 8?: 'i'}", $arr + $arr2); + assertType("array{'g', 2|'h'|'i', 3|'i'}|array{'h', 2|'i', 3}|array{'i', 2, 3}|array{0: 'a', 1: 2|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i', 2: 3|'c'|'d'|'e'|'f'|'g'|'h'|'i', 3?: 'd'|'e'|'f'|'g'|'h'|'i', 4?: 'e'|'f'|'g'|'h'|'i', 5?: 'f'|'g'|'h'|'i', 6?: 'g'|'h'|'i', 7?: 'h'|'i', 8?: 'i'}|array{0: 'b', 1: 2|'c'|'d'|'e'|'f'|'g'|'h'|'i', 2: 3|'d'|'e'|'f'|'g'|'h'|'i', 3?: 'e'|'f'|'g'|'h'|'i', 4?: 'f'|'g'|'h'|'i', 5?: 'g'|'h'|'i', 6?: 'h'|'i', 7?: 'i'}|array{0: 'c', 1: 2|'d'|'e'|'f'|'g'|'h'|'i', 2: 3|'e'|'f'|'g'|'h'|'i', 3?: 'f'|'g'|'h'|'i', 4?: 'g'|'h'|'i', 5?: 'h'|'i', 6?: 'i'}|array{0: 'd', 1: 2|'e'|'f'|'g'|'h'|'i', 2: 3|'f'|'g'|'h'|'i', 3?: 'g'|'h'|'i', 4?: 'h'|'i', 5?: 'i'}|array{0: 'e', 1: 2|'f'|'g'|'h'|'i', 2: 3|'g'|'h'|'i', 3?: 'h'|'i', 4?: 'i'}|array{0: 'f', 1: 2|'g'|'h'|'i', 2: 3|'h'|'i', 3?: 'i'}|array{1, 2, 3}", $arr + $arr2); } } diff --git a/tests/PHPStan/Analyser/data/tagged-unions.php b/tests/PHPStan/Analyser/data/tagged-unions.php new file mode 100644 index 0000000000..8f9e25dc0e --- /dev/null +++ b/tests/PHPStan/Analyser/data/tagged-unions.php @@ -0,0 +1,184 @@ +}", $meal); + if ($meal['type'] === 'pizza') { + assertType("array{type: 'pizza', toppings: array}", $meal); + } else { + assertType("array{type: 'pasta', salsa: string}", $meal); + } + assertType("array{type: 'pasta', salsa: string}|array{type: 'pizza', toppings: array}", $meal); + } +} + +class HelloWorld +{ + /** + * @return array{updated: true, id: int}|array{updated: false, id: null} + */ + public function sayHello(): array + { + return ['updated' => false, 'id' => 5]; + } + + public function doFoo() + { + $x = $this->sayHello(); + assertType("array{updated: false, id: null}|array{updated: true, id: int}", $x); + if ($x['updated']) { + assertType('array{updated: true, id: int}', $x); + } + } +} + +/** + * @psalm-type A array{tag: 'A', foo: bool} + * @psalm-type B array{tag: 'B'} + */ +class X { + /** @psalm-param A|B $arr */ + public function ooo(array $arr): void { + assertType("array{tag: 'A', foo: bool}|array{tag: 'B'}", $arr); + if ($arr['tag'] === 'A') { + assertType("array{tag: 'A', foo: bool}", $arr); + } else { + assertType("array{tag: 'B'}", $arr); + } + assertType("array{tag: 'A', foo: bool}|array{tag: 'B'}", $arr); + } +} + +class TipsFromArnaud +{ + + // https://github.com/phpstan/phpstan/issues/7666#issuecomment-1191563801 + + /** + * @param array{a: int}|array{a: int} $a + */ + public function doFoo(array $a): void + { + assertType('array{a: int}', $a); + } + + /** + * @param array{a: int}|array{a: string} $a + */ + public function doFoo2(array $a): void + { + // could be: array{a: int|string} + assertType('array{a: int}|array{a: string}', $a); + } + + /** + * @param array{a: int, b: int}|array{a: string, b: string} $a + */ + public function doFoo3(array $a): void + { + assertType('array{a: int, b: int}|array{a: string, b: string}', $a); + } + + /** + * @param array{a: int, b: string}|array{a: string, b:string} $a + */ + public function doFoo4(array $a): void + { + // could be: array{a: int|string, b: string} + assertType('array{a: int, b: string}|array{a: string, b: string}', $a); + } + + /** + * @param array{a: int, b: string, c: string}|array{a: string, b: string, c: string} $a + */ + public function doFoo5(array $a): void + { + // could be: array{a: int|string, b: string, c: string} + assertType('array{a: int, b: string, c: string}|array{a: string, b: string, c: string}', $a); + } + +} diff --git a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php index 1cf021be7f..34214e8c68 100644 --- a/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php +++ b/tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php @@ -84,7 +84,7 @@ public function testRule(): void 145, ], [ - 'Offset \'c\' does not exist on array{c: bool}|array{e: true}.', + 'Offset \'c\' does not exist on array{c: false}|array{c: true}|array{e: true}.', 171, ], [ diff --git a/tests/PHPStan/Rules/Arrays/data/bug-6364.php b/tests/PHPStan/Rules/Arrays/data/bug-6364.php index 3352a61c27..cf259552ee 100644 --- a/tests/PHPStan/Rules/Arrays/data/bug-6364.php +++ b/tests/PHPStan/Rules/Arrays/data/bug-6364.php @@ -33,12 +33,12 @@ public function doFoo(array $array) foreach ($array as $key => $data) { switch ($data['type']) { case 'Type1': - assertType("array{type: 'Type1', id: string, jobs: array}", $data); + assertType("array{type: 'Type1', id: string, jobs: array}", $data); echo $data['id']; print_r($data['jobs']); break; case 'Type3': - assertType("array{type: 'Type3', id: string, jobs: array}", $data); + assertType("array{type: 'Type3', id: string, jobs: array}", $data); $jobs = []; foreach ($data['jobs'] as $job => $extractor) { echo $job; @@ -46,13 +46,13 @@ public function doFoo(array $array) } break; case 'Type2': - assertType("array{type: 'Type2', id: string, job?: string, extractor?: int}|array{type: 'Type2', id: string, jobs: array}", $data); + assertType("array{type: 'Type2', id: string, job?: string, extractor?: int}", $data); echo $data['id']; echo $data['job'] ?? 'default'; echo $data['extractor'] ?? 0; break; case 'Type4': - assertType("array{type: 'Type4', id: string, job?: string, extractor?: int}|array{type: 'Type4', id: string, jobs: array}", $data); + assertType("array{type: 'Type4', id: string, job?: string}", $data); echo $data['id']; echo $data['job'] ?? 'default'; break; diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index ffba7a8ba9..0aea96e4c3 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -724,4 +724,15 @@ public function testBug7511(): void $this->analyse([__DIR__ . '/data/bug-7511.php'], []); } + public function testTaggedUnions(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/tagged-unions.php'], [ + [ + 'Method TaggedUnionReturnCheck\HelloWorld::sayHello() should return array{updated: false, id: null}|array{updated: true, id: int} but returns array{updated: false, id: 5}.', + 12, + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/tagged-unions.php b/tests/PHPStan/Rules/Methods/data/tagged-unions.php new file mode 100644 index 0000000000..1f76221d5a --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/tagged-unions.php @@ -0,0 +1,14 @@ + false, 'id' => 5]; + } +} diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 91b0f43c31..052e31b6d0 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -328,7 +328,7 @@ public function testBug6286(): void 19, ], [ - 'Property Bug6286\HelloWorld::$nestedDetails (array) does not accept non-empty-array.', + "Property Bug6286\HelloWorld::\$nestedDetails (array) does not accept non-empty-array.", 22, ], ]); diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index d0278a60a6..75595f2bef 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -714,8 +714,8 @@ public function dataUnion(): iterable new StringType(), ]), ], - ConstantArrayType::class, - 'array{foo: DateTimeImmutable|null, bar: int|string}', + UnionType::class, + 'array{foo: DateTimeImmutable, bar: int}|array{foo: null, bar: string}', ], [ [ @@ -732,8 +732,8 @@ public function dataUnion(): iterable new NullType(), ]), ], - ConstantArrayType::class, - 'array{foo: DateTimeImmutable|null, bar?: int}', + UnionType::class, + 'array{foo: DateTimeImmutable, bar: int}|array{foo: null}', ], [ [ @@ -754,8 +754,8 @@ public function dataUnion(): iterable new IntegerType(), ]), ], - ConstantArrayType::class, - 'array{foo: DateTimeImmutable|null, bar: int|string, baz?: int}', + UnionType::class, + 'array{foo: DateTimeImmutable, bar: int}|array{foo: null, bar: string, baz: int}', ], [ [ diff --git a/tests/PHPStan/Type/UnionTypeTest.php b/tests/PHPStan/Type/UnionTypeTest.php index 402ee1c1b8..b26e9a00ad 100644 --- a/tests/PHPStan/Type/UnionTypeTest.php +++ b/tests/PHPStan/Type/UnionTypeTest.php @@ -695,7 +695,7 @@ public function dataDescribe(): array ]), new ConstantStringType('aaa'), ), - '\'aaa\'|array{a: int|string, b: bool|float}', + '\'aaa\'|array{a: int, b: float}|array{a: string, b: bool}', 'array|string', ], [ @@ -757,7 +757,7 @@ public function dataDescribe(): array new FloatType(), ]), ), - 'array{0: int|string, 1?: bool, 2?: float}', + 'array{int, bool, float}|array{string}', 'array', ], [