From de109b6a55118377ba663e50608de59df667c9c6 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 9 Aug 2024 09:43:24 +0200 Subject: [PATCH 1/8] Fix bug in array-shape subtraction --- src/Type/Constant/ConstantArrayType.php | 11 ++++++++++ tests/PHPStan/Analyser/nsrt/bug11488.php | 26 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug11488.php diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 3853794348..67afcf87b2 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -366,6 +366,17 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return $type->isIterableAtLeastOnce()->negate(); } + if ( + $this->isList->yes() + && $type->isList->yes() + && count($this->keyTypes) !== count($type->keyTypes) + ) { + if (count($type->optionalKeys) > 0) { + return TrinaryLogic::createMaybe(); + } + return TrinaryLogic::createNo(); + } + $results = []; foreach ($this->keyTypes as $i => $keyType) { $hasOffset = $type->hasOffsetValueType($keyType); diff --git a/tests/PHPStan/Analyser/nsrt/bug11488.php b/tests/PHPStan/Analyser/nsrt/bug11488.php new file mode 100644 index 0000000000..60c407e634 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug11488.php @@ -0,0 +1,26 @@ + Date: Fri, 9 Aug 2024 10:01:23 +0200 Subject: [PATCH 2/8] more tests --- tests/PHPStan/Analyser/nsrt/bug11488.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug11488.php b/tests/PHPStan/Analyser/nsrt/bug11488.php index 60c407e634..b3817a2119 100644 --- a/tests/PHPStan/Analyser/nsrt/bug11488.php +++ b/tests/PHPStan/Analyser/nsrt/bug11488.php @@ -6,6 +6,28 @@ class Foo { + /** + * @param array{mixed}|array{0: mixed, 1?: string|null} $row + */ + protected function testOptionalKeys(array $row): void + { + if (count($row) === 1) { + assertType('array{0: mixed, 1?: string|null}', $row); + } + + if (count($row) !== 1) { + assertType('array{0: mixed, 1?: string|null}', $row); + } + + if (count($row) !== 2) { + assertType('array{0: mixed, 1?: string|null}', $row); + } + + if (count($row) !== 3) { + assertType('array{0: mixed, 1?: string|null}', $row); + } + } + /** * @param array{mixed}|array{mixed, string|null, mixed} $row */ From 21d99b45d157978ed8c41290a2b2233f65bc3833 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 9 Aug 2024 10:08:10 +0200 Subject: [PATCH 3/8] fix --- src/Type/Constant/ConstantArrayType.php | 4 +--- tests/PHPStan/Analyser/nsrt/bug11488.php | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 67afcf87b2..3763781e10 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -370,10 +370,8 @@ public function isSuperTypeOf(Type $type): TrinaryLogic $this->isList->yes() && $type->isList->yes() && count($this->keyTypes) !== count($type->keyTypes) + && count($type->optionalKeys) === 0 ) { - if (count($type->optionalKeys) > 0) { - return TrinaryLogic::createMaybe(); - } return TrinaryLogic::createNo(); } diff --git a/tests/PHPStan/Analyser/nsrt/bug11488.php b/tests/PHPStan/Analyser/nsrt/bug11488.php index b3817a2119..9c760a43fd 100644 --- a/tests/PHPStan/Analyser/nsrt/bug11488.php +++ b/tests/PHPStan/Analyser/nsrt/bug11488.php @@ -24,7 +24,8 @@ protected function testOptionalKeys(array $row): void } if (count($row) !== 3) { - assertType('array{0: mixed, 1?: string|null}', $row); + // should be array{0: mixed, 1?: string|null} + assertType('array{0: mixed, 1?: mixed}', $row); } } From f894e7b42b92df346065613ac56cdbe0c3238ddf Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 9 Aug 2024 10:19:43 +0200 Subject: [PATCH 4/8] fix --- src/Type/Constant/ConstantArrayType.php | 28 +++++++++++++++++-------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Type/Constant/ConstantArrayType.php b/src/Type/Constant/ConstantArrayType.php index 3763781e10..3422c8ce0b 100644 --- a/src/Type/Constant/ConstantArrayType.php +++ b/src/Type/Constant/ConstantArrayType.php @@ -366,15 +366,6 @@ public function isSuperTypeOf(Type $type): TrinaryLogic return $type->isIterableAtLeastOnce()->negate(); } - if ( - $this->isList->yes() - && $type->isList->yes() - && count($this->keyTypes) !== count($type->keyTypes) - && count($type->optionalKeys) === 0 - ) { - return TrinaryLogic::createNo(); - } - $results = []; foreach ($this->keyTypes as $i => $keyType) { $hasOffset = $type->hasOffsetValueType($keyType); @@ -396,6 +387,25 @@ public function isSuperTypeOf(Type $type): TrinaryLogic $results[] = $isValueSuperType; } + if ( + $this->isList->yes() + && $type->isList->yes() + && count($this->keyTypes) !== count($type->keyTypes) + && count($type->optionalKeys) === 0 + ) { + $keepSeparate = true; + foreach ($this->valueTypes as $valueType) { + if ($valueType->isConstantValue()->yes()) { + $keepSeparate = false; + break; + } + } + + if ($keepSeparate) { + return TrinaryLogic::createNo(); + } + } + return TrinaryLogic::createYes()->and(...$results); } From 282252ab86738048cbde5932cbe9580579ec3590 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 9 Aug 2024 10:29:41 +0200 Subject: [PATCH 5/8] more assertions --- tests/PHPStan/Analyser/nsrt/bug11488.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/PHPStan/Analyser/nsrt/bug11488.php b/tests/PHPStan/Analyser/nsrt/bug11488.php index 9c760a43fd..408ab0e49a 100644 --- a/tests/PHPStan/Analyser/nsrt/bug11488.php +++ b/tests/PHPStan/Analyser/nsrt/bug11488.php @@ -13,19 +13,28 @@ protected function testOptionalKeys(array $row): void { if (count($row) === 1) { assertType('array{0: mixed, 1?: string|null}', $row); + } else { + assertType('array{0: mixed, 1?: string|null}', $row); } if (count($row) !== 1) { assertType('array{0: mixed, 1?: string|null}', $row); + } else { + assertType('array{0: mixed, 1?: string|null}', $row); } if (count($row) !== 2) { assertType('array{0: mixed, 1?: string|null}', $row); + } else { + // should be array{0: mixed, 1: string|null} + assertType('array{mixed, mixed}', $row); } if (count($row) !== 3) { // should be array{0: mixed, 1?: string|null} assertType('array{0: mixed, 1?: mixed}', $row); + } else { + assertType('*NEVER*', $row); } } @@ -36,14 +45,20 @@ protected function test(array $row): void { if (count($row) !== 1) { assertType('array{mixed, string|null, mixed}', $row); + } else { + assertType('array{0: mixed, 1?: string|null}', $row); } if (count($row) !== 2) { assertType('array{mixed, string|null, mixed}|array{mixed}', $row); + } else { + assertType('*NEVER*', $row); } if (count($row) !== 3) { assertType('array{mixed}', $row); + } else { + assertType('array{mixed, string|null, mixed}', $row); } } } From 09f35267566fedd2ca4a0b285092fee05c1598d1 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Fri, 9 Aug 2024 10:33:16 +0200 Subject: [PATCH 6/8] Update bug11488.php --- tests/PHPStan/Analyser/nsrt/bug11488.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PHPStan/Analyser/nsrt/bug11488.php b/tests/PHPStan/Analyser/nsrt/bug11488.php index 408ab0e49a..b6acdaa452 100644 --- a/tests/PHPStan/Analyser/nsrt/bug11488.php +++ b/tests/PHPStan/Analyser/nsrt/bug11488.php @@ -46,7 +46,7 @@ protected function test(array $row): void if (count($row) !== 1) { assertType('array{mixed, string|null, mixed}', $row); } else { - assertType('array{0: mixed, 1?: string|null}', $row); + assertType('array{mixed}', $row); } if (count($row) !== 2) { From 2f692c6052c0472b222af88b41a2db1251390a97 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 12 Aug 2024 12:40:32 +0200 Subject: [PATCH 7/8] Update TypeCombinatorTest.php --- tests/PHPStan/Type/TypeCombinatorTest.php | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index e521c3edba..6ae059d2d5 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -4876,6 +4876,37 @@ public function dataRemove(): array ObjectShapeType::class, 'object{foo?: int}', ], + [ + new UnionType([ + new ConstantArrayType([ + new ConstantIntegerType(0), + ], [ + new MixedType(true), + ], + [0], [], true + ), + new ConstantArrayType([ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + new ConstantIntegerType(2), + ], [ + new MixedType(true), + new UnionType([new StringType(), new NullType()]), + new MixedType(true), + ], + [0], [], true + ), + ]), + new ConstantArrayType([ + new ConstantIntegerType(0), + ], [ + new MixedType(true), + ], + [0], [], true + ), + ConstantArrayType::class, + 'array{mixed, string|null, mixed}', + ], ]; } From 07a2469df77a21d68054e00b9596ee7bfe6ebfbe Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 12 Aug 2024 12:45:35 +0200 Subject: [PATCH 8/8] cs --- tests/PHPStan/Type/TypeCombinatorTest.php | 54 ++++++++++++++--------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/tests/PHPStan/Type/TypeCombinatorTest.php b/tests/PHPStan/Type/TypeCombinatorTest.php index 6ae059d2d5..a08e10e7c9 100644 --- a/tests/PHPStan/Type/TypeCombinatorTest.php +++ b/tests/PHPStan/Type/TypeCombinatorTest.php @@ -4878,31 +4878,43 @@ public function dataRemove(): array ], [ new UnionType([ - new ConstantArrayType([ - new ConstantIntegerType(0), - ], [ - new MixedType(true), - ], - [0], [], true + new ConstantArrayType( + [ + new ConstantIntegerType(0), + ], + [ + new MixedType(true), + ], + [0], + [], + true, ), - new ConstantArrayType([ + new ConstantArrayType( + [ + new ConstantIntegerType(0), + new ConstantIntegerType(1), + new ConstantIntegerType(2), + ], + [ + new MixedType(true), + new UnionType([new StringType(), new NullType()]), + new MixedType(true), + ], + [0], + [], + true, + ), + ]), + new ConstantArrayType( + [ new ConstantIntegerType(0), - new ConstantIntegerType(1), - new ConstantIntegerType(2), - ], [ - new MixedType(true), - new UnionType([new StringType(), new NullType()]), + ], + [ new MixedType(true), ], - [0], [], true - ), - ]), - new ConstantArrayType([ - new ConstantIntegerType(0), - ], [ - new MixedType(true), - ], - [0], [], true + [0], + [], + true, ), ConstantArrayType::class, 'array{mixed, string|null, mixed}',