Skip to content

Commit 9ac6fd1

Browse files
committed
Allow accessing @property on interface when it requires extending class that allows dynamic properties
1 parent 34def42 commit 9ac6fd1

File tree

2 files changed

+46
-5
lines changed

2 files changed

+46
-5
lines changed

src/Reflection/ClassReflection.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
use PHPStan\Type\Generic\TemplateTypeVariance;
4646
use PHPStan\Type\Generic\TemplateTypeVarianceMap;
4747
use PHPStan\Type\Generic\TypeProjectionHelper;
48+
use PHPStan\Type\ObjectType;
4849
use PHPStan\Type\Type;
4950
use PHPStan\Type\TypeAlias;
5051
use PHPStan\Type\TypehintHelper;
@@ -421,7 +422,30 @@ private function allowsDynamicPropertiesExtensions(): bool
421422
return true;
422423
}
423424

424-
return $this->hasNativeMethod('__get') || $this->hasNativeMethod('__set') || $this->hasNativeMethod('__isset');
425+
$hasMagicMethod = $this->hasNativeMethod('__get') || $this->hasNativeMethod('__set') || $this->hasNativeMethod('__isset');
426+
if ($hasMagicMethod) {
427+
return true;
428+
}
429+
430+
foreach ($this->getRequireExtendsTags() as $extendsTag) {
431+
$type = $extendsTag->getType();
432+
if (!$type instanceof ObjectType) {
433+
continue;
434+
}
435+
436+
$reflection = $type->getClassReflection();
437+
if ($reflection === null) {
438+
continue;
439+
}
440+
441+
if (!$reflection->allowsDynamicPropertiesExtensions()) {
442+
continue;
443+
}
444+
445+
return true;
446+
}
447+
448+
return false;
425449
}
426450

427451
public function hasProperty(string $propertyName): bool

tests/PHPStan/Analyser/data/bug-10302.php

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ interface BatchAware
1414
}
1515

1616
/**
17-
* @property-read bool $busy
17+
* @property-read bool $busy2
1818
*/
1919
class Model
2020
{
@@ -27,12 +27,29 @@ public function __get(string $name)
2727
}
2828
}
2929

30-
class SomeModel extends Model implements BatchAware
30+
function (BatchAware $b): void
31+
{
32+
assertType('bool', $b->busy);
33+
assertType('bool', $b->busy2);
34+
};
35+
36+
class ModelWithoutAllowDynamicProperties
3137
{
3238

3339
}
3440

35-
function (BatchAware $b): void
41+
/**
42+
* @property-read bool $busy
43+
* @phpstan-require-extends ModelWithoutAllowDynamicProperties
44+
*/
45+
interface BatchAwareWithoutAllowDynamicProperties
3646
{
37-
assertType('bool', $b->busy);
47+
48+
}
49+
50+
function (BatchAwareWithoutAllowDynamicProperties $b): void
51+
{
52+
$result = $b->busy; // @phpstan-ignore-line
53+
54+
assertType('*ERROR*', $result);
3855
};

0 commit comments

Comments
 (0)