From b69949fe1ecc1bc2812170d09cc86e549630778e Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 19 Jul 2022 14:24:54 +0200 Subject: [PATCH] Fix --- src/Reflection/ClassReflection.php | 11 ++++ src/Rules/Properties/AccessPropertiesRule.php | 12 +--- src/Type/ObjectType.php | 6 +- .../AccessPropertiesInAssignRuleTest.php | 3 +- .../Properties/AccessPropertiesRuleTest.php | 60 +++++++++---------- .../Properties/data/dynamic-properties.php | 12 ++++ 6 files changed, 58 insertions(+), 46 deletions(-) diff --git a/src/Reflection/ClassReflection.php b/src/Reflection/ClassReflection.php index 0f8f88ee3a3..4391b8097ba 100644 --- a/src/Reflection/ClassReflection.php +++ b/src/Reflection/ClassReflection.php @@ -339,6 +339,17 @@ private function collectTraits(ReflectionClass|ReflectionEnum $class): array return $traits; } + public function allowsDynamicProperties(): bool + { + if (!$this->phpVersion->deprecatesDynamicProperties()) { + return true; + } + + $attributes = $this->reflection->getAttributes('AllowDynamicProperties'); + + return count($attributes) > 0; + } + public function hasProperty(string $propertyName): bool { if ($this->isEnum()) { diff --git a/src/Rules/Properties/AccessPropertiesRule.php b/src/Rules/Properties/AccessPropertiesRule.php index f985d6c23df..55d2e237f2a 100644 --- a/src/Rules/Properties/AccessPropertiesRule.php +++ b/src/Rules/Properties/AccessPropertiesRule.php @@ -8,7 +8,6 @@ use PHPStan\Analyser\NullsafeOperatorHelper; use PHPStan\Analyser\Scope; use PHPStan\Internal\SprintfHelper; -use PHPStan\Php\PhpVersion; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleError; @@ -33,7 +32,6 @@ class AccessPropertiesRule implements Rule public function __construct( private ReflectionProvider $reflectionProvider, private RuleLevelHelper $ruleLevelHelper, - private PhpVersion $phpVersion, private bool $reportMagicProperties, private bool $checkDynamicProperties, ) @@ -167,15 +165,7 @@ private function processSingleProperty(Scope $scope, PropertyFetch $node, string private function canAccessUndefinedProperties(Scope $scope, Node\Expr $node): bool { - if (!$scope->isUndefinedExpressionAllowed($node)) { - return false; - } - - if ($this->checkDynamicProperties) { - return false; - } - - return !$this->phpVersion->deprecatesDynamicProperties(); + return $scope->isUndefinedExpressionAllowed($node) && !$this->checkDynamicProperties; } } diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 92d0d4b415e..9febf101f3c 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -129,7 +129,11 @@ public function hasProperty(string $propertyName): TrinaryLogic return TrinaryLogic::createYes(); } - return TrinaryLogic::createMaybe(); + if ($classReflection->allowsDynamicProperties()) { + return TrinaryLogic::createMaybe(); + } + + return TrinaryLogic::createNo(); } public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): PropertyReflection diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php index b0ae7f3725e..861bc158be8 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesInAssignRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Properties; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; @@ -18,7 +17,7 @@ protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); return new AccessPropertiesInAssignRule( - new AccessPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false), new PhpVersion(PHP_VERSION_ID), true, true), + new AccessPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, false, true, false), true, true), ); } diff --git a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php index 710c491665f..30e981bdf3e 100644 --- a/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Properties/AccessPropertiesRuleTest.php @@ -2,7 +2,6 @@ namespace PHPStan\Rules\Properties; -use PHPStan\Php\PhpVersion; use PHPStan\Rules\Rule; use PHPStan\Rules\RuleLevelHelper; use PHPStan\Testing\RuleTestCase; @@ -20,12 +19,10 @@ class AccessPropertiesRuleTest extends RuleTestCase private bool $checkDynamicProperties; - private int $phpVersionId; - protected function getRule(): Rule { $reflectionProvider = $this->createReflectionProvider(); - return new AccessPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false), new PhpVersion($this->phpVersionId), true, $this->checkDynamicProperties); + return new AccessPropertiesRule($reflectionProvider, new RuleLevelHelper($reflectionProvider, true, $this->checkThisOnly, $this->checkUnionTypes, false), true, $this->checkDynamicProperties); } public function testAccessProperties(): void @@ -33,7 +30,6 @@ public function testAccessProperties(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse( [__DIR__ . '/data/access-properties.php'], [ @@ -165,7 +161,6 @@ public function testAccessPropertiesWithoutUnionTypes(): void $this->checkThisOnly = false; $this->checkUnionTypes = false; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse( [__DIR__ . '/data/access-properties.php'], [ @@ -280,7 +275,6 @@ public function testRuleAssignOp(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/access-properties-assign-op.php'], [ [ 'Access to an undefined property TestAccessProperties\AssignOpNonexistentProperty::$flags.', @@ -294,7 +288,6 @@ public function testAccessPropertiesOnThisOnly(): void $this->checkThisOnly = true; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse( [__DIR__ . '/data/access-properties.php'], [ @@ -315,7 +308,6 @@ public function testAccessPropertiesAfterIsNullInBooleanOr(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/access-properties-after-isnull.php'], [ [ 'Cannot access property $fooProperty on null.', @@ -357,7 +349,6 @@ public function testDateIntervalChildProperties(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/date-interval-child-properties.php'], [ [ 'Access to an undefined property AccessPropertiesDateIntervalChild\DateIntervalChild::$nonexistent.', @@ -371,7 +362,6 @@ public function testClassExists(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/access-properties-class-exists.php'], [ [ @@ -402,7 +392,6 @@ public function testMixin(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/mixin.php'], [ [ 'Access to an undefined property MixinProperties\GenericFoo::$namee.', @@ -416,7 +405,6 @@ public function testBug3947(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-3947.php'], []); } @@ -425,7 +413,6 @@ public function testNullSafe(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/nullsafe-property-fetch.php'], [ [ @@ -456,7 +443,6 @@ public function testBug3371(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-3371.php'], []); } @@ -465,7 +451,6 @@ public function testBug4527(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-4527.php'], []); } @@ -474,7 +459,6 @@ public function testBug4808(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-4808.php'], []); } @@ -486,7 +470,6 @@ public function testBug5868(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-5868.php'], [ [ 'Cannot access property $child on Bug5868PropertyFetch\Foo|null.', @@ -516,7 +499,6 @@ public function testBug6385(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-6385.php'], [ [ 'Access to an undefined property UnitEnum::$value.', @@ -537,7 +519,6 @@ public function testBug6566(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-6566.php'], []); } @@ -546,7 +527,6 @@ public function testBug6899(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $errors = [ [ 'Cannot access property $prop on string.', @@ -583,7 +563,6 @@ public function testBug6026(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-6026.php'], []); } @@ -592,7 +571,6 @@ public function testBug3659(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $errors = []; if (PHP_VERSION_ID >= 80200) { $errors[] = [ @@ -630,13 +608,35 @@ public function dataDynamicProperties(): array 'Access to an undefined property DynamicProperties\Bar::$dynamicProperty.', 16, ], + [ + 'Access to an undefined property DynamicProperties\Baz::$dynamicProperty.', + 23, + ], ]; + if (PHP_VERSION_ID < 80200) { + $errors[] = [ + 'Access to an undefined property DynamicProperties\Baz::$dynamicProperty.', + 26, + ]; + $errors[] = [ + 'Access to an undefined property DynamicProperties\Baz::$dynamicProperty.', + 27, + ]; + $errors[] = [ + 'Access to an undefined property DynamicProperties\Baz::$dynamicProperty.', + 28, + ]; + } + return [ - [false, 80000, []], - [true, 80000, $errors], - [false, 80200, $errors], - [true, 80200, $errors], + [false, PHP_VERSION_ID < 80200 ? [ + [ + 'Access to an undefined property DynamicProperties\Baz::$dynamicProperty.', + 23, + ], + ] : $errors], + [true, $errors], ]; } @@ -644,12 +644,11 @@ public function dataDynamicProperties(): array * @dataProvider dataDynamicProperties * @param mixed[] $errors */ - public function testDynamicProperties(bool $checkDynamicProperties, int $phpVersionId, array $errors): void + public function testDynamicProperties(bool $checkDynamicProperties, array $errors): void { $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = $checkDynamicProperties; - $this->phpVersionId = $phpVersionId; $this->analyse([__DIR__ . '/data/dynamic-properties.php'], $errors); } @@ -658,7 +657,6 @@ public function testBug4559(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $errors = []; if (PHP_VERSION_ID >= 80200) { $errors[] = [ @@ -674,7 +672,6 @@ public function testBug3171(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = false; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-3171.php'], []); } @@ -683,7 +680,6 @@ public function testBug3171OnDynamicProperties(): void $this->checkThisOnly = false; $this->checkUnionTypes = true; $this->checkDynamicProperties = true; - $this->phpVersionId = PHP_VERSION_ID; $this->analyse([__DIR__ . '/data/bug-3171.php'], []); } diff --git a/tests/PHPStan/Rules/Properties/data/dynamic-properties.php b/tests/PHPStan/Rules/Properties/data/dynamic-properties.php index 45d32a9f7c0..0a90b9ad5dd 100644 --- a/tests/PHPStan/Rules/Properties/data/dynamic-properties.php +++ b/tests/PHPStan/Rules/Properties/data/dynamic-properties.php @@ -17,3 +17,15 @@ public function doBar() { } } +#[\AllowDynamicProperties] +class Baz { + public function doBaz() { + echo $this->dynamicProperty; + } + public function doBar() { + isset($this->dynamicProperty); + empty($this->dynamicProperty); + $this->dynamicProperty ?? 'test'; + } +} +