Skip to content

Commit cb9571b

Browse files
committed
Read class constant type when generalizing the type for dynamicConstantNames
1 parent 714bb44 commit cb9571b

File tree

4 files changed

+95
-5
lines changed

4 files changed

+95
-5
lines changed

src/Analyser/ConstantResolver.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use PHPStan\Type\UnionType;
2121
use function array_key_exists;
2222
use function in_array;
23+
use function sprintf;
2324
use const INF;
2425
use const NAN;
2526
use const PHP_INT_SIZE;
@@ -299,6 +300,22 @@ public function resolveConstantType(string $constantName, Type $constantType): T
299300
return $constantType;
300301
}
301302

303+
public function resolveClassConstantType(string $className, string $constantName, Type $constantType, ?Type $nativeType): Type
304+
{
305+
$lookupConstantName = sprintf('%s::%s', $className, $constantName);
306+
if (in_array($lookupConstantName, $this->dynamicConstantNames, true)) {
307+
if ($nativeType !== null) {
308+
return $nativeType;
309+
}
310+
311+
if ($constantType->isConstantValue()->yes()) {
312+
return $constantType->generalize(GeneralizePrecision::lessSpecific());
313+
}
314+
}
315+
316+
return $constantType;
317+
}
318+
302319
private function getReflectionProvider(): ReflectionProvider
303320
{
304321
return $this->reflectionProviderProvider->getReflectionProvider();

src/Reflection/InitializerExprTypeResolver.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
use PHPStan\Type\ThisType;
6262
use PHPStan\Type\Type;
6363
use PHPStan\Type\TypeCombinator;
64+
use PHPStan\Type\TypehintHelper;
6465
use PHPStan\Type\TypeTraverser;
6566
use PHPStan\Type\TypeUtils;
6667
use PHPStan\Type\TypeWithClassName;
@@ -78,7 +79,6 @@
7879
use function is_int;
7980
use function max;
8081
use function min;
81-
use function sprintf;
8282
use function strtolower;
8383
use const INF;
8484

@@ -1883,9 +1883,15 @@ function (Type $type, callable $traverse): Type {
18831883
}
18841884
$reflectionConstantDeclaringClass = $reflectionConstant->getDeclaringClass();
18851885
$constantType = $this->getType($reflectionConstant->getValueExpression(), InitializerExprContext::fromClass($reflectionConstantDeclaringClass->getName(), $reflectionConstantDeclaringClass->getFileName() ?: null));
1886-
$types[] = $this->constantResolver->resolveConstantType(
1887-
sprintf('%s::%s', $constantClassReflection->getName(), $constantName),
1886+
$nativeType = null;
1887+
if ($reflectionConstant->getType() !== null) {
1888+
$nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), null, $constantClassReflection);
1889+
}
1890+
$types[] = $this->constantResolver->resolveClassConstantType(
1891+
$constantClassReflection->getName(),
1892+
$constantName,
18881893
$constantType,
1894+
$nativeType,
18891895
);
18901896
continue;
18911897
}
@@ -1909,9 +1915,15 @@ function (Type $type, callable $traverse): Type {
19091915
$constantType = $this->getType($constantReflection->getValueExpr(), InitializerExprContext::fromClassReflection($constantReflection->getDeclaringClass()));
19101916
}
19111917

1912-
$constantType = $this->constantResolver->resolveConstantType(
1913-
sprintf('%s::%s', $constantClassReflection->getName(), $constantName),
1918+
$nativeType = null;
1919+
if ($constantReflection instanceof ClassConstantReflection) {
1920+
$nativeType = $constantReflection->getNativeType();
1921+
}
1922+
$constantType = $this->constantResolver->resolveClassConstantType(
1923+
$constantClassReflection->getName(),
1924+
$constantName,
19141925
$constantType,
1926+
$nativeType,
19151927
);
19161928
$types[] = $constantType;
19171929
}

tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8413,6 +8413,52 @@ public function testDynamicConstants(
84138413
);
84148414
}
84158415

8416+
public function dataDynamicConstantsWithNativeTypes(): array
8417+
{
8418+
return [
8419+
[
8420+
'int',
8421+
'DynamicConstantNativeTypes\Foo::FOO',
8422+
],
8423+
[
8424+
'int|string',
8425+
'DynamicConstantNativeTypes\Foo::BAR',
8426+
],
8427+
[
8428+
'int',
8429+
'$foo::FOO',
8430+
],
8431+
[
8432+
'int|string',
8433+
'$foo::BAR',
8434+
],
8435+
];
8436+
}
8437+
8438+
/**
8439+
* @dataProvider dataDynamicConstantsWithNativeTypes
8440+
*/
8441+
public function testDynamicConstantsWithNativeTypes(
8442+
string $description,
8443+
string $expression,
8444+
): void
8445+
{
8446+
if (PHP_VERSION_ID < 80300) {
8447+
$this->markTestSkipped('Test requires PHP 8.3.');
8448+
}
8449+
8450+
$this->assertTypes(
8451+
__DIR__ . '/data/dynamic-constant-native-types.php',
8452+
$description,
8453+
$expression,
8454+
'die',
8455+
[
8456+
'DynamicConstantNativeTypes\Foo::FOO',
8457+
'DynamicConstantNativeTypes\Foo::BAR',
8458+
],
8459+
);
8460+
}
8461+
84168462
public function dataIsset(): array
84178463
{
84188464
return [
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php // lint >= 8.3
2+
3+
namespace DynamicConstantNativeTypes;
4+
5+
final class Foo
6+
{
7+
8+
public const int FOO = 123;
9+
public const int|string BAR = 123;
10+
11+
}
12+
13+
function (Foo $foo): void {
14+
die;
15+
};

0 commit comments

Comments
 (0)