Skip to content

Commit acfad41

Browse files
phpstan-botclaude
andcommitted
Return TrinaryLogic from expressionContainsNonPureCall
Return TrinaryLogic::createMaybe() when reflection cannot be resolved for a callable, distinguishing it from definitely-impure calls (yes) and definitely-pure calls (no). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e731e88 commit acfad41

1 file changed

Lines changed: 59 additions & 51 deletions

File tree

src/Analyser/TypeSpecifier.php

Lines changed: 59 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2538,7 +2538,7 @@ private function createForExpr(
25382538
}
25392539
}
25402540

2541-
if (!($expr instanceof AlwaysRememberedExpr) && $this->expressionContainsNonPureCall($expr, $scope)) {
2541+
if (!($expr instanceof AlwaysRememberedExpr) && !$this->expressionContainsNonPureCall($expr, $scope)->no()) {
25422542
if (isset($containsNull) && !$containsNull) {
25432543
return $this->createNullsafeTypes($originalExpr, $scope, $context, $type);
25442544
}
@@ -2574,76 +2574,84 @@ private function createForExpr(
25742574
return $types;
25752575
}
25762576

2577-
private function expressionContainsNonPureCall(Expr $expr, Scope $scope): bool
2577+
private function expressionContainsNonPureCall(Expr $expr, Scope $scope): TrinaryLogic
25782578
{
25792579
$nodeFinder = new NodeFinder();
2580-
$found = $nodeFinder->findFirst([$expr], function (Node $node) use ($scope): bool {
2581-
if (!$node instanceof Expr) {
2582-
return false;
2583-
}
2580+
$callNodes = $nodeFinder->find([$expr], static function (Node $node): bool {
2581+
return $node instanceof FuncCall || $node instanceof MethodCall || $node instanceof StaticCall;
2582+
});
25842583

2585-
if ($node instanceof FuncCall) {
2586-
if ($node->name instanceof Name) {
2587-
if (!$this->reflectionProvider->hasFunction($node->name, $scope)) {
2588-
return true;
2584+
$result = TrinaryLogic::createNo();
2585+
foreach ($callNodes as $callNode) {
2586+
if ($callNode instanceof FuncCall) {
2587+
if ($callNode->name instanceof Name) {
2588+
if (!$this->reflectionProvider->hasFunction($callNode->name, $scope)) {
2589+
$result = $result->or(TrinaryLogic::createMaybe());
2590+
continue;
25892591
}
2590-
$hasSideEffects = $this->reflectionProvider->getFunction($node->name, $scope)->hasSideEffects();
2591-
return $hasSideEffects->yes()
2592-
|| (!$this->rememberPossiblyImpureFunctionValues && !$hasSideEffects->no());
2593-
}
2594-
2595-
$nameType = $scope->getType($node->name);
2596-
if ($nameType->isCallable()->yes()) {
2597-
$isPure = null;
2598-
foreach ($nameType->getCallableParametersAcceptors($scope) as $variant) {
2599-
$variantIsPure = $variant->isPure();
2600-
$isPure = $isPure === null ? $variantIsPure : $isPure->and($variantIsPure);
2592+
$hasSideEffects = $this->reflectionProvider->getFunction($callNode->name, $scope)->hasSideEffects();
2593+
if ($hasSideEffects->yes()
2594+
|| (!$this->rememberPossiblyImpureFunctionValues && !$hasSideEffects->no())) {
2595+
return TrinaryLogic::createYes();
26012596
}
2602-
if ($isPure !== null) {
2603-
return $isPure->no()
2604-
|| (!$this->rememberPossiblyImpureFunctionValues && !$isPure->yes());
2597+
} else {
2598+
$nameType = $scope->getType($callNode->name);
2599+
if ($nameType->isCallable()->yes()) {
2600+
$isPure = null;
2601+
foreach ($nameType->getCallableParametersAcceptors($scope) as $variant) {
2602+
$variantIsPure = $variant->isPure();
2603+
$isPure = $isPure === null ? $variantIsPure : $isPure->and($variantIsPure);
2604+
}
2605+
if ($isPure !== null
2606+
&& ($isPure->no() || (!$this->rememberPossiblyImpureFunctionValues && !$isPure->yes()))) {
2607+
return TrinaryLogic::createYes();
2608+
}
26052609
}
26062610
}
2607-
2608-
return false;
2609-
}
2610-
2611-
if ($node instanceof MethodCall) {
2612-
if ($node->name instanceof Identifier) {
2613-
$calledOnType = $scope->getType($node->var);
2614-
$methodReflection = $scope->getMethodReflection($calledOnType, $node->name->name);
2611+
} elseif ($callNode instanceof MethodCall) {
2612+
if ($callNode->name instanceof Identifier) {
2613+
$calledOnType = $scope->getType($callNode->var);
2614+
$methodReflection = $scope->getMethodReflection($calledOnType, $callNode->name->name);
26152615
if ($methodReflection === null) {
2616-
return true;
2616+
$result = $result->or(TrinaryLogic::createMaybe());
2617+
continue;
26172618
}
26182619
$hasSideEffects = $methodReflection->hasSideEffects();
2619-
return $hasSideEffects->yes()
2620-
|| (!$this->rememberPossiblyImpureFunctionValues && !$hasSideEffects->no());
2620+
if ($hasSideEffects->yes()
2621+
|| (!$this->rememberPossiblyImpureFunctionValues && !$hasSideEffects->no())) {
2622+
return TrinaryLogic::createYes();
2623+
}
2624+
} else {
2625+
$result = $result->or(TrinaryLogic::createMaybe());
26212626
}
2622-
return true;
2623-
}
2624-
2625-
if ($node instanceof StaticCall) {
2626-
if ($node->name instanceof Identifier) {
2627-
if ($node->class instanceof Name) {
2628-
$calledOnType = $scope->resolveTypeByName($node->class);
2627+
} elseif ($callNode instanceof StaticCall) {
2628+
if ($callNode->name instanceof Identifier) {
2629+
if ($callNode->class instanceof Name) {
2630+
$calledOnType = $scope->resolveTypeByName($callNode->class);
26292631
} else {
2630-
$calledOnType = $scope->getType($node->class);
2632+
$calledOnType = $scope->getType($callNode->class);
26312633
}
2632-
$methodReflection = $scope->getMethodReflection($calledOnType, $node->name->name);
2634+
$methodReflection = $scope->getMethodReflection($calledOnType, $callNode->name->name);
26332635
if ($methodReflection === null) {
2634-
return true;
2636+
$result = $result->or(TrinaryLogic::createMaybe());
2637+
continue;
26352638
}
26362639
$hasSideEffects = $methodReflection->hasSideEffects();
2637-
return $hasSideEffects->yes()
2638-
|| (!$this->rememberPossiblyImpureFunctionValues && !$hasSideEffects->no());
2640+
if ($hasSideEffects->yes()
2641+
|| (!$this->rememberPossiblyImpureFunctionValues && !$hasSideEffects->no())) {
2642+
return TrinaryLogic::createYes();
2643+
}
2644+
} else {
2645+
$result = $result->or(TrinaryLogic::createMaybe());
26392646
}
2640-
return true;
26412647
}
26422648

2643-
return false;
2644-
});
2649+
if ($result->yes()) {
2650+
return $result;
2651+
}
2652+
}
26452653

2646-
return $found !== null;
2654+
return $result;
26472655
}
26482656

26492657
private function createNullsafeTypes(Expr $expr, Scope $scope, TypeSpecifierContext $context, ?Type $type): SpecifiedTypes

0 commit comments

Comments
 (0)