Skip to content

Commit 6958f86

Browse files
committed
[PHP 8.5] Global constants support attributes
1 parent 73aedf6 commit 6958f86

File tree

13 files changed

+329
-5
lines changed

13 files changed

+329
-5
lines changed

build/ignore-by-php-version.neon.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
$includes[] = __DIR__ . '/new-phpunit.neon';
4242
}
4343

44+
if (PHP_VERSION_ID < 80500) {
45+
$includes[] = __DIR__ . '/pre-php-85.neon';
46+
}
47+
4448
$config = [];
4549
$config['includes'] = $includes;
4650

build/pre-php-85.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
parameters:
2+
excludePaths:
3+
- ../src/Rules/Constants/ConstantAttributesRule.php

src/Php/PhpVersion.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,4 +449,9 @@ public function deprecatesBacktickOperator(): bool
449449
return $this->versionId >= 80500;
450450
}
451451

452+
public function supportsAttributesOnGlobalConstants(): bool
453+
{
454+
return $this->versionId >= 80500;
455+
}
456+
452457
}

src/Reflection/BetterReflection/BetterReflectionProvider.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
use PHPStan\Analyser\Scope;
99
use PHPStan\BetterReflection\Identifier\Exception\InvalidIdentifierName;
1010
use PHPStan\BetterReflection\NodeCompiler\Exception\UnableToCompileNode;
11+
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionAttributeFactory;
1112
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionClass;
1213
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionFunction;
1314
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionParameter;
15+
use PHPStan\BetterReflection\Reflection\ReflectionAttribute as BetterReflectionAttribute;
1416
use PHPStan\BetterReflection\Reflection\ReflectionEnum;
1517
use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound;
1618
use PHPStan\BetterReflection\Reflector\Reflector;
@@ -450,12 +452,20 @@ public function getConstant(Node\Name $nameNode, ?NamespaceAnswerer $namespaceAn
450452
}
451453
}
452454

455+
if (!$isDeprecated) {
456+
$isDeprecated = $constantReflection->isDeprecated();
457+
}
458+
453459
return $this->cachedConstants[$constantName] = new RuntimeConstantReflection(
454460
$constantName,
455461
$constantValueType,
456462
$fileName,
457463
TrinaryLogic::createFromBoolean($isDeprecated),
458464
$deprecatedDescription,
465+
$this->attributeReflectionFactory->fromNativeReflection(
466+
array_map(static fn (BetterReflectionAttribute $betterReflectionAttribute) => ReflectionAttributeFactory::create($betterReflectionAttribute), $constantReflection->getAttributes()),
467+
InitializerExprContext::fromGlobalConstant($constantReflection),
468+
),
459469
);
460470
}
461471

src/Reflection/ClassConstantReflection.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,4 @@ public function hasNativeType(): bool;
2121

2222
public function getNativeType(): ?Type;
2323

24-
/**
25-
* @return list<AttributeReflection>
26-
*/
27-
public function getAttributes(): array;
28-
2924
}

src/Reflection/Constant/RuntimeConstantReflection.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@
22

33
namespace PHPStan\Reflection\Constant;
44

5+
use PHPStan\Reflection\AttributeReflection;
56
use PHPStan\Reflection\ConstantReflection;
67
use PHPStan\TrinaryLogic;
78
use PHPStan\Type\Type;
89

910
final class RuntimeConstantReflection implements ConstantReflection
1011
{
1112

13+
/**
14+
* @param list<AttributeReflection> $attributes
15+
*/
1216
public function __construct(
1317
private string $name,
1418
private Type $valueType,
1519
private ?string $fileName,
1620
private TrinaryLogic $isDeprecated,
1721
private ?string $deprecatedDescription,
22+
private array $attributes,
1823
)
1924
{
2025
}
@@ -49,4 +54,9 @@ public function isInternal(): TrinaryLogic
4954
return TrinaryLogic::createNo();
5055
}
5156

57+
public function getAttributes(): array
58+
{
59+
return $this->attributes;
60+
}
61+
5262
}

src/Reflection/ConstantReflection.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,9 @@ public function isInternal(): TrinaryLogic;
2121

2222
public function getFileName(): ?string;
2323

24+
/**
25+
* @return list<AttributeReflection>
26+
*/
27+
public function getAttributes(): array;
28+
2429
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Constants;
4+
5+
use Attribute;
6+
use PhpParser\Node;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\DependencyInjection\RegisteredRule;
9+
use PHPStan\Php\PhpVersion;
10+
use PHPStan\Rules\AttributesCheck;
11+
use PHPStan\Rules\Rule;
12+
use PHPStan\Rules\RuleErrorBuilder;
13+
use const PHP_VERSION_ID;
14+
15+
/**
16+
* @implements Rule<Node\Stmt\Const_>
17+
*/
18+
#[RegisteredRule(level: 0)]
19+
final class ConstantAttributesRule implements Rule
20+
{
21+
22+
public function __construct(
23+
private AttributesCheck $attributesCheck,
24+
private PhpVersion $phpVersion,
25+
)
26+
{
27+
}
28+
29+
public function getNodeType(): string
30+
{
31+
return Node\Stmt\Const_::class;
32+
}
33+
34+
public function processNode(Node $node, Scope $scope): array
35+
{
36+
if ($node->attrGroups === []) {
37+
return [];
38+
}
39+
40+
if (!$this->phpVersion->supportsAttributesOnGlobalConstants()) {
41+
return [
42+
RuleErrorBuilder::message('Attributes on global constants are supported only on PHP 8.5 and later.')
43+
->identifier('constant.attributesNotSupported')
44+
->nonIgnorable()
45+
->build(),
46+
];
47+
}
48+
49+
if (PHP_VERSION_ID < 80500) {
50+
// because of Attribute::TARGET_CONSTANT constant
51+
return [
52+
RuleErrorBuilder::message('ConstantAttributesRule requires PHP 8.5 runtime to check the code.')
53+
->identifier('constant.attributesRuleCannotRun')
54+
->nonIgnorable()
55+
->build(),
56+
];
57+
}
58+
59+
return $this->attributesCheck->check(
60+
$scope,
61+
$node->attrGroups,
62+
Attribute::TARGET_CONSTANT,
63+
'constant',
64+
);
65+
}
66+
67+
}

tests/PHPStan/Reflection/AttributeReflectionTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,21 @@ public static function dataAttributeReflections(): iterable
135135
];
136136
}
137137

138+
if (PHP_VERSION_ID >= 80500) {
139+
yield [
140+
$reflectionProvider->getConstant(new Name('AttributeReflectionTest\\ExampleConstWithAttribute'), null)->getAttributes(),
141+
[
142+
[
143+
MyAttr::class,
144+
[
145+
'one' => '1',
146+
'two' => '2',
147+
],
148+
],
149+
],
150+
];
151+
}
152+
138153
yield [
139154
$foo->getConstructor()->getOnlyVariant()->getParameters()[0]->getAttributes(),
140155
[

tests/PHPStan/Reflection/ReflectionProviderTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,36 @@ public function testFunctionDeprecated(string $functionName, bool $isDeprecated)
108108
$this->assertEquals(TrinaryLogic::createFromBoolean($isDeprecated), $function->isDeprecated());
109109
}
110110

111+
public static function dataConstantDeprecated(): iterable
112+
{
113+
if (PHP_VERSION_ID >= 80500) {
114+
yield [
115+
'AttributeReflectionTest\\ExampleConstWithAttribute',
116+
false,
117+
];
118+
yield [
119+
'AttributeReflectionTest\\DeprecatedConst',
120+
true,
121+
];
122+
}
123+
124+
yield [
125+
'AttributeReflectionTest\\DeprecatedConstWithPhpDoc',
126+
true,
127+
];
128+
}
129+
130+
/**
131+
* @param non-empty-string $constantName
132+
*/
133+
#[DataProvider('dataConstantDeprecated')]
134+
public function testConstantDeprecated(string $constantName, bool $isDeprecated): void
135+
{
136+
$reflectionProvider = self::createReflectionProvider();
137+
$constant = $reflectionProvider->getConstant(new Name($constantName), null);
138+
$this->assertSame(TrinaryLogic::createFromBoolean($isDeprecated)->describe(), $constant->isDeprecated()->describe());
139+
}
140+
111141
public static function dataMethodThrowType(): array
112142
{
113143
return [

0 commit comments

Comments
 (0)