Skip to content

Commit

Permalink
Read class constant type when generalizing the type for `dynamicConst…
Browse files Browse the repository at this point in the history
…antNames`
  • Loading branch information
ondrejmirtes committed Nov 8, 2023
1 parent 714bb44 commit cb9571b
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 5 deletions.
17 changes: 17 additions & 0 deletions src/Analyser/ConstantResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use PHPStan\Type\UnionType;
use function array_key_exists;
use function in_array;
use function sprintf;
use const INF;
use const NAN;
use const PHP_INT_SIZE;
Expand Down Expand Up @@ -299,6 +300,22 @@ public function resolveConstantType(string $constantName, Type $constantType): T
return $constantType;
}

public function resolveClassConstantType(string $className, string $constantName, Type $constantType, ?Type $nativeType): Type
{
$lookupConstantName = sprintf('%s::%s', $className, $constantName);
if (in_array($lookupConstantName, $this->dynamicConstantNames, true)) {
if ($nativeType !== null) {
return $nativeType;
}

if ($constantType->isConstantValue()->yes()) {
return $constantType->generalize(GeneralizePrecision::lessSpecific());
}
}

return $constantType;
}

private function getReflectionProvider(): ReflectionProvider
{
return $this->reflectionProviderProvider->getReflectionProvider();
Expand Down
22 changes: 17 additions & 5 deletions src/Reflection/InitializerExprTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
use PHPStan\Type\ThisType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypehintHelper;
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\TypeWithClassName;
Expand All @@ -78,7 +79,6 @@
use function is_int;
use function max;
use function min;
use function sprintf;
use function strtolower;
use const INF;

Expand Down Expand Up @@ -1883,9 +1883,15 @@ function (Type $type, callable $traverse): Type {
}
$reflectionConstantDeclaringClass = $reflectionConstant->getDeclaringClass();
$constantType = $this->getType($reflectionConstant->getValueExpression(), InitializerExprContext::fromClass($reflectionConstantDeclaringClass->getName(), $reflectionConstantDeclaringClass->getFileName() ?: null));
$types[] = $this->constantResolver->resolveConstantType(
sprintf('%s::%s', $constantClassReflection->getName(), $constantName),
$nativeType = null;
if ($reflectionConstant->getType() !== null) {
$nativeType = TypehintHelper::decideTypeFromReflection($reflectionConstant->getType(), null, $constantClassReflection);
}
$types[] = $this->constantResolver->resolveClassConstantType(
$constantClassReflection->getName(),
$constantName,
$constantType,
$nativeType,
);
continue;
}
Expand All @@ -1909,9 +1915,15 @@ function (Type $type, callable $traverse): Type {
$constantType = $this->getType($constantReflection->getValueExpr(), InitializerExprContext::fromClassReflection($constantReflection->getDeclaringClass()));
}

$constantType = $this->constantResolver->resolveConstantType(
sprintf('%s::%s', $constantClassReflection->getName(), $constantName),
$nativeType = null;
if ($constantReflection instanceof ClassConstantReflection) {
$nativeType = $constantReflection->getNativeType();
}
$constantType = $this->constantResolver->resolveClassConstantType(
$constantClassReflection->getName(),
$constantName,
$constantType,
$nativeType,
);
$types[] = $constantType;
}
Expand Down
46 changes: 46 additions & 0 deletions tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8413,6 +8413,52 @@ public function testDynamicConstants(
);
}

public function dataDynamicConstantsWithNativeTypes(): array
{
return [
[
'int',
'DynamicConstantNativeTypes\Foo::FOO',
],
[
'int|string',
'DynamicConstantNativeTypes\Foo::BAR',
],
[
'int',
'$foo::FOO',
],
[
'int|string',
'$foo::BAR',
],
];
}

/**
* @dataProvider dataDynamicConstantsWithNativeTypes
*/
public function testDynamicConstantsWithNativeTypes(
string $description,
string $expression,
): void
{
if (PHP_VERSION_ID < 80300) {
$this->markTestSkipped('Test requires PHP 8.3.');
}

$this->assertTypes(
__DIR__ . '/data/dynamic-constant-native-types.php',
$description,
$expression,
'die',
[
'DynamicConstantNativeTypes\Foo::FOO',
'DynamicConstantNativeTypes\Foo::BAR',
],
);
}

public function dataIsset(): array
{
return [
Expand Down
15 changes: 15 additions & 0 deletions tests/PHPStan/Analyser/data/dynamic-constant-native-types.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php // lint >= 8.3

namespace DynamicConstantNativeTypes;

final class Foo
{

public const int FOO = 123;
public const int|string BAR = 123;

}

function (Foo $foo): void {
die;
};

0 comments on commit cb9571b

Please sign in to comment.