phpstan-src icon indicating copy to clipboard operation
phpstan-src copied to clipboard

Implements BackedEnumFromDynamicStaticMethodThrowTypeExtension

Open VincentLanglet opened this issue 7 months ago • 3 comments

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.

VincentLanglet avatar Jul 25 '25 07:07 VincentLanglet

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 ?

VincentLanglet avatar Aug 30 '25 15:08 VincentLanglet

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 @throws tag 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
}

VincentLanglet avatar Sep 21 '25 19:09 VincentLanglet

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.

VincentLanglet avatar Dec 05 '25 15:12 VincentLanglet