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\MutatingScope;
8: use PHPStan\Analyser\Scope;
9: use PHPStan\Reflection\FunctionReflection;
10: use PHPStan\Reflection\MethodReflection;
11: use PHPStan\Reflection\ParameterReflection;
12: use PHPStan\Type\Type;
13: use function array_pop;
14:
15: final class FiberScope extends MutatingScope
16: {
17:
18: /** @var Expr[] */
19: private array $truthyValueExprs = [];
20:
21: /** @var Expr[] */
22: private array $falseyValueExprs = [];
23:
24: private ?MutatingScope $mutatingScope = null;
25:
26: public function toFiberScope(): self
27: {
28: return $this;
29: }
30:
31: public function toMutatingScope(): MutatingScope
32: {
33: if ($this->mutatingScope !== null) {
34: return $this->mutatingScope;
35: }
36:
37: return $this->mutatingScope = $this->scopeFactory->toMutatingFactory()->create(
38: $this->context,
39: $this->isDeclareStrictTypes(),
40: $this->getFunction(),
41: $this->getNamespace(),
42: $this->expressionTypes,
43: $this->nativeExpressionTypes,
44: $this->conditionalExpressions,
45: $this->inClosureBindScopeClasses,
46: $this->getAnonymousFunctionReflection(),
47: $this->isInFirstLevelStatement(),
48: $this->currentlyAssignedExpressions,
49: $this->currentlyAllowedUndefinedExpressions,
50: $this->inFunctionCallsStack,
51: $this->afterExtractCall,
52: $this->getParentScope(),
53: $this->nativeTypesPromoted,
54: );
55: }
56:
57: /** @api */
58: public function getType(Expr $node): Type
59: {
60: /** @var Scope $beforeScope */
61: $beforeScope = Fiber::suspend(
62: new BeforeScopeForExprRequest($node, $this),
63: );
64:
65: $scope = $this->preprocessScope($beforeScope->toMutatingScope());
66: return $scope->getType($node);
67: }
68:
69: public function getScopeType(Expr $expr): Type
70: {
71: return $this->toMutatingScope()->getType($expr);
72: }
73:
74: public function getScopeNativeType(Expr $expr): Type
75: {
76: return $this->toMutatingScope()->getNativeType($expr);
77: }
78:
79: /** @api */
80: public function getNativeType(Expr $expr): Type
81: {
82: /** @var Scope $beforeScope */
83: $beforeScope = Fiber::suspend(
84: new BeforeScopeForExprRequest($expr, $this),
85: );
86:
87: $scope = $this->preprocessScope($beforeScope->toMutatingScope());
88: return $scope->getNativeType($expr);
89: }
90:
91: public function getKeepVoidType(Expr $node): Type
92: {
93: /** @var Scope $beforeScope */
94: $beforeScope = Fiber::suspend(
95: new BeforeScopeForExprRequest($node, $this),
96: );
97:
98: $scope = $this->preprocessScope($beforeScope->toMutatingScope());
99:
100: return $scope->getKeepVoidType($node);
101: }
102:
103: public function filterByTruthyValue(Expr $expr): self
104: {
105: /** @var self $scope */
106: $scope = parent::filterByTruthyValue($expr);
107: $scope->truthyValueExprs = $this->truthyValueExprs;
108: $scope->truthyValueExprs[] = $expr;
109:
110: return $scope;
111: }
112:
113: public function filterByFalseyValue(Expr $expr): self
114: {
115: /** @var self $scope */
116: $scope = parent::filterByTruthyValue($expr);
117: $scope->falseyValueExprs = $this->falseyValueExprs;
118: $scope->falseyValueExprs[] = $expr;
119:
120: return $scope;
121: }
122:
123: private function preprocessScope(MutatingScope $scope): Scope
124: {
125: if ($this->nativeTypesPromoted) {
126: $scope = $scope->doNotTreatPhpDocTypesAsCertain();
127: }
128:
129: foreach ($this->truthyValueExprs as $expr) {
130: $scope = $scope->filterByTruthyValue($expr);
131: }
132: foreach ($this->falseyValueExprs as $expr) {
133: $scope = $scope->filterByFalseyValue($expr);
134: }
135:
136: return $scope;
137: }
138:
139: /**
140: * @param MethodReflection|FunctionReflection|null $reflection
141: */
142: public function pushInFunctionCall($reflection, ?ParameterReflection $parameter, bool $rememberTypes): self
143: {
144: /** @var self $scope */
145: $scope = parent::pushInFunctionCall($reflection, $parameter, $rememberTypes);
146: $scope->truthyValueExprs = $this->truthyValueExprs;
147: $scope->falseyValueExprs = $this->falseyValueExprs;
148:
149: return $scope;
150: }
151:
152: public function popInFunctionCall(): self
153: {
154: $stack = $this->inFunctionCallsStack;
155: array_pop($stack);
156:
157: /** @var self $scope */
158: $scope = parent::popInFunctionCall();
159: $scope->truthyValueExprs = $this->truthyValueExprs;
160: $scope->falseyValueExprs = $this->falseyValueExprs;
161:
162: return $scope;
163: }
164:
165: public function getParentScope(): ?MutatingScope
166: {
167: $parent = parent::getParentScope();
168: if ($parent === null) {
169: return null;
170: }
171:
172: return $parent->toFiberScope();
173: }
174:
175: }
176: