Skip to content

Commit

Permalink
Precise exception tracking for everyone
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Sep 12, 2021
1 parent 8933c7e commit 4588e73
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 114 deletions.
1 change: 0 additions & 1 deletion conf/bleedingEdge.neon
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ parameters:
objectFromNewClass: true
skipCheckGenericClasses: []
rememberFunctionValues: true
preciseExceptionTracking: true
apiRules: true
deepInspectTypes: true
neverInGenericReturnType: true
Expand Down
12 changes: 2 additions & 10 deletions conf/config.level4.neon
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ rules:
- PHPStan\Rules\Comparison\NumberComparisonOperatorsConstantConditionRule
- PHPStan\Rules\DeadCode\NoopRule
- PHPStan\Rules\DeadCode\UnreachableStatementRule
- PHPStan\Rules\Exceptions\CatchWithUnthrownExceptionRule
- PHPStan\Rules\Exceptions\OverwrittenExitPointByFinallyRule
- PHPStan\Rules\Functions\CallToFunctionStamentWithoutSideEffectsRule
- PHPStan\Rules\Methods\CallToConstructorStatementWithoutSideEffectsRule
- PHPStan\Rules\Methods\CallToMethodStamentWithoutSideEffectsRule
Expand All @@ -17,10 +19,6 @@ rules:
- PHPStan\Rules\TooWideTypehints\TooWideFunctionReturnTypehintRule

conditionalTags:
PHPStan\Rules\Exceptions\CatchWithUnthrownExceptionRule:
phpstan.rules.rule: %featureToggles.preciseExceptionTracking%
PHPStan\Rules\Exceptions\OverwrittenExitPointByFinallyRule:
phpstan.rules.rule: %featureToggles.preciseExceptionTracking%
PHPStan\Rules\DeadCode\UnusedPrivateConstantRule:
phpstan.rules.rule: %featureToggles.unusedClassElements%
PHPStan\Rules\DeadCode\UnusedPrivateMethodRule:
Expand Down Expand Up @@ -149,12 +147,6 @@ services:
tags:
- phpstan.rules.rule

-
class: PHPStan\Rules\Exceptions\CatchWithUnthrownExceptionRule

-
class: PHPStan\Rules\Exceptions\OverwrittenExitPointByFinallyRule

-
class: PHPStan\Rules\TooWideTypehints\TooWideMethodReturnTypehintRule
arguments:
Expand Down
3 changes: 0 additions & 3 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ parameters:
- RecursiveArrayIterator
- WeakMap
rememberFunctionValues: false
preciseExceptionTracking: false
apiRules: false
deepInspectTypes: false
neverInGenericReturnType: false
Expand Down Expand Up @@ -228,7 +227,6 @@ parametersSchema:
objectFromNewClass: bool(),
skipCheckGenericClasses: listOf(string()),
rememberFunctionValues: bool(),
preciseExceptionTracking: bool(),
apiRules: bool(),
deepInspectTypes: bool(),
neverInGenericReturnType: bool(),
Expand Down Expand Up @@ -479,7 +477,6 @@ services:
earlyTerminatingMethodCalls: %earlyTerminatingMethodCalls%
earlyTerminatingFunctionCalls: %earlyTerminatingFunctionCalls%
implicitThrows: %implicitThrows%
preciseExceptionTracking: %featureToggles.preciseExceptionTracking%

-
implement: PHPStan\Analyser\ResultCache\ResultCacheManagerFactory
Expand Down
151 changes: 55 additions & 96 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,6 @@ class NodeScopeResolver

private bool $implicitThrows;

private bool $preciseExceptionTracking;

/** @var bool[] filePath(string) => bool(true) */
private array $analysedFiles = [];

Expand All @@ -178,7 +176,6 @@ class NodeScopeResolver
* @param string[][] $earlyTerminatingMethodCalls className(string) => methods(string[])
* @param array<int, string> $earlyTerminatingFunctionCalls
* @param bool $implicitThrows
* @param bool $preciseExceptionTracking
*/
public function __construct(
ReflectionProvider $reflectionProvider,
Expand All @@ -196,8 +193,7 @@ public function __construct(
bool $polluteScopeWithAlwaysIterableForeach,
array $earlyTerminatingMethodCalls,
array $earlyTerminatingFunctionCalls,
bool $implicitThrows,
bool $preciseExceptionTracking
bool $implicitThrows
)
{
$this->reflectionProvider = $reflectionProvider;
Expand All @@ -216,7 +212,6 @@ public function __construct(
$this->earlyTerminatingMethodCalls = $earlyTerminatingMethodCalls;
$this->earlyTerminatingFunctionCalls = $earlyTerminatingFunctionCalls;
$this->implicitThrows = $implicitThrows;
$this->preciseExceptionTracking = $preciseExceptionTracking;
}

/**
Expand Down Expand Up @@ -1180,89 +1175,78 @@ private function processStmtNode(
foreach ($stmt->catches as $catchNode) {
$nodeCallback($catchNode, $scope);

if ($this->preciseExceptionTracking) {
$catchType = TypeCombinator::union(...array_map(static function (Name $name): Type {
return new ObjectType($name->toString());
}, $catchNode->types));
$originalCatchType = $catchType;
$catchType = TypeCombinator::remove($catchType, $pastCatchTypes);
$pastCatchTypes = TypeCombinator::union($pastCatchTypes, $originalCatchType);
$matchingThrowPoints = [];
$newThrowPoints = [];
foreach ($throwPoints as $throwPoint) {
if (!$throwPoint->isExplicit() && !$catchType->isSuperTypeOf(new ObjectType(\Throwable::class))->yes()) {
continue;
}
$isSuperType = $catchType->isSuperTypeOf($throwPoint->getType());
if ($isSuperType->no()) {
continue;
}
$catchType = TypeCombinator::union(...array_map(static function (Name $name): Type {
return new ObjectType($name->toString());
}, $catchNode->types));
$originalCatchType = $catchType;
$catchType = TypeCombinator::remove($catchType, $pastCatchTypes);
$pastCatchTypes = TypeCombinator::union($pastCatchTypes, $originalCatchType);
$matchingThrowPoints = [];
$newThrowPoints = [];
foreach ($throwPoints as $throwPoint) {
if (!$throwPoint->isExplicit() && !$catchType->isSuperTypeOf(new ObjectType(\Throwable::class))->yes()) {
continue;
}
$isSuperType = $catchType->isSuperTypeOf($throwPoint->getType());
if ($isSuperType->no()) {
continue;
}
$matchingThrowPoints[] = $throwPoint;
}
$hasExplicit = count($matchingThrowPoints) > 0;
foreach ($throwPoints as $throwPoint) {
$isSuperType = $catchType->isSuperTypeOf($throwPoint->getType());
if (!$hasExplicit && !$isSuperType->no()) {
$matchingThrowPoints[] = $throwPoint;
}
$hasExplicit = count($matchingThrowPoints) > 0;
foreach ($throwPoints as $throwPoint) {
$isSuperType = $catchType->isSuperTypeOf($throwPoint->getType());
if (!$hasExplicit && !$isSuperType->no()) {
$matchingThrowPoints[] = $throwPoint;
}
if ($isSuperType->yes()) {
continue;
}
$newThrowPoints[] = $throwPoint->subtractCatchType($catchType);
if ($isSuperType->yes()) {
continue;
}
$throwPoints = $newThrowPoints;

if (count($matchingThrowPoints) === 0) {
$throwableThrowPoints = [];
if ($originalCatchType->isSuperTypeOf(new ObjectType(\Throwable::class))->yes()) {
foreach ($branchScopeResult->getThrowPoints() as $originalThrowPoint) {
if (!$originalThrowPoint->canContainAnyThrowable()) {
continue;
}
$newThrowPoints[] = $throwPoint->subtractCatchType($catchType);
}
$throwPoints = $newThrowPoints;

$throwableThrowPoints[] = $originalThrowPoint;
if (count($matchingThrowPoints) === 0) {
$throwableThrowPoints = [];
if ($originalCatchType->isSuperTypeOf(new ObjectType(\Throwable::class))->yes()) {
foreach ($branchScopeResult->getThrowPoints() as $originalThrowPoint) {
if (!$originalThrowPoint->canContainAnyThrowable()) {
continue;
}
}

if (count($throwableThrowPoints) === 0) {
$nodeCallback(new CatchWithUnthrownExceptionNode($catchNode, $catchType, $originalCatchType), $scope);
continue;
$throwableThrowPoints[] = $originalThrowPoint;
}

$matchingThrowPoints = $throwableThrowPoints;
}

$catchScope = null;
foreach ($matchingThrowPoints as $matchingThrowPoint) {
if ($catchScope === null) {
$catchScope = $matchingThrowPoint->getScope();
} else {
$catchScope = $catchScope->mergeWith($matchingThrowPoint->getScope());
}
if (count($throwableThrowPoints) === 0) {
$nodeCallback(new CatchWithUnthrownExceptionNode($catchNode, $catchType, $originalCatchType), $scope);
continue;
}

$variableName = null;
if ($catchNode->var !== null) {
if (!is_string($catchNode->var->name)) {
throw new \PHPStan\ShouldNotHappenException();
}
$matchingThrowPoints = $throwableThrowPoints;
}

$variableName = $catchNode->var->name;
$catchScope = null;
foreach ($matchingThrowPoints as $matchingThrowPoint) {
if ($catchScope === null) {
$catchScope = $matchingThrowPoint->getScope();
} else {
$catchScope = $catchScope->mergeWith($matchingThrowPoint->getScope());
}
}

$catchScopeResult = $this->processStmtNodes($catchNode, $catchNode->stmts, $catchScope->enterCatchType($catchType, $variableName), $nodeCallback);
$catchScopeForFinally = $catchScopeResult->getScope();
} else {
$initialScope = $scope;
if (count($throwPoints) > 0) {
$initialScope = $throwPoints[0]->getScope();
$variableName = null;
if ($catchNode->var !== null) {
if (!is_string($catchNode->var->name)) {
throw new \PHPStan\ShouldNotHappenException();
}

$catchScopeForFinally = $this->processCatchNode($catchNode, $branchScope, $nodeCallback)->getScope();
$catchScopeResult = $this->processCatchNode($catchNode, $initialScope->mergeWith($branchScope), static function (): void {
});
$variableName = $catchNode->var->name;
}

$catchScopeResult = $this->processStmtNodes($catchNode, $catchNode->stmts, $catchScope->enterCatchType($catchType, $variableName), $nodeCallback);
$catchScopeForFinally = $catchScopeResult->getScope();

$finalScope = $catchScopeResult->isAlwaysTerminating() ? $finalScope : $catchScopeResult->getScope()->mergeWith($finalScope);
$alwaysTerminating = $alwaysTerminating && $catchScopeResult->isAlwaysTerminating();
$hasYield = $hasYield || $catchScopeResult->hasYield();
Expand Down Expand Up @@ -1501,31 +1485,6 @@ private function createAstClassReflection(Node\Stmt\ClassLike $stmt, Scope $scop
);
}

/**
* @param Node\Stmt\Catch_ $catchNode
* @param MutatingScope $catchScope
* @param callable(\PhpParser\Node $node, Scope $scope): void $nodeCallback
* @return StatementResult
*/
private function processCatchNode(
Node\Stmt\Catch_ $catchNode,
MutatingScope $catchScope,
callable $nodeCallback
): StatementResult
{
$variableName = null;
if ($catchNode->var !== null) {
if (!is_string($catchNode->var->name)) {
throw new \PHPStan\ShouldNotHappenException();
}

$variableName = $catchNode->var->name;
}

$catchScope = $catchScope->enterCatch($catchNode->types, $variableName);
return $this->processStmtNodes($catchNode, $catchNode->stmts, $catchScope, $nodeCallback);
}

private function lookForEnterVariableAssign(MutatingScope $scope, Expr $expr): MutatingScope
{
if (!$expr instanceof ArrayDimFetch || $expr->dim !== null) {
Expand Down
1 change: 0 additions & 1 deletion src/Testing/RuleTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ private function getAnalyser(): Analyser
$this->shouldPolluteScopeWithAlwaysIterableForeach(),
[],
[],
true,
true
);
$fileAnalyser = new FileAnalyser(
Expand Down
1 change: 0 additions & 1 deletion src/Testing/TypeInferenceTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ public function processFile(
true,
$this->getEarlyTerminatingMethodCalls(),
$this->getEarlyTerminatingFunctionCalls(),
true,
true
);
$resolver->setAnalysedFiles(array_map(static function (string $file) use ($fileHelper): string {
Expand Down
2 changes: 0 additions & 2 deletions tests/PHPStan/Analyser/AnalyserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -515,11 +515,9 @@ private function createAnalyser(bool $reportUnmatchedIgnoredErrors): \PHPStan\An
$typeSpecifier,
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
false,
false,
true,
[],
[],
true,
true
);
$lexer = new \PhpParser\Lexer(['usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos']]);
Expand Down

0 comments on commit 4588e73

Please sign in to comment.