From fbdf0da9b7075ce4d4933d6289dcfd89266556f0 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Tue, 7 Feb 2023 10:56:49 +0100 Subject: [PATCH] Fix false positive about unused class elements when the class uses a trait --- src/Type/ThisType.php | 16 ++++- .../DeadCode/UnusedPrivateMethodRuleTest.php | 9 +++ ...nused-method-false-positive-with-trait.php | 72 +++++++++++++++++++ tests/PHPStan/Type/ObjectTypeTest.php | 6 ++ 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/DeadCode/data/unused-method-false-positive-with-trait.php diff --git a/src/Type/ThisType.php b/src/Type/ThisType.php index 441ebd2a53..d78c700ba7 100644 --- a/src/Type/ThisType.php +++ b/src/Type/ThisType.php @@ -30,7 +30,21 @@ public function changeBaseClass(ClassReflection $classReflection): StaticType public function describe(VerbosityLevel $level): string { - return sprintf('$this(%s)', $this->getStaticObjectType()->describe($level)); + $callback = fn () => sprintf('$this(%s)', $this->getStaticObjectType()->describe($level)); + return $level->handle( + $callback, + $callback, + $callback, + function () use ($callback): string { + $base = $callback(); + $trait = $this->getTraitReflection(); + if ($trait === null) { + return $base; + } + + return sprintf('%s-trait-%s', $base, $trait->getDisplayName()); + }, + ); } public function isSuperTypeOf(Type $type): TrinaryLogic diff --git a/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php b/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php index 60fe27b782..f10ed61c91 100644 --- a/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php +++ b/tests/PHPStan/Rules/DeadCode/UnusedPrivateMethodRuleTest.php @@ -90,4 +90,13 @@ public function testBug7389(): void ]); } + public function testFalsePositiveWithTraitUse(): void + { + if (PHP_VERSION_ID < 80100) { + $this->markTestSkipped('This test needs PHP 8.1'); + } + + $this->analyse([__DIR__ . '/data/unused-method-false-positive-with-trait.php'], []); + } + } diff --git a/tests/PHPStan/Rules/DeadCode/data/unused-method-false-positive-with-trait.php b/tests/PHPStan/Rules/DeadCode/data/unused-method-false-positive-with-trait.php new file mode 100644 index 0000000000..24e2c3256d --- /dev/null +++ b/tests/PHPStan/Rules/DeadCode/data/unused-method-false-positive-with-trait.php @@ -0,0 +1,72 @@ += 8.1 + +namespace UnusedMethodFalsePositiveWithTrait; + +use ReflectionEnum; + +enum LocalOnlineReservationTime: string +{ + + use LabeledEnumTrait; + + case MORNING = 'morning'; + case AFTERNOON = 'afternoon'; + case EVENING = 'evening'; + + public static function getPeriodForHour(string $hour): self + { + $hour = self::hourToNumber($hour); + + throw new \Exception('Internal error'); + } + + private static function hourToNumber(string $hour): int + { + return (int) str_replace(':', '', $hour); + } + +} + +trait LabeledEnumTrait +{ + + use EnumTrait; + +} + +trait EnumTrait +{ + + /** + * @return list + */ + public static function getDeprecatedEnums(): array + { + static $cache = []; + if ($cache === []) { + $reflection = new ReflectionEnum(self::class); + $cases = $reflection->getCases(); + + foreach ($cases as $case) { + $docComment = $case->getDocComment(); + if ($docComment === false || !str_contains($docComment, '@deprecated')) { + continue; + } + $cache[] = self::from($case->getBackingValue()); + } + } + + return $cache; + } + + public function isDeprecated(): bool + { + return $this->equalsAny(self::getDeprecatedEnums()); + } + + public function equalsAny(...$that): bool + { + return in_array($this, $that, true); + } + +} diff --git a/tests/PHPStan/Type/ObjectTypeTest.php b/tests/PHPStan/Type/ObjectTypeTest.php index be6c66eeb1..59daa398c8 100644 --- a/tests/PHPStan/Type/ObjectTypeTest.php +++ b/tests/PHPStan/Type/ObjectTypeTest.php @@ -34,6 +34,7 @@ use Throwable; use ThrowPoints\TryCatch\MyInvalidArgumentException; use Traversable; +use UnusedMethodFalsePositiveWithTrait\LocalOnlineReservationTime; use function count; use function sprintf; use const PHP_VERSION_ID; @@ -432,6 +433,11 @@ public function dataIsSuperTypeOf(): array new ObjectType(DateTime::class), TrinaryLogic::createNo(), ], + 61 => [ + new ObjectType(LocalOnlineReservationTime::class), + new ThisType($reflectionProvider->getClass(LocalOnlineReservationTime::class)), + TrinaryLogic::createYes(), + ], ]; }