diff --git a/composer.json b/composer.json index 7944a66b..b41128a3 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ ], "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.0" + "phpstan/phpstan": "^2.1.7" }, "require-dev": { "doctrine/orm": "^2.19 || ^3.0", diff --git a/composer.lock b/composer.lock index d174171c..62bc082d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e51941e12fd9900904207112b51a0acf", + "content-hash": "953d022d9b7124b5720353ddda09c3ab", "packages": [ { "name": "phpstan/phpstan", - "version": "2.1.6", + "version": "2.1.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c" + "reference": "12567f91a74036d56ba0af6d56c8e73ac0e8d850" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", - "reference": "6eaec7c6c9e90dcfe46ad1e1ffa5171e2dab641c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/12567f91a74036d56ba0af6d56c8e73ac0e8d850", + "reference": "12567f91a74036d56ba0af6d56c8e73ac0e8d850", "shasum": "" }, "require": { @@ -62,7 +62,7 @@ "type": "github" } ], - "time": "2025-02-19T15:46:42+00:00" + "time": "2025-03-05T13:43:55+00:00" } ], "packages-dev": [ diff --git a/src/Collector/MethodCallCollector.php b/src/Collector/MethodCallCollector.php index 89306380..a6b8e6b4 100644 --- a/src/Collector/MethodCallCollector.php +++ b/src/Collector/MethodCallCollector.php @@ -114,8 +114,8 @@ private function registerMethodCall( if ($methodCall instanceof New_) { if ($methodCall->class instanceof Expr) { - $callerType = $scope->getType($methodCall->class); - $possibleDescendantCall = true; + $callerType = $scope->getType($methodCall); + $possibleDescendantCall = null; } elseif ($methodCall->class instanceof Name) { $callerType = $scope->resolveTypeByName($methodCall->class); @@ -126,15 +126,15 @@ private function registerMethodCall( } } else { $callerType = $scope->getType($methodCall->var); - $possibleDescendantCall = true; + $possibleDescendantCall = null; } foreach ($methodNames as $methodName) { - foreach ($this->getDeclaringTypesWithMethod($scope, $callerType, $methodName, TrinaryLogic::createNo()) as $className) { + foreach ($this->getDeclaringTypesWithMethod($methodName, $callerType, TrinaryLogic::createNo(), $possibleDescendantCall) as $methodRef) { $this->registerUsage( new ClassMethodUsage( $this->usageOriginDetector->detectOrigin($scope), - new ClassMethodRef($className, $methodName, $possibleDescendantCall), + $methodRef, ), $methodCall, $scope, @@ -152,7 +152,7 @@ private function registerStaticCall( if ($staticCall->class instanceof Expr) { $callerType = $scope->getType($staticCall->class); - $possibleDescendantCall = true; + $possibleDescendantCall = null; } else { $callerType = $scope->resolveTypeByName($staticCall->class); @@ -160,11 +160,11 @@ private function registerStaticCall( } foreach ($methodNames as $methodName) { - foreach ($this->getDeclaringTypesWithMethod($scope, $callerType, $methodName, TrinaryLogic::createYes()) as $className) { + foreach ($this->getDeclaringTypesWithMethod($methodName, $callerType, TrinaryLogic::createYes(), $possibleDescendantCall) as $methodRef) { $this->registerUsage( new ClassMethodUsage( $this->usageOriginDetector->detectOrigin($scope), - new ClassMethodRef($className, $methodName, $possibleDescendantCall), + $methodRef, ), $staticCall, $scope, @@ -186,14 +186,11 @@ private function registerArrayCallable( $caller = $typeAndName->getType(); $methodName = $typeAndName->getMethod(); - // currently always true, see https://github.com/phpstan/phpstan-src/pull/3372 - $possibleDescendantCall = !$caller->isClassString()->yes(); - - foreach ($this->getDeclaringTypesWithMethod($scope, $caller, $methodName, TrinaryLogic::createMaybe()) as $className) { + foreach ($this->getDeclaringTypesWithMethod($methodName, $caller, TrinaryLogic::createMaybe()) as $methodRef) { $this->registerUsage( new ClassMethodUsage( $this->usageOriginDetector->detectOrigin($scope), - new ClassMethodRef($className, $methodName, $possibleDescendantCall), + $methodRef, ), $array, $scope, @@ -221,11 +218,11 @@ private function registerClone(Clone_ $node, Scope $scope): void $methodName = '__clone'; $callerType = $scope->getType($node->expr); - foreach ($this->getDeclaringTypesWithMethod($scope, $callerType, $methodName, TrinaryLogic::createNo()) as $className) { + foreach ($this->getDeclaringTypesWithMethod($methodName, $callerType, TrinaryLogic::createNo()) as $methodRef) { $this->registerUsage( new ClassMethodUsage( $this->usageOriginDetector->detectOrigin($scope), - new ClassMethodRef($className, $methodName, true), + $methodRef, ), $node, $scope, @@ -257,13 +254,13 @@ private function getMethodName(CallLike $call, Scope $scope): array } /** - * @return list|null> + * @return list */ private function getDeclaringTypesWithMethod( - Scope $scope, - Type $type, string $methodName, - TrinaryLogic $isStaticCall + Type $type, + TrinaryLogic $isStaticCall, + ?bool $isPossibleDescendant = null ): array { $typeNoNull = TypeCombinator::removeNull($type); // remove null to support nullsafe calls @@ -273,7 +270,8 @@ private function getDeclaringTypesWithMethod( $result = []; foreach ($classReflections as $classReflection) { - $result[] = $classReflection->getName(); + $possibleDescendant = $isPossibleDescendant ?? !$classReflection->isFinal(); + $result[] = new ClassMethodRef($classReflection->getName(), $methodName, $possibleDescendant); } if ($this->trackMixedAccess) { @@ -281,7 +279,7 @@ private function getDeclaringTypesWithMethod( $canBeClassStringCall = !$typeNoNull->isClassString()->no() && !$isStaticCall->no(); if ($result === [] && ($canBeObjectCall || $canBeClassStringCall)) { - $result[] = null; // call over unknown type + $result[] = new ClassMethodRef(null, $methodName, true); // call over unknown type } } diff --git a/tests/Rule/data/methods/array-map-1.php b/tests/Rule/data/methods/array-map-1.php index f7952045..12368076 100644 --- a/tests/Rule/data/methods/array-map-1.php +++ b/tests/Rule/data/methods/array-map-1.php @@ -12,6 +12,7 @@ public function __construct() [$this, 'method4']; [self::class, 'method5']; ['static', 'method6']; // https://github.com/phpstan/phpstan/issues/11594 + [new self(), 'method7']; } public function method1(string $foo): void {} // error: Unused DeadMap\ArrayMapTest::method1 @@ -20,6 +21,7 @@ public function method3(): void {} public function method4(): void {} public static function method5(): void {} public static function method6(): void {} // error: Unused DeadMap\ArrayMapTest::method6 + public function method7(): void {} } class Child extends ArrayMapTest { @@ -29,6 +31,7 @@ public function method3(): void {} public function method4(): void {} public static function method5(): void {} // should be reported (https://github.com/phpstan/phpstan-src/pull/3372) public static function method6(): void {} // error: Unused DeadMap\Child::method6 + public function method7(): void {} // error: Unused DeadMap\Child::method7 } diff --git a/tests/Rule/data/methods/clone.php b/tests/Rule/data/methods/clone.php index 8d9d2aff..d25a9c19 100644 --- a/tests/Rule/data/methods/clone.php +++ b/tests/Rule/data/methods/clone.php @@ -6,6 +6,10 @@ class CloneClass1 { public function __clone() {} } +class CloneClassChild extends CloneClass1 { + public function __clone() {} // error: Unused DeadClone\CloneClassChild::__clone +} + class CloneClass2 { public function __clone() {} // error: Unused DeadClone\CloneClass2::__clone } diff --git a/tests/Rule/data/methods/parent-call-5.php b/tests/Rule/data/methods/parent-call-5.php index 4e256936..5190f6dc 100644 --- a/tests/Rule/data/methods/parent-call-5.php +++ b/tests/Rule/data/methods/parent-call-5.php @@ -10,9 +10,7 @@ public function method() {} class ChildClass extends ParentClass { - public function method() {} + public function method() {} // error: Unused ParentCall5\ChildClass::method } -// this cannot be call over descendant, but there is no such info in PHPStan's ObjectType -// ideally, ChildClass::method() should be marked as dead, but it is currently impossible (new ParentClass())->method();