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