Skip to content

Commit 6d73f01

Browse files
phpstan-botclaude
andcommitted
Extend trait context detection to cover self-typed variables and fix multiple method names
- Add isExpressionDependentOnTraitContext() that checks both AST-level ($this, self::, static::) and type-level (object type matches using class) dependency on trait context - Fix method_exists handling to support multiple constant method names (e.g. 'foo'|'bar') instead of requiring exactly one - Add null check for getTraitReflection() - Update ConstantConditionRuleHelper with same type-level check - Fix testBug7599: method_exists($enum, 'barMethod') where $enum is typed as self in a trait should not report errors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4f8d0b3 commit 6d73f01

3 files changed

Lines changed: 48 additions & 17 deletions

File tree

src/Rules/Comparison/ConstantConditionRuleHelper.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ private function shouldSkip(Scope $scope, Expr $expr): bool
6767
if (ExpressionDependsOnThisHelper::isExpressionDependentOnThis($arg->value)) {
6868
return true;
6969
}
70+
71+
$classReflection = $scope->getClassReflection();
72+
if ($classReflection !== null) {
73+
$argType = $this->treatPhpDocTypesAsCertain ? $scope->getType($arg->value) : $scope->getNativeType($arg->value);
74+
foreach ($argType->getObjectClassNames() as $className) {
75+
if ($className === $classReflection->getName()) {
76+
return true;
77+
}
78+
}
79+
}
7080
}
7181
}
7282
}

src/Rules/Comparison/ImpossibleCheckTypeHelper.php

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -199,17 +199,22 @@ public function findSpecifiedType(
199199
} elseif ($functionName === 'method_exists' && $argsCount >= 2) {
200200
$objectArg = $args[0]->value;
201201

202-
if ($scope->isInTrait() && ExpressionDependsOnThisHelper::isExpressionDependentOnThis($objectArg)) {
202+
if ($this->isExpressionDependentOnTraitContext($scope, $objectArg)) {
203203
$traitReflection = $scope->getTraitReflection();
204+
if ($traitReflection === null) {
205+
return null;
206+
}
204207
$methodArgValue = $args[1]->value;
205208
$methodArgType = $this->treatPhpDocTypesAsCertain ? $scope->getType($methodArgValue) : $scope->getNativeType($methodArgValue);
206209
$constantMethodNames = $methodArgType->getConstantStrings();
207-
if (
208-
count($constantMethodNames) !== 1
209-
|| !$traitReflection->hasNativeMethod($constantMethodNames[0]->getValue())
210-
) {
210+
if (count($constantMethodNames) === 0) {
211211
return null;
212212
}
213+
foreach ($constantMethodNames as $constantMethodName) {
214+
if (!$traitReflection->hasNativeMethod($constantMethodName->getValue())) {
215+
return null;
216+
}
217+
}
213218
}
214219
$objectType = $this->treatPhpDocTypesAsCertain ? $scope->getType($objectArg) : $scope->getNativeType($objectArg);
215220

@@ -323,7 +328,7 @@ public function findSpecifiedType(
323328
continue;
324329
}
325330

326-
if ($scope->isInTrait() && ExpressionDependsOnThisHelper::isExpressionDependentOnThis($sureType[0])) {
331+
if ($this->isExpressionDependentOnTraitContext($scope, $sureType[0])) {
327332
$results[] = TrinaryLogic::createMaybe();
328333
continue;
329334
}
@@ -354,7 +359,7 @@ public function findSpecifiedType(
354359
continue;
355360
}
356361

357-
if ($scope->isInTrait() && ExpressionDependsOnThisHelper::isExpressionDependentOnThis($sureNotType[0])) {
362+
if ($this->isExpressionDependentOnTraitContext($scope, $sureNotType[0])) {
358363
$results[] = TrinaryLogic::createMaybe();
359364
continue;
360365
}
@@ -379,6 +384,31 @@ public function findSpecifiedType(
379384
return $result->maybe() ? null : $result->yes();
380385
}
381386

387+
private function isExpressionDependentOnTraitContext(Scope $scope, Expr $expr): bool
388+
{
389+
if (!$scope->isInTrait()) {
390+
return false;
391+
}
392+
393+
if (ExpressionDependsOnThisHelper::isExpressionDependentOnThis($expr)) {
394+
return true;
395+
}
396+
397+
$classReflection = $scope->getClassReflection();
398+
if ($classReflection === null) {
399+
return false;
400+
}
401+
402+
$type = $this->treatPhpDocTypesAsCertain ? $scope->getType($expr) : $scope->getNativeType($expr);
403+
foreach ($type->getObjectClassNames() as $className) {
404+
if ($className === $classReflection->getName()) {
405+
return true;
406+
}
407+
}
408+
409+
return false;
410+
}
411+
382412
private static function isSpecified(Scope $scope, Expr $node, Expr $expr): bool
383413
{
384414
if ($expr === $node) {

tests/PHPStan/Rules/Comparison/ImpossibleCheckTypeFunctionCallRuleTest.php

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,16 +1208,7 @@ public function testBug13023(): void
12081208
public function testBug7599(): void
12091209
{
12101210
$this->treatPhpDocTypesAsCertain = true;
1211-
$this->analyse([__DIR__ . '/data/bug-7599.php'], [
1212-
[
1213-
'Call to function method_exists() with Bug7599\SecondEnum::Baz and \'barMethod\' will always evaluate to true.',
1214-
13,
1215-
],
1216-
[
1217-
'Call to function method_exists() with Bug7599\TestEnum::Bar|Bug7599\TestEnum::Foo and \'barMethod\' will always evaluate to false.',
1218-
13,
1219-
],
1220-
]);
1211+
$this->analyse([__DIR__ . '/data/bug-7599.php'], []);
12211212
}
12221213

12231214
public function testBug9095(): void

0 commit comments

Comments
 (0)