Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="6.4.0@04f312ac6ea48ba1c3e5db4d815bf6d74641c0ee">
<files psalm-version="6.15.1@28dc127af1b5aecd52314f6f645bafc10d0e11f9">
<file src="src/Reflection/Attribute/ReflectionAttributeHelper.php">
<ImpureMethodCall>
<code><![CDATA[toLowerString]]></code>
Expand Down Expand Up @@ -99,8 +99,6 @@
<code><![CDATA[reflectClass]]></code>
<code><![CDATA[reflectClass]]></code>
<code><![CDATA[reflectClass]]></code>
<code><![CDATA[reflectClass]]></code>
<code><![CDATA[reflectClass]]></code>
<code><![CDATA[toLowerString]]></code>
</ImpureMethodCall>
</file>
Expand Down
72 changes: 35 additions & 37 deletions src/Reflection/ReflectionClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,9 @@ private function getAnonymousClassNamePrefix(): string
return $this->parentClassName;
}

if ($this->implementsClassNames !== []) {
return $this->implementsClassNames[0];
$implementsClassName = $this->getInterfaceClassNames();
if ($implementsClassName !== []) {
return $implementsClassName[0];
}

return 'class';
Expand Down Expand Up @@ -1298,17 +1299,21 @@ public function getTraits(): array
}

/**
* @param array<class-string, self> $interfaces
* @param list<class-string> $interfaceClassNames
*
* @return array<class-string, self>
* @return list<class-string>
*/
private function addStringableInterface(array $interfaces): array
private function addStringableInterfaceClassName(array $interfaceClassNames): array
{
/** @psalm-var class-string $stringableClassName */
$stringableClassName = Stringable::class;

if (array_key_exists($stringableClassName, $interfaces) || ($this->isInterface && $this->getName() === $stringableClassName)) {
return $interfaces;
if ($this->isInterface && $this->getName() === $stringableClassName) {
return $interfaceClassNames;
}

if (in_array($stringableClassName, $interfaceClassNames, true)) {
return $interfaceClassNames;
}

$methods = $this->immediateMethods;
Expand All @@ -1322,7 +1327,7 @@ private function addStringableInterface(array $interfaces): array
$stringableInterfaceReflection = $this->reflector->reflectClass($stringableClassName);

if ($stringableInterfaceReflection->isInternal()) {
$interfaces[$stringableClassName] = $stringableInterfaceReflection;
$interfaceClassNames[] = $stringableClassName;
}
} catch (IdentifierNotFound) {
// Stringable interface does not exist on target PHP version
Expand All @@ -1333,28 +1338,25 @@ private function addStringableInterface(array $interfaces): array
}
}

return $interfaces;
return $interfaceClassNames;
}

/**
* @param array<class-string, self> $interfaces
*
* @return array<class-string, self>
* @param list<class-string> $interfaceClassNames
*
* @psalm-suppress MoreSpecificReturnType
* @return list<class-string>
*/
private function addEnumInterfaces(array $interfaces): array
private function addEnumInterfaceClassNames(array $interfaceClassNames): array
{
assert($this->isEnum === true);

$interfaces[UnitEnum::class] = $this->reflector->reflectClass(UnitEnum::class);
$interfaceClassNames[] = UnitEnum::class;

if ($this->isBackedEnum) {
$interfaces[BackedEnum::class] = $this->reflector->reflectClass(BackedEnum::class);
$interfaceClassNames[] = BackedEnum::class;
}

/** @psalm-suppress LessSpecificReturnStatement */
return $interfaces;
return $interfaceClassNames;
}

/** @return list<trait-string> */
Expand Down Expand Up @@ -1574,7 +1576,13 @@ private function lowerCasedMethodHash(string $className, string $methodName): st
/** @return list<class-string> */
public function getInterfaceClassNames(): array
{
return $this->implementsClassNames;
$implementsClassNames = $this->implementsClassNames;

if ($this->isEnum) {
$implementsClassNames = $this->addEnumInterfaceClassNames($implementsClassNames);
}

return $this->addStringableInterfaceClassName($implementsClassNames);
}

/**
Expand Down Expand Up @@ -1610,19 +1618,15 @@ public function getImmediateInterfaces(): array
return [];
}

$interfaces = array_combine(
$this->implementsClassNames,
$implementsClassName = $this->getInterfaceClassNames();

return array_combine(
$implementsClassName,
array_map(
fn (string $interfaceClassName): ReflectionClass => $this->reflector->reflectClass($interfaceClassName),
$this->implementsClassNames,
$implementsClassName,
),
);

if ($this->isEnum) {
$interfaces = $this->addEnumInterfaces($interfaces);
}

return $this->addStringableInterface($interfaces);
}

/**
Expand Down Expand Up @@ -1755,21 +1759,15 @@ private function getCurrentClassImplementedInterfacesIndexedByName(): array
return array_slice($this->getInterfacesHierarchy(AlreadyVisitedClasses::createEmpty()), 1);
}

$interfaces = array_merge(
return array_merge(
[],
...array_map(
fn (string $interfaceClassName): array => $this->reflector
->reflectClass($interfaceClassName)
->getInterfacesHierarchy(AlreadyVisitedClasses::createEmpty()),
$this->implementsClassNames,
$this->getInterfaceClassNames(),
),
);

if ($this->isEnum) {
$interfaces = $this->addEnumInterfaces($interfaces);
}

return $this->addStringableInterface($interfaces);
}

/**
Expand Down Expand Up @@ -1797,7 +1795,7 @@ private function getInterfacesHierarchy(AlreadyVisitedClasses $alreadyVisitedCla
}
}

return $this->addStringableInterface($interfaces);
return $interfaces;
}

/**
Expand Down
13 changes: 11 additions & 2 deletions test/unit/Reflection/ReflectionClassTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -509,12 +509,12 @@ public static function getInterfaceClassNamesDataProvider(): array
[
__DIR__ . '/../Fixture/Enums.php',
IntEnum::class,
['Roave\BetterReflectionTest\Fixture\InterfaceForEnum'],
['Roave\BetterReflectionTest\Fixture\InterfaceForEnum', UnitEnum::class, BackedEnum::class],
],
[
__DIR__ . '/../Fixture/Enums.php',
Fixture\IsDeprecated::class,
[],
[UnitEnum::class],
],
];
}
Expand Down Expand Up @@ -1865,6 +1865,7 @@ public function testGetInterfacesForPureEnum(): void

self::assertSame([InterfaceForEnum::class, UnitEnum::class], $classInfo->getInterfaceNames());
self::assertArrayHasKey(UnitEnum::class, $classInfo->getImmediateInterfaces());
self::assertContains(UnitEnum::class, $classInfo->getInterfaceClassNames());
}

public function testGetInterfaceNamesForBackedEnum(): void
Expand All @@ -1878,7 +1879,9 @@ public function testGetInterfaceNamesForBackedEnum(): void

self::assertSame([InterfaceForEnum::class, UnitEnum::class, BackedEnum::class], $classInfo->getInterfaceNames());
self::assertArrayHasKey(UnitEnum::class, $classInfo->getImmediateInterfaces());
self::assertContains(UnitEnum::class, $classInfo->getInterfaceClassNames());
self::assertArrayHasKey(BackedEnum::class, $classInfo->getImmediateInterfaces());
self::assertContains(BackedEnum::class, $classInfo->getInterfaceClassNames());
}

public function testGetInterfaces(): void
Expand Down Expand Up @@ -2761,26 +2764,32 @@ public function __toString();

$classImplementingStringable = $reflector->reflectClass('ClassHasStringable');
self::assertContains(Stringable::class, $classImplementingStringable->getInterfaceNames());
self::assertContains(Stringable::class, $classImplementingStringable->getInterfaceClassNames());
self::assertArrayHasKey(Stringable::class, $classImplementingStringable->getImmediateInterfaces());

$classNotImplementingStringable = $reflector->reflectClass('ClassHasStringableAutomatically');
self::assertContains(Stringable::class, $classNotImplementingStringable->getInterfaceNames());
self::assertContains(Stringable::class, $classNotImplementingStringable->getInterfaceClassNames());
self::assertArrayHasKey(Stringable::class, $classNotImplementingStringable->getImmediateInterfaces());

$classNotImplementingStringable = $reflector->reflectClass('ClassHasStringableAutomaticallyWithLowercasedMethodName');
self::assertContains(Stringable::class, $classNotImplementingStringable->getInterfaceNames());
self::assertContains(Stringable::class, $classNotImplementingStringable->getInterfaceClassNames());
self::assertArrayHasKey(Stringable::class, $classNotImplementingStringable->getImmediateInterfaces());

$interfaceExtendingStringable = $reflector->reflectClass('InterfaceHasStringable');
self::assertContains(Stringable::class, $interfaceExtendingStringable->getInterfaceNames());
self::assertContains(Stringable::class, $interfaceExtendingStringable->getInterfaceClassNames());
self::assertArrayHasKey(Stringable::class, $interfaceExtendingStringable->getImmediateInterfaces());

$interfaceNotExtendingStringable = $reflector->reflectClass('InterfaceHasStringableAutomatically');
self::assertContains(Stringable::class, $interfaceNotExtendingStringable->getInterfaceNames());
self::assertContains(Stringable::class, $interfaceNotExtendingStringable->getInterfaceClassNames());
self::assertArrayHasKey(Stringable::class, $interfaceNotExtendingStringable->getImmediateInterfaces());

$stringable = $reflector->reflectClass('Stringable');
self::assertNotContains(Stringable::class, $stringable->getInterfaceNames());
self::assertNotContains(Stringable::class, $stringable->getInterfaceClassNames());
self::assertArrayNotHasKey(Stringable::class, $stringable->getImmediateInterfaces());
}

Expand Down
Loading