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 count;
14: use function defined;
15:
16: final class FiberScope extends MutatingScope
17: {
18:
19: private const EXPR_TYPE_ATTRIBUTE_NAME = 'fnsrType';
20:
21: private const EXPR_NATIVE_TYPE_ATTRIBUTE_NAME = 'fnsrNativeType';
22:
23: /** @var Expr[] */
24: private array $truthyValueExprs = [];
25:
26: /** @var Expr[] */
27: private array $falseyValueExprs = [];
28:
29: private ?MutatingScope $mutatingScope = null;
30:
31: public function toFiberScope(): self
32: {
33: return $this;
34: }
35:
36: public function toMutatingScope(): MutatingScope
37: {
38: if ($this->mutatingScope !== null) {
39: return $this->mutatingScope;
40: }
41:
42: return $this->mutatingScope = $this->scopeFactory->toMutatingFactory()->create(
43: $this->context,
44: $this->isDeclareStrictTypes(),
45: $this->getFunction(),
46: $this->getNamespace(),
47: $this->expressionTypes,
48: $this->nativeExpressionTypes,
49: $this->conditionalExpressions,
50: $this->inClosureBindScopeClasses,
51: $this->getAnonymousFunctionReflection(),
52: $this->isInFirstLevelStatement(),
53: $this->currentlyAssignedExpressions,
54: $this->currentlyAllowedUndefinedExpressions,
55: $this->inFunctionCallsStack,
56: $this->afterExtractCall,
57: $this->getParentScope(),
58: $this->nativeTypesPromoted,
59: );
60: }
61:
62: /** @api */
63: public function getType(Expr $node): Type
64: {
65: $shouldCache = defined('__PHPSTAN_RUNNING__') && !$this->isInTrait() && count($this->truthyValueExprs) === 0 && count($this->falseyValueExprs) === 0 && !$this->nativeTypesPromoted;
66: if ($shouldCache) {
67: $cachedType = $node->getAttribute(self::EXPR_TYPE_ATTRIBUTE_NAME);
68: if ($cachedType !== null) {
69: return $cachedType;
70: }
71: }
72:
73: /** @var Scope $beforeScope */
74: $beforeScope = Fiber::suspend(
75: new BeforeScopeForExprRequest($node, $this),
76: );
77:
78: $scope = $this->preprocessScope($beforeScope->toMutatingScope());
79: $type = $scope->getType($node);
80:
81: if ($shouldCache) {
82: $node->setAttribute(self::EXPR_TYPE_ATTRIBUTE_NAME, $type);
83: }
84:
85: return $type;
86: }
87:
88: public function getScopeType(Expr $expr): Type
89: {
90: return $this->toMutatingScope()->getType($expr);
91: }
92:
93: public function getScopeNativeType(Expr $expr): Type
94: {
95: return $this->toMutatingScope()->getNativeType($expr);
96: }
97:
98: /** @api */
99: public function getNativeType(Expr $expr): Type
100: {
101: $shouldCache = defined('__PHPSTAN_RUNNING__') && !$this->isInTrait() && count($this->truthyValueExprs) === 0 && count($this->falseyValueExprs) === 0 && !$this->nativeTypesPromoted;
102: if ($shouldCache) {
103: $cachedType = $expr->getAttribute(self::EXPR_NATIVE_TYPE_ATTRIBUTE_NAME);
104: if ($cachedType !== null) {
105: return $cachedType;
106: }
107: }
108:
109: /** @var Scope $beforeScope */
110: $beforeScope = Fiber::suspend(
111: new BeforeScopeForExprRequest($expr, $this),
112: );
113:
114: $scope = $this->preprocessScope($beforeScope->toMutatingScope());
115: $type = $scope->getNativeType($expr);
116:
117: if ($shouldCache) {
118: $expr->setAttribute(self::EXPR_NATIVE_TYPE_ATTRIBUTE_NAME, $type);
119: }
120:
121: return $type;
122: }
123:
124: public function getKeepVoidType(Expr $node): Type
125: {
126: /** @var Scope $beforeScope */
127: $beforeScope = Fiber::suspend(
128: new BeforeScopeForExprRequest($node, $this),
129: );
130:
131: $scope = $this->preprocessScope($beforeScope->toMutatingScope());
132:
133: return $scope->getKeepVoidType($node);
134: }
135:
136: public function filterByTruthyValue(Expr $expr): self
137: {
138: /** @var self $scope */
139: $scope = parent::filterByTruthyValue($expr);
140: $scope->truthyValueExprs = $this->truthyValueExprs;
141: $scope->truthyValueExprs[] = $expr;
142:
143: return $scope;
144: }
145:
146: public function filterByFalseyValue(Expr $expr): self
147: {
148: /** @var self $scope */
149: $scope = parent::filterByTruthyValue($expr);
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: // no need to track this in rules, the type will be correct anyway
178: return $this;
179: }
180:
181: public function popInFunctionCall(): self
182: {
183: // no need to track this in rules, the type will be correct anyway
184: return $this;
185: }
186:
187: public function getParentScope(): ?MutatingScope
188: {
189: $parent = parent::getParentScope();
190: if ($parent === null) {
191: return null;
192: }
193:
194: return $parent->toFiberScope();
195: }
196:
197: }
198: