Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add ReflectionClassConstant constructor throw type extension
PhpStorm stubs now declare @throws \Exception on ReflectionClassConstant::__construct.
Mirror the ReflectionProperty extension so that constructing it for a known class
constant is not treated as throwing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W2hMwFhyMX7JHn6oVKrTPD
  • Loading branch information
ondrejmirtes and claude committed Sep 14, 2026
commit 14db9d8560d8cbe48b586909a9a4854980121453
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Php;

use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Type\DynamicStaticMethodThrowTypeExtension;
use PHPStan\Type\NeverType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use ReflectionClassConstant;
use function count;

#[AutowiredService]
final class ReflectionClassConstantConstructorThrowTypeExtension implements DynamicStaticMethodThrowTypeExtension
{

public function __construct(private ReflectionProvider $reflectionProvider)
{
}

public function isStaticMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === '__construct' && $methodReflection->getDeclaringClass()->getName() === ReflectionClassConstant::class;
}

public function getThrowTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type
{
if (count($methodCall->getArgs()) < 2) {
return $methodReflection->getThrowType();
}

$valueType = $scope->getType($methodCall->getArgs()[0]->value);
$constantType = $scope->getType($methodCall->getArgs()[1]->value);
foreach ($valueType->getConstantStrings() as $constantString) {
if (!$this->reflectionProvider->hasClass($constantString->getValue())) {
return $methodReflection->getThrowType();
}

$classReflection = $this->reflectionProvider->getClass($constantString->getValue());
foreach ($constantType->getConstantStrings() as $constantConstantString) {
if (!$classReflection->hasConstant($constantConstantString->getValue())) {
return $methodReflection->getThrowType();
}
}

$valueType = TypeCombinator::remove($valueType, $constantString);
}

if (!$valueType instanceof NeverType) {
return $methodReflection->getThrowType();
}

// Look for non constantStrings value.
foreach ($constantType->getConstantStrings() as $constantConstantString) {
$constantType = TypeCombinator::remove($constantType, $constantConstantString);
}

if (!$constantType instanceof NeverType) {
return $methodReflection->getThrowType();
}

return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ public function testRule(): void
'Dead catch - ArithmeticError is never thrown in the try block.',
762,
],
[
'Dead catch - Exception is never thrown in the try block.',
802,
],
]);
}

Expand Down Expand Up @@ -233,6 +237,10 @@ public function testRuleWithoutReportingUncheckedException(): void
'Dead catch - Exception is never thrown in the try block.',
555,
],
[
'Dead catch - Exception is never thrown in the try block.',
802,
],
]);
}

Expand Down Expand Up @@ -842,4 +850,15 @@ public function testBug9826(): void
$this->analyse([__DIR__ . '/data/bug-9826.php'], []);
}

#[RequiresPhp('>= 8.0.0')]
public function testTriggerErrorThrowType(): void
{
$this->analyse([__DIR__ . '/data/trigger-error-throw-type.php'], [
[
'Dead catch - Exception is never thrown in the try block.',
39,
],
]);
}

}
48 changes: 48 additions & 0 deletions tests/PHPStan/Rules/Exceptions/data/unthrown-exception.php
Original file line number Diff line number Diff line change
Expand Up @@ -790,3 +790,51 @@ public function doFoo(int $int, int $negativeInt, int $positiveInt): void
}

}

class TestReflectionClassConstant
{
public const FOO = 1;

public function doFoo(): void
{
try {
new \ReflectionClassConstant(self::class, 'FOO');
} catch (\Exception $e) {

}
}

public function doBar(): void
{
try {
new \ReflectionClassConstant(self::class, 'BAR');
} catch (\Exception $e) {

}
try {
new \ReflectionClassConstant(\DateTime::class, 'BAR');
} catch (\Exception $e) {

}
}

public function doBaz(string $string): void
{
try {
new \ReflectionClassConstant($string, $string);
} catch (\Exception $e) {

}
try {
new \ReflectionClassConstant(self::class, $string);
} catch (\Exception $e) {

}
try {
new \ReflectionClassConstant($string, 'FOO');
} catch (\Exception $e) {

}
}

}