1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Analyser\Fiber;
4:
5: use Fiber;
6: use PhpParser\Node\Expr;
7: use PHPStan\Analyser\ExpressionResult;
8: use PHPStan\Analyser\MutatingScope;
9: use PHPStan\Analyser\Scope;
10: use PHPStan\Node\Expr\TypeExpr;
11: use PHPStan\Reflection\FunctionReflection;
12: use PHPStan\Reflection\MethodReflection;
13: use PHPStan\Reflection\ParameterReflection;
14: use PHPStan\Type\Type;
15: use function array_pop;
16: use function count;
17:
18: final class FiberScope extends MutatingScope
19: {
20:
21: /** @var Expr[] */
22: private array $truthyValueExprs = [];
23:
24: /** @var Expr[] */
25: private array $falseyValueExprs = [];
26:
27: private ?MutatingScope $mutatingScope = null;
28:
29: public function toFiberScope(): self
30: {
31: return $this;
32: }
33:
34: public function toMutatingScope(): MutatingScope
35: {
36: if ($this->mutatingScope !== null) {
37: return $this->mutatingScope;
38: }
39:
40: return $this->mutatingScope = $this->scopeFactory->toMutatingFactory()->create(
41: $this->context,
42: $this->isDeclareStrictTypes(),
43: $this->getFunction(),
44: $this->getNamespace(),
45: $this->expressionTypes,
46: $this->nativeExpressionTypes,
47: $this->conditionalExpressions,
48: $this->inClosureBindScopeClasses,
49: $this->getAnonymousFunctionReflection(),
50: $this->isInFirstLevelStatement(),
51: $this->currentlyAssignedExpressions,
52: $this->currentlyAllowedUndefinedExpressions,
53: $this->inFunctionCallsStack,
54: $this->afterExtractCall,
55: $this->getParentScope(),
56: $this->nativeTypesPromoted,
57: );
58: }
59:
60: /** @api */
61: public function getType(Expr $node): Type
62: {
63: if ($node instanceof TypeExpr) {
64: // Scope-independent by construction - suspending would park this
65: // fiber until the end of the function because the node is never
66: // visited by NodeScopeResolver, and would resolve to the same type.
67: return $node->getExprType();
68: }
69:
70: /** @var ExpressionResult $expressionResult */
71: $expressionResult = Fiber::suspend(
72: new ExpressionResultRequest($node, $this),
73: );
74:
75: if (
76: !$this->nativeTypesPromoted
77: && count($this->truthyValueExprs) === 0
78: && count($this->falseyValueExprs) === 0
79: ) {
80: return $expressionResult->getType();
81: }
82:
83: $scope = $this->preprocessScope($expressionResult->getBeforeScope());
84: return $scope->getType($node);
85: }
86:
87: public function getScopeType(Expr $expr): Type
88: {
89: return $this->toMutatingScope()->getType($expr);
90: }
91:
92: public function getScopeNativeType(Expr $expr): Type
93: {
94: return $this->toMutatingScope()->getNativeType($expr);
95: }
96:
97: /** @api */
98: public function getNativeType(Expr $expr): Type
99: {
100: if ($expr instanceof TypeExpr) {
101: // See getType() - same reasoning
102: return $expr->getExprType();
103: }
104:
105: /** @var ExpressionResult $expressionResult */
106: $expressionResult = Fiber::suspend(
107: new ExpressionResultRequest($expr, $this),
108: );
109:
110: if (
111: !$this->nativeTypesPromoted
112: && count($this->truthyValueExprs) === 0
113: && count($this->falseyValueExprs) === 0
114: ) {
115: return $expressionResult->getNativeType();
116: }
117:
118: $scope = $this->preprocessScope($expressionResult->getBeforeScope());
119: return $scope->getNativeType($expr);
120: }
121:
122: public function getKeepVoidType(Expr $node): Type
123: {
124: /** @var ExpressionResult $expressionResult */
125: $expressionResult = Fiber::suspend(
126: new ExpressionResultRequest($node, $this),
127: );
128:
129: $scope = $this->preprocessScope($expressionResult->getBeforeScope());
130:
131: return $scope->getKeepVoidType($node);
132: }
133:
134: public function filterByTruthyValue(Expr $expr): self
135: {
136: /** @var self $scope */
137: $scope = parent::filterByTruthyValue($expr);
138: $scope->truthyValueExprs = $this->truthyValueExprs;
139: $scope->falseyValueExprs = $this->falseyValueExprs;
140: $scope->truthyValueExprs[] = $expr;
141:
142: return $scope;
143: }
144:
145: public function filterByFalseyValue(Expr $expr): self
146: {
147: /** @var self $scope */
148: $scope = parent::filterByFalseyValue($expr);
149: $scope->truthyValueExprs = $this->truthyValueExprs;
150: $scope->falseyValueExprs = $this->falseyValueExprs;
151: $scope->falseyValueExprs[] = $expr;
152:
153: return $scope;
154: }
155:
156: private function preprocessScope(MutatingScope $scope): Scope
157: {
158: if ($this->nativeTypesPromoted) {
159: $scope = $scope->doNotTreatPhpDocTypesAsCertain();
160: }
161:
162: foreach ($this->truthyValueExprs as $expr) {
163: $scope = $scope->filterByTruthyValue($expr);
164: }
165: foreach ($this->falseyValueExprs as $expr) {
166: $scope = $scope->filterByFalseyValue($expr);
167: }
168:
169: return $scope;
170: }
171:
172: /**
173: * @param MethodReflection|FunctionReflection|null $reflection
174: */
175: public function pushInFunctionCall($reflection, ?ParameterReflection $parameter, bool $rememberTypes): self
176: {
177: /** @var self $scope */
178: $scope = parent::pushInFunctionCall($reflection, $parameter, $rememberTypes);
179: $scope->truthyValueExprs = $this->truthyValueExprs;
180: $scope->falseyValueExprs = $this->falseyValueExprs;
181:
182: return $scope;
183: }
184:
185: public function popInFunctionCall(): self
186: {
187: $stack = $this->inFunctionCallsStack;
188: array_pop($stack);
189:
190: /** @var self $scope */
191: $scope = parent::popInFunctionCall();
192: $scope->truthyValueExprs = $this->truthyValueExprs;
193: $scope->falseyValueExprs = $this->falseyValueExprs;
194:
195: return $scope;
196: }
197:
198: public function getParentScope(): ?MutatingScope
199: {
200: $parent = parent::getParentScope();
201: if ($parent === null) {
202: return null;
203: }
204:
205: return $parent->toFiberScope();
206: }
207:
208: }
209: