Skip to content

Commit 732eacc

Browse files
ondrejmirtesclaude
andcommitted
Do not infer false for get_class() without arguments on PHP 8
get_class() without arguments outside of a class returns false on PHP 7, but PHP 8 throws Error instead. In a function it always throws, so it is never now. Top-level code might be included from a method and a closure bound to an object, where it returns the class name, so it is class-string there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dhb4ssXuKVWcVBcNFpsdn7
1 parent cdb9f8b commit 732eacc

4 files changed

Lines changed: 68 additions & 1 deletion

File tree

‎src/Type/Php/GetClassDynamicReturnTypeExtension.php‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
use PhpParser\Node\Expr\FuncCall;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\DependencyInjection\AutowiredService;
8+
use PHPStan\Php\PhpVersion;
89
use PHPStan\Reflection\FunctionReflection;
910
use PHPStan\Type\ClassStringType;
1011
use PHPStan\Type\Constant\ConstantBooleanType;
1112
use PHPStan\Type\Constant\ConstantStringType;
1213
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
14+
use PHPStan\Type\NeverType;
1315
use PHPStan\Type\Type;
1416
use PHPStan\Type\TypeUtils;
1517
use function count;
@@ -18,6 +20,10 @@
1820
final class GetClassDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
1921
{
2022

23+
public function __construct(private PhpVersion $phpVersion)
24+
{
25+
}
26+
2127
public function isFunctionSupported(FunctionReflection $functionReflection): bool
2228
{
2329
return $functionReflection->getName() === 'get_class';
@@ -36,6 +42,16 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
3642
return new ConstantStringType($scope->getClassReflection()->getName(), true);
3743
}
3844

45+
// PHP 8 throws Error instead of returning false. Top-level code
46+
// might be included from a method and a closure bound to an object.
47+
if ($this->phpVersion->throwsValueErrorForInternalFunctions()) {
48+
if ($scope->getFunction() !== null && !$scope->isInAnonymousFunction()) {
49+
return new NeverType(true);
50+
}
51+
52+
return new ClassStringType();
53+
}
54+
3955
return new ConstantBooleanType(false);
4056
}
4157

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php // lint < 8.0
2+
3+
namespace GetClassWithoutArgumentPhp7;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
public function doFoo(): void
11+
{
12+
assertType('\'GetClassWithoutArgumentPhp7\\\\Foo\'', get_class());
13+
}
14+
15+
}
16+
17+
function doBar(): void
18+
{
19+
assertType('false', get_class());
20+
}
21+
22+
function (): void {
23+
assertType('false', get_class());
24+
};
25+
26+
assertType('false', get_class());
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php // lint >= 8.0
2+
3+
namespace GetClassWithoutArgumentPhp8;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
class Foo
8+
{
9+
10+
public function doFoo(): void
11+
{
12+
assertType('\'GetClassWithoutArgumentPhp8\\\\Foo\'', get_class());
13+
}
14+
15+
}
16+
17+
function doBar(): void
18+
{
19+
assertType('*NEVER*', get_class());
20+
}
21+
22+
function (): void {
23+
assertType('class-string', get_class());
24+
};
25+
26+
assertType('class-string', get_class());

‎tests/PHPStan/Analyser/nsrt/get-parent-class.php‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ function (string $s) {
3737
assertType('class-string|false', get_parent_class(NonexistentClass::class));
3838
assertType('class-string|false', get_parent_class(1));
3939
assertType('\'ParentClass\\\\Foo\'', get_parent_class(\ParentClass\Bar::class));
40-
assertType('false', get_class());
4140
};
4241

4342
trait FooTrait

0 commit comments

Comments
 (0)