Skip to content

Commit 17d4a03

Browse files
Narrow type on setting offsets of properties
Co-authored-by: Ondrej Mirtes <ondrej@mirtes.cz>
1 parent 11ef303 commit 17d4a03

File tree

5 files changed

+157
-6
lines changed

5 files changed

+157
-6
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5344,9 +5344,17 @@ private function processAssignVar(
53445344
$originalVar = $var;
53455345
$assignedPropertyExpr = $assignedExpr;
53465346
while ($var instanceof ArrayDimFetch) {
5347-
$varForSetOffsetValue = $var->var;
5348-
if ($varForSetOffsetValue instanceof PropertyFetch || $varForSetOffsetValue instanceof StaticPropertyFetch) {
5349-
$varForSetOffsetValue = new OriginalPropertyTypeExpr($varForSetOffsetValue);
5347+
if (
5348+
$var->var instanceof PropertyFetch
5349+
|| $var->var instanceof StaticPropertyFetch
5350+
) {
5351+
if (((new ObjectType(ArrayAccess::class))->isSuperTypeOf($scope->getType($var->var))->yes())) {
5352+
$varForSetOffsetValue = $var->var;
5353+
} else {
5354+
$varForSetOffsetValue = new OriginalPropertyTypeExpr($var->var);
5355+
}
5356+
} else {
5357+
$varForSetOffsetValue = $var->var;
53505358
}
53515359
$assignedPropertyExpr = new SetOffsetValueTypeExpr(
53525360
$varForSetOffsetValue,
@@ -5682,9 +5690,17 @@ static function (): void {
56825690
$dimFetchStack = [];
56835691
$assignedPropertyExpr = $assignedExpr;
56845692
while ($var instanceof ExistingArrayDimFetch) {
5685-
$varForSetOffsetValue = $var->getVar();
5686-
if ($varForSetOffsetValue instanceof PropertyFetch || $varForSetOffsetValue instanceof StaticPropertyFetch) {
5687-
$varForSetOffsetValue = new OriginalPropertyTypeExpr($varForSetOffsetValue);
5693+
if (
5694+
$var->getVar() instanceof PropertyFetch
5695+
|| $var->getVar() instanceof StaticPropertyFetch
5696+
) {
5697+
if (((new ObjectType(ArrayAccess::class))->isSuperTypeOf($scope->getType($var->getVar()))->yes())) {
5698+
$varForSetOffsetValue = $var->getVar();
5699+
} else {
5700+
$varForSetOffsetValue = new OriginalPropertyTypeExpr($var->getVar());
5701+
}
5702+
} else {
5703+
$varForSetOffsetValue = $var->getVar();
56885704
}
56895705
$assignedPropertyExpr = new SetExistingOffsetValueTypeExpr(
56905706
$varForSetOffsetValue,

tests/PHPStan/Rules/Properties/TypesAssignedToPropertiesRuleTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,28 @@ public function testBug12131(): void
689689
]);
690690
}
691691

692+
public function testBug6398(): void
693+
{
694+
$this->checkExplicitMixed = true;
695+
$this->analyse([__DIR__ . '/data/bug-6398.php'], []);
696+
}
697+
698+
public function testBug6571(): void
699+
{
700+
$this->checkExplicitMixed = true;
701+
$this->analyse([__DIR__ . '/data/bug-6571.php'], []);
702+
}
703+
704+
public function testBug12565(): void
705+
{
706+
if (PHP_VERSION_ID < 80000) {
707+
$this->markTestSkipped('Test requires PHP 8.0.');
708+
}
709+
710+
$this->checkExplicitMixed = true;
711+
$this->analyse([__DIR__ . '/data/bug-12565.php'], []);
712+
}
713+
692714
public function testShortBodySetHook(): void
693715
{
694716
if (PHP_VERSION_ID < 80400) {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug12565;
4+
5+
class EntryType {
6+
public string $title = '';
7+
public string $subTitle = '';
8+
}
9+
/**
10+
* @implements \ArrayAccess<int, EntryType>
11+
*/
12+
class ArrayLike implements \ArrayAccess {
13+
14+
/** @var EntryType[] */
15+
private array $values = [];
16+
public function offsetExists(mixed $offset): bool
17+
{
18+
return isset($this->values[$offset]);
19+
}
20+
21+
public function offsetGet(mixed $offset): EntryType
22+
{
23+
return $this->values[$offset] ?? new EntryType();
24+
}
25+
26+
public function offsetSet(mixed $offset, mixed $value): void
27+
{
28+
$this->values[$offset] = $value;
29+
}
30+
31+
public function offsetUnset(mixed $offset): void
32+
{
33+
unset($this->values[$offset]);
34+
}
35+
}
36+
37+
class Wrapper {
38+
public ?ArrayLike $myArrayLike;
39+
40+
public function __construct()
41+
{
42+
$this->myArrayLike = new ArrayLike();
43+
44+
}
45+
}
46+
47+
$baz = new Wrapper();
48+
$baz->myArrayLike = new ArrayLike();
49+
$baz->myArrayLike[1] = new EntryType();
50+
$baz->myArrayLike[1]->title = "Test";
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Bug6398;
4+
5+
class AsyncTask{
6+
/**
7+
* @phpstan-var \ArrayObject<int, array<string, mixed>>|null
8+
*/
9+
private static $threadLocalStorage = null;
10+
11+
/**
12+
* @param mixed $complexData the data to store
13+
*/
14+
protected function storeLocal(string $key, $complexData) : void{
15+
if(self::$threadLocalStorage === null){
16+
self::$threadLocalStorage = new \ArrayObject();
17+
}
18+
self::$threadLocalStorage[spl_object_id($this)][$key] = $complexData;
19+
}
20+
21+
/**
22+
* @return mixed
23+
*/
24+
protected function fetchLocal(string $key){
25+
$id = spl_object_id($this);
26+
if(self::$threadLocalStorage === null or !isset(self::$threadLocalStorage[$id][$key])){
27+
throw new \InvalidArgumentException("No matching thread-local data found on this thread");
28+
}
29+
30+
return self::$threadLocalStorage[$id][$key];
31+
}
32+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php declare(strict_types = 1); // lint >= 7.4
2+
3+
namespace Bug6571;
4+
5+
interface ClassLoader{}
6+
7+
class HelloWorld
8+
{
9+
/** @var \Threaded|\ClassLoader[]|null */
10+
private ?\Threaded $classLoaders = null;
11+
12+
/**
13+
* @param \ClassLoader[] $autoloaders
14+
*/
15+
public function setClassLoaders(?array $autoloaders = null) : void{
16+
if($autoloaders === null){
17+
$autoloaders = [];
18+
}
19+
20+
if($this->classLoaders === null){
21+
$this->classLoaders = new \Threaded();
22+
}else{
23+
foreach($this->classLoaders as $k => $autoloader){
24+
unset($this->classLoaders[$k]);
25+
}
26+
}
27+
foreach($autoloaders as $autoloader){
28+
$this->classLoaders[] = $autoloader;
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)