diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index 21f17e12c..7ea948ce2 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -213,10 +213,14 @@
+
+
+
+
diff --git a/src/Reflection/ReflectionProperty.php b/src/Reflection/ReflectionProperty.php
index 8fce08736..8761a32df 100644
--- a/src/Reflection/ReflectionProperty.php
+++ b/src/Reflection/ReflectionProperty.php
@@ -666,16 +666,32 @@ private function assertObject(mixed $object): object
/** @return int-mask-of */
private function computeModifiers(PropertyNode $node): int
{
- $modifiers = $node->isReadonly() ? ReflectionPropertyAdapter::IS_READONLY : 0;
+ $modifiers = $node->isReadonly() ? CoreReflectionProperty::IS_READONLY : 0;
$modifiers += $node->isStatic() ? CoreReflectionProperty::IS_STATIC : 0;
$modifiers += $node->isPrivate() ? CoreReflectionProperty::IS_PRIVATE : 0;
- $modifiers += $node->isPrivateSet() ? ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY : 0;
+ $modifiers += ! $node->isPrivate() && $node->isPrivateSet() ? ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY : 0;
$modifiers += $node->isProtected() ? CoreReflectionProperty::IS_PROTECTED : 0;
- $modifiers += $node->isProtectedSet() ? ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY : 0;
+ $modifiers += ! $node->isProtected() && $node->isProtectedSet() ? ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY : 0;
$modifiers += $node->isPublic() ? CoreReflectionProperty::IS_PUBLIC : 0;
$modifiers += $node->isFinal() ? ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY : 0;
$modifiers += $node->isAbstract() ? ReflectionPropertyAdapter::IS_ABSTRACT_COMPATIBILITY : 0;
+ if (
+ ! ($modifiers & ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY)
+ && ($modifiers & ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY)
+ ) {
+ $modifiers += ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY;
+ }
+
+ if (
+ ! ($modifiers & (ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY))
+ && ! $node->isPublicSet()
+ && $node->isPublic()
+ && ($modifiers & CoreReflectionProperty::IS_READONLY)
+ ) {
+ $modifiers += ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY;
+ }
+
/** @phpstan-ignore return.type */
return $modifiers;
}
diff --git a/test/unit/Fixture/AsymetricVisibilityClass.php b/test/unit/Fixture/AsymmetricVisibilityClass.php
similarity index 96%
rename from test/unit/Fixture/AsymetricVisibilityClass.php
rename to test/unit/Fixture/AsymmetricVisibilityClass.php
index 4e7bb1a0b..8241b99c5 100644
--- a/test/unit/Fixture/AsymetricVisibilityClass.php
+++ b/test/unit/Fixture/AsymmetricVisibilityClass.php
@@ -2,7 +2,7 @@
namespace Roave\BetterReflectionTest\Fixture;
-class AsymetricVisibilityClass
+class AsymmetricVisibilityClass
{
public public(set) string $publicPublicSet = 'string';
public protected(set) string $publicProtectedSet = 'string';
@@ -22,4 +22,3 @@ public function __construct(
{
}
}
-
diff --git a/test/unit/Fixture/AsymetricVisibilityClassExport.txt b/test/unit/Fixture/AsymmetricVisibilityClassExport.txt
similarity index 87%
rename from test/unit/Fixture/AsymetricVisibilityClassExport.txt
rename to test/unit/Fixture/AsymmetricVisibilityClassExport.txt
index 5c5f3f270..369c8005b 100644
--- a/test/unit/Fixture/AsymetricVisibilityClassExport.txt
+++ b/test/unit/Fixture/AsymmetricVisibilityClassExport.txt
@@ -1,5 +1,5 @@
-Class [ class Roave\BetterReflectionTest\Fixture\AsymetricVisibilityClass ] {
- @@ %s/Fixture/AsymetricVisibilityClass.php 5-24
+Class [ class Roave\BetterReflectionTest\Fixture\AsymmetricVisibilityClass ] {
+ @@ %s/Fixture/AsymmetricVisibilityClass.php 5-24
- Constants [0] {
}
@@ -27,7 +27,7 @@ Class [ class Roave\BetterReflectionTest\Fixture\AsymetricVisibilityClass
- Methods [1] {
Method [ public method __construct ] {
- @@ %s/Fixture/AsymetricVisibilityClass.php 14 - 23
+ @@ %s/Fixture/AsymmetricVisibilityClass.php 14 - 23
- Parameters [6] {
Parameter #0 [ string $promotedPublicPublicSet ]
diff --git a/test/unit/Fixture/AsymmetricVisibilityImplicitFinal.php b/test/unit/Fixture/AsymmetricVisibilityImplicitFinal.php
new file mode 100644
index 000000000..0cbaf596e
--- /dev/null
+++ b/test/unit/Fixture/AsymmetricVisibilityImplicitFinal.php
@@ -0,0 +1,10 @@
+, 1: int}> */
- public static function getPropertiesWithAsymetricVisibilityFilterDataProvider(): array
+ public static function getPropertiesWithAsymmetricVisibilityFilterDataProvider(): array
{
return [
[CoreReflectionProperty::IS_PUBLIC, 6],
[CoreReflectionProperty::IS_PROTECTED, 4],
[CoreReflectionProperty::IS_PRIVATE, 2],
- [ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY, 4],
- [ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY, 6],
+ [ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY, 2],
+ [ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY, 4],
[
CoreReflectionProperty::IS_PUBLIC |
ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY,
- 10,
+ 8,
],
];
}
/** @param int-mask-of $filter */
- #[DataProvider('getPropertiesWithAsymetricVisibilityFilterDataProvider')]
- public function testGetPropertiesWithAsymetricVisibilityFilter(int $filter, int $count): void
+ #[DataProvider('getPropertiesWithAsymmetricVisibilityFilterDataProvider')]
+ public function testGetPropertiesWithAsymmetricVisibilityFilter(int $filter, int $count): void
{
- $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymetricVisibilityClass.php', $this->astLocator));
- $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymetricVisibilityClass');
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymmetricVisibilityClass.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymmetricVisibilityClass');
self::assertCount($count, $classInfo->getProperties($filter));
self::assertCount($count, $classInfo->getImmediateProperties($filter));
@@ -2308,13 +2308,13 @@ public function testToString(): void
);
}
- public function testToStringWithAsymetricVisibility(): void
+ public function testToStringWithAsymmetricVisibility(): void
{
- $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymetricVisibilityClass.php', $this->astLocator));
- $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymetricVisibilityClass');
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymmetricVisibilityClass.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymmetricVisibilityClass');
self::assertStringMatchesFormat(
- file_get_contents(__DIR__ . '/../Fixture/AsymetricVisibilityClassExport.txt'),
+ file_get_contents(__DIR__ . '/../Fixture/AsymmetricVisibilityClassExport.txt'),
$classInfo->__toString(),
);
}
diff --git a/test/unit/Reflection/ReflectionPropertyTest.php b/test/unit/Reflection/ReflectionPropertyTest.php
index 1659f54ea..4ccff5977 100644
--- a/test/unit/Reflection/ReflectionPropertyTest.php
+++ b/test/unit/Reflection/ReflectionPropertyTest.php
@@ -148,6 +148,24 @@ public function testVisibilityMethods(): void
self::assertFalse($onlyPublicProp->isReadOnly());
}
+ public function isImplicitPublic(): void
+ {
+ $php = <<<'PHP'
+ astLocator)))->reflectClass('Foo');
+ $propertyReflection = $classReflection->getProperty('boo');
+
+ self::assertTrue($propertyReflection->isPublic());
+ self::assertSame(CoreReflectionProperty::IS_PUBLIC, $propertyReflection->getModifiers());
+ }
+
public function testIsStatic(): void
{
$classInfo = $this->reflector->reflectClass(ExampleClass::class);
@@ -254,7 +272,7 @@ public static function modifierProvider(): array
['protectedProperty', CoreReflectionProperty::IS_PROTECTED],
['privateProperty', CoreReflectionProperty::IS_PRIVATE],
['publicStaticProperty', CoreReflectionProperty::IS_PUBLIC | CoreReflectionProperty::IS_STATIC],
- ['readOnlyProperty', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_READONLY],
+ ['readOnlyProperty', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_READONLY | ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY],
['finalPublicProperty', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY],
];
}
@@ -897,30 +915,30 @@ public function testWithImplementingClass(): void
}
/** @return list}> */
- public static function asymetricVisibilityModifierProvider(): array
+ public static function asymmetricVisibilityModifierProvider(): array
{
return [
['publicPublicSet', CoreReflectionProperty::IS_PUBLIC],
['publicProtectedSet', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY],
- ['publicPrivateSet', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY],
- ['protectedProtectedSet', CoreReflectionProperty::IS_PROTECTED | ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY],
- ['protectedPrivateSet', CoreReflectionProperty::IS_PROTECTED | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY],
- ['privatePrivateSet', CoreReflectionProperty::IS_PRIVATE | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY],
+ ['publicPrivateSet', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY | ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY],
+ ['protectedProtectedSet', CoreReflectionProperty::IS_PROTECTED],
+ ['protectedPrivateSet', CoreReflectionProperty::IS_PROTECTED | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY | ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY],
+ ['privatePrivateSet', CoreReflectionProperty::IS_PRIVATE],
['promotedPublicPublicSet', CoreReflectionProperty::IS_PUBLIC],
['promotedPublicProtectedSet', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY],
- ['promotedPublicPrivateSet', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY],
- ['promotedProtectedProtectedSet', CoreReflectionProperty::IS_PROTECTED | ReflectionPropertyAdapter::IS_PROTECTED_SET_COMPATIBILITY],
- ['promotedProtectedPrivateSet', CoreReflectionProperty::IS_PROTECTED | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY],
- ['promotedPrivatePrivateSet', CoreReflectionProperty::IS_PRIVATE | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY],
+ ['promotedPublicPrivateSet', CoreReflectionProperty::IS_PUBLIC | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY | ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY],
+ ['promotedProtectedProtectedSet', CoreReflectionProperty::IS_PROTECTED],
+ ['promotedProtectedPrivateSet', CoreReflectionProperty::IS_PROTECTED | ReflectionPropertyAdapter::IS_PRIVATE_SET_COMPATIBILITY | ReflectionPropertyAdapter::IS_FINAL_COMPATIBILITY],
+ ['promotedPrivatePrivateSet', CoreReflectionProperty::IS_PRIVATE],
];
}
/** @param non-empty-string $propertyName */
- #[DataProvider('asymetricVisibilityModifierProvider')]
- public function testGetAsymetricVisibilityMethods(string $propertyName, int $expectedModifier): void
+ #[DataProvider('asymmetricVisibilityModifierProvider')]
+ public function testGetAsymmetricVisibilityMethods(string $propertyName, int $expectedModifier): void
{
- $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymetricVisibilityClass.php', $this->astLocator));
- $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymetricVisibilityClass');
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymmetricVisibilityClass.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymmetricVisibilityClass');
$property = $classInfo->getProperty($propertyName);
self::assertSame($expectedModifier, $property->getModifiers());
@@ -928,8 +946,8 @@ public function testGetAsymetricVisibilityMethods(string $propertyName, int $exp
public function testIsProtectedSet(): void
{
- $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymetricVisibilityClass.php', $this->astLocator));
- $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymetricVisibilityClass');
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymmetricVisibilityClass.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymmetricVisibilityClass');
$publicPublicSetProperty = $classInfo->getProperty('publicPublicSet');
$publicProtectedSetProperty = $classInfo->getProperty('publicProtectedSet');
@@ -940,8 +958,8 @@ public function testIsProtectedSet(): void
public function testIsPrivateSet(): void
{
- $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymetricVisibilityClass.php', $this->astLocator));
- $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymetricVisibilityClass');
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymmetricVisibilityClass.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymmetricVisibilityClass');
$protectedProtectedSet = $classInfo->getProperty('protectedProtectedSet');
$protectedPrivateSet = $classInfo->getProperty('protectedPrivateSet');
@@ -950,6 +968,50 @@ public function testIsPrivateSet(): void
self::assertTrue($protectedPrivateSet->isPrivateSet());
}
+ /** @return list */
+ public static function asymmetricVisibilityImplicitFinalProvider(): array
+ {
+ return [
+ ['publicPrivateSetIsFinal', true],
+ ['protectedPrivateSetIsFinal', true],
+ ['privatePrivateSetIsNotFinal', false],
+ ];
+ }
+
+ /** @param non-empty-string $propertyName */
+ #[DataProvider('asymmetricVisibilityImplicitFinalProvider')]
+ public function testAsymmetricVisibilityImplicitFinal(string $propertyName, bool $isFinal): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymmetricVisibilityImplicitFinal.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymmetricVisibilityImplicitFinal');
+ $property = $classInfo->getProperty($propertyName);
+
+ self::assertSame($isFinal, $property->isFinal());
+ }
+
+ /** @return list */
+ public static function asymmetricVisibilityImplicitProtectedSetProvider(): array
+ {
+ return [
+ ['publicPublicSet', false],
+ ['publicProtectedSet', true],
+ ['publicPrivateSet', false],
+ ['protected', false],
+ ['publicImplicitProtectedSet', true],
+ ];
+ }
+
+ /** @param non-empty-string $propertyName */
+ #[DataProvider('asymmetricVisibilityImplicitProtectedSetProvider')]
+ public function testAsymmetricVisibilityImplicitProtectedSet(string $propertyName, bool $isProtectedSet): void
+ {
+ $reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/AsymmetricVisibilityImplicitProtectedSet.php', $this->astLocator));
+ $classInfo = $reflector->reflectClass('Roave\BetterReflectionTest\Fixture\AsymmetricVisibilityImplicitProtectedSet');
+ $property = $classInfo->getProperty($propertyName);
+
+ self::assertSame($isProtectedSet, $property->isProtectedSet());
+ }
+
public function testIsAbstract(): void
{
$reflector = new DefaultReflector(new SingleFileSourceLocator(__DIR__ . '/../Fixture/PropertyHooks.php', $this->astLocator));