Skip to content

Commit eb31077

Browse files
ondrejmirtesclaude
andcommitted
Convert rule-facing FiberScope at the new-world hook boundary
Replaces the blanket processExprNode conversion with the root cause: the hooks are the boundary between the rule-facing world and the engine. Rules hold FiberScopes and feed them straight into the old-world dispatcher (ImpossibleCheckTypeHelper passes the rule's scope to specifyTypesInCondition, phpstan-phpunit's assert extension builds a synthetic BooleanOr there), so resolveTypeOfNewWorldHandlerNode() and specifyTypesOfNewWorldHandlerNode() can run with $this being a FiberScope. They now call toMutatingScope() - identity on a plain scope, a state-preserving copy on a FiberScope - before invoking result callbacks and on-demand processing. Without the conversion the engine processes synthetic nodes on the rule-facing scope, whose type asks suspend: wasteful inside a rule fiber, fatal outside one ("Cannot suspend outside of a fiber"). Found by an ExpressionResult creation tripwire after the CI-only crash never reproduced locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent be672de commit eb31077

2 files changed

Lines changed: 15 additions & 16 deletions

File tree

src/Analyser/MutatingScope.php

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,11 @@ private function resolveType(string $exprString, Expr $node): Type
10121012
*/
10131013
private function resolveTypeOfNewWorldHandlerNode(Expr $node): Type
10141014
{
1015+
// the hooks are the boundary between the rule-facing world and the
1016+
// engine - a rule's FiberScope must not flow into result callbacks or
1017+
// on-demand processing, where its suspending type asks crash outside
1018+
// a fiber
1019+
$scope = $this->toMutatingScope();
10151020
$storage = $this->expressionResultStorageStack->getCurrent();
10161021
if ($storage !== null) {
10171022
$result = $storage->findExpressionResult($node);
@@ -1023,18 +1028,18 @@ private function resolveTypeOfNewWorldHandlerNode(Expr $node): Type
10231028
));
10241029
}
10251030

1026-
return $result->getTypeForScope($this);
1031+
return $result->getTypeForScope($scope);
10271032
}
10281033
}
10291034

10301035
// a synthetic node, or no analysis in progress
10311036
$onDemandResult = $this->container->getByType(NodeScopeResolver::class)->processExprOnDemand(
10321037
$node,
1033-
$this,
1038+
$scope,
10341039
$storage !== null ? $storage->duplicate() : new ExpressionResultStorage(),
10351040
);
10361041

1037-
return $onDemandResult->getTypeForScope($this);
1042+
return $onDemandResult->getTypeForScope($scope);
10381043
}
10391044

10401045
/**
@@ -1077,22 +1082,26 @@ private function getCurrentTypesOfSpecifiedExpr(Expr $expr): ?array
10771082
*/
10781083
public function specifyTypesOfNewWorldHandlerNode(Expr $node, TypeSpecifierContext $context): ?SpecifiedTypes
10791084
{
1085+
// see resolveTypeOfNewWorldHandlerNode() - rules ask the dispatcher
1086+
// with their FiberScope (e.g. ImpossibleCheckTypeHelper), the engine
1087+
// side of the boundary works with the mutating flavor
1088+
$scope = $this->toMutatingScope();
10801089
$storage = $this->expressionResultStorageStack->getCurrent();
10811090
if ($storage !== null) {
10821091
$result = $storage->findExpressionResult($node);
10831092
if ($result !== null) {
1084-
return $result->getSpecifiedTypesForScope($this, $context);
1093+
return $result->getSpecifiedTypesForScope($scope, $context);
10851094
}
10861095
}
10871096

10881097
// a synthetic node, or no analysis in progress
10891098
$onDemandResult = $this->container->getByType(NodeScopeResolver::class)->processExprOnDemand(
10901099
$node,
1091-
$this,
1100+
$scope,
10921101
$storage !== null ? $storage->duplicate() : new ExpressionResultStorage(),
10931102
);
10941103

1095-
return $onDemandResult->getSpecifiedTypesForScope($this, $context);
1104+
return $onDemandResult->getSpecifiedTypesForScope($scope, $context);
10961105
}
10971106

10981107
/**

src/Analyser/NodeScopeResolver.php

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
use PhpParser\NodeVisitorAbstract;
5353
use PHPStan\Analyser\ExprHandler\AssignHandler;
5454
use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper;
55-
use PHPStan\Analyser\Fiber\FiberScope;
5655
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass;
5756
use PHPStan\BetterReflection\Reflection\ReflectionEnum;
5857
use PHPStan\BetterReflection\Reflector\Reflector;
@@ -2804,15 +2803,6 @@ public function processExprNode(
28042803
ExpressionContext $context,
28052804
): ExpressionResult
28062805
{
2807-
if ($scope instanceof FiberScope) {
2808-
// the engine never processes on the rule-facing FiberScope - one can
2809-
// arrive here through a stored result's memoized truthy/falsey scope
2810-
// (first computed inside a rule fiber) consumed by a handler for a
2811-
// child's processing scope; its type asks would suspend outside
2812-
// a fiber
2813-
$scope = $scope->toMutatingScope();
2814-
}
2815-
28162806
if ($this->returnStoredExpressionResults) {
28172807
$storedResult = $storage->findExpressionResult($expr);
28182808
if ($storedResult !== null) {

0 commit comments

Comments
 (0)