Skip to content

Commit b455c3e

Browse files
staabmclaude
authored andcommitted
Resolve "supported only on PHP X" checks from Scope::getPhpVersion() instead of the injected PhpVersion
- Add 20 range-aware `supports*(): TrinaryLogic` methods to `PHPStan\Php\PhpVersions` mirroring their `PhpVersion` counterparts, so scope-narrowed PHP version ranges can answer feature-support queries. - Switch every rule that emits a non-ignorable "… is supported only on PHP X and later." error to `$scope->getPhpVersion()->supportsX()->yes()`, and drop the now-unused `PhpVersion` constructor dependency: - `Constants/NativeTypedClassConstantRule` (the reported case), `Constants/FinalConstantRule`, `Constants/DynamicClassConstantFetchRule`, `Constants/ConstantAttributesRule` - `Classes/ReadOnlyClassRule` (both readonly and anonymous readonly classes), `Classes/ClassConstantRule` (`::class` on an expression), `Classes/InvalidPromotedPropertiesRule` (promoted and final promoted properties) - `Properties/ReadOnlyPropertyRule`, `Properties/PropertiesInInterfaceRule`, `Properties/PropertyInClassRule` (final properties, property hooks, asymmetric visibility on static properties), `Properties/PropertyAttributesRule` - `Methods/MethodCallableRule`, `Methods/StaticMethodCallableRule`, `Functions/FunctionCallableRule` (first-class callables) - `Cast/VoidCastRule`, `Exceptions/ThrowExpressionRule`, `Traits/TraitAttributesRule`, `Functions/ExistingClassesInArrowFunctionTypehintsRule` (never return type) - `FunctionDefinitionCheck` native union types, covering the four `ExistingClassesIn*TypehintsRule` rules and property hooks - Add `tests/PHPStan/Rules/php-version-*.neon` fixtures and rework the affected rule tests to configure the analysed PHP version through the container instead of handing a fake `PhpVersion` to the rule, so the scope agrees with the configured version. - Add 17 regression fixtures declaring the same construct inside `if (PHP_VERSION_ID >= X)`, inside `if (PHP_VERSION_ID < X)` and unguarded; only the latter two are expected to be reported. - `FunctionCallParametersCheck` (named arguments), `NoncapturingCatchRule` and `FinalPrivateMethodRule` already used the scope and needed no change. Closes phpstan/phpstan#13133 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 715eccd commit b455c3e

65 files changed

Lines changed: 1186 additions & 217 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎src/Php/PhpVersions.php‎

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,99 @@ public function supportsMaxMemoryLimit(): TrinaryLogic
6666
return IntegerRangeType::fromInterval(80500, null)->isSuperTypeOf($this->phpVersions)->result;
6767
}
6868

69+
public function supportsThrowExpression(): TrinaryLogic
70+
{
71+
return IntegerRangeType::fromInterval(80000, null)->isSuperTypeOf($this->phpVersions)->result;
72+
}
73+
74+
public function supportsClassConstantOnExpression(): TrinaryLogic
75+
{
76+
return IntegerRangeType::fromInterval(80000, null)->isSuperTypeOf($this->phpVersions)->result;
77+
}
78+
79+
public function supportsPromotedProperties(): TrinaryLogic
80+
{
81+
return IntegerRangeType::fromInterval(80000, null)->isSuperTypeOf($this->phpVersions)->result;
82+
}
83+
84+
public function supportsNativeUnionTypes(): TrinaryLogic
85+
{
86+
return IntegerRangeType::fromInterval(80000, null)->isSuperTypeOf($this->phpVersions)->result;
87+
}
88+
89+
public function supportsFinalConstants(): TrinaryLogic
90+
{
91+
return IntegerRangeType::fromInterval(80100, null)->isSuperTypeOf($this->phpVersions)->result;
92+
}
93+
94+
public function supportsReadOnlyProperties(): TrinaryLogic
95+
{
96+
return IntegerRangeType::fromInterval(80100, null)->isSuperTypeOf($this->phpVersions)->result;
97+
}
98+
99+
public function supportsFirstClassCallables(): TrinaryLogic
100+
{
101+
return IntegerRangeType::fromInterval(80100, null)->isSuperTypeOf($this->phpVersions)->result;
102+
}
103+
104+
public function supportsReadOnlyClasses(): TrinaryLogic
105+
{
106+
return IntegerRangeType::fromInterval(80200, null)->isSuperTypeOf($this->phpVersions)->result;
107+
}
108+
109+
public function supportsNeverReturnTypeInArrowFunction(): TrinaryLogic
110+
{
111+
return IntegerRangeType::fromInterval(80200, null)->isSuperTypeOf($this->phpVersions)->result;
112+
}
113+
114+
public function supportsDynamicClassConstantFetch(): TrinaryLogic
115+
{
116+
return IntegerRangeType::fromInterval(80300, null)->isSuperTypeOf($this->phpVersions)->result;
117+
}
118+
119+
public function supportsReadOnlyAnonymousClasses(): TrinaryLogic
120+
{
121+
return IntegerRangeType::fromInterval(80300, null)->isSuperTypeOf($this->phpVersions)->result;
122+
}
123+
124+
public function supportsPropertyHooks(): TrinaryLogic
125+
{
126+
return IntegerRangeType::fromInterval(80400, null)->isSuperTypeOf($this->phpVersions)->result;
127+
}
128+
129+
public function supportsFinalProperties(): TrinaryLogic
130+
{
131+
return IntegerRangeType::fromInterval(80400, null)->isSuperTypeOf($this->phpVersions)->result;
132+
}
133+
134+
public function supportsAsymmetricVisibilityForStaticProperties(): TrinaryLogic
135+
{
136+
return IntegerRangeType::fromInterval(80500, null)->isSuperTypeOf($this->phpVersions)->result;
137+
}
138+
139+
public function supportsFinalPromotedProperties(): TrinaryLogic
140+
{
141+
return IntegerRangeType::fromInterval(80500, null)->isSuperTypeOf($this->phpVersions)->result;
142+
}
143+
144+
public function supportsVoidCast(): TrinaryLogic
145+
{
146+
return IntegerRangeType::fromInterval(80500, null)->isSuperTypeOf($this->phpVersions)->result;
147+
}
148+
149+
public function supportsAttributesOnGlobalConstants(): TrinaryLogic
150+
{
151+
return IntegerRangeType::fromInterval(80500, null)->isSuperTypeOf($this->phpVersions)->result;
152+
}
153+
154+
public function supportsDeprecatedTraits(): TrinaryLogic
155+
{
156+
return IntegerRangeType::fromInterval(80500, null)->isSuperTypeOf($this->phpVersions)->result;
157+
}
158+
159+
public function supportsOverrideAttributeOnProperty(): TrinaryLogic
160+
{
161+
return IntegerRangeType::fromInterval(80500, null)->isSuperTypeOf($this->phpVersions)->result;
162+
}
163+
69164
}

‎src/Rules/Cast/VoidCastRule.php‎

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\DependencyInjection\RegisteredRule;
8-
use PHPStan\Php\PhpVersion;
98
use PHPStan\Rules\Rule;
109
use PHPStan\Rules\RuleErrorBuilder;
1110

@@ -16,12 +15,6 @@
1615
final class VoidCastRule implements Rule
1716
{
1817

19-
public function __construct(
20-
private PhpVersion $phpVersion,
21-
)
22-
{
23-
}
24-
2518
public function getNodeType(): string
2619
{
2720
return Node\Expr\Cast\Void_::class;
@@ -30,7 +23,7 @@ public function getNodeType(): string
3023
public function processNode(Node $node, Scope $scope): array
3124
{
3225
$errors = [];
33-
if (!$this->phpVersion->supportsVoidCast()) {
26+
if (!$scope->getPhpVersion()->supportsVoidCast()->yes()) {
3427
$errors[] = RuleErrorBuilder::message('The (void) cast is supported only on PHP 8.5 and later.')
3528
->identifier('cast.voidNotSupported')
3629
->nonIgnorable()

‎src/Rules/Classes/ClassConstantRule.php‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
use PHPStan\Analyser\Scope;
1111
use PHPStan\DependencyInjection\RegisteredRule;
1212
use PHPStan\Internal\SprintfHelper;
13-
use PHPStan\Php\PhpVersion;
1413
use PHPStan\Reflection\ReflectionProvider;
1514
use PHPStan\Rules\ClassNameCheck;
1615
use PHPStan\Rules\ClassNameNodePair;
@@ -42,7 +41,6 @@ public function __construct(
4241
private ReflectionProvider $reflectionProvider,
4342
private RuleLevelHelper $ruleLevelHelper,
4443
private ClassNameCheck $classCheck,
45-
private PhpVersion $phpVersion,
4644
private NonStringableDynamicAccessCheck $nonStringableDynamicAccessCheck,
4745
)
4846
{
@@ -196,7 +194,7 @@ private function processSingleClassConstFetch(Scope $scope, ClassConstFetch $nod
196194
}
197195

198196
if (strtolower($constantName) === 'class') {
199-
if (!$this->phpVersion->supportsClassConstantOnExpression()) {
197+
if (!$scope->getPhpVersion()->supportsClassConstantOnExpression()->yes()) {
200198
return [
201199
RuleErrorBuilder::message('Accessing ::class constant on an expression is supported only on PHP 8.0 and later.')
202200
->identifier('classConstant.notSupported')

‎src/Rules/Classes/InvalidPromotedPropertiesRule.php‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\DependencyInjection\RegisteredRule;
8-
use PHPStan\Php\PhpVersion;
98
use PHPStan\Rules\Rule;
109
use PHPStan\Rules\RuleErrorBuilder;
1110
use PHPStan\ShouldNotHappenException;
@@ -19,10 +18,6 @@
1918
final class InvalidPromotedPropertiesRule implements Rule
2019
{
2120

22-
public function __construct(private PhpVersion $phpVersion)
23-
{
24-
}
25-
2621
public function getNodeType(): string
2722
{
2823
return Node\FunctionLike::class;
@@ -50,7 +45,7 @@ public function processNode(Node $node, Scope $scope): array
5045
return [];
5146
}
5247

53-
if (!$this->phpVersion->supportsPromotedProperties()) {
48+
if (!$scope->getPhpVersion()->supportsPromotedProperties()->yes()) {
5449
return [
5550
RuleErrorBuilder::message(
5651
'Promoted properties are supported only on PHP 8.0 and later.',
@@ -89,7 +84,7 @@ public function processNode(Node $node, Scope $scope): array
8984
throw new ShouldNotHappenException();
9085
}
9186

92-
if ($param->isFinal() && !$this->phpVersion->supportsFinalPromotedProperties()) {
87+
if ($param->isFinal() && !$scope->getPhpVersion()->supportsFinalPromotedProperties()->yes()) {
9388
$errors[] = RuleErrorBuilder::message(
9489
'Final promoted properties are supported only on PHP 8.5 and later.',
9590
)->identifier('property.invalidPromoted')->nonIgnorable()->line($param->getStartLine())->build();

‎src/Rules/Classes/ReadOnlyClassRule.php‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\DependencyInjection\RegisteredRule;
88
use PHPStan\Node\InClassNode;
9-
use PHPStan\Php\PhpVersion;
109
use PHPStan\Rules\Rule;
1110
use PHPStan\Rules\RuleErrorBuilder;
1211

@@ -17,10 +16,6 @@
1716
final class ReadOnlyClassRule implements Rule
1817
{
1918

20-
public function __construct(private PhpVersion $phpVersion)
21-
{
22-
}
23-
2419
public function getNodeType(): string
2520
{
2621
return InClassNode::class;
@@ -33,7 +28,7 @@ public function processNode(Node $node, Scope $scope): array
3328
return [];
3429
}
3530
if ($classReflection->isAnonymous()) {
36-
if ($this->phpVersion->supportsReadOnlyAnonymousClasses()) {
31+
if ($scope->getPhpVersion()->supportsReadOnlyAnonymousClasses()->yes()) {
3732
return [];
3833
}
3934

@@ -45,7 +40,7 @@ public function processNode(Node $node, Scope $scope): array
4540
];
4641
}
4742

48-
if ($this->phpVersion->supportsReadOnlyClasses()) {
43+
if ($scope->getPhpVersion()->supportsReadOnlyClasses()->yes()) {
4944
return [];
5045
}
5146

‎src/Rules/Constants/ConstantAttributesRule.php‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use PHPStan\Analyser\NodeCallbackInvoker;
99
use PHPStan\Analyser\Scope;
1010
use PHPStan\DependencyInjection\RegisteredRule;
11-
use PHPStan\Php\PhpVersion;
1211
use PHPStan\Rules\AttributesCheck;
1312
use PHPStan\Rules\Rule;
1413
use PHPStan\Rules\RuleErrorBuilder;
@@ -23,7 +22,6 @@ final class ConstantAttributesRule implements Rule
2322

2423
public function __construct(
2524
private AttributesCheck $attributesCheck,
26-
private PhpVersion $phpVersion,
2725
)
2826
{
2927
}
@@ -39,7 +37,7 @@ public function processNode(Node $node, Scope&NodeCallbackInvoker&CollectedDataE
3937
return [];
4038
}
4139

42-
if (!$this->phpVersion->supportsAttributesOnGlobalConstants()) {
40+
if (!$scope->getPhpVersion()->supportsAttributesOnGlobalConstants()->yes()) {
4341
return [
4442
RuleErrorBuilder::message('Attributes on global constants are supported only on PHP 8.5 and later.')
4543
->identifier('constant.attributesNotSupported')

‎src/Rules/Constants/DynamicClassConstantFetchRule.php‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use PhpParser\Node\Expr\ClassConstFetch;
77
use PHPStan\Analyser\Scope;
88
use PHPStan\DependencyInjection\RegisteredRule;
9-
use PHPStan\Php\PhpVersion;
109
use PHPStan\Rules\Rule;
1110
use PHPStan\Rules\RuleErrorBuilder;
1211
use PHPStan\Rules\RuleLevelHelper;
@@ -22,7 +21,7 @@
2221
final class DynamicClassConstantFetchRule implements Rule
2322
{
2423

25-
public function __construct(private PhpVersion $phpVersion, private RuleLevelHelper $ruleLevelHelper)
24+
public function __construct(private RuleLevelHelper $ruleLevelHelper)
2625
{
2726
}
2827

@@ -37,7 +36,7 @@ public function processNode(Node $node, Scope $scope): array
3736
return [];
3837
}
3938

40-
if (!$this->phpVersion->supportsDynamicClassConstantFetch()) {
39+
if (!$scope->getPhpVersion()->supportsDynamicClassConstantFetch()->yes()) {
4140
return [
4241
RuleErrorBuilder::message('Fetching class constants with a dynamic name is supported only on PHP 8.3 and later.')
4342
->identifier('classConstant.dynamicFetch')

‎src/Rules/Constants/FinalConstantRule.php‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use PhpParser\Node\Stmt\ClassConst;
77
use PHPStan\Analyser\Scope;
88
use PHPStan\DependencyInjection\RegisteredRule;
9-
use PHPStan\Php\PhpVersion;
109
use PHPStan\Rules\Rule;
1110
use PHPStan\Rules\RuleErrorBuilder;
1211

@@ -15,10 +14,6 @@
1514
final class FinalConstantRule implements Rule
1615
{
1716

18-
public function __construct(private PhpVersion $phpVersion)
19-
{
20-
}
21-
2217
public function getNodeType(): string
2318
{
2419
return ClassConst::class;
@@ -30,7 +25,7 @@ public function processNode(Node $node, Scope $scope): array
3025
return [];
3126
}
3227

33-
if ($this->phpVersion->supportsFinalConstants()) {
28+
if ($scope->getPhpVersion()->supportsFinalConstants()->yes()) {
3429
return [];
3530
}
3631

‎src/Rules/Exceptions/ThrowExpressionRule.php‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use PHPStan\Analyser\Scope;
77
use PHPStan\DependencyInjection\RegisteredRule;
88
use PHPStan\Parser\StandaloneThrowExprVisitor;
9-
use PHPStan\Php\PhpVersion;
109
use PHPStan\Rules\Rule;
1110
use PHPStan\Rules\RuleErrorBuilder;
1211

@@ -17,18 +16,14 @@
1716
final class ThrowExpressionRule implements Rule
1817
{
1918

20-
public function __construct(private PhpVersion $phpVersion)
21-
{
22-
}
23-
2419
public function getNodeType(): string
2520
{
2621
return Node\Expr\Throw_::class;
2722
}
2823

2924
public function processNode(Node $node, Scope $scope): array
3025
{
31-
if ($this->phpVersion->supportsThrowExpression()) {
26+
if ($scope->getPhpVersion()->supportsThrowExpression()->yes()) {
3227
return [];
3328
}
3429

‎src/Rules/FunctionDefinitionCheck.php‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public function checkAnonymousFunction(
126126
if (
127127
!$unionTypeReported
128128
&& $param->type instanceof UnionType
129-
&& !$this->phpVersion->supportsNativeUnionTypes()
129+
&& !$scope->getPhpVersion()->supportsNativeUnionTypes()->yes()
130130
) {
131131
$errors[] = RuleErrorBuilder::message($unionTypesMessage)
132132
->line($param->getStartLine())
@@ -229,7 +229,7 @@ public function checkAnonymousFunction(
229229
if (
230230
!$unionTypeReported
231231
&& $returnTypeNode instanceof UnionType
232-
&& !$this->phpVersion->supportsNativeUnionTypes()
232+
&& !$scope->getPhpVersion()->supportsNativeUnionTypes()->yes()
233233
) {
234234
$errors[] = RuleErrorBuilder::message($unionTypesMessage)
235235
->line($returnTypeNode->getStartLine())
@@ -373,7 +373,7 @@ private function checkParametersAcceptor(
373373
{
374374
$errors = [];
375375
$parameterNodes = $functionNode->getParams();
376-
if (!$this->phpVersion->supportsNativeUnionTypes()) {
376+
if (!$scope->getPhpVersion()->supportsNativeUnionTypes()->yes()) {
377377
$unionTypeReported = false;
378378
foreach ($parameterNodes as $parameterNode) {
379379
if (!$parameterNode->type instanceof UnionType) {

0 commit comments

Comments
 (0)