diff --git a/src/Type/Generic/TemplateConstantArrayType.php b/src/Type/Generic/TemplateConstantArrayType.php index fbc13bad6d..9ea9cee28f 100644 --- a/src/Type/Generic/TemplateConstantArrayType.php +++ b/src/Type/Generic/TemplateConstantArrayType.php @@ -35,4 +35,42 @@ public function __construct( $this->default = $default; } + public function setOffsetValueType(?Type $offsetType, Type $valueType, bool $unionValues = true): Type + { + if ($this->isOffsetWithinBound($offsetType, $valueType)) { + return $this; + } + + return parent::setOffsetValueType($offsetType, $valueType, $unionValues); + } + + public function setExistingOffsetValueType(Type $offsetType, Type $valueType): Type + { + if ($this->isOffsetWithinBound($offsetType, $valueType)) { + return $this; + } + + return parent::setExistingOffsetValueType($offsetType, $valueType); + } + + private function isOffsetWithinBound(?Type $offsetType, Type $valueType): bool + { + if ($offsetType === null) { + return false; + } + + $boundKeyTypes = $this->bound->getKeyTypes(); + $boundValueTypes = $this->bound->getValueTypes(); + + foreach ($boundKeyTypes as $i => $boundKeyType) { + if (!$offsetType->equals($boundKeyType)) { + continue; + } + + return $boundValueTypes[$i]->accepts($valueType, true)->yes(); + } + + return false; + } + } diff --git a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php index 84e81f0f33..40f4e46571 100644 --- a/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php @@ -1310,4 +1310,9 @@ public function testBug9669(): void $this->analyse([__DIR__ . '/data/bug-9669.php'], []); } + public function testBug10172(): void + { + $this->analyse([__DIR__ . '/data/bug-10172.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Methods/data/bug-10172.php b/tests/PHPStan/Rules/Methods/data/bug-10172.php new file mode 100644 index 0000000000..434ac1ae39 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-10172.php @@ -0,0 +1,21 @@ +} + * + * @param T $a + * + * @return T + */ + public function foo(array $a): array + { + foreach ($a['data'] as $i) { + } + + return $a; + } +} diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 29c2c32aca..a159d25d46 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -1003,4 +1003,9 @@ public function testCloneWith(): void ]); } + public function testBug7170(): void + { + $this->analyse([__DIR__ . '/data/bug-7170.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Properties/data/bug-7170.php b/tests/PHPStan/Rules/Properties/data/bug-7170.php new file mode 100644 index 0000000000..1d5ff5d05b --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-7170.php @@ -0,0 +1,44 @@ +} + */ +class Data +{ + /** + * @var Tdata + */ + private $data; + + /** + * @param Tdata $data + */ + public function __construct(array $data = []) + { + $this->data = $data; + } + + public function setExtensionProperty(): void + { + if (!isset($this->data['extension'])) { + $this->data['extension'] = []; + } + } +} + +class NonGeneric +{ + /** + * @var array{extension?: array} + */ + private $data; + + public function setData(): void + { + if (!isset($this->data['extension'])) { + $this->data['extension'] = []; + } + } +}