Skip to content

Commit

Permalink
Test PHP 8.2 dynamic properties behaviour
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Sep 23, 2022
1 parent e2384d6 commit 8bd7370
Show file tree
Hide file tree
Showing 15 changed files with 260 additions and 22 deletions.
22 changes: 16 additions & 6 deletions src/Reflection/ClassReflection.php
Expand Up @@ -350,23 +350,33 @@ public function allowsDynamicProperties(): bool
return true;
}

if ($this->hasNativeMethod('__get') || $this->hasNativeMethod('__set') || $this->hasNativeMethod('__isset')) {
return true;
$class = $this;
$attributes = $class->reflection->getAttributes('AllowDynamicProperties');
while (count($attributes) === 0 && $class->getParentClass() !== null) {
$attributes = $class->getParentClass()->reflection->getAttributes('AllowDynamicProperties');
$class = $class->getParentClass();
}

$attributes = $this->reflection->getAttributes('AllowDynamicProperties');

return count($attributes) > 0;
}

private function allowsDynamicPropertiesExtensions(): bool
{
if ($this->allowsDynamicProperties()) {
return true;
}

return $this->hasNativeMethod('__get') || $this->hasNativeMethod('__set') || $this->hasNativeMethod('__isset');
}

public function hasProperty(string $propertyName): bool
{
if ($this->isEnum()) {
return $this->hasNativeProperty($propertyName);
}

foreach ($this->propertiesClassReflectionExtensions as $i => $extension) {
if ($i > 0 && !$this->allowsDynamicProperties()) {
if ($i > 0 && !$this->allowsDynamicPropertiesExtensions()) {
continue;
}
if ($extension->hasProperty($this, $propertyName)) {
Expand Down Expand Up @@ -518,7 +528,7 @@ public function getProperty(string $propertyName, ClassMemberAccessAnswerer $sco
}
if (!isset($this->properties[$key])) {
foreach ($this->propertiesClassReflectionExtensions as $i => $extension) {
if ($i > 0 && !$this->allowsDynamicProperties()) {
if ($i > 0 && !$this->allowsDynamicPropertiesExtensions()) {
continue;
}
if (!$extension->hasProperty($this, $propertyName)) {
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Analyser/data/bug-1216.php
Expand Up @@ -2,6 +2,7 @@

namespace Bug1216;

use AllowDynamicProperties;
use function PHPStan\Testing\assertType;

abstract class Foo
Expand All @@ -27,6 +28,7 @@ trait Bar
* @property string $bar
* @property string $untypedBar
*/
#[AllowDynamicProperties]
class Baz extends Foo
{

Expand Down
Expand Up @@ -2,6 +2,7 @@

namespace ClassPhpDocsNamespace;

use AllowDynamicProperties;
use function PHPStan\Testing\assertType;

/**
Expand All @@ -16,6 +17,7 @@
* @property-write string $baz
* @phpstan-property-write int $baz
*/
#[AllowDynamicProperties]
class PhpstanProperties
{
public function doFoo()
Expand Down
2 changes: 2 additions & 0 deletions tests/PHPStan/Analyser/data/properties-defined.php
Expand Up @@ -2,13 +2,15 @@

namespace PropertiesNamespace;

use AllowDynamicProperties;
use DOMDocument;
use SomeNamespace\Sit as Dolor;

/**
* @property-read int $readOnlyProperty
* @property-read int $overriddenReadOnlyProperty
*/
#[AllowDynamicProperties]
class Bar extends DOMDocument
{

Expand Down
Expand Up @@ -2,6 +2,7 @@

namespace AnnotationsProperties;

use AllowDynamicProperties;
use OtherNamespace\Test as OtherTest;
use OtherNamespace\Ipsum;

Expand All @@ -12,6 +13,7 @@
* @property Ipsum $conflictingProperty
* @property Foo $overridenProperty
*/
#[AllowDynamicProperties]
class Foo implements FooInterface
{

Expand Down
55 changes: 54 additions & 1 deletion tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php
Expand Up @@ -395,7 +395,7 @@ public function testMixin(): void
$this->analyse([__DIR__ . '/data/mixin.php'], [
[
'Access to an undefined property MixinProperties\GenericFoo<ReflectionClass>::$namee.',
51,
55,
],
]);
}
Expand Down Expand Up @@ -656,4 +656,57 @@ public function testBug3171OnDynamicProperties(): void
$this->analyse([__DIR__ . '/data/bug-3171.php'], []);
}

public function dataTrueAndFalse(): array
{
return [
[true],
[false],
];
}

/**
* @dataProvider dataTrueAndFalse
*/
public function testPhp82AndDynamicProperties(bool $b): void
{
$errors = [];
if (PHP_VERSION_ID >= 80200) {
$errors[] = [
'Access to an undefined property Php82DynamicProperties\ClassA::$properties.',
34,
];
$errors[] = [
'Access to an undefined property Php82DynamicProperties\HelloWorld::$world.',
71,
];
} elseif ($b) {
$errors[] = [
'Access to an undefined property Php82DynamicProperties\HelloWorld::$world.',
71,
];
}
$this->checkThisOnly = false;
$this->checkUnionTypes = true;
$this->checkDynamicProperties = $b;
$this->analyse([__DIR__ . '/data/php-82-dynamic-properties.php'], $errors);
}

/**
* @dataProvider dataTrueAndFalse
*/
public function testPhp82AndDynamicPropertiesAllow(bool $b): void
{
$errors = [];
if ($b) {
$errors[] = [
'Access to an undefined property Php82DynamicPropertiesAllow\HelloWorld::$world.',
75,
];
}
$this->checkThisOnly = false;
$this->checkUnionTypes = true;
$this->checkDynamicProperties = $b;
$this->analyse([__DIR__ . '/data/php-82-dynamic-properties-allow.php'], $errors);
}

}
Expand Up @@ -25,11 +25,11 @@ public function testPropertyMustBeReadableInAssignOp(): void
$this->analyse([__DIR__ . '/data/writing-to-read-only-properties.php'], [
[
'Property WritingToReadOnlyProperties\Foo::$writeOnlyProperty is not readable.',
22,
25,
],
[
'Property WritingToReadOnlyProperties\Foo::$writeOnlyProperty is not readable.',
32,
35,
],
]);
}
Expand All @@ -40,7 +40,7 @@ public function testPropertyMustBeReadableInAssignOpCheckThisOnly(): void
$this->analyse([__DIR__ . '/data/writing-to-read-only-properties.php'], [
[
'Property WritingToReadOnlyProperties\Foo::$writeOnlyProperty is not readable.',
22,
25,
],
]);
}
Expand All @@ -51,11 +51,11 @@ public function testReadingWriteOnlyProperties(): void
$this->analyse([__DIR__ . '/data/reading-write-only-properties.php'], [
[
'Property ReadingWriteOnlyProperties\Foo::$writeOnlyProperty is not readable.',
17,
20,
],
[
'Property ReadingWriteOnlyProperties\Foo::$writeOnlyProperty is not readable.',
22,
25,
],
]);
}
Expand All @@ -66,7 +66,7 @@ public function testReadingWriteOnlyPropertiesCheckThisOnly(): void
$this->analyse([__DIR__ . '/data/reading-write-only-properties.php'], [
[
'Property ReadingWriteOnlyProperties\Foo::$writeOnlyProperty is not readable.',
17,
20,
],
]);
}
Expand Down
Expand Up @@ -115,11 +115,11 @@ public function testBug1216(): void
$this->analyse([__DIR__ . '/data/bug-1216.php'], [
[
'Property Bug1216PropertyTest\Baz::$untypedBar (string) does not accept int.',
35,
38,
],
[
'Property Bug1216PropertyTest\Dummy::$foo (Exception) does not accept stdClass.',
59,
62,
],
]);
}
Expand Down
Expand Up @@ -25,11 +25,11 @@ public function testCheckThisOnlyProperties(): void
$this->analyse([__DIR__ . '/data/writing-to-read-only-properties.php'], [
[
'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.',
15,
18,
],
[
'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.',
16,
19,
],
]);
}
Expand All @@ -40,23 +40,23 @@ public function testCheckAllProperties(): void
$this->analyse([__DIR__ . '/data/writing-to-read-only-properties.php'], [
[
'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.',
15,
18,
],
[
'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.',
16,
19,
],
[
'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.',
25,
28,
],
[
'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.',
26,
29,
],
[
'Property WritingToReadOnlyProperties\Foo::$readOnlyProperty is not writable.',
35,
38,
],
]);
}
Expand Down
3 changes: 3 additions & 0 deletions tests/PHPStan/Rules/Properties/data/bug-1216.php
Expand Up @@ -2,6 +2,8 @@

namespace Bug1216PropertyTest;

use AllowDynamicProperties;

abstract class Foo
{
/**
Expand All @@ -25,6 +27,7 @@ trait Bar
* @property string $bar
* @property string $untypedBar
*/
#[AllowDynamicProperties]
class Baz extends Foo
{

Expand Down
4 changes: 4 additions & 0 deletions tests/PHPStan/Rules/Properties/data/mixin.php
Expand Up @@ -2,6 +2,8 @@

namespace MixinProperties;

use AllowDynamicProperties;

class Foo
{

Expand All @@ -12,6 +14,7 @@ class Foo
/**
* @mixin Foo
*/
#[AllowDynamicProperties]
class Bar
{

Expand All @@ -34,6 +37,7 @@ function (Baz $baz): void {
* @template T
* @mixin T
*/
#[AllowDynamicProperties]
class GenericFoo
{

Expand Down

0 comments on commit 8bd7370

Please sign in to comment.