diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index ecf824a240..e31c6c4c3c 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -5344,9 +5344,17 @@ private function processAssignVar( $originalVar = $var; $assignedPropertyExpr = $assignedExpr; while ($var instanceof ArrayDimFetch) { - $varForSetOffsetValue = $var->var; - if ($varForSetOffsetValue instanceof PropertyFetch || $varForSetOffsetValue instanceof StaticPropertyFetch) { - $varForSetOffsetValue = new OriginalPropertyTypeExpr($varForSetOffsetValue); + if ( + $var->var instanceof PropertyFetch + || $var->var instanceof StaticPropertyFetch + ) { + if (((new ObjectType(ArrayAccess::class))->isSuperTypeOf($scope->getType($var->var))->yes())) { + $varForSetOffsetValue = $var->var; + } else { + $varForSetOffsetValue = new OriginalPropertyTypeExpr($var->var); + } + } else { + $varForSetOffsetValue = $var->var; } $assignedPropertyExpr = new SetOffsetValueTypeExpr( $varForSetOffsetValue, @@ -5682,9 +5690,17 @@ static function (): void { $dimFetchStack = []; $assignedPropertyExpr = $assignedExpr; while ($var instanceof ExistingArrayDimFetch) { - $varForSetOffsetValue = $var->getVar(); - if ($varForSetOffsetValue instanceof PropertyFetch || $varForSetOffsetValue instanceof StaticPropertyFetch) { - $varForSetOffsetValue = new OriginalPropertyTypeExpr($varForSetOffsetValue); + if ( + $var->getVar() instanceof PropertyFetch + || $var->getVar() instanceof StaticPropertyFetch + ) { + if (((new ObjectType(ArrayAccess::class))->isSuperTypeOf($scope->getType($var->getVar()))->yes())) { + $varForSetOffsetValue = $var->getVar(); + } else { + $varForSetOffsetValue = new OriginalPropertyTypeExpr($var->getVar()); + } + } else { + $varForSetOffsetValue = $var->getVar(); } $assignedPropertyExpr = new SetExistingOffsetValueTypeExpr( $varForSetOffsetValue, diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 533c53c25d..7c80c91cd6 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -689,6 +689,28 @@ public function testBug12131(): void ]); } + public function testBug6398(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-6398.php'], []); + } + + public function testBug6571(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-6571.php'], []); + } + + public function testBug12565(): void + { + if (PHP_VERSION_ID < 80000) { + $this->markTestSkipped('Test requires PHP 8.0.'); + } + + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-12565.php'], []); + } + public function testShortBodySetHook(): void { if (PHP_VERSION_ID < 80400) { diff --git a/tests/PHPStan/Rules/Properties/data/bug-12565.php b/tests/PHPStan/Rules/Properties/data/bug-12565.php new file mode 100755 index 0000000000..12fafa7469 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-12565.php @@ -0,0 +1,50 @@ + + */ +class ArrayLike implements \ArrayAccess { + + /** @var EntryType[] */ + private array $values = []; + public function offsetExists(mixed $offset): bool + { + return isset($this->values[$offset]); + } + + public function offsetGet(mixed $offset): EntryType + { + return $this->values[$offset] ?? new EntryType(); + } + + public function offsetSet(mixed $offset, mixed $value): void + { + $this->values[$offset] = $value; + } + + public function offsetUnset(mixed $offset): void + { + unset($this->values[$offset]); + } +} + +class Wrapper { + public ?ArrayLike $myArrayLike; + + public function __construct() + { + $this->myArrayLike = new ArrayLike(); + + } +} + +$baz = new Wrapper(); +$baz->myArrayLike = new ArrayLike(); +$baz->myArrayLike[1] = new EntryType(); +$baz->myArrayLike[1]->title = "Test"; diff --git a/tests/PHPStan/Rules/Properties/data/bug-6398.php b/tests/PHPStan/Rules/Properties/data/bug-6398.php new file mode 100644 index 0000000000..b1b824d541 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-6398.php @@ -0,0 +1,32 @@ +>|null + */ + private static $threadLocalStorage = null; + + /** + * @param mixed $complexData the data to store + */ + protected function storeLocal(string $key, $complexData) : void{ + if(self::$threadLocalStorage === null){ + self::$threadLocalStorage = new \ArrayObject(); + } + self::$threadLocalStorage[spl_object_id($this)][$key] = $complexData; + } + + /** + * @return mixed + */ + protected function fetchLocal(string $key){ + $id = spl_object_id($this); + if(self::$threadLocalStorage === null or !isset(self::$threadLocalStorage[$id][$key])){ + throw new \InvalidArgumentException("No matching thread-local data found on this thread"); + } + + return self::$threadLocalStorage[$id][$key]; + } +} diff --git a/tests/PHPStan/Rules/Properties/data/bug-6571.php b/tests/PHPStan/Rules/Properties/data/bug-6571.php new file mode 100644 index 0000000000..3ce06cc10d --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-6571.php @@ -0,0 +1,31 @@ += 7.4 + +namespace Bug6571; + +interface ClassLoader{} + +class HelloWorld +{ + /** @var \Threaded|\ClassLoader[]|null */ + private ?\Threaded $classLoaders = null; + + /** + * @param \ClassLoader[] $autoloaders + */ + public function setClassLoaders(?array $autoloaders = null) : void{ + if($autoloaders === null){ + $autoloaders = []; + } + + if($this->classLoaders === null){ + $this->classLoaders = new \Threaded(); + }else{ + foreach($this->classLoaders as $k => $autoloader){ + unset($this->classLoaders[$k]); + } + } + foreach($autoloaders as $autoloader){ + $this->classLoaders[] = $autoloader; + } + } +}