diff --git a/src/PhpDoc/TypeNodeResolver.php b/src/PhpDoc/TypeNodeResolver.php index 9fc4e5836a..7ad5d80e29 100644 --- a/src/PhpDoc/TypeNodeResolver.php +++ b/src/PhpDoc/TypeNodeResolver.php @@ -1049,9 +1049,7 @@ function (CallableTypeParameterNode $parameterNode) use ($nameScope, &$isVariadi private function resolveArrayShapeNode(ArrayShapeNode $typeNode, NameScope $nameScope): Type { $builder = ConstantArrayTypeBuilder::createEmpty(); - if (count($typeNode->items) > ConstantArrayTypeBuilder::ARRAY_COUNT_LIMIT) { - $builder->degradeToGeneralArray(true); - } + $builder->disableArrayDegradation(); foreach ($typeNode->items as $itemNode) { if ($itemNode->valueType instanceof CallableTypeNode) { diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index 4d919a0e5a..0a8695faea 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -35,6 +35,8 @@ final class ConstantArrayTypeBuilder private bool $degradeToGeneralArray = false; + private bool $disableArrayDegradation = false; + private ?bool $degradeClosures = null; private bool $oversized = false; @@ -84,7 +86,11 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt } if (!$this->degradeToGeneralArray) { - if ($valueType instanceof ClosureType && $this->degradeClosures !== false) { + if ( + $valueType instanceof ClosureType + && $this->degradeClosures !== false + && !$this->disableArrayDegradation + ) { $numClosures = 1; foreach ($this->valueTypes as $innerType) { if (!($innerType instanceof ClosureType)) { @@ -147,7 +153,10 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt $this->optionalKeys[] = count($this->keyTypes) - 1; } - if (count($this->keyTypes) > self::ARRAY_COUNT_LIMIT) { + if ( + !$this->disableArrayDegradation + && count($this->keyTypes) > self::ARRAY_COUNT_LIMIT + ) { $this->degradeToGeneralArray = true; $this->oversized = true; } @@ -220,7 +229,10 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt $this->optionalKeys[] = count($this->keyTypes) - 1; } - if (count($this->keyTypes) > self::ARRAY_COUNT_LIMIT) { + if ( + !$this->disableArrayDegradation + && count($this->keyTypes) > self::ARRAY_COUNT_LIMIT + ) { $this->degradeToGeneralArray = true; $this->oversized = true; } @@ -296,6 +308,10 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt public function degradeToGeneralArray(bool $oversized = false): void { + if ($this->disableArrayDegradation) { + throw new ShouldNotHappenException(); + } + $this->degradeToGeneralArray = true; $this->oversized = $this->oversized || $oversized; } @@ -305,6 +321,13 @@ public function disableClosureDegradation(): void $this->degradeClosures = false; } + public function disableArrayDegradation(): void + { + $this->degradeToGeneralArray = false; + $this->oversized = false; + $this->disableArrayDegradation = true; + } + public function getArray(): Type { $keyTypesCount = count($this->keyTypes); diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index db1a3d6b6b..cf56a34adc 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1637,6 +1637,12 @@ public function testBug13945Two(): void $this->assertNoErrors($errors); } + public function testBigPhpdocArrayShape(): void + { + $errors = $this->runAnalyse(__DIR__ . '/nsrt/bug-14012b.php'); + $this->assertNoErrors($errors); + } + /** * @param string[]|null $allAnalysedFiles * @return list diff --git a/tests/PHPStan/Analyser/nsrt/bug-14012b.php b/tests/PHPStan/Analyser/nsrt/bug-14012b.php new file mode 100644 index 0000000000..0b82760e40 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-14012b.php @@ -0,0 +1,293 @@ +assertSame('non-empty-array', $array->describe(VerbosityLevel::precise())); } + public function testDegradesWhileDisableArrayDegradation(): void + { + $builder = ConstantArrayTypeBuilder::createEmpty(); + $builder->disableArrayDegradation(); + for ($i = 0; $i < 30; $i++) { + $builder->setOffsetValueType(new StringType(), new ConstantIntegerType($i)); + } + $builder->setOffsetValueType(new StringType(), new IntegerType()); + + $array = $builder->getArray(); + $this->assertSame('non-empty-array', $array->describe(VerbosityLevel::precise())); + } + + public function testDisableArrayDegradation(): void + { + $builder = ConstantArrayTypeBuilder::createEmpty(); + $builder->disableArrayDegradation(); + for ($i = 0; $i < 300; $i++) { + $builder->setOffsetValueType(new ConstantIntegerType($i), new ConstantIntegerType($i)); + } + + $array = $builder->getArray(); + $this->assertSame('array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299}', $array->describe(VerbosityLevel::precise())); + } + + public function testArrayDegradation(): void + { + $builder = ConstantArrayTypeBuilder::createEmpty(); + for ($i = 0; $i < 300; $i++) { + $builder->setOffsetValueType(new ConstantIntegerType($i), new ConstantIntegerType($i)); + } + + $array = $builder->getArray(); + $this->assertSame('non-empty-array<0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116|117|118|119|120|121|122|123|124|125|126|127|128|129|130|131|132|133|134|135|136|137|138|139|140|141|142|143|144|145|146|147|148|149|150|151|152|153|154|155|156|157|158|159|160|161|162|163|164|165|166|167|168|169|170|171|172|173|174|175|176|177|178|179|180|181|182|183|184|185|186|187|188|189|190|191|192|193|194|195|196|197|198|199|200|201|202|203|204|205|206|207|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|223|224|225|226|227|228|229|230|231|232|233|234|235|236|237|238|239|240|241|242|243|244|245|246|247|248|249|250|251|252|253|254|255|256|257|258|259|260|261|262|263|264|265|266|267|268|269|270|271|272|273|274|275|276|277|278|279|280|281|282|283|284|285|286|287|288|289|290|291|292|293|294|295|296|297|298|299, 0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116|117|118|119|120|121|122|123|124|125|126|127|128|129|130|131|132|133|134|135|136|137|138|139|140|141|142|143|144|145|146|147|148|149|150|151|152|153|154|155|156|157|158|159|160|161|162|163|164|165|166|167|168|169|170|171|172|173|174|175|176|177|178|179|180|181|182|183|184|185|186|187|188|189|190|191|192|193|194|195|196|197|198|199|200|201|202|203|204|205|206|207|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|223|224|225|226|227|228|229|230|231|232|233|234|235|236|237|238|239|240|241|242|243|244|245|246|247|248|249|250|251|252|253|254|255|256|257|258|259|260|261|262|263|264|265|266|267|268|269|270|271|272|273|274|275|276|277|278|279|280|281|282|283|284|285|286|287|288|289|290|291|292|293|294|295|296|297|298|299>&oversized-array', $array->describe(VerbosityLevel::precise())); + } + public function testIsList(): void { $builder = ConstantArrayTypeBuilder::createEmpty();