Implements BackedEnumFromDynamicStaticMethodThrowTypeExtension
Closes https://github.com/phpstan/phpstan/issues/13297
Currently, even doing a simple Enum::from(1) where 1 is a value valid, reports missing @throws phpdoc.
I really don't want people to discourage from defensive code. I don't want to report an error when someone catches an exception because the argument type is only guarded with a PHPDoc for example.
If I use getNative type, it will report a lot of false-positive when using ::from, as exemple this method https://github.com/phpstan/phpstan-src/pull/4156/files#diff-45aec6a97db9e1a9901c9edd9dbe1859275e5e55b5cfd4f373f051dfe040110eR12 will require to add @throws ValueError while I don't want to because I DO trust my phpdoc, since I'm the only user of my methods.
This is the same than DynamicReturnTypeExtension which remove false from the return type of a method based on the Type (and not the nativeType) which means that an extra-safety check === false will be reported as never true.
getNativeType please
Shouldn't it be at least conditioned by treatphpdoctypesascertain ? Other way it won't remove lot of useless @throws...
Or maybe DynamicMethodThrowTypeExtension should be thought differently...
I understand that you want to allow
try {
Enum::from($foo);
} catch (...) {}
but what I try to avoid is the error
Method Foo::bar() throws checked exception
Can't the DynamicMethodThrowTypeExtension return a "BenevolentThrowType" then ? Something like an unchecked exception or an implicit throw point ?
Friendly ping @ondrejmirtes
Cf my comment https://github.com/phpstan/phpstan-src/pull/4156#issuecomment-3239333714 I dunno how to move on this PR.
If I use the nativeType as asked
- I'll get a different behavior from all the other DynamicThrowTypeExtension.
- I won't solve the fact PHPStan ask for a
@throwstag on such method.
/** @param value-of<Foo> $int */
public function sayHello(int $int): void
{
Foo::from($int);
}
Moreover I feel like writing
try {
$enum = Enum::from($foo);
} catch (ValueError) {
// dothings
}
is kinda weird, because it's the purpose of the method tryFrom
$enum = Enum::tryFrom($foo);
if (null === $enum) {
// dothings
}
I don't think the mutant
$valueType = $scope->getType($arguments[0]->value);
- if (!$valueType->isConstantScalarValue()->yes()) {
+ if ($valueType->isConstantScalarValue()->no()) {
return $methodReflection->getThrowType();
}
$enumCases = $methodReflection->getDeclaringClass()->getEnumCases();
is relevant because the whole if could be removed since there is later a check
if (!$backingValueType->isSuperTypeOf($valueType)->yes()) {
return $methodReflection->getThrowType();
}
It's just an early return to reduce the computation.