Skip to content

Commit a57e75d

Browse files
committed
Merge remote-tracking branch 'origin/2.1.x' into 2.2.x
2 parents c86f75a + 678e790 commit a57e75d

42 files changed

Lines changed: 1543 additions & 4 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎Makefile‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ lint:
4242
--exclude tests/PHPStan/Levels/data/namedArguments.php \
4343
--exclude tests/PHPStan/Rules/Keywords/data/continue-break.php \
4444
--exclude tests/PHPStan/Rules/Keywords/data/continue-break-property-hook.php \
45+
--exclude tests/PHPStan/Rules/Keywords/data/goto-undefined-label.php \
46+
--exclude tests/PHPStan/Rules/Keywords/data/goto-undefined-label-property-hook.php \
47+
--exclude tests/PHPStan/Rules/Keywords/data/unused-label-property-hook.php \
4548
--exclude tests/PHPStan/Rules/Keywords/data/bug-13790-break.php \
4649
--exclude tests/PHPStan/Rules/Keywords/data/bug-13790-continue.php \
4750
--exclude tests/PHPStan/Rules/Properties/data/invalid-callable-property-type.php \

‎conf/bleedingEdge.neon‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ parameters:
1919
checkDateIntervalConstructor: true
2020
reportMethodPurityOverride: true
2121
checkDynamicConstantNameValues: true
22+
unusedLabel: true

‎conf/config.level4.neon‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ conditionalTags:
1212
phpstan.rules.rule: %exceptions.check.tooWideThrowType%
1313
PHPStan\Rules\Exceptions\TooWidePropertyHookThrowTypeRule:
1414
phpstan.rules.rule: %exceptions.check.tooWideThrowType%
15+
PHPStan\Rules\Keywords\UnusedLabelRule:
16+
phpstan.rules.rule: %featureToggles.unusedLabel%
1517

1618
parameters:
1719
checkAdvancedIsset: true
@@ -30,3 +32,6 @@ services:
3032
class: PHPStan\Rules\Exceptions\TooWidePropertyHookThrowTypeRule
3133
arguments:
3234
checkProtectedAndPublicMethods: %checkTooWideThrowTypesInProtectedAndPublicMethods%
35+
36+
-
37+
class: PHPStan\Rules\Keywords\UnusedLabelRule

‎conf/config.neon‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ parameters:
4646
checkDateIntervalConstructor: false
4747
reportMethodPurityOverride: false
4848
checkDynamicConstantNameValues: false
49+
unusedLabel: false
4950
fileExtensions:
5051
- php
5152
checkAdvancedIsset: false

‎conf/parametersSchema.neon‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ parametersSchema:
4848
checkDateIntervalConstructor: bool()
4949
reportMethodPurityOverride: bool()
5050
checkDynamicConstantNameValues: bool()
51+
unusedLabel: bool()
5152
])
5253
fileExtensions: listOf(string())
5354
checkAdvancedIsset: bool()

‎src/Analyser/MutatingScope.php‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3303,6 +3303,7 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
33033303
}
33043304

33053305
$conditions = [];
3306+
$originallySpecifiedExprStrings = $specifiedExpressions;
33063307
$prevSpecifiedCount = -1;
33073308
while (count($specifiedExpressions) !== $prevSpecifiedCount) {
33083309
$prevSpecifiedCount = count($specifiedExpressions);
@@ -3313,6 +3314,12 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
33133314

33143315
// Pass 1: Prefer exact matches
33153316
foreach ($conditionalExpressions as $conditionalExpression) {
3317+
if (
3318+
$conditionalExpression->getTypeHolder()->getCertainty()->no()
3319+
&& array_key_exists($conditionalExprString, $originallySpecifiedExprStrings)
3320+
) {
3321+
continue;
3322+
}
33163323
foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) {
33173324
if (
33183325
!array_key_exists($holderExprString, $specifiedExpressions)

‎src/Analyser/NodeScopeResolver.php‎

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
use PhpParser\Node\Stmt\Echo_;
3939
use PhpParser\Node\Stmt\For_;
4040
use PhpParser\Node\Stmt\Foreach_;
41+
use PhpParser\Node\Stmt\Goto_;
4142
use PhpParser\Node\Stmt\If_;
4243
use PhpParser\Node\Stmt\InlineHTML;
4344
use PhpParser\Node\Stmt\Return_;
@@ -110,6 +111,7 @@
110111
use PHPStan\Node\VarTagChangedExpressionTypeNode;
111112
use PHPStan\Parser\ArrowFunctionArgVisitor;
112113
use PHPStan\Parser\ClosureArgVisitor;
114+
use PHPStan\Parser\GotoLabelVisitor;
113115
use PHPStan\Parser\ImmediatelyInvokedClosureVisitor;
114116
use PHPStan\Parser\LineAttributesVisitor;
115117
use PHPStan\Parser\Parser;
@@ -408,12 +410,59 @@ private function processStmtNodesInternalWithoutFlushingPendingFibers(
408410
|| $parentNode instanceof Node\Stmt\ClassMethod
409411
|| $parentNode instanceof PropertyHookStatementNode
410412
|| $parentNode instanceof Expr\Closure;
413+
411414
foreach ($stmts as $i => $stmt) {
412-
if ($alreadyTerminated && !($stmt instanceof Node\Stmt\Function_ || $stmt instanceof Node\Stmt\ClassLike)) {
415+
if ($alreadyTerminated && !($stmt instanceof Node\Stmt\Function_ || $stmt instanceof Node\Stmt\ClassLike || $stmt instanceof Node\Stmt\Label)) {
413416
continue;
414417
}
415418

416419
$isLast = $i === $stmtCount - 1;
420+
421+
$nestedLabelNames = $stmt->getAttribute(GotoLabelVisitor::NESTED_BACKWARD_GOTO_LABELS_ATTRIBUTE);
422+
if ($nestedLabelNames !== null && $context->isTopLevel()) {
423+
$originalStorage = $storage;
424+
$bodyScope = $scope;
425+
$count = 0;
426+
do {
427+
$prevScope = $bodyScope;
428+
$tempStorage = $originalStorage->duplicate();
429+
$bodyScopeResult = $this->processStmtNodesInternal(
430+
$parentNode,
431+
[$stmt],
432+
$bodyScope,
433+
$tempStorage,
434+
new NoopNodeCallback(),
435+
$context->enterDeep(),
436+
);
437+
438+
$gotoScope = null;
439+
foreach ($bodyScopeResult->getExitPoints() as $ep) {
440+
$epStmt = $ep->getStatement();
441+
if (!($epStmt instanceof Goto_) || !isset($nestedLabelNames[$epStmt->name->toString()])) {
442+
continue;
443+
}
444+
445+
$gotoScope = $gotoScope === null ? $ep->getScope() : $gotoScope->mergeWith($ep->getScope());
446+
}
447+
448+
if ($gotoScope !== null) {
449+
$bodyScope = $scope->mergeWith($gotoScope);
450+
}
451+
452+
if ($bodyScope->equals($prevScope)) {
453+
break;
454+
}
455+
456+
if ($count >= self::GENERALIZE_AFTER_ITERATION) {
457+
$bodyScope = $prevScope->generalizeWith($bodyScope);
458+
}
459+
$count++;
460+
} while ($count < self::LOOP_SCOPE_ITERATIONS);
461+
462+
$scope = $bodyScope;
463+
$storage = $originalStorage;
464+
}
465+
417466
$statementResult = $this->processStmtNode(
418467
$stmt,
419468
$scope,
@@ -424,6 +473,76 @@ private function processStmtNodesInternalWithoutFlushingPendingFibers(
424473
$scope = $statementResult->getScope();
425474
$hasYield = $hasYield || $statementResult->hasYield();
426475

476+
if ($stmt instanceof Node\Stmt\Label) {
477+
$labelName = $stmt->name->toString();
478+
479+
$newExitPoints = [];
480+
foreach ($exitPoints as $exitPoint) {
481+
$exitStmt = $exitPoint->getStatement();
482+
if ($exitStmt instanceof Goto_ && $exitStmt->name->toString() === $labelName) {
483+
if ($alreadyTerminated) {
484+
$scope = $exitPoint->getScope();
485+
$alreadyTerminated = false;
486+
} else {
487+
$scope = $scope->mergeWith($exitPoint->getScope());
488+
}
489+
} else {
490+
$newExitPoints[] = $exitPoint;
491+
}
492+
}
493+
$exitPoints = $newExitPoints;
494+
495+
if ($alreadyTerminated) {
496+
continue;
497+
}
498+
499+
if ($stmt->getAttribute(GotoLabelVisitor::HAS_BACKWARD_GOTO_ATTRIBUTE) === true && $context->isTopLevel()) {
500+
$originalStorage = $storage;
501+
$bodyStmts = array_slice($stmts, $i + 1);
502+
$bodyScope = $scope;
503+
$count = 0;
504+
do {
505+
$prevScope = $bodyScope;
506+
$bodyScope = $bodyScope->mergeWith($scope);
507+
$tempStorage = $originalStorage->duplicate();
508+
$bodyScopeResult = $this->processStmtNodesInternal(
509+
$parentNode,
510+
$bodyStmts,
511+
$bodyScope,
512+
$tempStorage,
513+
new NoopNodeCallback(),
514+
$context->enterDeep(),
515+
);
516+
517+
$gotoScope = null;
518+
foreach ($bodyScopeResult->getExitPoints() as $ep) {
519+
$epStmt = $ep->getStatement();
520+
if (!($epStmt instanceof Goto_) || $epStmt->name->toString() !== $labelName) {
521+
continue;
522+
}
523+
524+
$gotoScope = $gotoScope === null ? $ep->getScope() : $gotoScope->mergeWith($ep->getScope());
525+
}
526+
527+
if ($gotoScope !== null) {
528+
$bodyScope = $scope->mergeWith($gotoScope);
529+
}
530+
531+
if ($bodyScope->equals($prevScope)) {
532+
break;
533+
}
534+
535+
if ($count >= self::GENERALIZE_AFTER_ITERATION) {
536+
$bodyScope = $prevScope->generalizeWith($bodyScope);
537+
}
538+
$count++;
539+
} while ($count < self::LOOP_SCOPE_ITERATIONS);
540+
541+
$scope = $bodyScope;
542+
$storage = $originalStorage;
543+
}
544+
}
545+
427546
if ($shouldCheckLastStatement && $isLast) {
428547
$endStatements = $statementResult->getEndStatements();
429548
if (count($endStatements) > 0) {
@@ -917,6 +1036,18 @@ public function processStmtNode(
9171036
return new InternalStatementResult($scope, $hasYield, true, [
9181037
new InternalStatementExitPoint($stmt, $scope),
9191038
], $overridingThrowPoints ?? $throwPoints, $impurePoints);
1039+
} elseif ($stmt instanceof Goto_) {
1040+
$hasYield = false;
1041+
$throwPoints = [];
1042+
$impurePoints = [];
1043+
1044+
return new InternalStatementResult($scope, $hasYield, true, [
1045+
new InternalStatementExitPoint($stmt, $scope),
1046+
], $overridingThrowPoints ?? $throwPoints, $impurePoints);
1047+
} elseif ($stmt instanceof Node\Stmt\Label) {
1048+
$hasYield = false;
1049+
$throwPoints = $overridingThrowPoints ?? [];
1050+
$impurePoints = [];
9201051
} elseif ($stmt instanceof Node\Stmt\Expression) {
9211052
if ($stmt->expr instanceof Expr\Throw_) {
9221053
$scope = $stmtScope;
@@ -4800,6 +4931,9 @@ private function getNextUnreachableStatements(array $nodes, bool $earlyBinding):
48004931
$stmts = [];
48014932
$isPassedUnreachableStatement = false;
48024933
foreach ($nodes as $node) {
4934+
if ($node instanceof Node\Stmt\Label) {
4935+
break;
4936+
}
48034937
if ($earlyBinding && ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\HaltCompiler)) {
48044938
continue;
48054939
}

0 commit comments

Comments
 (0)