Skip to content

Commit 1854985

Browse files
committed
Allow mixed type dynamic constants.
Addresses phpstan/phpstan#7520
1 parent d73acef commit 1854985

File tree

10 files changed

+170
-7
lines changed

10 files changed

+170
-7
lines changed

conf/config.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ parameters:
176176
- ZEND_DEBUG_BUILD
177177
- ZEND_THREAD_SAFE
178178
- E_ALL # different on PHP 8.4
179+
mixedTypeConstantNames:
179180
customRulesetUsed: null
180181
editorUrl: null
181182
editorUrlTitle: null

conf/parametersSchema.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ parametersSchema:
140140
resultCachePath: string()
141141
resultCacheChecksProjectExtensionFilesDependencies: bool()
142142
dynamicConstantNames: listOf(string())
143+
mixedTypeConstantNames: listOf(string())
143144
customRulesetUsed: schema(bool(), nullable())
144145
rootDir: string()
145146
tmpDir: string()

src/Analyser/ConstantResolver.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ final class ConstantResolver
3333

3434
/**
3535
* @param string[] $dynamicConstantNames
36+
* @param string[] $mixedTypeConstantNames
3637
*/
37-
public function __construct(private ReflectionProviderProvider $reflectionProviderProvider, private array $dynamicConstantNames)
38+
public function __construct(private ReflectionProviderProvider $reflectionProviderProvider, private array $dynamicConstantNames, private array $mixedTypeConstantNames)
3839
{
3940
}
4041

@@ -302,8 +303,14 @@ public function resolvePredefinedConstant(string $resolvedConstantName): ?Type
302303

303304
public function resolveConstantType(string $constantName, Type $constantType): Type
304305
{
305-
if ($constantType->isConstantValue()->yes() && in_array($constantName, $this->dynamicConstantNames, true)) {
306-
return $constantType->generalize(GeneralizePrecision::lessSpecific());
306+
if ($constantType->isConstantValue()->yes()) {
307+
if (in_array($constantName, $this->dynamicConstantNames, true)) {
308+
return $constantType->generalize(GeneralizePrecision::lessSpecific());
309+
}
310+
311+
if (in_array($constantName, $this->mixedTypeConstantNames, true)) {
312+
return new MixedType();
313+
}
307314
}
308315

309316
return $constantType;
@@ -322,6 +329,16 @@ public function resolveClassConstantType(string $className, string $constantName
322329
}
323330
}
324331

332+
if (in_array($lookupConstantName, $this->mixedTypeConstantNames, true)) {
333+
if ($nativeType !== null) {
334+
return $nativeType;
335+
}
336+
337+
if ($constantType->isConstantValue()->yes()) {
338+
return new MixedType();
339+
}
340+
}
341+
325342
return $constantType;
326343
}
327344

src/Analyser/ConstantResolverFactory.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public function create(): ConstantResolver
2020
return new ConstantResolver(
2121
$this->reflectionProviderProvider,
2222
$this->container->getParameter('dynamicConstantNames'),
23+
$this->container->getParameter('mixedTypeConstantNames') ?? [],
2324
);
2425
}
2526

src/DependencyInjection/ValidateIgnoredErrorsExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public function loadConfiguration(): void
6565
$reflectionProviderProvider = new DirectReflectionProviderProvider($reflectionProvider);
6666
ReflectionProviderStaticAccessor::registerInstance($reflectionProvider);
6767
PhpVersionStaticAccessor::registerInstance(new PhpVersion(PHP_VERSION_ID));
68-
$constantResolver = new ConstantResolver($reflectionProviderProvider, []);
68+
$constantResolver = new ConstantResolver($reflectionProviderProvider, [], []);
6969

7070
$phpDocParserConfig = new ParserConfig([]);
7171
$ignoredRegexValidator = new IgnoredRegexValidator(

src/Testing/PHPStanTestCase.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,21 @@ public static function getClassReflectionExtensionRegistryProvider(): ClassRefle
127127

128128
/**
129129
* @param string[] $dynamicConstantNames
130+
* @param string[] $mixedTypeConstantNames
130131
*/
131-
public static function createScopeFactory(ReflectionProvider $reflectionProvider, TypeSpecifier $typeSpecifier, array $dynamicConstantNames = []): ScopeFactory
132+
public static function createScopeFactory(ReflectionProvider $reflectionProvider, TypeSpecifier $typeSpecifier, array $dynamicConstantNames = [], array $mixedTypeConstantNames = []): ScopeFactory
132133
{
133134
$container = self::getContainer();
134135

135136
if (count($dynamicConstantNames) === 0) {
136137
$dynamicConstantNames = $container->getParameter('dynamicConstantNames');
137138
}
139+
if (count($mixedTypeConstantNames) === 0) {
140+
$mixedTypeConstantNames = $container->getParameter('mixedTypeConstantNames') ?? [];
141+
}
138142

139143
$reflectionProviderProvider = new DirectReflectionProviderProvider($reflectionProvider);
140-
$constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames);
144+
$constantResolver = new ConstantResolver($reflectionProviderProvider, $dynamicConstantNames, $mixedTypeConstantNames);
141145

142146
$initializerExprTypeResolver = new InitializerExprTypeResolver(
143147
$constantResolver,

src/Testing/TypeInferenceTestCase.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@ abstract class TypeInferenceTestCase extends PHPStanTestCase
4949
/**
5050
* @param callable(Node , Scope ): void $callback
5151
* @param string[] $dynamicConstantNames
52+
* @param string[] $mixedTypeConstantNames
5253
*/
5354
public static function processFile(
5455
string $file,
5556
callable $callback,
5657
array $dynamicConstantNames = [],
58+
array $mixedTypeConstantNames = [],
5759
): void
5860
{
5961
$reflectionProvider = self::createReflectionProvider();
@@ -88,7 +90,7 @@ public static function processFile(
8890
);
8991
$resolver->setAnalysedFiles(array_map(static fn (string $file): string => $fileHelper->normalizePath($file), array_merge([$file], static::getAdditionalAnalysedFiles())));
9092

91-
$scopeFactory = self::createScopeFactory($reflectionProvider, $typeSpecifier, $dynamicConstantNames);
93+
$scopeFactory = self::createScopeFactory($reflectionProvider, $typeSpecifier, $dynamicConstantNames, $mixedTypeConstantNames);
9294
$scope = $scopeFactory->create(ScopeContext::create($file));
9395

9496
$resolver->processNodes(

tests/PHPStan/Analyser/LegacyNodeScopeResolverTest.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8535,6 +8535,102 @@ public function testDynamicConstantsWithNativeTypes(
85358535
);
85368536
}
85378537

8538+
public function dataMixedTypeConstants(): array
8539+
{
8540+
return [
8541+
[
8542+
'mixed',
8543+
'MixedTypeConstants\MixedTypeConstantClass::MIXED_TYPE_CONSTANT_IN_CLASS',
8544+
],
8545+
[
8546+
"'abc123def'",
8547+
'MixedTypeConstants\MixedTypeConstantClass::PURE_CONSTANT_IN_CLASS',
8548+
],
8549+
[
8550+
"'xyz'",
8551+
'MixedTypeConstants\NoMixedTypeConstantClass::MIXED_TYPE_CONSTANT_IN_CLASS',
8552+
],
8553+
[
8554+
'false',
8555+
'GLOBAL_MIXED_TYPE_CONSTANT',
8556+
],
8557+
[
8558+
'123',
8559+
'GLOBAL_PURE_CONSTANT',
8560+
],
8561+
];
8562+
}
8563+
8564+
/**
8565+
* @dataProvider dataMixedTypeConstants
8566+
*/
8567+
public function testMixedTypeConstants(
8568+
string $description,
8569+
string $expression,
8570+
): void
8571+
{
8572+
$this->assertTypes(
8573+
__DIR__ . '/data/mixed-type-constant.php',
8574+
$description,
8575+
$expression,
8576+
'die',
8577+
[],
8578+
true,
8579+
[
8580+
'MixedTypeConstants\\MixedTypeConstantClass::MIXED_TYPE_CONSTANT_IN_CLASS',
8581+
'GLOBAL_MIXED_TYPE_CONSTANT',
8582+
],
8583+
);
8584+
}
8585+
8586+
public function dataMixedTypeConstantsWithNativeTypes(): array
8587+
{
8588+
return [
8589+
[
8590+
'int',
8591+
'MixedTypeConstantNativeTypes\Foo::FOO',
8592+
],
8593+
[
8594+
'int|string',
8595+
'MixedTypeConstantNativeTypes\Foo::BAR',
8596+
],
8597+
[
8598+
'int',
8599+
'$foo::FOO',
8600+
],
8601+
[
8602+
'int|string',
8603+
'$foo::BAR',
8604+
],
8605+
];
8606+
}
8607+
8608+
/**
8609+
* @dataProvider dataMixedTypeConstantsWithNativeTypes
8610+
*/
8611+
public function testMixedTypeConstantsWithNativeTypes(
8612+
string $description,
8613+
string $expression,
8614+
): void
8615+
{
8616+
if (PHP_VERSION_ID < 80300) {
8617+
$this->markTestSkipped('Test requires PHP 8.3.');
8618+
}
8619+
8620+
$this->assertTypes(
8621+
__DIR__ . '/data/mixed-type-constant-native-types.php',
8622+
$description,
8623+
$expression,
8624+
'die',
8625+
[],
8626+
true,
8627+
[
8628+
'MixedTypeConstantNativeTypes\Foo::FOO',
8629+
'MixedTypeConstantNativeTypes\Foo::BAR',
8630+
],
8631+
);
8632+
}
8633+
85388634
public function dataIsset(): array
85398635
{
85408636
return [
@@ -9545,6 +9641,7 @@ public function testTryCatchScope(
95459641

95469642
/**
95479643
* @param string[] $dynamicConstantNames
9644+
* @param string[] $mixedTypeConstantNames
95489645
*/
95499646
private function assertTypes(
95509647
string $file,
@@ -9553,6 +9650,7 @@ private function assertTypes(
95539650
string $evaluatedPointExpression = 'die',
95549651
array $dynamicConstantNames = [],
95559652
bool $useCache = true,
9653+
array $mixedTypeConstantNames = [],
95569654
): void
95579655
{
95589656
$assertType = function (Scope $scope) use ($expression, $description, $evaluatedPointExpression): void {
@@ -9587,6 +9685,7 @@ static function (Node $node, Scope $scope) use ($file, $evaluatedPointExpression
95879685
$assertType($scope);
95889686
},
95899687
$dynamicConstantNames,
9688+
$mixedTypeConstantNames,
95909689
);
95919690
}
95929691

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 MixedTypeConstantNativeTypes;
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+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace MixedTypeConstants;
4+
5+
define('GLOBAL_PURE_CONSTANT', 123);
6+
define('GLOBAL_MIXED_TYPE_CONSTANT', false);
7+
8+
class MixedTypeConstantClass
9+
{
10+
const MIXED_TYPE_CONSTANT_IN_CLASS = 'abcdef';
11+
const PURE_CONSTANT_IN_CLASS = 'abc123def';
12+
}
13+
14+
class NoMixedTypeConstantClass
15+
{
16+
// constant name is same as in MixedTypeConstantClass, just to test
17+
const MIXED_TYPE_CONSTANT_IN_CLASS = 'xyz';
18+
19+
private function rip()
20+
{
21+
die;
22+
}
23+
}

0 commit comments

Comments
 (0)