Skip to content

Create the implicit throw point of a constructor on the new expression - #6687

Merged
ondrejmirtes merged 2 commits into
phpstan:2.3.xfrom
janatjak:implicit-constructor-throw-point-on-new
Oct 6, 2026
Merged

ondrejmirtes merged 2 commits into
phpstan:2.3.xfrom
janatjak:implicit-constructor-throw-point-on-new

Conversation

@janatjak

@janatjak janatjak commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

NewHandler::getConstructorThrowPoint() builds a synthetic StaticCall so that dynamic throw type extensions and ArgumentsNormalizer can work with the constructor call. The explicit throw points (from @throws or from an extension) are created on the New_ expression, but the implicit one (constructor without @throws, exceptions.implicitThrows: true) was created on the synthetic StaticCall.

That node has no file positions, so VariableFlowBuilder::throws() drops the throw point: it keeps only throw points whose node is the expression itself or shares its start/end positions. TryCatchHandler works with the throw points directly and still sees it, so the two analyses disagree: the catch is reachable for CatchWithUnthrownExceptionRule (no "Dead catch" is reported), but it is dead for the variable liveness analysis behind UnusedMethodParametersRule, which then reports a parameter read only inside such a catch as unused.

This PR creates the implicit throw point on $new, like the explicit ones. The turbo mirror in turbo-ext/src/NewHandler.cpp gets the same change, followed by the make bump-turbo commit. The regression test data is the reproducer from the issue's playground link.

Verified locally: make tests (22448 tests), the new regression test fails before the change and passes after it, both with and without the turbo extension loaded; tests/PHPStan/Rules/Exceptions also passes with the extension loaded.

Closes phpstan/phpstan#15396

getConstructorThrowPoint() builds a synthetic StaticCall for the dynamic
throw type extensions and ArgumentsNormalizer. The explicit throw points
are created on the New_ expression, but the implicit one was created on
the synthetic StaticCall, which has no file positions, so
VariableFlowBuilder::throws() dropped it. TryCatchHandler still saw it,
so the catch was reachable for CatchWithUnthrownExceptionRule but dead
for the variable liveness analysis, and UnusedMethodParametersRule
reported a parameter read only inside such a catch as unused.
@ondrejmirtes

Copy link
Copy Markdown
Member

Thank you!

@ondrejmirtes
ondrejmirtes merged commit a8c8529 into phpstan:2.3.x Oct 6, 2026
659 of 674 checks passed
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.

False positive method.unusedParameter for a parameter read only in a catch around new of a class whose constructor has no @throws

2 participants