From 9876d88c82320c07eff26ed91ce12131c237efb7 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 20 Mar 2025 09:16:57 +0100 Subject: [PATCH 1/4] tests --- .../TypesAssignedToPropertiesRuleTest.php | 24 +++++++++ .../Rules/Properties/data/bug-12565.php | 50 +++++++++++++++++++ .../Rules/Properties/data/bug-6398.php | 32 ++++++++++++ .../Rules/Properties/data/bug-6571.php | 31 ++++++++++++ .../Rules/Properties/data/bug-7880.php | 14 ++++++ 5 files changed, 151 insertions(+) create mode 100755 tests/PHPStan/Rules/Properties/data/bug-12565.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-6398.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-6571.php create mode 100644 tests/PHPStan/Rules/Properties/data/bug-7880.php diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index 533c53c25d..b74ee13281 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -689,6 +689,30 @@ 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 testBug7880(): void + { + $this->checkExplicitMixed = true; + $this->analyse([__DIR__ . '/data/bug-7880.php'], []); + } + + public function testBug12565(): void + { + $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; + } + } +} diff --git a/tests/PHPStan/Rules/Properties/data/bug-7880.php b/tests/PHPStan/Rules/Properties/data/bug-7880.php new file mode 100644 index 0000000000..c90f6ba014 --- /dev/null +++ b/tests/PHPStan/Rules/Properties/data/bug-7880.php @@ -0,0 +1,14 @@ +a !== null) { + $this->a['b'] = "baz"; + } + } +} From a4bf7cdc88fd6fc4032f83d5db2bafb4ca8fc735 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 20 Mar 2025 09:33:02 +0100 Subject: [PATCH 2/4] Fix for ArrayAccess --- src/Analyser/NodeScopeResolver.php | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) 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, From 1193ebb60958746827ab9ab741003680e2af0eab Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 20 Mar 2025 09:36:46 +0100 Subject: [PATCH 3/4] Remove non-working test --- .../TypesAssignedToPropertiesRuleTest.php | 6 ------ tests/PHPStan/Rules/Properties/data/bug-7880.php | 14 -------------- 2 files changed, 20 deletions(-) delete mode 100644 tests/PHPStan/Rules/Properties/data/bug-7880.php diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index b74ee13281..b38d2d0e37 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -701,12 +701,6 @@ public function testBug6571(): void $this->analyse([__DIR__ . '/data/bug-6571.php'], []); } - public function testBug7880(): void - { - $this->checkExplicitMixed = true; - $this->analyse([__DIR__ . '/data/bug-7880.php'], []); - } - public function testBug12565(): void { $this->checkExplicitMixed = true; diff --git a/tests/PHPStan/Rules/Properties/data/bug-7880.php b/tests/PHPStan/Rules/Properties/data/bug-7880.php deleted file mode 100644 index c90f6ba014..0000000000 --- a/tests/PHPStan/Rules/Properties/data/bug-7880.php +++ /dev/null @@ -1,14 +0,0 @@ -a !== null) { - $this->a['b'] = "baz"; - } - } -} From 280b718ba54c060c5e81d66f7c1e9b738500b9d2 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 20 Mar 2025 09:47:42 +0100 Subject: [PATCH 4/4] Works only on 8+ --- .../Rules/Properties/TypesAssignedToPropertiesRuleTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php index b38d2d0e37..7c80c91cd6 100644 --- a/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php @@ -703,6 +703,10 @@ public function testBug6571(): void 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'], []); }