2016-05-09 14:56:07 +02:00
|
|
|
<?php
|
2021-05-05 07:22:29 +02:00
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
namespace Psalm\Internal\Analyzer;
|
2016-05-09 14:56:07 +02:00
|
|
|
|
|
|
|
use PhpParser;
|
2021-12-03 20:11:20 +01:00
|
|
|
use Psalm\Internal\Provider\NodeDataProvider;
|
|
|
|
use Psalm\NodeTypeProvider;
|
2021-05-05 07:22:29 +02:00
|
|
|
|
2021-06-08 04:55:21 +02:00
|
|
|
use function array_diff;
|
2021-05-05 07:22:29 +02:00
|
|
|
use function array_filter;
|
|
|
|
use function array_intersect;
|
|
|
|
use function array_unique;
|
|
|
|
use function array_values;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function count;
|
2021-05-05 07:22:29 +02:00
|
|
|
use function in_array;
|
2016-05-09 14:56:07 +02:00
|
|
|
|
2018-12-02 00:37:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2023-10-26 17:00:29 +02:00
|
|
|
final class ScopeAnalyzer
|
2016-05-09 14:56:07 +02:00
|
|
|
{
|
2020-09-20 18:54:46 +02:00
|
|
|
public const ACTION_END = 'END';
|
|
|
|
public const ACTION_BREAK = 'BREAK';
|
|
|
|
public const ACTION_CONTINUE = 'CONTINUE';
|
|
|
|
public const ACTION_LEAVE_SWITCH = 'LEAVE_SWITCH';
|
2022-12-04 13:37:29 +01:00
|
|
|
public const ACTION_LEAVE_LOOP = 'LEAVE_LOOP';
|
2020-09-20 18:54:46 +02:00
|
|
|
public const ACTION_NONE = 'NONE';
|
|
|
|
public const ACTION_RETURN = 'RETURN';
|
2017-11-28 06:25:21 +01:00
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
2020-12-29 12:42:12 +01:00
|
|
|
* @param array<PhpParser\Node> $stmts
|
|
|
|
* @param list<'loop'|'switch'> $break_types
|
|
|
|
* @param bool $return_is_exit Exit and Throw statements are treated differently from return if this is false
|
|
|
|
* @return list<self::ACTION_*>
|
2021-05-17 19:35:15 +02:00
|
|
|
* @psalm-suppress ComplexMethod nothing much we can do
|
2016-11-02 07:29:00 +01:00
|
|
|
*/
|
2020-10-15 16:15:55 +02:00
|
|
|
public static function getControlActions(
|
2018-02-23 21:39:33 +01:00
|
|
|
array $stmts,
|
2021-12-03 20:11:20 +01:00
|
|
|
?NodeDataProvider $nodes,
|
2021-05-17 14:27:24 +02:00
|
|
|
array $break_types,
|
2020-10-12 21:46:47 +02:00
|
|
|
bool $return_is_exit = true
|
2020-10-12 21:02:52 +02:00
|
|
|
): array {
|
2016-06-20 22:18:31 +02:00
|
|
|
if (empty($stmts)) {
|
2018-01-03 03:23:48 +01:00
|
|
|
return [self::ACTION_NONE];
|
2016-06-20 22:18:31 +02:00
|
|
|
}
|
|
|
|
|
2017-11-28 06:25:21 +01:00
|
|
|
$control_actions = [];
|
|
|
|
|
2021-09-25 02:34:21 +02:00
|
|
|
foreach ($stmts as $stmt) {
|
2016-06-21 00:10:24 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Return_ ||
|
|
|
|
$stmt instanceof PhpParser\Node\Stmt\Throw_ ||
|
2018-04-17 18:16:25 +02:00
|
|
|
($stmt instanceof PhpParser\Node\Stmt\Expression && $stmt->expr instanceof PhpParser\Node\Expr\Exit_)
|
2016-06-21 00:10:24 +02:00
|
|
|
) {
|
2018-02-23 21:39:33 +01:00
|
|
|
if (!$return_is_exit && $stmt instanceof PhpParser\Node\Stmt\Return_) {
|
2022-01-06 21:12:51 +01:00
|
|
|
$stmt_return_type = null;
|
|
|
|
if ($nodes && $stmt->expr) {
|
|
|
|
$stmt_return_type = $nodes->getType($stmt->expr);
|
|
|
|
}
|
|
|
|
|
|
|
|
// don't consider a return if the expression never returns (e.g. a throw inside a short closure)
|
2022-01-14 21:13:34 +01:00
|
|
|
if ($stmt_return_type && $stmt_return_type->isNever()) {
|
2022-07-30 00:17:21 +02:00
|
|
|
return array_values(array_unique([...$control_actions, ...[self::ACTION_END]]));
|
2022-01-06 21:12:51 +01:00
|
|
|
}
|
|
|
|
|
2022-07-30 00:17:21 +02:00
|
|
|
return array_values(array_unique([...$control_actions, ...[self::ACTION_RETURN]]));
|
2018-02-23 21:39:33 +01:00
|
|
|
}
|
|
|
|
|
2022-07-30 00:17:21 +02:00
|
|
|
return array_values(array_unique([...$control_actions, ...[self::ACTION_END]]));
|
2017-11-28 06:25:21 +01:00
|
|
|
}
|
|
|
|
|
2018-07-13 05:26:08 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Expression) {
|
2019-01-02 23:16:04 +01:00
|
|
|
// This allows calls to functions that always exit to act as exit statements themselves
|
2019-11-25 17:44:54 +01:00
|
|
|
if ($nodes
|
|
|
|
&& ($stmt_expr_type = $nodes->getType($stmt->expr))
|
|
|
|
&& $stmt_expr_type->isNever()
|
2019-01-02 23:16:04 +01:00
|
|
|
) {
|
2022-07-30 00:17:21 +02:00
|
|
|
return array_values(array_unique([...$control_actions, ...[self::ACTION_END]]));
|
2019-01-02 23:16:04 +01:00
|
|
|
}
|
|
|
|
|
2018-07-13 05:26:08 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-11-28 06:25:21 +01:00
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Continue_) {
|
2021-05-17 15:26:14 +02:00
|
|
|
$count = !$stmt->num
|
|
|
|
? 1
|
|
|
|
: ($stmt->num instanceof PhpParser\Node\Scalar\LNumber ? $stmt->num->value : null);
|
|
|
|
|
|
|
|
if ($break_types && $count !== null && count($break_types) >= $count) {
|
2022-12-13 21:40:19 +01:00
|
|
|
/** @psalm-suppress InvalidArrayOffset Some int-range improvements are needed */
|
2021-05-17 15:26:14 +02:00
|
|
|
if ($break_types[count($break_types) - $count] === 'switch') {
|
2022-07-30 00:17:21 +02:00
|
|
|
return [...$control_actions, ...[self::ACTION_LEAVE_SWITCH]];
|
2021-05-17 14:27:24 +02:00
|
|
|
}
|
|
|
|
|
2021-05-17 15:26:14 +02:00
|
|
|
return array_values($control_actions);
|
2018-01-24 06:01:08 +01:00
|
|
|
}
|
|
|
|
|
2022-07-30 00:17:21 +02:00
|
|
|
return array_values(array_unique([...$control_actions, ...[self::ACTION_CONTINUE]]));
|
2017-11-28 06:25:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Break_) {
|
2021-05-17 15:26:14 +02:00
|
|
|
$count = !$stmt->num
|
|
|
|
? 1
|
|
|
|
: ($stmt->num instanceof PhpParser\Node\Scalar\LNumber ? $stmt->num->value : null);
|
|
|
|
|
|
|
|
if ($break_types && $count !== null && count($break_types) >= $count) {
|
2022-12-13 21:40:19 +01:00
|
|
|
/** @psalm-suppress InvalidArrayOffset Some int-range improvements are needed */
|
2021-05-17 15:26:14 +02:00
|
|
|
if ($break_types[count($break_types) - $count] === 'switch') {
|
2022-07-30 00:17:21 +02:00
|
|
|
return [...$control_actions, ...[self::ACTION_LEAVE_SWITCH]];
|
2021-05-17 14:27:24 +02:00
|
|
|
}
|
|
|
|
|
2022-12-13 21:40:19 +01:00
|
|
|
/** @psalm-suppress InvalidArrayOffset Some int-range improvements are needed */
|
2022-12-04 13:37:29 +01:00
|
|
|
if ($break_types[count($break_types) - $count] === 'loop') {
|
|
|
|
return [...$control_actions, ...[self::ACTION_LEAVE_LOOP]];
|
|
|
|
}
|
|
|
|
|
2021-05-17 15:26:14 +02:00
|
|
|
return array_values($control_actions);
|
2018-06-17 02:01:33 +02:00
|
|
|
}
|
|
|
|
|
2022-07-30 00:17:21 +02:00
|
|
|
return array_values(array_unique([...$control_actions, ...[self::ACTION_BREAK]]));
|
2016-06-20 22:18:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\If_) {
|
2020-10-15 16:15:55 +02:00
|
|
|
$if_statement_actions = self::getControlActions(
|
2020-01-27 18:17:12 +01:00
|
|
|
$stmt->stmts,
|
|
|
|
$nodes,
|
2021-05-05 07:22:29 +02:00
|
|
|
$break_types,
|
2022-12-18 17:15:15 +01:00
|
|
|
$return_is_exit,
|
2020-01-27 18:17:12 +01:00
|
|
|
);
|
|
|
|
|
2021-05-18 00:52:25 +02:00
|
|
|
$all_leave = !array_filter(
|
|
|
|
$if_statement_actions,
|
2022-12-18 17:15:15 +01:00
|
|
|
static fn(string $action): bool => $action === self::ACTION_NONE,
|
2021-05-18 00:52:25 +02:00
|
|
|
);
|
|
|
|
|
2018-01-24 06:01:08 +01:00
|
|
|
$else_statement_actions = $stmt->else
|
2021-05-05 07:22:29 +02:00
|
|
|
? self::getControlActions(
|
|
|
|
$stmt->else->stmts,
|
|
|
|
$nodes,
|
|
|
|
$break_types,
|
2022-12-18 17:15:15 +01:00
|
|
|
$return_is_exit,
|
2021-05-05 07:22:29 +02:00
|
|
|
) : [];
|
2016-06-20 22:18:31 +02:00
|
|
|
|
2021-05-18 00:52:25 +02:00
|
|
|
$all_leave = $all_leave
|
|
|
|
&& $else_statement_actions
|
|
|
|
&& !array_filter(
|
|
|
|
$else_statement_actions,
|
2022-12-18 17:15:15 +01:00
|
|
|
static fn(string $action): bool => $action === self::ACTION_NONE,
|
2021-05-18 00:52:25 +02:00
|
|
|
);
|
2017-11-28 06:25:21 +01:00
|
|
|
|
|
|
|
$all_elseif_actions = [];
|
|
|
|
|
|
|
|
if ($stmt->elseifs) {
|
2016-06-20 22:18:31 +02:00
|
|
|
foreach ($stmt->elseifs as $elseif) {
|
2020-10-15 16:15:55 +02:00
|
|
|
$elseif_control_actions = self::getControlActions(
|
2018-07-13 05:26:08 +02:00
|
|
|
$elseif->stmts,
|
2019-11-25 17:44:54 +01:00
|
|
|
$nodes,
|
2021-05-05 07:22:29 +02:00
|
|
|
$break_types,
|
2022-12-18 17:15:15 +01:00
|
|
|
$return_is_exit,
|
2018-07-13 05:26:08 +02:00
|
|
|
);
|
2017-11-28 06:25:21 +01:00
|
|
|
|
2021-05-18 00:52:25 +02:00
|
|
|
$all_leave = $all_leave
|
|
|
|
&& !array_filter(
|
|
|
|
$elseif_control_actions,
|
2022-12-18 17:15:15 +01:00
|
|
|
static fn(string $action): bool => $action === self::ACTION_NONE,
|
2021-05-18 00:52:25 +02:00
|
|
|
);
|
2017-11-28 06:25:21 +01:00
|
|
|
|
2022-07-30 00:17:21 +02:00
|
|
|
$all_elseif_actions = [...$elseif_control_actions, ...$all_elseif_actions];
|
2016-06-20 22:18:31 +02:00
|
|
|
}
|
2017-11-28 06:25:21 +01:00
|
|
|
}
|
2016-06-20 22:18:31 +02:00
|
|
|
|
2021-05-18 00:52:25 +02:00
|
|
|
if ($all_leave) {
|
|
|
|
return array_values(
|
2022-07-30 00:17:21 +02:00
|
|
|
array_unique([
|
|
|
|
...$control_actions,
|
|
|
|
...$if_statement_actions,
|
|
|
|
...$else_statement_actions,
|
2022-12-18 17:15:15 +01:00
|
|
|
...$all_elseif_actions,
|
|
|
|
]),
|
2021-05-18 00:52:25 +02:00
|
|
|
);
|
2016-06-20 22:18:31 +02:00
|
|
|
}
|
2017-11-28 06:25:21 +01:00
|
|
|
|
2020-09-21 17:18:30 +02:00
|
|
|
$control_actions = array_filter(
|
2022-07-30 00:17:21 +02:00
|
|
|
[...$control_actions, ...$if_statement_actions, ...$else_statement_actions, ...$all_elseif_actions],
|
2022-12-18 17:15:15 +01:00
|
|
|
static fn(string $action): bool => $action !== self::ACTION_NONE,
|
2017-11-28 06:25:21 +01:00
|
|
|
);
|
2016-06-20 22:18:31 +02:00
|
|
|
}
|
|
|
|
|
2016-06-28 19:56:44 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Switch_) {
|
2018-02-23 21:39:33 +01:00
|
|
|
$has_ended = false;
|
2018-01-24 22:15:53 +01:00
|
|
|
$has_non_breaking_default = false;
|
2016-06-28 20:28:45 +02:00
|
|
|
$has_default_terminator = false;
|
2016-06-28 19:56:44 +02:00
|
|
|
|
2021-05-18 14:52:41 +02:00
|
|
|
$all_case_actions = [];
|
|
|
|
|
2016-06-28 19:56:44 +02:00
|
|
|
// iterate backwards in a case statement
|
2017-11-28 06:25:21 +01:00
|
|
|
for ($d = count($stmt->cases) - 1; $d >= 0; --$d) {
|
|
|
|
$case = $stmt->cases[$d];
|
2016-06-20 22:18:31 +02:00
|
|
|
|
2021-05-05 07:22:29 +02:00
|
|
|
$case_actions = self::getControlActions(
|
|
|
|
$case->stmts,
|
|
|
|
$nodes,
|
2022-07-30 00:17:21 +02:00
|
|
|
[...$break_types, ...['switch']],
|
2022-12-18 17:15:15 +01:00
|
|
|
$return_is_exit,
|
2021-05-05 07:22:29 +02:00
|
|
|
);
|
2017-11-28 06:25:21 +01:00
|
|
|
|
2018-06-17 02:01:33 +02:00
|
|
|
if (array_intersect([
|
|
|
|
self::ACTION_LEAVE_SWITCH,
|
|
|
|
self::ACTION_BREAK,
|
2022-12-18 17:15:15 +01:00
|
|
|
self::ACTION_CONTINUE,
|
2018-06-17 02:01:33 +02:00
|
|
|
], $case_actions)
|
|
|
|
) {
|
2017-11-28 06:25:21 +01:00
|
|
|
continue 2;
|
2016-06-20 22:18:31 +02:00
|
|
|
}
|
|
|
|
|
2018-01-24 22:15:53 +01:00
|
|
|
if (!$case->cond) {
|
|
|
|
$has_non_breaking_default = true;
|
|
|
|
}
|
|
|
|
|
2021-05-18 00:52:25 +02:00
|
|
|
$case_does_end = !array_diff(
|
|
|
|
$control_actions,
|
2022-12-18 17:15:15 +01:00
|
|
|
[self::ACTION_END, self::ACTION_RETURN],
|
2021-05-18 00:52:25 +02:00
|
|
|
);
|
2016-06-28 19:56:44 +02:00
|
|
|
|
2018-02-23 21:39:33 +01:00
|
|
|
if ($case_does_end) {
|
|
|
|
$has_ended = true;
|
2016-06-20 22:18:31 +02:00
|
|
|
}
|
|
|
|
|
2022-07-30 00:17:21 +02:00
|
|
|
$all_case_actions = [...$all_case_actions, ...$case_actions];
|
2021-05-18 14:52:41 +02:00
|
|
|
|
2018-02-23 21:39:33 +01:00
|
|
|
if (!$case_does_end && !$has_ended) {
|
2017-11-28 06:25:21 +01:00
|
|
|
continue 2;
|
2016-06-20 22:18:31 +02:00
|
|
|
}
|
|
|
|
|
2018-02-23 21:39:33 +01:00
|
|
|
if ($has_non_breaking_default && $case_does_end) {
|
2016-06-28 19:56:44 +02:00
|
|
|
$has_default_terminator = true;
|
|
|
|
}
|
2016-06-20 22:18:31 +02:00
|
|
|
}
|
|
|
|
|
2021-06-09 15:06:23 +02:00
|
|
|
$all_case_actions = array_filter(
|
|
|
|
$all_case_actions,
|
2022-12-18 17:15:15 +01:00
|
|
|
static fn(string $action): bool => $action !== self::ACTION_NONE,
|
2021-06-09 15:06:23 +02:00
|
|
|
);
|
2021-05-18 14:52:41 +02:00
|
|
|
|
2021-10-13 13:27:56 +02:00
|
|
|
if ($has_default_terminator || $stmt->getAttribute('allMatched', false)) {
|
2022-07-30 00:17:21 +02:00
|
|
|
return array_values(array_unique([...$control_actions, ...$all_case_actions]));
|
2017-11-28 06:25:21 +01:00
|
|
|
}
|
2021-06-09 15:06:23 +02:00
|
|
|
|
2022-07-30 00:17:21 +02:00
|
|
|
$control_actions = [...$control_actions, ...$all_case_actions];
|
2016-06-20 22:18:31 +02:00
|
|
|
}
|
|
|
|
|
2019-09-29 22:12:52 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Do_
|
|
|
|
|| $stmt instanceof PhpParser\Node\Stmt\While_
|
2020-01-27 18:17:12 +01:00
|
|
|
|| $stmt instanceof PhpParser\Node\Stmt\Foreach_
|
|
|
|
|| $stmt instanceof PhpParser\Node\Stmt\For_
|
2019-09-29 22:12:52 +02:00
|
|
|
) {
|
2021-05-17 14:27:24 +02:00
|
|
|
$loop_actions = self::getControlActions(
|
2020-01-27 18:17:12 +01:00
|
|
|
$stmt->stmts,
|
|
|
|
$nodes,
|
2022-07-30 00:17:21 +02:00
|
|
|
[...$break_types, ...['loop']],
|
2022-12-18 17:15:15 +01:00
|
|
|
$return_is_exit,
|
2020-01-27 18:17:12 +01:00
|
|
|
);
|
2017-11-28 06:25:21 +01:00
|
|
|
|
2020-09-21 17:18:30 +02:00
|
|
|
$control_actions = array_filter(
|
2022-07-30 00:17:21 +02:00
|
|
|
[...$control_actions, ...$loop_actions],
|
2022-12-18 17:15:15 +01:00
|
|
|
static fn(string $action): bool => $action !== self::ACTION_NONE,
|
2020-09-21 17:18:30 +02:00
|
|
|
);
|
2021-09-02 21:47:10 +02:00
|
|
|
|
2022-12-18 18:19:22 +01:00
|
|
|
if (($stmt instanceof PhpParser\Node\Stmt\While_
|
|
|
|
|| $stmt instanceof PhpParser\Node\Stmt\Do_)
|
2021-09-02 21:47:10 +02:00
|
|
|
&& $nodes
|
|
|
|
&& ($stmt_expr_type = $nodes->getType($stmt->cond))
|
2021-09-04 14:08:04 +02:00
|
|
|
&& $stmt_expr_type->isAlwaysTruthy()
|
2022-12-04 13:37:29 +01:00
|
|
|
&& !in_array(self::ACTION_LEAVE_LOOP, $control_actions, true)
|
2021-09-02 21:47:10 +02:00
|
|
|
) {
|
|
|
|
//infinite while loop that only return don't have an exit path
|
|
|
|
$have_exit_path = (bool)array_diff(
|
|
|
|
$control_actions,
|
2022-12-18 17:15:15 +01:00
|
|
|
[self::ACTION_END, self::ACTION_RETURN],
|
2021-09-02 21:47:10 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
if (!$have_exit_path) {
|
|
|
|
return array_values(array_unique($control_actions));
|
|
|
|
}
|
|
|
|
}
|
2021-10-10 10:22:41 +02:00
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\For_
|
|
|
|
&& $nodes
|
2022-12-04 13:37:29 +01:00
|
|
|
&& !in_array(self::ACTION_LEAVE_LOOP, $control_actions, true)
|
2021-10-10 10:22:41 +02:00
|
|
|
) {
|
2021-10-10 10:27:22 +02:00
|
|
|
$is_infinite_loop = true;
|
|
|
|
if ($stmt->cond) {
|
|
|
|
foreach ($stmt->cond as $cond) {
|
|
|
|
$stmt_expr_type = $nodes->getType($cond);
|
|
|
|
if (!$stmt_expr_type || !$stmt_expr_type->isAlwaysTruthy()) {
|
|
|
|
$is_infinite_loop = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-10-10 10:22:41 +02:00
|
|
|
|
2021-10-10 10:27:22 +02:00
|
|
|
if ($is_infinite_loop) {
|
|
|
|
//infinite while loop that only return don't have an exit path
|
|
|
|
$have_exit_path = (bool)array_diff(
|
|
|
|
$control_actions,
|
2022-12-18 17:15:15 +01:00
|
|
|
[self::ACTION_END, self::ACTION_RETURN],
|
2021-10-10 10:27:22 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
if (!$have_exit_path) {
|
|
|
|
return array_values(array_unique($control_actions));
|
|
|
|
}
|
2021-10-10 10:22:41 +02:00
|
|
|
}
|
|
|
|
}
|
2022-12-04 13:37:29 +01:00
|
|
|
|
|
|
|
$control_actions = array_filter(
|
|
|
|
$control_actions,
|
2022-12-18 17:15:15 +01:00
|
|
|
static fn(string $action): bool => $action !== self::ACTION_LEAVE_LOOP,
|
2022-12-04 13:37:29 +01:00
|
|
|
);
|
2016-06-21 00:10:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\TryCatch) {
|
2020-10-15 16:15:55 +02:00
|
|
|
$try_statement_actions = self::getControlActions(
|
2019-11-25 17:44:54 +01:00
|
|
|
$stmt->stmts,
|
|
|
|
$nodes,
|
2021-05-05 07:22:29 +02:00
|
|
|
$break_types,
|
2022-12-18 17:15:15 +01:00
|
|
|
$return_is_exit,
|
2019-11-25 17:44:54 +01:00
|
|
|
);
|
2017-11-28 06:25:21 +01:00
|
|
|
|
2021-05-18 00:52:25 +02:00
|
|
|
$try_leaves = !array_filter(
|
|
|
|
$try_statement_actions,
|
2022-12-18 17:15:15 +01:00
|
|
|
static fn(string $action): bool => $action === self::ACTION_NONE,
|
2021-05-18 00:52:25 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
$all_catch_actions = [];
|
|
|
|
|
2017-11-28 06:25:21 +01:00
|
|
|
if ($stmt->catches) {
|
2021-05-18 00:52:25 +02:00
|
|
|
$all_leave = $try_leaves;
|
2017-11-28 06:25:21 +01:00
|
|
|
|
2016-06-21 00:10:24 +02:00
|
|
|
foreach ($stmt->catches as $catch) {
|
2020-10-15 16:15:55 +02:00
|
|
|
$catch_actions = self::getControlActions(
|
2019-11-25 17:44:54 +01:00
|
|
|
$catch->stmts,
|
|
|
|
$nodes,
|
2021-05-05 07:22:29 +02:00
|
|
|
$break_types,
|
2022-12-18 17:15:15 +01:00
|
|
|
$return_is_exit,
|
2019-11-25 17:44:54 +01:00
|
|
|
);
|
2017-11-28 06:25:21 +01:00
|
|
|
|
2021-05-18 00:52:25 +02:00
|
|
|
$all_leave = $all_leave
|
|
|
|
&& !array_filter(
|
|
|
|
$catch_actions,
|
2022-12-18 17:15:15 +01:00
|
|
|
static fn(string $action): bool => $action === self::ACTION_NONE,
|
2021-05-18 00:52:25 +02:00
|
|
|
);
|
2017-11-28 06:25:21 +01:00
|
|
|
|
2021-05-18 00:52:25 +02:00
|
|
|
if (!$all_leave) {
|
2022-07-30 00:17:21 +02:00
|
|
|
$control_actions = [...$control_actions, ...$catch_actions];
|
2021-05-18 00:52:25 +02:00
|
|
|
} else {
|
2022-07-30 00:17:21 +02:00
|
|
|
$all_catch_actions = [...$all_catch_actions, ...$catch_actions];
|
2016-06-21 00:10:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-18 00:52:25 +02:00
|
|
|
if ($all_leave && $try_statement_actions !== [self::ACTION_NONE]) {
|
|
|
|
return array_values(
|
|
|
|
array_unique(
|
2022-12-18 17:15:15 +01:00
|
|
|
[...$control_actions, ...$try_statement_actions, ...$all_catch_actions],
|
|
|
|
),
|
2021-05-18 00:52:25 +02:00
|
|
|
);
|
2017-11-28 06:25:21 +01:00
|
|
|
}
|
2021-05-18 00:52:25 +02:00
|
|
|
} elseif ($try_leaves) {
|
2022-07-30 00:17:21 +02:00
|
|
|
return array_values(array_unique([...$control_actions, ...$try_statement_actions]));
|
2016-06-21 00:10:24 +02:00
|
|
|
}
|
|
|
|
|
2021-12-08 05:42:17 +01:00
|
|
|
if ($stmt->finally && $stmt->finally->stmts) {
|
|
|
|
$finally_statement_actions = self::getControlActions(
|
|
|
|
$stmt->finally->stmts,
|
|
|
|
$nodes,
|
|
|
|
$break_types,
|
2022-12-18 17:15:15 +01:00
|
|
|
$return_is_exit,
|
2021-12-08 05:42:17 +01:00
|
|
|
);
|
2018-01-29 01:02:31 +01:00
|
|
|
|
2021-12-08 05:42:17 +01:00
|
|
|
if (!in_array(self::ACTION_NONE, $finally_statement_actions, true)) {
|
2022-07-30 00:17:21 +02:00
|
|
|
return [...array_filter(
|
|
|
|
$control_actions,
|
2022-12-18 17:15:15 +01:00
|
|
|
static fn(string $action): bool => $action !== self::ACTION_NONE,
|
2022-07-30 00:17:21 +02:00
|
|
|
), ...$finally_statement_actions];
|
2018-01-29 01:02:31 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-21 17:18:30 +02:00
|
|
|
$control_actions = array_filter(
|
2022-07-30 00:17:21 +02:00
|
|
|
[...$control_actions, ...$try_statement_actions],
|
2022-12-18 17:15:15 +01:00
|
|
|
static fn(string $action): bool => $action !== self::ACTION_NONE,
|
2020-09-21 17:18:30 +02:00
|
|
|
);
|
2016-06-20 22:18:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-02 22:57:40 +01:00
|
|
|
$control_actions[] = self::ACTION_NONE;
|
2017-11-28 06:25:21 +01:00
|
|
|
|
2021-05-05 07:22:29 +02:00
|
|
|
return array_values(array_unique($control_actions));
|
2016-06-20 22:18:31 +02:00
|
|
|
}
|
|
|
|
|
2016-11-02 07:29:00 +01:00
|
|
|
/**
|
2016-12-17 06:48:31 +01:00
|
|
|
* @param array<PhpParser\Node> $stmts
|
2016-11-02 07:29:00 +01:00
|
|
|
*/
|
2021-12-03 20:11:20 +01:00
|
|
|
public static function onlyThrowsOrExits(NodeTypeProvider $type_provider, array $stmts): bool
|
2016-06-17 00:52:12 +02:00
|
|
|
{
|
|
|
|
if (empty($stmts)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-05-27 02:05:57 +02:00
|
|
|
for ($i = count($stmts) - 1; $i >= 0; --$i) {
|
2016-06-17 00:52:12 +02:00
|
|
|
$stmt = $stmts[$i];
|
|
|
|
|
2019-05-02 05:45:03 +02:00
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Throw_
|
|
|
|
|| ($stmt instanceof PhpParser\Node\Stmt\Expression
|
|
|
|
&& $stmt->expr instanceof PhpParser\Node\Expr\Exit_)
|
|
|
|
) {
|
2016-06-17 00:52:12 +02:00
|
|
|
return true;
|
|
|
|
}
|
2020-05-26 21:02:23 +02:00
|
|
|
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Expression) {
|
|
|
|
$stmt_type = $type_provider->getType($stmt->expr);
|
|
|
|
|
|
|
|
if ($stmt_type && $stmt_type->isNever()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2016-06-17 00:52:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2022-10-27 11:56:29 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param array<PhpParser\Node> $stmts
|
|
|
|
*/
|
|
|
|
public static function onlyThrows(array $stmts): bool
|
|
|
|
{
|
|
|
|
$stmts_count = count($stmts);
|
|
|
|
if ($stmts_count !== 1) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($stmts as $stmt) {
|
|
|
|
if ($stmt instanceof PhpParser\Node\Stmt\Throw_) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2016-05-09 14:56:07 +02:00
|
|
|
}
|