1: <?php declare(strict_types = 1);
2:
3: namespace PHPStan\Analyser;
4:
5: use PhpParser\Node\Scalar\Int_;
6: use PhpParser\Node\Stmt;
7: use PHPStan\Turbo\ShadowedByTurboExtension;
8:
9: /**
10: * @api
11: */
12: #[ShadowedByTurboExtension(implementation: __DIR__ . '/../../turbo-ext/src/StatementResult.cpp')]
13: final class StatementResult
14: {
15:
16: /**
17: * @param StatementExitPoint[] $exitPoints
18: * @param ThrowPoint[] $throwPoints
19: * @param ImpurePoint[] $impurePoints
20: * @param EndStatementResult[] $endStatements
21: */
22: public function __construct(
23: private Scope $scope,
24: private bool $hasYield,
25: private bool $isAlwaysTerminating,
26: private array $exitPoints,
27: private array $throwPoints,
28: private array $impurePoints,
29: private array $endStatements = [],
30: )
31: {
32: }
33:
34: public function getScope(): Scope
35: {
36: return $this->scope;
37: }
38:
39: public function hasYield(): bool
40: {
41: return $this->hasYield;
42: }
43:
44: public function isAlwaysTerminating(): bool
45: {
46: return $this->isAlwaysTerminating;
47: }
48:
49: public function filterOutLoopExitPoints(): self
50: {
51: if (!$this->isAlwaysTerminating) {
52: return $this;
53: }
54:
55: foreach ($this->exitPoints as $exitPoint) {
56: $statement = $exitPoint->getStatement();
57: if (!$statement instanceof Stmt\Break_ && !$statement instanceof Stmt\Continue_) {
58: continue;
59: }
60:
61: $num = $statement->num;
62: if (!$num instanceof Int_) {
63: return new self($this->scope, $this->hasYield, false, $this->exitPoints, $this->throwPoints, $this->impurePoints);
64: }
65:
66: if ($num->value !== 1) {
67: continue;
68: }
69:
70: return new self($this->scope, $this->hasYield, false, $this->exitPoints, $this->throwPoints, $this->impurePoints);
71: }
72:
73: return $this;
74: }
75:
76: /**
77: * @return StatementExitPoint[]
78: */
79: public function getExitPoints(): array
80: {
81: return $this->exitPoints;
82: }
83:
84: /**
85: * @param class-string<Stmt\Continue_>|class-string<Stmt\Break_> $stmtClass
86: * @return list<StatementExitPoint>
87: */
88: public function getExitPointsByType(string $stmtClass): array
89: {
90: $exitPoints = [];
91: foreach ($this->exitPoints as $exitPoint) {
92: $statement = $exitPoint->getStatement();
93: if (!$statement instanceof $stmtClass) {
94: continue;
95: }
96:
97: $value = $statement->num;
98: if ($value === null) {
99: $exitPoints[] = $exitPoint;
100: continue;
101: }
102:
103: if (!$value instanceof Int_) {
104: $exitPoints[] = $exitPoint;
105: continue;
106: }
107:
108: $value = $value->value;
109: if ($value !== 1) {
110: continue;
111: }
112:
113: $exitPoints[] = $exitPoint;
114: }
115:
116: return $exitPoints;
117: }
118:
119: /**
120: * @return list<StatementExitPoint>
121: */
122: public function getExitPointsForOuterLoop(): array
123: {
124: $exitPoints = [];
125: foreach ($this->exitPoints as $exitPoint) {
126: $statement = $exitPoint->getStatement();
127: if (!$statement instanceof Stmt\Continue_ && !$statement instanceof Stmt\Break_) {
128: $exitPoints[] = $exitPoint;
129: continue;
130: }
131: if ($statement->num === null) {
132: continue;
133: }
134: if (!$statement->num instanceof Int_) {
135: continue;
136: }
137: $value = $statement->num->value;
138: if ($value === 1) {
139: continue;
140: }
141:
142: $newNode = null;
143: if ($value > 2) {
144: $newNode = new Int_($value - 1);
145: }
146: if ($statement instanceof Stmt\Continue_) {
147: $newStatement = new Stmt\Continue_($newNode);
148: } else {
149: $newStatement = new Stmt\Break_($newNode);
150: }
151:
152: $exitPoints[] = new StatementExitPoint($newStatement, $exitPoint->getScope());
153: }
154:
155: return $exitPoints;
156: }
157:
158: /**
159: * @return ThrowPoint[]
160: */
161: public function getThrowPoints(): array
162: {
163: return $this->throwPoints;
164: }
165:
166: /**
167: * @return ImpurePoint[]
168: */
169: public function getImpurePoints(): array
170: {
171: return $this->impurePoints;
172: }
173:
174: /**
175: * Top-level StatementResult represents the state of the code
176: * at the end of control flow statements like If_ or TryCatch.
177: *
178: * It shows how Scope etc. looks like after If_ no matter
179: * which code branch was executed.
180: *
181: * For If_, "end statements" contain the state of the code
182: * at the end of each branch - if, elseifs, else, including the last
183: * statement node in each branch.
184: *
185: * For nested ifs, end statements try to contain the last non-control flow
186: * statement like Return_ or Throw_, instead of If_, TryCatch, or Foreach_.
187: *
188: * @return EndStatementResult[]
189: */
190: public function getEndStatements(): array
191: {
192: return $this->endStatements;
193: }
194:
195: }
196: