From b196d1851f30b158c13529ad12c682aa532b3a5b Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 9 Feb 2026 18:14:12 +0100 Subject: [PATCH 01/14] Narrow PHP_VERSION_ID on version_compare() --- src/Analyser/MutatingScope.php | 92 ++++++++++++ src/Php/SimplePhpVersionParser.php | 26 ++++ ...narrow-phpversionid-on-version-compare.php | 142 ++++++++++++++++++ 3 files changed, 260 insertions(+) create mode 100644 src/Php/SimplePhpVersionParser.php create mode 100644 tests/PHPStan/Analyser/nsrt/narrow-phpversionid-on-version-compare.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 0a48658184..30779a88d2 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -57,6 +57,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\Php\PhpVersionFactory; use PHPStan\Php\PhpVersions; +use PHPStan\Php\SimplePhpVersionParser; use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\AttributeReflection; @@ -124,6 +125,7 @@ use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; +use PHPStan\Type\Php\VersionCompareFunctionDynamicReturnTypeExtension; use PHPStan\Type\StaticType; use PHPStan\Type\StaticTypeFactory; use PHPStan\Type\StringType; @@ -254,6 +256,53 @@ public function __construct( $this->namespace = $namespace; } + private function getComparePhpVersionType(string $value, ?Expr $operator): ?Type + { + $parsedVersion = SimplePhpVersionParser::parseVersion($value); + if ($parsedVersion === null) { + return null; + } + + if ($operator !== null) { + $operators = $this->getType($operator)->getConstantStrings(); + if (count($operators) !== 1) { + return null; + } + + $operatorString = $operators[0]->getValue(); + } else { + $operatorString = '<'; + } + + if (!in_array($operatorString, VersionCompareFunctionDynamicReturnTypeExtension::VALID_OPERATORS, true)) { + return null; + } + + if ($operatorString === '<' || $operatorString === 'lt') { + return IntegerRangeType::fromInterval(null, $parsedVersion->getVersionId() - 1); + } + if ($operatorString === '<=' || $operatorString === 'le') { + return IntegerRangeType::fromInterval(null, $parsedVersion->getVersionId()); + } + + if ($operatorString === '>' || $operatorString === 'gt') { + return IntegerRangeType::fromInterval($parsedVersion->getVersionId() + 1, null); + } + if ($operatorString === '>=' || $operatorString === 'ge') { + return IntegerRangeType::fromInterval($parsedVersion->getVersionId(), null); + } + + if ( + $operatorString === '==' + || $operatorString === '=' + || $operatorString === 'eq' + ) { + return new ConstantIntegerType($parsedVersion->getVersionId()); + } + + return TypeCombinator::remove(new IntegerType(), new ConstantIntegerType($parsedVersion->getVersionId())); + } + public function toFiberScope(): self { if (PHP_VERSION_ID < 80100) { @@ -3359,6 +3408,49 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, } $scope = $this; + if ($expr instanceof FuncCall && $expr->name instanceof Name) { + $args = $expr->getArgs(); + if (count($args) >= 2) { + $functionName = $this->reflectionProvider->resolveFunctionName($expr->name, $this); + if ($functionName === 'version_compare') { + $version1 = $args[0]->value; + $version2 = $args[1]->value; + + if ( + $version1 instanceof ConstFetch + && $version1->name->name === 'PHP_VERSION' + && $version2 instanceof String_ + ) { + $integerVersionRange = $this->getComparePhpVersionType($version2->value, isset($args[2]) ? $args[2]->value : null); + if ($integerVersionRange !== null) { + $scope = $scope->specifyExpressionType( + new ConstFetch(new Name('\\PHP_VERSION_ID')), + $integerVersionRange, + $integerVersionRange, + TrinaryLogic::createYes(), + ); + } + } + + if ( + $version2 instanceof ConstFetch + && $version2->name->name === 'PHP_VERSION' + && $version1 instanceof String_ + ) { + $integerVersionRange = $this->getComparePhpVersionType($version1->value, isset($args[2]) ? $args[2]->value : null); + if ($integerVersionRange !== null) { + $scope = $scope->specifyExpressionType( + new ConstFetch(new Name('\\PHP_VERSION_ID')), + $integerVersionRange, + $integerVersionRange, + TrinaryLogic::createYes(), + ); + } + } + } + } + } + if ( $expr instanceof Expr\ArrayDimFetch && $expr->dim !== null diff --git a/src/Php/SimplePhpVersionParser.php b/src/Php/SimplePhpVersionParser.php new file mode 100644 index 0000000000..01c9e9dce5 --- /dev/null +++ b/src/Php/SimplePhpVersionParser.php @@ -0,0 +1,26 @@ +', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', 'lt' ) + ) { + $x = PHP_VERSION_ID; + assertType('int', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', '<=' ) + ) { + $x = PHP_VERSION_ID; + assertType('int', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', 'le' ) + ) { + $x = PHP_VERSION_ID; + assertType('int', $x); + } +} + +function greater(): void +{ + if ( + version_compare( PHP_VERSION, '8.4', '>' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80401, max>', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', 'gt' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80401, max>', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', '>=' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80400, max>', $x); + } + if ( + version_compare( PHP_VERSION, '8.4', 'ge' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80400, max>', $x); + } +} + +function equal(): void +{ + if ( + version_compare( PHP_VERSION, '8.4', '=' ) + ) { + $x = PHP_VERSION_ID; + assertType('80400', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', '==' ) + ) { + $x = PHP_VERSION_ID; + assertType('80400', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', 'eq' ) + ) { + $x = PHP_VERSION_ID; + assertType('80400', $x); + } +} + + +function not(): void +{ + if ( + version_compare( PHP_VERSION, '8.4', '!=' ) + ) { + $x = PHP_VERSION_ID; + assertType('int|int<80401, max>', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', '<>' ) + ) { + $x = PHP_VERSION_ID; + assertType('int|int<80401, max>', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', 'ne' ) + ) { + $x = PHP_VERSION_ID; + assertType('int|int<80401, max>', $x); + } +} + +function inverseOperandLower(): void +{ + if ( + version_compare( '8.3.12', PHP_VERSION, '<' ) + ) { + $x = PHP_VERSION_ID; + assertType('int', $x); + } + + if ( + version_compare( '8.3.12', PHP_VERSION, '>=' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80312, max>', $x); + } + + if ( + version_compare( '8.3.12', PHP_VERSION, '>' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80313, max>', $x); + } + +} From 14d03b1c6dc9e228069c3e4236fb72454d56d97f Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 9 Feb 2026 19:33:11 +0100 Subject: [PATCH 02/14] cs --- src/Analyser/MutatingScope.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 30779a88d2..b1f5ba43b6 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -278,24 +278,22 @@ private function getComparePhpVersionType(string $value, ?Expr $operator): ?Type return null; } - if ($operatorString === '<' || $operatorString === 'lt') { + if (in_array($operatorString, ['<', 'lt'], true)) { return IntegerRangeType::fromInterval(null, $parsedVersion->getVersionId() - 1); } - if ($operatorString === '<=' || $operatorString === 'le') { + if (in_array($operatorString, ['<=', 'le'], true)) { return IntegerRangeType::fromInterval(null, $parsedVersion->getVersionId()); } - if ($operatorString === '>' || $operatorString === 'gt') { + if (in_array($operatorString, ['>', 'gt'], true)) { return IntegerRangeType::fromInterval($parsedVersion->getVersionId() + 1, null); } - if ($operatorString === '>=' || $operatorString === 'ge') { + if (in_array($operatorString, ['>=', 'ge'], true)) { return IntegerRangeType::fromInterval($parsedVersion->getVersionId(), null); } if ( - $operatorString === '==' - || $operatorString === '=' - || $operatorString === 'eq' + in_array($operatorString, ['==', '=', 'eq'], true) ) { return new ConstantIntegerType($parsedVersion->getVersionId()); } From fc98ecbfbb9826792eb7a3f2d224dac2ad687662 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 9 Feb 2026 19:34:45 +0100 Subject: [PATCH 03/14] Update MutatingScope.php --- src/Analyser/MutatingScope.php | 90 +++++++++++++++++----------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index b1f5ba43b6..7dd8ec0a00 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -256,51 +256,6 @@ public function __construct( $this->namespace = $namespace; } - private function getComparePhpVersionType(string $value, ?Expr $operator): ?Type - { - $parsedVersion = SimplePhpVersionParser::parseVersion($value); - if ($parsedVersion === null) { - return null; - } - - if ($operator !== null) { - $operators = $this->getType($operator)->getConstantStrings(); - if (count($operators) !== 1) { - return null; - } - - $operatorString = $operators[0]->getValue(); - } else { - $operatorString = '<'; - } - - if (!in_array($operatorString, VersionCompareFunctionDynamicReturnTypeExtension::VALID_OPERATORS, true)) { - return null; - } - - if (in_array($operatorString, ['<', 'lt'], true)) { - return IntegerRangeType::fromInterval(null, $parsedVersion->getVersionId() - 1); - } - if (in_array($operatorString, ['<=', 'le'], true)) { - return IntegerRangeType::fromInterval(null, $parsedVersion->getVersionId()); - } - - if (in_array($operatorString, ['>', 'gt'], true)) { - return IntegerRangeType::fromInterval($parsedVersion->getVersionId() + 1, null); - } - if (in_array($operatorString, ['>=', 'ge'], true)) { - return IntegerRangeType::fromInterval($parsedVersion->getVersionId(), null); - } - - if ( - in_array($operatorString, ['==', '=', 'eq'], true) - ) { - return new ConstantIntegerType($parsedVersion->getVersionId()); - } - - return TypeCombinator::remove(new IntegerType(), new ConstantIntegerType($parsedVersion->getVersionId())); - } - public function toFiberScope(): self { if (PHP_VERSION_ID < 80100) { @@ -3520,6 +3475,51 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, return $scope; } + private function getComparePhpVersionType(string $value, ?Expr $operator): ?Type + { + $parsedVersion = SimplePhpVersionParser::parseVersion($value); + if ($parsedVersion === null) { + return null; + } + + if ($operator !== null) { + $operators = $this->getType($operator)->getConstantStrings(); + if (count($operators) !== 1) { + return null; + } + + $operatorString = $operators[0]->getValue(); + } else { + $operatorString = '<'; + } + + if (!in_array($operatorString, VersionCompareFunctionDynamicReturnTypeExtension::VALID_OPERATORS, true)) { + return null; + } + + if (in_array($operatorString, ['<', 'lt'], true)) { + return IntegerRangeType::fromInterval(null, $parsedVersion->getVersionId() - 1); + } + if (in_array($operatorString, ['<=', 'le'], true)) { + return IntegerRangeType::fromInterval(null, $parsedVersion->getVersionId()); + } + + if (in_array($operatorString, ['>', 'gt'], true)) { + return IntegerRangeType::fromInterval($parsedVersion->getVersionId() + 1, null); + } + if (in_array($operatorString, ['>=', 'ge'], true)) { + return IntegerRangeType::fromInterval($parsedVersion->getVersionId(), null); + } + + if ( + in_array($operatorString, ['==', '=', 'eq'], true) + ) { + return new ConstantIntegerType($parsedVersion->getVersionId()); + } + + return TypeCombinator::remove(new IntegerType(), new ConstantIntegerType($parsedVersion->getVersionId())); + } + public function assignExpression(Expr $expr, Type $type, Type $nativeType): self { $scope = $this; From 6b6b83eaa53f3f2314b9487d4932ff00e1c5d6d5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Feb 2026 07:03:45 +0100 Subject: [PATCH 04/14] WIP: Create VersionCompareFunctionTypeSpecifyingExtension.php --- src/Analyser/MutatingScope.php | 88 ----------- ...CompareFunctionTypeSpecifyingExtension.php | 149 ++++++++++++++++++ ...narrow-phpversionid-on-version-compare.php | 20 ++- 3 files changed, 165 insertions(+), 92 deletions(-) create mode 100644 src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 7dd8ec0a00..a8ac709195 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -3361,49 +3361,6 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, } $scope = $this; - if ($expr instanceof FuncCall && $expr->name instanceof Name) { - $args = $expr->getArgs(); - if (count($args) >= 2) { - $functionName = $this->reflectionProvider->resolveFunctionName($expr->name, $this); - if ($functionName === 'version_compare') { - $version1 = $args[0]->value; - $version2 = $args[1]->value; - - if ( - $version1 instanceof ConstFetch - && $version1->name->name === 'PHP_VERSION' - && $version2 instanceof String_ - ) { - $integerVersionRange = $this->getComparePhpVersionType($version2->value, isset($args[2]) ? $args[2]->value : null); - if ($integerVersionRange !== null) { - $scope = $scope->specifyExpressionType( - new ConstFetch(new Name('\\PHP_VERSION_ID')), - $integerVersionRange, - $integerVersionRange, - TrinaryLogic::createYes(), - ); - } - } - - if ( - $version2 instanceof ConstFetch - && $version2->name->name === 'PHP_VERSION' - && $version1 instanceof String_ - ) { - $integerVersionRange = $this->getComparePhpVersionType($version1->value, isset($args[2]) ? $args[2]->value : null); - if ($integerVersionRange !== null) { - $scope = $scope->specifyExpressionType( - new ConstFetch(new Name('\\PHP_VERSION_ID')), - $integerVersionRange, - $integerVersionRange, - TrinaryLogic::createYes(), - ); - } - } - } - } - } - if ( $expr instanceof Expr\ArrayDimFetch && $expr->dim !== null @@ -3475,51 +3432,6 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, return $scope; } - private function getComparePhpVersionType(string $value, ?Expr $operator): ?Type - { - $parsedVersion = SimplePhpVersionParser::parseVersion($value); - if ($parsedVersion === null) { - return null; - } - - if ($operator !== null) { - $operators = $this->getType($operator)->getConstantStrings(); - if (count($operators) !== 1) { - return null; - } - - $operatorString = $operators[0]->getValue(); - } else { - $operatorString = '<'; - } - - if (!in_array($operatorString, VersionCompareFunctionDynamicReturnTypeExtension::VALID_OPERATORS, true)) { - return null; - } - - if (in_array($operatorString, ['<', 'lt'], true)) { - return IntegerRangeType::fromInterval(null, $parsedVersion->getVersionId() - 1); - } - if (in_array($operatorString, ['<=', 'le'], true)) { - return IntegerRangeType::fromInterval(null, $parsedVersion->getVersionId()); - } - - if (in_array($operatorString, ['>', 'gt'], true)) { - return IntegerRangeType::fromInterval($parsedVersion->getVersionId() + 1, null); - } - if (in_array($operatorString, ['>=', 'ge'], true)) { - return IntegerRangeType::fromInterval($parsedVersion->getVersionId(), null); - } - - if ( - in_array($operatorString, ['==', '=', 'eq'], true) - ) { - return new ConstantIntegerType($parsedVersion->getVersionId()); - } - - return TypeCombinator::remove(new IntegerType(), new ConstantIntegerType($parsedVersion->getVersionId())); - } - public function assignExpression(Expr $expr, Type $type, Type $nativeType): self { $scope = $this; diff --git a/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php b/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php new file mode 100644 index 0000000000..594bc30c5b --- /dev/null +++ b/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php @@ -0,0 +1,149 @@ +typeSpecifier = $typeSpecifier; + } + + public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool + { + return strtolower($functionReflection->getName())==='version_compare' && $context->true(); + } + + public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes + { + if (!$node->name instanceof Name) { + return new SpecifiedTypes(); + } + + $args = $node->getArgs(); + if (count($args) < 2) { + return new SpecifiedTypes(); + } + + $version1 = $args[0]->value; + $version2 = $args[1]->value; + + if ($scope->getNamespace() !== null) { + $constName = 'PHP_VERSION_ID'; + } else { + $constName = '\\PHP_VERSION_ID'; + } + + if ( + $version1 instanceof ConstFetch + && $version1->name->name === 'PHP_VERSION' + && $version2 instanceof String_ + ) { + $integerVersionRange = $this->getComparePhpVersionType($version2->value, isset($args[2]) ? $args[2]->value : null, $scope); + + if ($integerVersionRange !== null) { + return $this->typeSpecifier->create( + new ConstFetch(new Name($constName)), + TypeCombinator::intersect($scope->getPhpVersion()->getType(), $integerVersionRange), + $context, + $scope, + ); + } + } + + if ( + $version2 instanceof ConstFetch + && $version2->name->name === 'PHP_VERSION' + && $version1 instanceof String_ + ) { + $integerVersionRange = $this->getComparePhpVersionType($version1->value, isset($args[2]) ? $args[2]->value : null, $scope); + if ($integerVersionRange !== null) { + return $this->typeSpecifier->create( + new ConstFetch(new Name($constName)), + TypeCombinator::intersect($scope->getPhpVersion()->getType(), $integerVersionRange), + $context, + $scope, + ); + } + } + + return new SpecifiedTypes(); + } + + private function getComparePhpVersionType(string $value, ?Expr $operator, Scope $scope): ?Type + { + $parsedVersion = SimplePhpVersionParser::parseVersion($value); + if ($parsedVersion === null) { + return null; + } + + if ($operator !== null) { + $operators = $scope->getType($operator)->getConstantStrings(); + if (count($operators) !== 1) { + return null; + } + + $operatorString = $operators[0]->getValue(); + } else { + $operatorString = '<'; + } + + if (!in_array($operatorString, VersionCompareFunctionDynamicReturnTypeExtension::VALID_OPERATORS, true)) { + return null; + } + + if (in_array($operatorString, ['<', 'lt'], true)) { + return IntegerRangeType::fromInterval(null, $parsedVersion->getVersionId() - 1); + } + if (in_array($operatorString, ['<=', 'le'], true)) { + return IntegerRangeType::fromInterval(null, $parsedVersion->getVersionId()); + } + + if (in_array($operatorString, ['>', 'gt'], true)) { + return IntegerRangeType::fromInterval($parsedVersion->getVersionId() + 1, null); + } + if (in_array($operatorString, ['>=', 'ge'], true)) { + return IntegerRangeType::fromInterval($parsedVersion->getVersionId(), null); + } + + if ( + in_array($operatorString, ['==', '=', 'eq'], true) + ) { + return new ConstantIntegerType($parsedVersion->getVersionId()); + } + + return TypeCombinator::remove(new IntegerType(), new ConstantIntegerType($parsedVersion->getVersionId())); + } +} diff --git a/tests/PHPStan/Analyser/nsrt/narrow-phpversionid-on-version-compare.php b/tests/PHPStan/Analyser/nsrt/narrow-phpversionid-on-version-compare.php index d610c4c428..66303526aa 100644 --- a/tests/PHPStan/Analyser/nsrt/narrow-phpversionid-on-version-compare.php +++ b/tests/PHPStan/Analyser/nsrt/narrow-phpversionid-on-version-compare.php @@ -12,28 +12,28 @@ function lower(): void version_compare( PHP_VERSION, '8.4', '<' ) ) { $x = PHP_VERSION_ID; - assertType('int', $x); + assertType('int<50207, 80599>', $x); } if ( version_compare( PHP_VERSION, '8.4', 'lt' ) ) { $x = PHP_VERSION_ID; - assertType('int', $x); + assertType('int<50207, 80399>', $x); } if ( version_compare( PHP_VERSION, '8.4', '<=' ) ) { $x = PHP_VERSION_ID; - assertType('int', $x); + assertType('int<50207, 80400>', $x); } if ( version_compare( PHP_VERSION, '8.4', 'le' ) ) { $x = PHP_VERSION_ID; - assertType('int', $x); + assertType('int<50207, 80400>', $x); } } @@ -138,5 +138,17 @@ function inverseOperandLower(): void $x = PHP_VERSION_ID; assertType('int<80313, max>', $x); } +} + +function narrow(): void { + if (PHP_VERSION_ID < 80000) { + return; + } + if ( + version_compare( PHP_VERSION, '8.4', '<' ) + ) { + $x = PHP_VERSION_ID; + assertType('int', $x); + } } From 4be0c38c016b92022ad6fb2042e605d2daec00ec Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Feb 2026 08:46:38 +0100 Subject: [PATCH 05/14] Update VersionCompareFunctionTypeSpecifyingExtension.php --- .../Php/VersionCompareFunctionTypeSpecifyingExtension.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php b/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php index 594bc30c5b..14caca4591 100644 --- a/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php @@ -71,7 +71,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n && $version1->name->name === 'PHP_VERSION' && $version2 instanceof String_ ) { - $integerVersionRange = $this->getComparePhpVersionType($version2->value, isset($args[2]) ? $args[2]->value : null, $scope); + $integerVersionRange = $this->getVersionCompareType($version2->value, isset($args[2]) ? $args[2]->value : null, $scope); if ($integerVersionRange !== null) { return $this->typeSpecifier->create( @@ -88,7 +88,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n && $version2->name->name === 'PHP_VERSION' && $version1 instanceof String_ ) { - $integerVersionRange = $this->getComparePhpVersionType($version1->value, isset($args[2]) ? $args[2]->value : null, $scope); + $integerVersionRange = $this->getVersionCompareType($version1->value, isset($args[2]) ? $args[2]->value : null, $scope); if ($integerVersionRange !== null) { return $this->typeSpecifier->create( new ConstFetch(new Name($constName)), @@ -102,7 +102,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n return new SpecifiedTypes(); } - private function getComparePhpVersionType(string $value, ?Expr $operator, Scope $scope): ?Type + private function getVersionCompareType(string $value, ?Expr $operator, Scope $scope): ?Type { $parsedVersion = SimplePhpVersionParser::parseVersion($value); if ($parsedVersion === null) { From 3ce44dd1a39f9ca31be91977476739c5ae3be1ef Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Feb 2026 08:49:38 +0100 Subject: [PATCH 06/14] Update VersionCompareFunctionTypeSpecifyingExtension.php --- .../Php/VersionCompareFunctionTypeSpecifyingExtension.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php b/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php index 14caca4591..ee0aa26957 100644 --- a/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php @@ -74,9 +74,10 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $integerVersionRange = $this->getVersionCompareType($version2->value, isset($args[2]) ? $args[2]->value : null, $scope); if ($integerVersionRange !== null) { + $narrowedVersion = TypeCombinator::intersect($scope->getPhpVersion()->getType(), $integerVersionRange); return $this->typeSpecifier->create( new ConstFetch(new Name($constName)), - TypeCombinator::intersect($scope->getPhpVersion()->getType(), $integerVersionRange), + $narrowedVersion, $context, $scope, ); @@ -90,9 +91,10 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n ) { $integerVersionRange = $this->getVersionCompareType($version1->value, isset($args[2]) ? $args[2]->value : null, $scope); if ($integerVersionRange !== null) { + $narrowedVersion = TypeCombinator::intersect($scope->getPhpVersion()->getType(), $integerVersionRange); return $this->typeSpecifier->create( new ConstFetch(new Name($constName)), - TypeCombinator::intersect($scope->getPhpVersion()->getType(), $integerVersionRange), + $narrowedVersion, $context, $scope, ); From 2f7d098967ee2f74d874dff293f9879de8e60efd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Feb 2026 08:51:30 +0100 Subject: [PATCH 07/14] Create narrow-phpversionid-on-version-compare-global-scope.php --- ...sionid-on-version-compare-global-scope.php | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 narrow-phpversionid-on-version-compare-global-scope.php diff --git a/narrow-phpversionid-on-version-compare-global-scope.php b/narrow-phpversionid-on-version-compare-global-scope.php new file mode 100644 index 0000000000..8d610f85ea --- /dev/null +++ b/narrow-phpversionid-on-version-compare-global-scope.php @@ -0,0 +1,21 @@ +', $x); + + if ( + version_compare( PHP_VERSION, '8.4', '<' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80000, 80399>', $x); + } +} + From cfdc348bbabbc96054102e3aae11cd41865775cd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Feb 2026 08:53:55 +0100 Subject: [PATCH 08/14] Update narrow-phpversionid-on-version-compare.php --- .../nsrt/narrow-phpversionid-on-version-compare.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/PHPStan/Analyser/nsrt/narrow-phpversionid-on-version-compare.php b/tests/PHPStan/Analyser/nsrt/narrow-phpversionid-on-version-compare.php index 66303526aa..633e28ade8 100644 --- a/tests/PHPStan/Analyser/nsrt/narrow-phpversionid-on-version-compare.php +++ b/tests/PHPStan/Analyser/nsrt/narrow-phpversionid-on-version-compare.php @@ -8,11 +8,14 @@ function lower(): void { + $x = PHP_VERSION_ID; + assertType('int<50207, 80599>', $x); + if ( version_compare( PHP_VERSION, '8.4', '<' ) ) { $x = PHP_VERSION_ID; - assertType('int<50207, 80599>', $x); + assertType('int<50207, 80399>', $x); } if ( @@ -149,6 +152,6 @@ function narrow(): void { version_compare( PHP_VERSION, '8.4', '<' ) ) { $x = PHP_VERSION_ID; - assertType('int', $x); + assertType('int<80000, 80399>', $x); } } From 3a58f64b6d43190c5bcc3ad334926ea2c0b0d191 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Feb 2026 09:08:23 +0100 Subject: [PATCH 09/14] move test into e2e --- .github/workflows/e2e-tests.yml | 4 ++++ e2e/version-compare-phpversionid/composer.json | 5 +++++ .../narrow-phpversionid-on-version-compare.php | 0 3 files changed, 9 insertions(+) create mode 100644 e2e/version-compare-phpversionid/composer.json rename {tests/PHPStan/Analyser/nsrt => e2e/version-compare-phpversionid}/narrow-phpversionid-on-version-compare.php (100%) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 80abad6e94..1aa11917f7 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -407,6 +407,10 @@ jobs: cd e2e/composer-version-config composer install ../../bin/phpstan analyze test.php --level=0 + - script: | + cd e2e/version-compare-phpversionid + composer install + ../../bin/phpstan analyze narrow-phpversionid-on-version-compare.php --level=0 - script: | cd e2e/bug13425 timeout 15 ../bashunit -a exit_code "1" "../../bin/phpstan analyze src/ plugins/" diff --git a/e2e/version-compare-phpversionid/composer.json b/e2e/version-compare-phpversionid/composer.json new file mode 100644 index 0000000000..e5f83a420c --- /dev/null +++ b/e2e/version-compare-phpversionid/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "php": "^8" + } +} diff --git a/tests/PHPStan/Analyser/nsrt/narrow-phpversionid-on-version-compare.php b/e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare.php similarity index 100% rename from tests/PHPStan/Analyser/nsrt/narrow-phpversionid-on-version-compare.php rename to e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare.php From f0940c11921e353b04081f4fb1b0b27dcd0dc7e3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Feb 2026 09:15:03 +0100 Subject: [PATCH 10/14] adjust --- e2e/version-compare-phpversionid/.gitignore | 2 ++ .../narrow-phpversionid-on-version-compare.php | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 e2e/version-compare-phpversionid/.gitignore diff --git a/e2e/version-compare-phpversionid/.gitignore b/e2e/version-compare-phpversionid/.gitignore new file mode 100644 index 0000000000..3a9875b460 --- /dev/null +++ b/e2e/version-compare-phpversionid/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare.php b/e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare.php index 633e28ade8..e680fd0144 100644 --- a/e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare.php +++ b/e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare.php @@ -8,35 +8,42 @@ function lower(): void { + // add a upper bound, so we don't need to adjust + // the test when PHPStan adds support for PHP8.6+ + if (PHP_VERSION_ID > 80599) { + return; + } + + // lower limit inferred from composer.json $x = PHP_VERSION_ID; - assertType('int<50207, 80599>', $x); + assertType('int<80000, 80599>', $x); if ( version_compare( PHP_VERSION, '8.4', '<' ) ) { $x = PHP_VERSION_ID; - assertType('int<50207, 80399>', $x); + assertType('int<80000, 80399>', $x); } if ( version_compare( PHP_VERSION, '8.4', 'lt' ) ) { $x = PHP_VERSION_ID; - assertType('int<50207, 80399>', $x); + assertType('int<80000, 80399>', $x); } if ( version_compare( PHP_VERSION, '8.4', '<=' ) ) { $x = PHP_VERSION_ID; - assertType('int<50207, 80400>', $x); + assertType('int<80000, 80400>', $x); } if ( version_compare( PHP_VERSION, '8.4', 'le' ) ) { $x = PHP_VERSION_ID; - assertType('int<50207, 80400>', $x); + assertType('int<80000, 80400>', $x); } } From 3577566b952d064094c31e5ed6a207d6fafd0ffe Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Feb 2026 09:19:57 +0100 Subject: [PATCH 11/14] fix --- ...narrow-phpversionid-on-version-compare.php | 137 ------------------ ...CompareFunctionTypeSpecifyingExtension.php | 10 +- 2 files changed, 2 insertions(+), 145 deletions(-) diff --git a/e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare.php b/e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare.php index e680fd0144..4335f9e55c 100644 --- a/e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare.php +++ b/e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare.php @@ -24,141 +24,4 @@ function lower(): void $x = PHP_VERSION_ID; assertType('int<80000, 80399>', $x); } - - if ( - version_compare( PHP_VERSION, '8.4', 'lt' ) - ) { - $x = PHP_VERSION_ID; - assertType('int<80000, 80399>', $x); - } - - if ( - version_compare( PHP_VERSION, '8.4', '<=' ) - ) { - $x = PHP_VERSION_ID; - assertType('int<80000, 80400>', $x); - } - - if ( - version_compare( PHP_VERSION, '8.4', 'le' ) - ) { - $x = PHP_VERSION_ID; - assertType('int<80000, 80400>', $x); - } -} - -function greater(): void -{ - if ( - version_compare( PHP_VERSION, '8.4', '>' ) - ) { - $x = PHP_VERSION_ID; - assertType('int<80401, max>', $x); - } - - if ( - version_compare( PHP_VERSION, '8.4', 'gt' ) - ) { - $x = PHP_VERSION_ID; - assertType('int<80401, max>', $x); - } - - if ( - version_compare( PHP_VERSION, '8.4', '>=' ) - ) { - $x = PHP_VERSION_ID; - assertType('int<80400, max>', $x); - } - if ( - version_compare( PHP_VERSION, '8.4', 'ge' ) - ) { - $x = PHP_VERSION_ID; - assertType('int<80400, max>', $x); - } -} - -function equal(): void -{ - if ( - version_compare( PHP_VERSION, '8.4', '=' ) - ) { - $x = PHP_VERSION_ID; - assertType('80400', $x); - } - - if ( - version_compare( PHP_VERSION, '8.4', '==' ) - ) { - $x = PHP_VERSION_ID; - assertType('80400', $x); - } - - if ( - version_compare( PHP_VERSION, '8.4', 'eq' ) - ) { - $x = PHP_VERSION_ID; - assertType('80400', $x); - } -} - - -function not(): void -{ - if ( - version_compare( PHP_VERSION, '8.4', '!=' ) - ) { - $x = PHP_VERSION_ID; - assertType('int|int<80401, max>', $x); - } - - if ( - version_compare( PHP_VERSION, '8.4', '<>' ) - ) { - $x = PHP_VERSION_ID; - assertType('int|int<80401, max>', $x); - } - - if ( - version_compare( PHP_VERSION, '8.4', 'ne' ) - ) { - $x = PHP_VERSION_ID; - assertType('int|int<80401, max>', $x); - } -} - -function inverseOperandLower(): void -{ - if ( - version_compare( '8.3.12', PHP_VERSION, '<' ) - ) { - $x = PHP_VERSION_ID; - assertType('int', $x); - } - - if ( - version_compare( '8.3.12', PHP_VERSION, '>=' ) - ) { - $x = PHP_VERSION_ID; - assertType('int<80312, max>', $x); - } - - if ( - version_compare( '8.3.12', PHP_VERSION, '>' ) - ) { - $x = PHP_VERSION_ID; - assertType('int<80313, max>', $x); - } -} - -function narrow(): void { - if (PHP_VERSION_ID < 80000) { - return; - } - - if ( - version_compare( PHP_VERSION, '8.4', '<' ) - ) { - $x = PHP_VERSION_ID; - assertType('int<80000, 80399>', $x); - } } diff --git a/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php b/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php index ee0aa26957..f39f220eb6 100644 --- a/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php @@ -60,12 +60,6 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n $version1 = $args[0]->value; $version2 = $args[1]->value; - if ($scope->getNamespace() !== null) { - $constName = 'PHP_VERSION_ID'; - } else { - $constName = '\\PHP_VERSION_ID'; - } - if ( $version1 instanceof ConstFetch && $version1->name->name === 'PHP_VERSION' @@ -76,7 +70,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n if ($integerVersionRange !== null) { $narrowedVersion = TypeCombinator::intersect($scope->getPhpVersion()->getType(), $integerVersionRange); return $this->typeSpecifier->create( - new ConstFetch(new Name($constName)), + new ConstFetch(new Name('\\PHP_VERSION_ID')), $narrowedVersion, $context, $scope, @@ -93,7 +87,7 @@ public function specifyTypes(FunctionReflection $functionReflection, FuncCall $n if ($integerVersionRange !== null) { $narrowedVersion = TypeCombinator::intersect($scope->getPhpVersion()->getType(), $integerVersionRange); return $this->typeSpecifier->create( - new ConstFetch(new Name($constName)), + new ConstFetch(new Name('\\PHP_VERSION_ID')), $narrowedVersion, $context, $scope, From eaebcaa65c41907ce1cb8e3ea409e551d580cad7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Feb 2026 09:26:04 +0100 Subject: [PATCH 12/14] cleanup --- src/Analyser/MutatingScope.php | 2 -- ...VersionCompareFunctionTypeSpecifyingExtension.php | 12 ++++-------- tests/PHPStan/Analyser/data/bug-5639.php | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index a8ac709195..0a48658184 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -57,7 +57,6 @@ use PHPStan\Php\PhpVersion; use PHPStan\Php\PhpVersionFactory; use PHPStan\Php\PhpVersions; -use PHPStan\Php\SimplePhpVersionParser; use PHPStan\PhpDoc\ResolvedPhpDocBlock; use PHPStan\Reflection\Assertions; use PHPStan\Reflection\AttributeReflection; @@ -125,7 +124,6 @@ use PHPStan\Type\NullType; use PHPStan\Type\ObjectType; use PHPStan\Type\ObjectWithoutClassType; -use PHPStan\Type\Php\VersionCompareFunctionDynamicReturnTypeExtension; use PHPStan\Type\StaticType; use PHPStan\Type\StaticTypeFactory; use PHPStan\Type\StringType; diff --git a/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php b/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php index f39f220eb6..5b871413ba 100644 --- a/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php +++ b/src/Type/Php/VersionCompareFunctionTypeSpecifyingExtension.php @@ -6,7 +6,6 @@ use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\FuncCall; use PhpParser\Node\Name; -use PhpParser\Node\Name\FullyQualified; use PhpParser\Node\Scalar\String_; use PHPStan\Analyser\Scope; use PHPStan\Analyser\SpecifiedTypes; @@ -16,7 +15,6 @@ use PHPStan\DependencyInjection\AutowiredService; use PHPStan\Php\SimplePhpVersionParser; use PHPStan\Reflection\FunctionReflection; -use PHPStan\Reflection\ReflectionProvider; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\FunctionTypeSpecifyingExtension; use PHPStan\Type\IntegerRangeType; @@ -25,16 +23,13 @@ use PHPStan\Type\TypeCombinator; use function count; use function in_array; +use function strtolower; #[AutowiredService] final class VersionCompareFunctionTypeSpecifyingExtension implements FunctionTypeSpecifyingExtension, TypeSpecifierAwareExtension { - private TypeSpecifier $typeSpecifier; - - public function __construct(private ReflectionProvider $reflectionProvider) - { - } + private TypeSpecifier $typeSpecifier; public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void { @@ -43,7 +38,7 @@ public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void public function isFunctionSupported(FunctionReflection $functionReflection, FuncCall $node, TypeSpecifierContext $context): bool { - return strtolower($functionReflection->getName())==='version_compare' && $context->true(); + return strtolower($functionReflection->getName()) === 'version_compare' && $context->true(); } public function specifyTypes(FunctionReflection $functionReflection, FuncCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes @@ -142,4 +137,5 @@ private function getVersionCompareType(string $value, ?Expr $operator, Scope $sc return TypeCombinator::remove(new IntegerType(), new ConstantIntegerType($parsedVersion->getVersionId())); } + } diff --git a/tests/PHPStan/Analyser/data/bug-5639.php b/tests/PHPStan/Analyser/data/bug-5639.php index c0eeed3370..740abfb915 100644 --- a/tests/PHPStan/Analyser/data/bug-5639.php +++ b/tests/PHPStan/Analyser/data/bug-5639.php @@ -2,7 +2,7 @@ namespace Bug5639; -if (\version_compare(\PHP_VERSION, '7.0', '<')) +if (\version_compare(\PHP_VERSION, '7.0', '<')) // @phpstan-ignore function.impossibleType { class Foo extends \Exception { function __toString(): string { From 9b9df9992d7ccccae349ee9cd29873ded7bd8fdf Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Feb 2026 09:55:20 +0100 Subject: [PATCH 13/14] Update narrow-phpversionid-on-version-compare.php --- ...narrow-phpversionid-on-version-compare.php | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare.php b/e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare.php index 4335f9e55c..e680fd0144 100644 --- a/e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare.php +++ b/e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare.php @@ -24,4 +24,141 @@ function lower(): void $x = PHP_VERSION_ID; assertType('int<80000, 80399>', $x); } + + if ( + version_compare( PHP_VERSION, '8.4', 'lt' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80000, 80399>', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', '<=' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80000, 80400>', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', 'le' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80000, 80400>', $x); + } +} + +function greater(): void +{ + if ( + version_compare( PHP_VERSION, '8.4', '>' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80401, max>', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', 'gt' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80401, max>', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', '>=' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80400, max>', $x); + } + if ( + version_compare( PHP_VERSION, '8.4', 'ge' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80400, max>', $x); + } +} + +function equal(): void +{ + if ( + version_compare( PHP_VERSION, '8.4', '=' ) + ) { + $x = PHP_VERSION_ID; + assertType('80400', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', '==' ) + ) { + $x = PHP_VERSION_ID; + assertType('80400', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', 'eq' ) + ) { + $x = PHP_VERSION_ID; + assertType('80400', $x); + } +} + + +function not(): void +{ + if ( + version_compare( PHP_VERSION, '8.4', '!=' ) + ) { + $x = PHP_VERSION_ID; + assertType('int|int<80401, max>', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', '<>' ) + ) { + $x = PHP_VERSION_ID; + assertType('int|int<80401, max>', $x); + } + + if ( + version_compare( PHP_VERSION, '8.4', 'ne' ) + ) { + $x = PHP_VERSION_ID; + assertType('int|int<80401, max>', $x); + } +} + +function inverseOperandLower(): void +{ + if ( + version_compare( '8.3.12', PHP_VERSION, '<' ) + ) { + $x = PHP_VERSION_ID; + assertType('int', $x); + } + + if ( + version_compare( '8.3.12', PHP_VERSION, '>=' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80312, max>', $x); + } + + if ( + version_compare( '8.3.12', PHP_VERSION, '>' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80313, max>', $x); + } +} + +function narrow(): void { + if (PHP_VERSION_ID < 80000) { + return; + } + + if ( + version_compare( PHP_VERSION, '8.4', '<' ) + ) { + $x = PHP_VERSION_ID; + assertType('int<80000, 80399>', $x); + } } From 4f03cb8d94fd222cef9eea4fee2e403815f6cbf5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Tue, 10 Feb 2026 09:58:33 +0100 Subject: [PATCH 14/14] mv --- .../narrow-phpversionid-on-version-compare-global-scope.php | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename narrow-phpversionid-on-version-compare-global-scope.php => e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare-global-scope.php (100%) diff --git a/narrow-phpversionid-on-version-compare-global-scope.php b/e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare-global-scope.php similarity index 100% rename from narrow-phpversionid-on-version-compare-global-scope.php rename to e2e/version-compare-phpversionid/narrow-phpversionid-on-version-compare-global-scope.php