Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions build/PHPStan/Build/RequiredPhpVersionVisitor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
<?php declare(strict_types = 1);

namespace PHPStan\Build;

use Override;
use PhpParser\Modifiers;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
use function in_array;
use function strtolower;

/**
* Detects the minimum PHP version a file requires to be parsed, based on the
* syntactic features it uses. Used to verify that analyser test fixtures carry
* a matching `// lint >= X.Y` comment so they get skipped on older PHP versions
* in CI instead of producing a hard parse error.
*/
final class RequiredPhpVersionVisitor extends NodeVisitorAbstract
{

public const PHP_8_0 = 80000;

private const PHP_8_1 = 80100;
private const PHP_8_2 = 80200;
private const PHP_8_3 = 80300;
private const PHP_8_4 = 80400;
private const PHP_8_5 = 80500;

private ?int $requiredVersionId = null;

private ?string $reason = null;

private ?int $reasonLine = null;

public function getRequiredVersionId(): ?int
{
return $this->requiredVersionId;
}

public function getReason(): ?string
{
return $this->reason;
}

public function getReasonLine(): ?int
{
return $this->reasonLine;
}

#[Override]
public function enterNode(Node $node): ?Node
{
if ($node instanceof Node\Stmt\Enum_) {
$this->require(self::PHP_8_1, 'enums', $node);
}

if ($node instanceof Node\Expr\BinaryOp\Pipe) {
$this->require(self::PHP_8_5, 'the pipe operator', $node);
}

if ($node instanceof Node\PropertyHook) {
$this->require(self::PHP_8_4, 'property hooks', $node);
}

if ($node instanceof Node\IntersectionType) {
$this->require(self::PHP_8_1, 'pure intersection types', $node);
}

if ($node instanceof Node\UnionType) {
foreach ($node->types as $innerType) {
if ($innerType instanceof Node\IntersectionType) {
$this->require(self::PHP_8_2, 'disjunctive normal form types', $innerType);
}
if (!($innerType instanceof Node\Identifier) || strtolower($innerType->name) !== 'true') {
continue;
}

$this->require(self::PHP_8_2, 'the standalone "true" type', $innerType);
}
}

if ($node instanceof Node\Stmt\Class_ && ($node->flags & Modifiers::READONLY) !== 0) {
$this->require(self::PHP_8_2, 'readonly classes', $node);
}

if ($node instanceof Node\Stmt\Property && ($node->flags & Modifiers::READONLY) !== 0) {
$this->require(self::PHP_8_1, 'readonly properties', $node);
}

if ($node instanceof Node\Param && ($node->flags & Modifiers::READONLY) !== 0) {
$this->require(self::PHP_8_1, 'readonly promoted properties', $node);
}

if (
($node instanceof Node\Param || $node instanceof Node\Stmt\Property)
&& ($node->flags & Modifiers::VISIBILITY_SET_MASK) !== 0
) {
$this->require(self::PHP_8_4, 'asymmetric visibility', $node);
}

if ($node instanceof Node\Stmt\ClassConst && $node->type !== null) {
$this->require(self::PHP_8_3, 'typed class constants', $node);
}

if ($node instanceof Node\Expr\ClassConstFetch && $node->name instanceof Node\Expr) {
$this->require(self::PHP_8_3, 'dynamic class constant fetch', $node);
}

if (
$node instanceof Node\Expr\FuncCall
|| $node instanceof Node\Expr\MethodCall
|| $node instanceof Node\Expr\NullsafeMethodCall
|| $node instanceof Node\Expr\StaticCall
) {
foreach ($node->args as $arg) {
if ($arg instanceof Node\VariadicPlaceholder) {
$this->require(self::PHP_8_1, 'first-class callable syntax', $arg);
break;
}
}
}

$this->checkStandaloneType($node);
$this->checkMixedType($node);

return null;
}

private function checkStandaloneType(Node $node): void
{
$type = $this->getDeclaredType($node);
if (!$type instanceof Node\Identifier) {
return;
}

if (!in_array(strtolower($type->name), ['null', 'false', 'true'], true)) {
return;
}

$this->require(self::PHP_8_2, 'standalone "null", "false" or "true" types', $type);
}

private function checkMixedType(Node $node): void
{
$type = $this->getDeclaredType($node);
if (!$type instanceof Node\Identifier) {
return;
}

if (strtolower($type->name) !== 'mixed') {
return;
}

$this->require(self::PHP_8_0, 'the mixed type', $type);
}

private function getDeclaredType(Node $node): ?Node
{
if (
$node instanceof Node\Param
|| $node instanceof Node\Stmt\Property
|| $node instanceof Node\Stmt\ClassConst
) {
return $node->type;
}

if (
$node instanceof Node\Stmt\Function_
|| $node instanceof Node\Stmt\ClassMethod
|| $node instanceof Node\Expr\Closure
|| $node instanceof Node\Expr\ArrowFunction
) {
return $node->returnType;
}

return null;
}

private function require(int $versionId, string $reason, Node $node): void
{
if ($this->requiredVersionId !== null && $this->requiredVersionId >= $versionId) {
return;
}

$this->requiredVersionId = $versionId;
$this->reason = $reason;
$this->reasonLine = $node->getStartLine();
}

}
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-10049-recursive.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php declare(strict_types = 1);
<?php declare(strict_types = 1); // lint >= 8.1

namespace Bug10049Recursive;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-10847.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.1

namespace Bug10847;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-11147.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.0

declare(strict_types=1);

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-11709.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.0

namespace Bug11709;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-12246.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php declare(strict_types = 1);
<?php declare(strict_types = 1); // lint >= 8.1

namespace Bug12246;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-12549.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.3

namespace Bug12549;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-12949.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.3

namespace Bug12949;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-14501.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php declare(strict_types=1);
<?php declare(strict_types=1); // lint >= 8.3

namespace Bug14501;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-14542.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.0

namespace Bug14542;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-7927.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php declare(strict_types = 1);
<?php declare(strict_types = 1); // lint >= 8.1

namespace Bug7927;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-7980.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php declare(strict_types = 1);
<?php declare(strict_types = 1); // lint >= 8.1

namespace Bug7980;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/bug-8072.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.1

namespace Bug8072;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.1

namespace ImmediatelyCalledFunctionWithoutImplicitThrow;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/param-out.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.0

namespace ParamOut;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.1

namespace ScopeInEnumMatchArmBody;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/data/unknown-mixed-type.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.0

namespace UnknownMixedType;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php // lint >= 8.0
<?php // lint >= 8.1

namespace ArrayKeyExistsSubtracted;

Expand Down
3 changes: 1 addition & 2 deletions tests/PHPStan/Analyser/nsrt/array-map.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<?php

<?php // lint >= 8.1
namespace ArrayMap;

use function array_map;
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/array_values.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php // lint >= 8.0
<?php // lint >= 8.1

namespace ArrayValues;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-10037.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php // lint >= 8.0
<?php // lint >= 8.2

declare(strict_types = 1);

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-10477.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.0

namespace Bug10477;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-11899.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.0

namespace Bug11899;

Expand Down
4 changes: 3 additions & 1 deletion tests/PHPStan/Analyser/nsrt/bug-12691.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?php declare(strict_types = 1);
<?php // lint >= 8.1

declare(strict_types = 1);

namespace Bug12691;

Expand Down
4 changes: 3 additions & 1 deletion tests/PHPStan/Analyser/nsrt/bug-13061.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?php declare(strict_types = 1);
<?php // lint >= 8.1

declare(strict_types = 1);

namespace Bug13061;

Expand Down
4 changes: 3 additions & 1 deletion tests/PHPStan/Analyser/nsrt/bug-13736.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?php declare(strict_types = 1);
<?php // lint >= 8.0

declare(strict_types = 1);

namespace Bug13736;

Expand Down
3 changes: 1 addition & 2 deletions tests/PHPStan/Analyser/nsrt/bug-13828.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<?php

<?php // lint >= 8.3
namespace Bug13828;

use function PHPStan\Testing\assertType;
Expand Down
3 changes: 1 addition & 2 deletions tests/PHPStan/Analyser/nsrt/bug-13872.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<?php

<?php // lint >= 8.1
namespace Bug13872;

use function is_array;
Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-13985.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.0

namespace Bug13985;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-14081.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.0

declare(strict_types = 1);

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-14558.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php
<?php // lint >= 8.0

declare(strict_types = 1);

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-14772.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php // lint >= 8.0
<?php // lint >= 8.1

declare(strict_types = 1);

Expand Down
4 changes: 3 additions & 1 deletion tests/PHPStan/Analyser/nsrt/bug-14774.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?php declare(strict_types = 1);
<?php // lint >= 8.1

declare(strict_types = 1);

namespace Bug14774;

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Analyser/nsrt/bug-4890-php8.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php // lint >= 8.0
<?php // lint >= 8.1

namespace Bug4890Php8;

Expand Down
Loading
Loading