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