Skip to content

Commit 8c3d347

Browse files
committed
Implement Scope->getPhpVersion()
1 parent 65ddbcb commit 8c3d347

File tree

6 files changed

+136
-36
lines changed

6 files changed

+136
-36
lines changed

src/Analyser/MutatingScope.php

+32
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
use PHPStan\Parser\NewAssignedToPropertyVisitor;
4848
use PHPStan\Parser\Parser;
4949
use PHPStan\Php\PhpVersion;
50+
use PHPStan\Php\PhpVersions;
5051
use PHPStan\PhpDoc\Tag\TemplateTag;
5152
use PHPStan\Reflection\Assertions;
5253
use PHPStan\Reflection\Callables\CallableParametersAcceptor;
@@ -144,6 +145,7 @@
144145
use function implode;
145146
use function in_array;
146147
use function is_bool;
148+
use function is_int;
147149
use function is_numeric;
148150
use function is_string;
149151
use function ltrim;
@@ -5721,4 +5723,34 @@ public function getIterableValueType(Type $iteratee): Type
57215723
return $iteratee->getIterableValueType();
57225724
}
57235725

5726+
public function getPhpVersions(): PhpVersions
5727+
{
5728+
$versionId = $this->getType(new ConstFetch(new Name('PHP_VERSION_ID')));
5729+
if ($versionId instanceof IntegerRangeType) {
5730+
if ($versionId->getMin() !== null && $versionId->getMax() !== null) {
5731+
return new PhpVersions([$versionId->getMin(), $versionId->getMax()]);
5732+
}
5733+
if ($versionId->getMin() !== null) {
5734+
return new PhpVersions([$versionId->getMin()]);
5735+
}
5736+
if ($versionId->getMax() !== null) {
5737+
return new PhpVersions([$versionId->getMax()]);
5738+
}
5739+
}
5740+
5741+
$scalars = $versionId->getConstantScalarValues();
5742+
if ($scalars !== []) {
5743+
$ints = [];
5744+
foreach ($scalars as $scalar) {
5745+
if (!is_int($scalar)) {
5746+
throw new ShouldNotHappenException();
5747+
}
5748+
$ints[] = $scalar;
5749+
}
5750+
return new PhpVersions($ints);
5751+
}
5752+
5753+
return new PhpVersions([$this->phpVersion->getVersionId()]);
5754+
}
5755+
57245756
}

src/Analyser/Scope.php

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use PhpParser\Node\Expr;
77
use PhpParser\Node\Name;
88
use PhpParser\Node\Param;
9+
use PHPStan\Php\PhpVersions;
910
use PHPStan\Reflection\ClassConstantReflection;
1011
use PHPStan\Reflection\ClassMemberAccessAnswerer;
1112
use PHPStan\Reflection\ClassReflection;
@@ -136,4 +137,6 @@ public function filterByFalseyValue(Expr $expr): self;
136137

137138
public function isInFirstLevelStatement(): bool;
138139

140+
public function getPhpVersions(): PhpVersions;
141+
139142
}

src/Php/PhpVersions.php

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Php;
4+
5+
use PHPStan\ShouldNotHappenException;
6+
use PHPStan\TrinaryLogic;
7+
use function max;
8+
use function min;
9+
10+
/**
11+
* @api
12+
*/
13+
final class PhpVersions
14+
{
15+
16+
private int $minVersionId;
17+
18+
private int $maxVersionId;
19+
20+
/**
21+
* @api
22+
* @param list<int> $phpVersionIds
23+
*/
24+
public function __construct(
25+
array $phpVersionIds,
26+
)
27+
{
28+
if ($phpVersionIds === []) {
29+
throw new ShouldNotHappenException();
30+
}
31+
32+
$normalizedPhpVersionIds = [];
33+
foreach ($phpVersionIds as $versionId) {
34+
// drop patch version part and replace with 00
35+
$normalizedPhpVersionIds[] = ((int) ($versionId / 100)) * 100;
36+
}
37+
38+
$this->minVersionId = min($normalizedPhpVersionIds);
39+
$this->maxVersionId = max($normalizedPhpVersionIds);
40+
}
41+
42+
public function producesWarningForFinalPrivateMethods(): TrinaryLogic
43+
{
44+
return $this->minPhpVersion(80000);
45+
}
46+
47+
private function minPhpVersion(int $versionId): TrinaryLogic
48+
{
49+
if ($this->minVersionId >= $versionId) {
50+
return TrinaryLogic::createYes();
51+
}
52+
if ($this->maxVersionId >= $versionId) {
53+
return TrinaryLogic::createMaybe();
54+
}
55+
return TrinaryLogic::createNo();
56+
}
57+
58+
}

src/Rules/Methods/FinalPrivateMethodRule.php

+1-8
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use PhpParser\Node;
66
use PHPStan\Analyser\Scope;
77
use PHPStan\Node\InClassMethodNode;
8-
use PHPStan\Php\PhpVersion;
98
use PHPStan\Rules\Rule;
109
use PHPStan\Rules\RuleErrorBuilder;
1110
use function sprintf;
@@ -14,12 +13,6 @@
1413
final class FinalPrivateMethodRule implements Rule
1514
{
1615

17-
public function __construct(
18-
private PhpVersion $phpVersion,
19-
)
20-
{
21-
}
22-
2316
public function getNodeType(): string
2417
{
2518
return InClassMethodNode::class;
@@ -28,7 +21,7 @@ public function getNodeType(): string
2821
public function processNode(Node $node, Scope $scope): array
2922
{
3023
$method = $node->getMethodReflection();
31-
if (!$this->phpVersion->producesWarningForFinalPrivateMethods()) {
24+
if ($scope->getPhpVersions()->producesWarningForFinalPrivateMethods()->no()) {
3225
return [];
3326
}
3427

tests/PHPStan/Rules/Methods/FinalPrivateMethodRuleTest.php

+12-28
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,34 @@
22

33
namespace PHPStan\Rules\Methods;
44

5-
use PHPStan\Php\PhpVersion;
65
use PHPStan\Rules\Rule;
76
use PHPStan\Testing\RuleTestCase;
87

98
/** @extends RuleTestCase<FinalPrivateMethodRule> */
109
class FinalPrivateMethodRuleTest extends RuleTestCase
1110
{
1211

13-
private int $phpVersionId;
14-
1512
protected function getRule(): Rule
1613
{
17-
return new FinalPrivateMethodRule(
18-
new PhpVersion($this->phpVersionId),
19-
);
14+
return new FinalPrivateMethodRule();
2015
}
2116

22-
public function dataRule(): array
17+
public function testRule(): void
2318
{
24-
return [
19+
$this->analyse([__DIR__ . '/data/final-private-method.php'], [
2520
[
26-
70400,
27-
[],
21+
'Private method FinalPrivateMethod\Foo::foo() cannot be final as it is never overridden by other classes.',
22+
8,
2823
],
2924
[
30-
80000,
31-
[
32-
[
33-
'Private method FinalPrivateMethod\Foo::foo() cannot be final as it is never overridden by other classes.',
34-
8,
35-
],
36-
],
25+
'Private method FinalPrivateMethod\FooBarPhp8orHigher::foo() cannot be final as it is never overridden by other classes.',
26+
39,
3727
],
38-
];
39-
}
40-
41-
/**
42-
* @dataProvider dataRule
43-
* @param list<array{0: string, 1: int, 2?: string}> $errors
44-
*/
45-
public function testRule(int $phpVersion, array $errors): void
46-
{
47-
$this->phpVersionId = $phpVersion;
48-
$this->analyse([__DIR__ . '/data/final-private-method.php'], $errors);
28+
[
29+
'Private method FinalPrivateMethod\FooBarPhp74OrHigher::foo() cannot be final as it is never overridden by other classes.',
30+
59,
31+
],
32+
]);
4933
}
5034

5135
}

tests/PHPStan/Rules/Methods/data/final-private-method.php

+30
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,33 @@ final private function __construct()
3131
}
3232

3333
}
34+
35+
if (PHP_VERSION_ID >= 80000) {
36+
class FooBarPhp8orHigher
37+
{
38+
39+
final private function foo(): void
40+
{
41+
}
42+
}
43+
}
44+
45+
if (PHP_VERSION_ID < 80000) {
46+
class FooBarPhp7
47+
{
48+
49+
final private function foo(): void
50+
{
51+
}
52+
}
53+
}
54+
55+
if (PHP_VERSION_ID > 70400) {
56+
class FooBarPhp74OrHigher
57+
{
58+
59+
final private function foo(): void
60+
{
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)