Skip to content

Commit 9f377bf

Browse files
committed
Separate StatementResult objects that typehint internal Scope implementation
1 parent 1789466 commit 9f377bf

10 files changed

+483
-125
lines changed

src/Analyser/EndStatementResult.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
use PhpParser\Node\Stmt;
66

7+
/**
8+
* @api
9+
*/
710
final class EndStatementResult
811
{
912

src/Analyser/ExpressionResult.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ final class ExpressionResult
1616
private ?MutatingScope $falseyScope = null;
1717

1818
/**
19-
* @param ThrowPoint[] $throwPoints
19+
* @param InternalThrowPoint[] $throwPoints
2020
* @param ImpurePoint[] $impurePoints
2121
* @param (callable(): MutatingScope)|null $truthyScopeCallback
2222
* @param (callable(): MutatingScope)|null $falseyScopeCallback
@@ -46,7 +46,7 @@ public function hasYield(): bool
4646
}
4747

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

src/Analyser/MutatingScope.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1430,7 +1430,7 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu
14301430
},
14311431
ExpressionContext::createDeep(),
14321432
);
1433-
$throwPoints = $arrowFunctionExprResult->getThrowPoints();
1433+
$throwPoints = array_map(static fn ($throwPoint) => $throwPoint->toPublic(), $arrowFunctionExprResult->getThrowPoints());
14341434
$impurePoints = array_merge($arrowFunctionImpurePoints, $arrowFunctionExprResult->getImpurePoints());
14351435
$usedVariables = [];
14361436
} else {

0 commit comments

Comments
 (0)