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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 6 additions & 6 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 19 additions & 21 deletions src/Collector/MethodCallCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -152,19 +152,19 @@ private function registerStaticCall(

if ($staticCall->class instanceof Expr) {
$callerType = $scope->getType($staticCall->class);
$possibleDescendantCall = true;
$possibleDescendantCall = null;

} else {
$callerType = $scope->resolveTypeByName($staticCall->class);
$possibleDescendantCall = $staticCall->class->toString() === 'static';
}

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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -257,13 +254,13 @@ private function getMethodName(CallLike $call, Scope $scope): array
}

/**
* @return list<class-string<object>|null>
* @return list<ClassMethodRef>
*/
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
Expand All @@ -273,15 +270,16 @@ 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) {
$canBeObjectCall = !$typeNoNull->isObject()->no() && !$isStaticCall->yes();
$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
}
}

Expand Down
3 changes: 3 additions & 0 deletions tests/Rule/data/methods/array-map-1.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -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

}

Expand Down
4 changes: 4 additions & 0 deletions tests/Rule/data/methods/clone.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
4 changes: 1 addition & 3 deletions tests/Rule/data/methods/parent-call-5.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();