Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/Analyser/ExprHandler/FuncCallHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
}

if ($functionReflection !== null) {
$functionThrowPoint = $this->getFunctionThrowPoint($functionReflection, $parametersAcceptor, $normalizedExpr, $scope);
$functionThrowPoint = $this->getFunctionThrowPoint($functionReflection, $parametersAcceptor, $normalizedExpr, $scope, $context);
if ($functionThrowPoint !== null) {
$throwPoints[] = $functionThrowPoint;
}
Expand Down Expand Up @@ -580,6 +580,7 @@ private function getFunctionThrowPoint(
?ParametersAcceptor $parametersAcceptor,
FuncCall $normalizedFuncCall,
MutatingScope $scope,
ExpressionContext $context,
): ?InternalThrowPoint
{
foreach ($this->dynamicThrowTypeExtensionProvider->getDynamicFunctionThrowTypeExtensions() as $extension) {
Expand Down Expand Up @@ -626,7 +627,7 @@ private function getFunctionThrowPoint(
|| count($normalizedFuncCall->getArgs()) > 0
) {
$functionReturnedType = $scope->getType($normalizedFuncCall);
if (!(new ObjectType(Throwable::class))->isSuperTypeOf($functionReturnedType)->yes()) {
if (!$context->isInThrow() || !(new ObjectType(Throwable::class))->isSuperTypeOf($functionReturnedType)->yes()) {
return InternalThrowPoint::createImplicit($scope, $normalizedFuncCall);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PHPStan\Analyser\ExpressionContext;
use PHPStan\Analyser\ExpressionResult;
use PHPStan\Analyser\ImpurePoint;
use PHPStan\Analyser\MutatingScope;
Expand Down Expand Up @@ -59,6 +60,7 @@ public function processImplicitToStringCall(Expr $expr, MutatingScope $scope): E
$toStringMethod->getOnlyVariant(),
new Expr\MethodCall($expr, new Identifier('__toString')),
$scope,
ExpressionContext::createDeep(),
);
if ($throwPoint !== null) {
$throwPoints[] = $throwPoint;
Expand Down
4 changes: 3 additions & 1 deletion src/Analyser/ExprHandler/Helper/MethodThrowPointHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\ExpressionContext;
use PHPStan\Analyser\InternalThrowPoint;
use PHPStan\Analyser\MutatingScope;
use PHPStan\DependencyInjection\AutowiredParameter;
Expand Down Expand Up @@ -35,6 +36,7 @@ public function getThrowPoint(
ParametersAcceptor $parametersAcceptor,
MethodCall|StaticCall $normalizedMethodCall,
MutatingScope $scope,
ExpressionContext $context,
): ?InternalThrowPoint
{
if ($normalizedMethodCall instanceof MethodCall) {
Expand Down Expand Up @@ -87,7 +89,7 @@ public function getThrowPoint(
}
} elseif ($this->implicitThrows) {
$methodReturnedType = $scope->getType($normalizedMethodCall);
if (!(new ObjectType(Throwable::class))->isSuperTypeOf($methodReturnedType)->yes()) {
if (!$context->isInThrow() || !(new ObjectType(Throwable::class))->isSuperTypeOf($methodReturnedType)->yes()) {
return InternalThrowPoint::createImplicit($scope, $normalizedMethodCall);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Analyser/ExprHandler/MethodCallHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$scope = $argsResult->getScope();

if ($methodReflection !== null) {
$methodThrowPoint = $this->methodThrowPointHelper->getThrowPoint($methodReflection, $parametersAcceptor, $normalizedExpr, $scope);
$methodThrowPoint = $this->methodThrowPointHelper->getThrowPoint($methodReflection, $parametersAcceptor, $normalizedExpr, $scope, $context);
if ($methodThrowPoint !== null) {
$throwPoints[] = $methodThrowPoint;
}
Expand Down
8 changes: 6 additions & 2 deletions src/Analyser/ExprHandler/NewHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex

if ($constructorReflection !== null && $parametersAcceptor !== null) {
$className ??= $constructorReflection->getDeclaringClass()->getName();
$constructorThrowPoint = $this->getConstructorThrowPoint($constructorReflection, $parametersAcceptor, $expr, new Name\FullyQualified($className), $expr->getArgs(), $scope);
$constructorThrowPoint = $this->getConstructorThrowPoint($constructorReflection, $parametersAcceptor, $expr, new Name\FullyQualified($className), $expr->getArgs(), $scope, $context);
if ($constructorThrowPoint !== null) {
$throwPoints[] = $constructorThrowPoint;
}
Expand Down Expand Up @@ -275,7 +275,7 @@ private function processConstructorReflection(string $className, New_ $expr, Mut
/**
* @param list<Node\Arg> $args
*/
private function getConstructorThrowPoint(MethodReflection $constructorReflection, ParametersAcceptor $parametersAcceptor, New_ $new, Name $className, array $args, MutatingScope $scope): ?InternalThrowPoint
private function getConstructorThrowPoint(MethodReflection $constructorReflection, ParametersAcceptor $parametersAcceptor, New_ $new, Name $className, array $args, MutatingScope $scope, ExpressionContext $context): ?InternalThrowPoint
{
$methodCall = new StaticCall($className, $constructorReflection->getName(), $args);
$normalizedMethodCall = ArgumentsNormalizer::reorderStaticCallArguments($parametersAcceptor, $methodCall);
Expand All @@ -300,6 +300,10 @@ private function getConstructorThrowPoint(MethodReflection $constructorReflectio
return InternalThrowPoint::createExplicit($scope, $throwType, $new, true);
}
} elseif ($this->implicitThrows) {
if (!$context->isInThrow()) {
Comment thread
VincentLanglet marked this conversation as resolved.
Outdated
return InternalThrowPoint::createImplicit($scope, $methodCall);
}

if (!$constructorReflection->getDeclaringClass()->is(Throwable::class)) {
return InternalThrowPoint::createImplicit($scope, $methodCall);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Analyser/ExprHandler/StaticCallHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$scopeFunction = $scope->getFunction();

if ($methodReflection !== null) {
$methodThrowPoint = $this->methodThrowPointHelper->getThrowPoint($methodReflection, $parametersAcceptor, $normalizedExpr, $scope);
$methodThrowPoint = $this->methodThrowPointHelper->getThrowPoint($methodReflection, $parametersAcceptor, $normalizedExpr, $scope, $context);
if ($methodThrowPoint !== null) {
$throwPoints[] = $methodThrowPoint;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Analyser/ExprHandler/ThrowHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function supports(Expr $expr): bool

public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
{
$exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, ExpressionContext::createDeep());
$exprResult = $nodeScopeResolver->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()->enterThrow());

return new ExpressionResult(
$scope,
Expand Down
15 changes: 13 additions & 2 deletions src/Analyser/ExpressionContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ private function __construct(
private bool $isDeep,
private ?string $inAssignRightSideVariableName,
private ?Expr $inAssignRightSideExpr,
private bool $inThrow = false,
)
{
}
Expand All @@ -31,17 +32,27 @@ public function enterDeep(): self
return $this;
}

return new self(true, $this->inAssignRightSideVariableName, $this->inAssignRightSideExpr);
return new self(true, $this->inAssignRightSideVariableName, $this->inAssignRightSideExpr, $this->inThrow);
}

public function isDeep(): bool
{
return $this->isDeep;
}

public function enterThrow(): self
{
return new self($this->isDeep, $this->inAssignRightSideVariableName, $this->inAssignRightSideExpr, true);
}

public function isInThrow(): bool
{
return $this->inThrow;
}

public function enterRightSideAssign(string $variableName, Expr $expr): self
{
return new self($this->isDeep, $variableName, $expr);
return new self($this->isDeep, $variableName, $expr, $this->inThrow);
}

public function getInAssignRightSideVariableName(): ?string
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/throw-points/try-catch.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ function (): void {
try {
$foo = new \InvalidArgumentException();
} catch (Throwable $e) {
assertVariableCertainty(TrinaryLogic::createYes(), $foo);
assertVariableCertainty(TrinaryLogic::createNo(), $foo);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -821,4 +821,9 @@ public function testBug7799(): void
$this->analyse([__DIR__ . '/data/bug-7799.php'], []);
}

public function testBug9826(): void
{
$this->analyse([__DIR__ . '/data/bug-9826.php'], []);
}

}
70 changes: 70 additions & 0 deletions tests/PHPStan/Rules/Exceptions/data/bug-9826.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php declare(strict_types = 1);

namespace Bug9826;

class Foo
{

public function returnVoid(): void
{

}

public function returnThrowable(): \Exception
{
return new \Exception();
}

public static function staticReturnThrowable(): \Exception
{
return new \Exception();
}

public function sayHello(): void
{
try {
$this->returnVoid();
} catch (\Throwable $e) {
// ok - implicit throws enabled
}

try {
$this->returnThrowable();
} catch (\Throwable $e) {
// ok - method can implicitly throw even though it returns Throwable
}

try {
self::staticReturnThrowable();
} catch (\Throwable $e) {
// ok - static method can implicitly throw even though it returns Throwable
}

try {
returnThrowable();
} catch (\Throwable $e) {
// ok - function can implicitly throw even though it returns Throwable
}

try {
new \Exception('test');
} catch (\Throwable $e) {
// ok - new Exception() can implicitly throw even though it constructs Throwable
}
}

}

function returnThrowable(): \Exception
{
return new \Exception();
}

function triggerErrorNeverReturns(): void
Comment thread
VincentLanglet marked this conversation as resolved.
Outdated
{
try {
$a = trigger_error("hello", E_USER_ERROR);
} catch (\Exception $e) {
// ok - trigger_error returns never (explicit), gets throw point like any never-returning function
}
}
Loading