diff --git a/src/Type/Constant/ConstantArrayTypeBuilder.php b/src/Type/Constant/ConstantArrayTypeBuilder.php index ad9340252e..c901306f28 100644 --- a/src/Type/Constant/ConstantArrayTypeBuilder.php +++ b/src/Type/Constant/ConstantArrayTypeBuilder.php @@ -258,6 +258,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt } if (count($scalarTypes) > 0 && count($scalarTypes) < self::ARRAY_COUNT_LIMIT) { $match = true; + $hasMatch = false; $valueTypes = $this->valueTypes; foreach ($scalarTypes as $scalarType) { $offsetMatch = false; @@ -273,6 +274,7 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt } if ($offsetMatch) { + $hasMatch = true; continue; } @@ -283,6 +285,26 @@ public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $opt $this->valueTypes = $valueTypes; return; } + + if (!$hasMatch && count($this->keyTypes) > 0) { + foreach ($scalarTypes as $scalarType) { + $this->keyTypes[] = $scalarType; + $this->valueTypes[] = $valueType; + $this->optionalKeys[] = count($this->keyTypes) - 1; + } + + $this->isList = TrinaryLogic::createNo(); + + if ( + !$this->disableArrayDegradation + && count($this->keyTypes) > self::ARRAY_COUNT_LIMIT + ) { + $this->degradeToGeneralArray = true; + $this->oversized = true; + } + + return; + } } $this->isList = TrinaryLogic::createNo(); diff --git a/tests/PHPStan/Analyser/nsrt/array-fill-keys.php b/tests/PHPStan/Analyser/nsrt/array-fill-keys.php index 9a56ef0cfb..39d1df4b5d 100644 --- a/tests/PHPStan/Analyser/nsrt/array-fill-keys.php +++ b/tests/PHPStan/Analyser/nsrt/array-fill-keys.php @@ -54,14 +54,14 @@ function withObjectKey() : array function withUnionKeys(): void { $arr1 = ['foo', rand(0, 1) ? 'bar1' : 'bar2', 'baz']; - assertType("non-empty-array<'bar1'|'bar2'|'baz'|'foo', 'b'>", array_fill_keys($arr1, 'b')); + assertType("array{foo: 'b', bar1?: 'b', bar2?: 'b', baz: 'b'}", array_fill_keys($arr1, 'b')); $arr2 = ['foo']; if (rand(0, 1)) { $arr2[] = 'bar'; } $arr2[] = 'baz'; - assertType("non-empty-array<'bar'|'baz'|'foo', 'b'>", array_fill_keys($arr2, 'b')); + assertType("array{foo: 'b', bar?: 'b', baz?: 'b'}", array_fill_keys($arr2, 'b')); } function withOptionalKeys(): void diff --git a/tests/PHPStan/Analyser/nsrt/bug-12665.php b/tests/PHPStan/Analyser/nsrt/bug-12665.php new file mode 100644 index 0000000000..d488e450ea --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12665.php @@ -0,0 +1,19 @@ + $s]; + foreach (['b', 'c'] as $letter) { + $array[$letter] = $i; + } + assertType('array{a: string, b?: int, c?: int}', $array); + return $array; + } +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-9907.php b/tests/PHPStan/Analyser/nsrt/bug-9907.php new file mode 100644 index 0000000000..f1bf25bc09 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-9907.php @@ -0,0 +1,18 @@ + null, $key => 'string']; + + assertType("array{id: null, foo?: 'string', bar?: 'string'}", $a); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/set-constant-union-offset-on-constant-array.php b/tests/PHPStan/Analyser/nsrt/set-constant-union-offset-on-constant-array.php new file mode 100644 index 0000000000..15638a7bd4 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/set-constant-union-offset-on-constant-array.php @@ -0,0 +1,20 @@ += 8.0')] + public function testBug11006(): void + { + $this->analyse([__DIR__ . '/data/bug-11006.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/bug-11006.php b/tests/PHPStan/Rules/Classes/data/bug-11006.php new file mode 100644 index 0000000000..ae20fb42e3 --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-11006.php @@ -0,0 +1,74 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug11006; + +class ProductParentPayloadDto +{ + /** @param null|'size_uk'|'size_us' $SizeAttributeCode */ + public function __construct( + public ?string $SizeAttributeCode, + ) { + } +} + +class AkeneoUpdateProductDto +{ + /** + * @param array{ + * ean?: array, + * osa_sizes?: array, + * size_uk?: array, + * size_us?: array, + * } $values + */ + public function __construct( + public array $values, + ) { + } +} + +class StringOrNullAttributeDto +{ + public function __construct( + public ?string $data, + ) { + } +} + +class StringAttributeDto +{ + public function __construct( + public string $data, + ) { + } +} + + +class PhpStanProblem +{ + public function example(ProductParentPayloadDto $productParentPayloadDto): void + { + if (null === $productParentPayloadDto->SizeAttributeCode) { + return; + } + + $values = [ + 'ean' => [ + new StringOrNullAttributeDto(''), + ], + $productParentPayloadDto->SizeAttributeCode => [ + new StringOrNullAttributeDto(''), + ], + // This part goes wrong + 'osa_sizes' => [ + new StringAttributeDto(''), + ], + ]; + + $productData = new AkeneoUpdateProductDto( + values: $values, + ); + } +} diff --git a/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php b/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php index 9f6af54439..c4e5f101a3 100644 --- a/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php +++ b/tests/PHPStan/Rules/Functions/PrintfParametersRuleTest.php @@ -137,4 +137,9 @@ public function testBug1889(): void ]); } + public function testBug8774(): void + { + $this->analyse([__DIR__ . '/data/bug-8774.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Functions/data/bug-8774.php b/tests/PHPStan/Rules/Functions/data/bug-8774.php new file mode 100644 index 0000000000..5b5c68f8e3 --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-8774.php @@ -0,0 +1,25 @@ + 'Posting on forum and comments', + 'DisableAvatar' => 'Avatar and Custom Icon', + ]; + + public static function handleModerate(): void + { + $summaryTemplates = [ + 'PermissionID' => "Class changed from '%s' to '%s'.", + 'Reset' => '%s reset.', + ]; + + foreach (self::DISABLE_KEYS_AND_LABELS as $key => $label) { + $summaryTemplates[$key] = "Disable $label status %s."; + } + + echo sprintf($summaryTemplates['Reset'], 'foo'); + } +} diff --git a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php index fb1b119745..605590d721 100644 --- a/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php +++ b/tests/PHPStan/Rules/Operators/InvalidBinaryOperationRuleTest.php @@ -821,4 +821,9 @@ public function testBug10595(): void $this->analyse([__DIR__ . '/data/bug-10595.php'], []); } + public function testBug14080(): void + { + $this->analyse([__DIR__ . '/data/bug-14080.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Operators/data/bug-14080.php b/tests/PHPStan/Rules/Operators/data/bug-14080.php new file mode 100644 index 0000000000..d15875d35e --- /dev/null +++ b/tests/PHPStan/Rules/Operators/data/bug-14080.php @@ -0,0 +1,19 @@ + 0, 'duplicates' => 0]; +$queryTypes = ['select', 'update', 'delete', 'insert']; +$queries = [['sql' => 'select', 'time' => 8234], ['sql' => 'select', 'time' => 4558], ['sql' => 'insert', 'time' => 9928]]; + +$queryTotals['time'] = array_sum(array_column($queries, 'time')); + +foreach ($queryTypes as $type) { + $tq = array_filter($queries, fn ($v) => str_starts_with(strtolower($v['sql']), $type)); + $tq_time = array_sum(array_column($tq, 'time')); + $queryTotals['all'] += count($tq); + $queryTotals[$type] = [ + 'count' => count($tq), + 'time' => $tq_time / $queryTotals['time'] * 100, + ]; +}