Skip to content

Fix phpstan/phpstan#5952: Allow throwing exceptions from __toString() not detected since level 4#5405

Open
phpstan-bot wants to merge 4 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-6rkqe4h
Open

Fix phpstan/phpstan#5952: Allow throwing exceptions from __toString() not detected since level 4#5405
phpstan-bot wants to merge 4 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-6rkqe4h

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When echo $obj is used where $obj has a __toString() method that can throw, PHPStan incorrectly reported "Dead catch - Exception is never thrown in the try block." This is because the echo statement handler in NodeScopeResolver did not track throw points from implicit __toString() calls.

Changes

  • Added __toString() throw point tracking to the echo statement handler in src/Analyser/NodeScopeResolver.php
  • The fix reuses the same pattern as CastStringHandler (explicit (string) cast): checks PhpVersion::throwsOnStringCast() (PHP >= 7.4) and uses MethodThrowPointHelper::getThrowPoint() to determine if the __toString() method can throw
  • Added regression test at tests/PHPStan/Rules/Exceptions/data/bug-5952.php

Root cause

The echo statement handler in NodeScopeResolver collected throw points from expression processing but did not account for PHP's implicit __toString() call when echoing an object. The explicit string cast handler (CastStringHandler) already had this logic, but it was not applied to echo statements.

Test

The regression test covers:

  • echo $obj where __toString() throws (implicitly via @throws void absence) - no dead catch error
  • echo $obj where __toString() has @throws \Exception - no dead catch error
  • echo $obj where __toString() has @throws void - dead catch correctly reported
  • echo 123 (non-object) - dead catch correctly reported

Fixes phpstan/phpstan#5952

- Added __toString() throw point tracking to echo statement handling in NodeScopeResolver
- Reuses existing CastStringHandler pattern: checks PhpVersion::throwsOnStringCast() and MethodThrowPointHelper
- New regression test in tests/PHPStan/Rules/Exceptions/data/bug-5952.php
- The root cause was that echo $obj did not consider implicit __toString() calls when collecting throw points
Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than calling directly MethodThrowPointHelper can't we simulate a MethodCall ?

Address review feedback: process a synthetic MethodCall expression through
processExprNode() instead of directly calling MethodThrowPointHelper. This
leverages the existing MethodCallHandler infrastructure for __toString()
throw point tracking in echo statements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

make tests also completed successfully. All tests pass, static analysis is clean, and the commit has been pushed. The review feedback has been fully addressed.

Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the phpversion check, it's already done in the toString resolution.

Also add a test for echo on an union

  • 123|Foo
  • 123|Bar
  • 123|Baz

The phpVersion->throwsOnStringCast() check is unnecessary because the
toString resolution already handles the PHP version gate internally.
Also adds regression tests for echo on union types (int|Foo, int|Bar,
int|Baz) as requested in review.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

Both background tasks have already been handled — make tests and make phpstan both passed, and the commit was pushed. Nothing else to do here.

Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fix for echo but I assume the same issue could occurs for some other situation where __toStirng is implicitely called. Is there a general fix ?

Extract ToStringThrowPointHelper to handle __toString() throw points
and impure points in a reusable way, and apply it to all places where
PHP implicitly calls __toString():
- echo (NodeScopeResolver)
- print (PrintHandler)
- string concatenation . (BinaryOpHandler)
- concat assignment .= (AssignOpHandler)
- string interpolation "$obj" / "{$obj}" (InterpolatedStringHandler)
- explicit (string) cast (CastStringHandler) - refactored to use helper

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

All clear - every background task has completed successfully. The review feedback has been fully addressed and pushed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants