Bug report
Since 2.3.0 (bleeding edge), a parameter of a private method that is only read inside a catch block wrapping new Foo() is reported as unused:
Method Bar::readOnlyInCatch() has an unused parameter $param. [method.unusedParameter]
This happens when the constructor of Foo has no @throws tag (so the throw point is implicit via exceptions.implicitThrows: true). At the same time no catch.neverThrown is reported for the same catch, so PHPStan knows the new can throw: the two analyses disagree. Adding @throws to the constructor makes the report disappear. Wrapping a method call instead of new (a method without @throws as well) is fine.
Cause, as far as I can tell: NewHandler::getConstructorThrowPoint() creates the implicit throw point on the synthetic StaticCall it builds for the throw type extensions, not on the New_ expression like the explicit throw points. The synthetic node has no file positions, so VariableFlowBuilder::throws() drops it and the variable liveness analysis behind UnusedMethodParametersRule treats the catch body as unreachable. TryCatchHandler works with the throw points directly, which is why CatchWithUnthrownExceptionRule is consistent with the runtime behaviour.
Fix: phpstan/phpstan-src#6687
Code snippet that reproduces the problem
https://phpstan.org/r/4a23c702-6f19-45cb-ad89-1db12330a9bb
Expected output
No errors.
Did PHPStan help you today? Did it make you happy in any way?
It did: the new unused-parameter rule already pointed at a leftover parameter in our codebase on the first run. This one was the only false positive.
Bug report
Since 2.3.0 (bleeding edge), a parameter of a private method that is only read inside a
catchblock wrappingnew Foo()is reported as unused:This happens when the constructor of
Foohas no@throwstag (so the throw point is implicit viaexceptions.implicitThrows: true). At the same time nocatch.neverThrownis reported for the samecatch, so PHPStan knows thenewcan throw: the two analyses disagree. Adding@throwsto the constructor makes the report disappear. Wrapping a method call instead ofnew(a method without@throwsas well) is fine.Cause, as far as I can tell:
NewHandler::getConstructorThrowPoint()creates the implicit throw point on the syntheticStaticCallit builds for the throw type extensions, not on theNew_expression like the explicit throw points. The synthetic node has no file positions, soVariableFlowBuilder::throws()drops it and the variable liveness analysis behindUnusedMethodParametersRuletreats thecatchbody as unreachable.TryCatchHandlerworks with the throw points directly, which is whyCatchWithUnthrownExceptionRuleis consistent with the runtime behaviour.Fix: phpstan/phpstan-src#6687
Code snippet that reproduces the problem
https://phpstan.org/r/4a23c702-6f19-45cb-ad89-1db12330a9bb
Expected output
No errors.
Did PHPStan help you today? Did it make you happy in any way?
It did: the new unused-parameter rule already pointed at a leftover parameter in our codebase on the first run. This one was the only false positive.