-
Notifications
You must be signed in to change notification settings - Fork 564
Fix phpstan/phpstan#5952: Allow throwing exceptions from __toString() not detected since level 4 #5405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix phpstan/phpstan#5952: Allow throwing exceptions from __toString() not detected since level 4 #5405
Changes from all commits
01f336d
546a487
a39518e
a85d75b
879b943
281d039
0e0de1c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| <?php declare(strict_types = 1); | ||
|
|
||
| namespace PHPStan\Analyser\ExprHandler\Helper; | ||
|
|
||
| use PhpParser\Node\Expr; | ||
| use PhpParser\Node\Identifier; | ||
| use PHPStan\Analyser\ExpressionResult; | ||
| use PHPStan\Analyser\ImpurePoint; | ||
| use PHPStan\Analyser\MutatingScope; | ||
| use PHPStan\DependencyInjection\AutowiredService; | ||
| use PHPStan\Php\PhpVersion; | ||
| use function sprintf; | ||
|
|
||
| #[AutowiredService] | ||
| final class ImplicitToStringCallHelper | ||
| { | ||
|
|
||
| public function __construct( | ||
| private PhpVersion $phpVersion, | ||
| private MethodThrowPointHelper $methodThrowPointHelper, | ||
| ) | ||
| { | ||
| } | ||
|
|
||
| public function processImplicitToStringCall(Expr $expr, MutatingScope $scope): ExpressionResult | ||
| { | ||
| $throwPoints = []; | ||
| $impurePoints = []; | ||
|
|
||
| $exprType = $scope->getType($expr); | ||
| $toStringMethod = $scope->getMethodReflection($exprType, '__toString'); | ||
| if ($toStringMethod === null) { | ||
| return new ExpressionResult( | ||
| $scope, | ||
| hasYield: false, | ||
| isAlwaysTerminating: false, | ||
| throwPoints: [], | ||
| impurePoints: [], | ||
| ); | ||
| } | ||
|
|
||
| if (!$toStringMethod->hasSideEffects()->no()) { | ||
| $impurePoints[] = new ImpurePoint( | ||
| $scope, | ||
| $expr, | ||
| 'methodCall', | ||
| sprintf('call to method %s::%s()', $toStringMethod->getDeclaringClass()->getDisplayName(), $toStringMethod->getName()), | ||
| $toStringMethod->isPure()->no(), | ||
| ); | ||
| } | ||
|
|
||
| if ($this->phpVersion->throwsOnStringCast()) { | ||
| $throwPoint = $this->methodThrowPointHelper->getThrowPoint( | ||
| $toStringMethod, | ||
| $toStringMethod->getOnlyVariant(), | ||
| new Expr\MethodCall($expr, new Identifier('__toString')), | ||
| $scope, | ||
| ); | ||
| if ($throwPoint !== null) { | ||
| $throwPoints[] = $throwPoint; | ||
| } | ||
| } | ||
|
|
||
| return new ExpressionResult( | ||
| $scope, | ||
| hasYield: false, | ||
| isAlwaysTerminating: false, | ||
| throwPoints: $throwPoints, | ||
| impurePoints: $impurePoints, | ||
| ); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,6 +49,7 @@ | |
| use PhpParser\NodeTraverser; | ||
| use PhpParser\NodeVisitorAbstract; | ||
| use PHPStan\Analyser\ExprHandler\AssignHandler; | ||
| use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper; | ||
| use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass; | ||
| use PHPStan\BetterReflection\Reflection\ReflectionEnum; | ||
| use PHPStan\BetterReflection\Reflector\Reflector; | ||
|
|
@@ -236,6 +237,7 @@ public function __construct( | |
| private readonly bool $implicitThrows, | ||
| #[AutowiredParameter] | ||
| private readonly bool $treatPhpDocTypesAsCertain, | ||
| private readonly ImplicitToStringCallHelper $implicitToStringCallHelper, | ||
| ) | ||
| { | ||
| $earlyTerminatingMethodNames = []; | ||
|
|
@@ -861,19 +863,22 @@ public function processStmtNode( | |
| } elseif ($stmt instanceof Echo_) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need more changes in NodeScopeResolver to reflect the changes made in ExprHandler classes with this PR?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All 11732 tests pass. Summary: After analyzing the codebase, no additional changes are needed in NodeScopeResolver.php. Here's why:
The I was unable to post this reply as a PR comment due to GitHub token permissions. You may want to reply to staabm's comment manually with this explanation. |
||
| $hasYield = false; | ||
| $throwPoints = []; | ||
| $impurePoints = []; | ||
| $isAlwaysTerminating = false; | ||
| foreach ($stmt->exprs as $echoExpr) { | ||
| $result = $this->processExprNode($stmt, $echoExpr, $scope, $storage, $nodeCallback, ExpressionContext::createDeep()); | ||
| $throwPoints = array_merge($throwPoints, $result->getThrowPoints()); | ||
| $impurePoints = array_merge($impurePoints, $result->getImpurePoints()); | ||
| $toStringResult = $this->implicitToStringCallHelper->processImplicitToStringCall($echoExpr, $scope); | ||
| $throwPoints = array_merge($throwPoints, $toStringResult->getThrowPoints()); | ||
| $impurePoints = array_merge($impurePoints, $toStringResult->getImpurePoints()); | ||
| $scope = $result->getScope(); | ||
| $hasYield = $hasYield || $result->hasYield(); | ||
| $isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating(); | ||
| } | ||
|
|
||
| $throwPoints = $overridingThrowPoints ?? $throwPoints; | ||
| $impurePoints = [ | ||
| new ImpurePoint($scope, $stmt, 'echo', 'echo', true), | ||
| ]; | ||
| $impurePoints[] = new ImpurePoint($scope, $stmt, 'echo', 'echo', true); | ||
| return new InternalStatementResult($scope, $hasYield, $isAlwaysTerminating, [], $throwPoints, $impurePoints); | ||
| } elseif ($stmt instanceof Return_) { | ||
| if ($stmt->expr !== null) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this PR is not only about throw points, but also impure points.
it seems the impurity portion does not have tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both background tasks confirmed complete. Everything was already pushed — nothing more needed.