Skip to content

Commit d7039e3

Browse files
HawkForgestaabm
authored andcommitted
Keep the analysed PHP version as the floor of a PHP_VERSION_ID < X guard
When a single PHP version is analysed (a fixed `phpVersion`, or no version range from the config or composer.json), PHP_VERSION_ID is int<50207, max> and Scope::getPhpVersion() treats that as no information and answers with the analysed version. A guard like `if (PHP_VERSION_ID < 80400)` narrows the constant to int<50207, 80399>, and getPhpVersion() returned that range as is, so the version dropped to PHP 5.2.7 inside the branch. Since the "supported only on PHP X" rules read the version from the scope, a class with promoted properties in such a branch got a non-ignorable property.promotedNotSupported error with `phpVersion: 80500`. Only the upper bound of that range comes from the guard. Raise the lower bound to the analysed version, or to the guard's upper bound when the guard excludes the analysed version. Guards with a lower bound (`PHP_VERSION_ID >= 80300`) and configured version ranges are unchanged. The turbo-ext mirror gets the same change. Closes phpstan/phpstan#15425
1 parent bb2b515 commit d7039e3

6 files changed

Lines changed: 200 additions & 75 deletions

File tree

‎src/Analyser/MutatingScope.php‎

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
use function is_string;
124124
use function ltrim;
125125
use function md5;
126+
use function min;
126127
use function preg_match;
127128
use function preg_match_all;
128129
use function spl_object_id;
@@ -5877,25 +5878,56 @@ public function getPhpVersion(): PhpVersions
58775878
{
58785879
$constType = $this->getGlobalConstantType(new Name('PHP_VERSION_ID'));
58795880
if ($constType !== null && !$this->isOverallPhpVersionRange($constType)) {
5881+
if (
5882+
$constType instanceof IntegerRangeType
5883+
&& $constType->getMin() === ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID
5884+
&& $constType->getMax() !== null
5885+
&& $this->getConfiguredPhpVersionRange() === null
5886+
) {
5887+
// A guard like `PHP_VERSION_ID < 80400` only gives an upper bound. Without
5888+
// a configured version range, the lower bound is merely the lowest version
5889+
// PHPStan knows, while outside the guard the single analysed PHP version
5890+
// applies. Keep the lower bound at that version, or as close to it as
5891+
// the guard allows.
5892+
$maxVersionId = $constType->getMax();
5893+
return new PhpVersions(IntegerRangeType::fromInterval(
5894+
min($this->services->phpVersion->getVersionId(), $maxVersionId),
5895+
$maxVersionId,
5896+
));
5897+
}
5898+
58805899
return new PhpVersions($constType);
58815900
}
58825901

5883-
// The analysed PHP version range comes either from the NEON phpVersion min/max
5884-
// config or from the composer.json "require.php" constraint - the very same
5885-
// source ConstantResolver narrows PHP_VERSION_ID with, so that
5886-
// Scope::getPhpVersion() never contradicts the PHP_VERSION_ID constant.
5902+
$configuredRange = $this->getConfiguredPhpVersionRange();
5903+
if ($configuredRange !== null) {
5904+
return new PhpVersions($configuredRange);
5905+
}
5906+
5907+
return new PhpVersions(new ConstantIntegerType($this->services->phpVersion->getVersionId()));
5908+
}
5909+
5910+
/**
5911+
* The analysed PHP version range comes either from the NEON phpVersion min/max
5912+
* config or from the composer.json "require.php" constraint - the very same
5913+
* source ConstantResolver narrows PHP_VERSION_ID with, so that
5914+
* Scope::getPhpVersion() never contradicts the PHP_VERSION_ID constant.
5915+
* Null when neither is set and a single PHP version is analysed.
5916+
*/
5917+
private function getConfiguredPhpVersionRange(): ?Type
5918+
{
58875919
[$minPhpVersion, $maxPhpVersion] = $this->services->configuredPhpVersionRangeHelper->getVersionRange();
58885920
if (
5889-
$minPhpVersion !== null
5890-
|| ($maxPhpVersion !== null && $maxPhpVersion->getVersionId() !== PhpVersionFactory::MAX_PHP_VERSION)
5921+
$minPhpVersion === null
5922+
&& ($maxPhpVersion === null || $maxPhpVersion->getVersionId() === PhpVersionFactory::MAX_PHP_VERSION)
58915923
) {
5892-
return new PhpVersions(IntegerRangeType::fromInterval(
5893-
$minPhpVersion !== null ? $minPhpVersion->getVersionId() : ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID,
5894-
$maxPhpVersion !== null ? $maxPhpVersion->getVersionId() : null,
5895-
));
5924+
return null;
58965925
}
58975926

5898-
return new PhpVersions(new ConstantIntegerType($this->services->phpVersion->getVersionId()));
5927+
return IntegerRangeType::fromInterval(
5928+
$minPhpVersion !== null ? $minPhpVersion->getVersionId() : ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID,
5929+
$maxPhpVersion !== null ? $maxPhpVersion->getVersionId() : null,
5930+
);
58995931
}
59005932

59015933
/**

‎tests/PHPStan/Rules/Classes/InvalidPromotedPropertiesRuleTest.php‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,10 @@ public function testConditionallyDeclaredClass(): void
141141
$this->analyse([__DIR__ . '/data/promoted-properties-php-versions.php'], $errors);
142142
}
143143

144+
#[RequiresPhp('>= 8.0.0')]
145+
public function testBug15425(): void
146+
{
147+
$this->analyse([__DIR__ . '/data/bug-15425.php'], []);
148+
}
149+
144150
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug15425;
6+
7+
if (PHP_VERSION_ID < 80400) {
8+
\PHPStan\dumpType(PHP_VERSION_ID);
9+
10+
final class Foo
11+
{
12+
public function __construct(public ?string $message = null)
13+
{
14+
}
15+
}
16+
}

‎turbo-ext/src/MutatingScope.cpp‎

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3678,24 +3678,71 @@ class MutatingScope
36783678
if (UNEXPECTED(constType.isUndef())) return zv::Val();
36793679

36803680
bool isOverallPhpVersionRange = false;
3681+
bool hasMinAnalyzableVersion = false;
3682+
zv::Val max;
36813683
if (constType.ref().isObject() && constType.ref().instanceOf(pt_ce_integer_range_type)) {
36823684
zv::Val min = pt_type_call(constType.ref().asObject(), PT_LC("getmin"), 0, NULL);
36833685
if (UNEXPECTED(min.isUndef())) return zv::Val();
36843686
if (min.ref().isLong() && min.ref().asLong() == PT_MS_PHP_MIN_ANALYZABLE_VERSION_ID) {
3685-
zv::Val max = pt_type_call(constType.ref().asObject(), PT_LC("getmax"), 0, NULL);
3687+
hasMinAnalyzableVersion = true;
3688+
max = pt_type_call(constType.ref().asObject(), PT_LC("getmax"), 0, NULL);
36863689
if (UNEXPECTED(max.isUndef())) return zv::Val();
36873690
if (max.isNull() || (max.ref().isLong() && max.ref().asLong() == PT_MS_MAX_PHP_VERSION)) {
36883691
isOverallPhpVersionRange = true;
36893692
}
36903693
}
36913694
}
36923695

3693-
if (!constType.isNull() && !isOverallPhpVersionRange) return pt_type_new(PT_CLASS_PHP_VERSIONS, 1, constType.raw());
3696+
if (!constType.isNull() && !isOverallPhpVersionRange) {
3697+
if (hasMinAnalyzableVersion && max.ref().isLong()) {
3698+
zv::Val configuredRange = getConfiguredPhpVersionRange();
3699+
if (UNEXPECTED(configuredRange.isUndef())) return zv::Val();
3700+
if (configuredRange.isNull()) {
3701+
// A guard like `PHP_VERSION_ID < 80400` only gives an upper bound. Without
3702+
// a configured version range, the lower bound is merely the lowest version
3703+
// PHPStan knows, while outside the guard the single analysed PHP version
3704+
// applies. Keep the lower bound at that version, or as close to it as
3705+
// the guard allows.
3706+
zv::Ref phpVersion = service(PT_MSS_PHP_VERSION);
3707+
if (UNEXPECTED(!phpVersion.isObject())) return uninitializedProperty("phpVersion");
3708+
zv::Val versionId = pt_php_version_get_version_id(phpVersion.raw());
3709+
if (UNEXPECTED(versionId.isUndef())) return zv::Val();
3710+
zend_long maxVersionId = max.ref().asLong();
3711+
zend_long minVersionId = zval_get_long(versionId.raw());
3712+
zval interval[2];
3713+
ZVAL_LONG(&interval[0], minVersionId < maxVersionId ? minVersionId : maxVersionId);
3714+
ZVAL_LONG(&interval[1], maxVersionId);
3715+
zv::Val versionRange = pt_type_call_static_ce(pt_ce_integer_range_type, PT_LC("frominterval"), 2, interval);
3716+
if (UNEXPECTED(versionRange.isUndef())) return zv::Val();
3717+
return pt_type_new(PT_CLASS_PHP_VERSIONS, 1, versionRange.raw());
3718+
}
3719+
}
3720+
3721+
return pt_type_new(PT_CLASS_PHP_VERSIONS, 1, constType.raw());
3722+
}
3723+
3724+
zv::Val configuredRange = getConfiguredPhpVersionRange();
3725+
if (UNEXPECTED(configuredRange.isUndef())) return zv::Val();
3726+
if (!configuredRange.isNull()) return pt_type_new(PT_CLASS_PHP_VERSIONS, 1, configuredRange.raw());
3727+
3728+
zv::Ref phpVersion = service(PT_MSS_PHP_VERSION);
3729+
if (UNEXPECTED(!phpVersion.isObject())) return uninitializedProperty("phpVersion");
3730+
zv::Val versionId = pt_php_version_get_version_id(phpVersion.raw());
3731+
if (UNEXPECTED(versionId.isUndef())) return zv::Val();
3732+
zval constantInteger;
3733+
if (UNEXPECTED(!pt_constant_integer_type_new(&constantInteger, zval_get_long(versionId.raw())))) return zv::Val();
3734+
zv::Val constantIntegerType = zv::Val::adopt(constantInteger);
3735+
return pt_type_new(PT_CLASS_PHP_VERSIONS, 1, constantIntegerType.raw());
3736+
}
36943737

3695-
// The analysed PHP version range comes either from the NEON phpVersion min/max
3696-
// config or from the composer.json "require.php" constraint - the very same
3697-
// source ConstantResolver narrows PHP_VERSION_ID with, so that
3698-
// Scope::getPhpVersion() never contradicts the PHP_VERSION_ID constant.
3738+
/* The analysed PHP version range comes either from the NEON phpVersion min/max
3739+
* config or from the composer.json "require.php" constraint - the very same
3740+
* source ConstantResolver narrows PHP_VERSION_ID with, so that
3741+
* Scope::getPhpVersion() never contradicts the PHP_VERSION_ID constant.
3742+
* NULL when neither is set and a single PHP version is analysed; UNDEF =
3743+
* pending exception */
3744+
zv::Val getConfiguredPhpVersionRange()
3745+
{
36993746
if (UNEXPECTED(!requireService())) return zv::Val();
37003747
zv::Ref rangeHelper = service(PT_MSS_CONFIGURED_PHP_VERSION_RANGE_HELPER);
37013748
zv::Val range = pt_type_call(rangeHelper.asObject(), PT_LC("getversionrange"), 0, NULL);
@@ -3722,36 +3769,25 @@ class MutatingScope
37223769
if (UNEXPECTED(maxVersionId.isUndef())) return zv::Val();
37233770
narrowed = !(maxVersionId.ref().isLong() && maxVersionId.ref().asLong() == PT_MS_MAX_PHP_VERSION);
37243771
}
3725-
if (narrowed) {
3726-
zval interval[2];
3727-
zv::Val minVersionId, maxVersionId;
3728-
if (minPhpVersion != NULL) {
3729-
minVersionId = pt_php_version_get_version_id(minPhpVersion);
3730-
if (UNEXPECTED(minVersionId.isUndef())) return zv::Val();
3731-
ZVAL_COPY_VALUE(&interval[0], minVersionId.raw());
3732-
} else {
3733-
ZVAL_LONG(&interval[0], PT_MS_PHP_MIN_ANALYZABLE_VERSION_ID);
3734-
}
3735-
if (maxPhpVersion != NULL) {
3736-
maxVersionId = pt_php_version_get_version_id(maxPhpVersion);
3737-
if (UNEXPECTED(maxVersionId.isUndef())) return zv::Val();
3738-
ZVAL_COPY_VALUE(&interval[1], maxVersionId.raw());
3739-
} else {
3740-
ZVAL_NULL(&interval[1]);
3741-
}
3742-
zv::Val versionRange = pt_type_call_static_ce(pt_ce_integer_range_type, PT_LC("frominterval"), 2, interval);
3743-
if (UNEXPECTED(versionRange.isUndef())) return zv::Val();
3744-
return pt_type_new(PT_CLASS_PHP_VERSIONS, 1, versionRange.raw());
3745-
}
3772+
if (!narrowed) return zv::Val::null();
37463773

3747-
zv::Ref phpVersion = service(PT_MSS_PHP_VERSION);
3748-
if (UNEXPECTED(!phpVersion.isObject())) return uninitializedProperty("phpVersion");
3749-
zv::Val versionId = pt_php_version_get_version_id(phpVersion.raw());
3750-
if (UNEXPECTED(versionId.isUndef())) return zv::Val();
3751-
zval constantInteger;
3752-
if (UNEXPECTED(!pt_constant_integer_type_new(&constantInteger, zval_get_long(versionId.raw())))) return zv::Val();
3753-
zv::Val constantIntegerType = zv::Val::adopt(constantInteger);
3754-
return pt_type_new(PT_CLASS_PHP_VERSIONS, 1, constantIntegerType.raw());
3774+
zval interval[2];
3775+
zv::Val minVersionId, maxVersionId;
3776+
if (minPhpVersion != NULL) {
3777+
minVersionId = pt_php_version_get_version_id(minPhpVersion);
3778+
if (UNEXPECTED(minVersionId.isUndef())) return zv::Val();
3779+
ZVAL_COPY_VALUE(&interval[0], minVersionId.raw());
3780+
} else {
3781+
ZVAL_LONG(&interval[0], PT_MS_PHP_MIN_ANALYZABLE_VERSION_ID);
3782+
}
3783+
if (maxPhpVersion != NULL) {
3784+
maxVersionId = pt_php_version_get_version_id(maxPhpVersion);
3785+
if (UNEXPECTED(maxVersionId.isUndef())) return zv::Val();
3786+
ZVAL_COPY_VALUE(&interval[1], maxVersionId.raw());
3787+
} else {
3788+
ZVAL_NULL(&interval[1]);
3789+
}
3790+
return pt_type_call_static_ce(pt_ce_integer_range_type, PT_LC("frominterval"), 2, interval);
37553791
}
37563792

37573793
/* $this->getPhpVersion()->supportsNamedArguments()->no() negated — the

‎turbo-ext/src/generated/MutatingScope.h‎

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -477,21 +477,22 @@ inline constexpr char strings[] =
477477
"getIterableValueType\0" /* 8442 */
478478
"PHPStan\\Php\\PhpVersions\0" /* 8463 */
479479
"getPhpVersion\0" /* 8487 */
480-
"isOverallPhpVersionRange\0" /* 8501 */
481-
"PhpParser\\Node\0" /* 8526 */
482-
"invokeNodeCallback\0" /* 8541 */
483-
"collectorType\0" /* 8560 */
484-
"data\0" /* 8574 */
485-
"emitCollectedData\0" /* 8579 */
486-
"extensionClass\0" /* 8597 */
487-
"trackValueDependency\0" /* 8612 */
488-
"file\0" /* 8633 */
489-
"trackFileDependency\0" /* 8638 */
490-
"directory\0" /* 8658 */
491-
"pattern\0" /* 8668 */
492-
"'*'\0" /* 8676 */
493-
"trackDirectoryDependency\0" /* 8680 */
494-
"trackClassDependency"; /* 8705 */
480+
"getConfiguredPhpVersionRange\0" /* 8501 */
481+
"isOverallPhpVersionRange\0" /* 8530 */
482+
"PhpParser\\Node\0" /* 8555 */
483+
"invokeNodeCallback\0" /* 8570 */
484+
"collectorType\0" /* 8589 */
485+
"data\0" /* 8603 */
486+
"emitCollectedData\0" /* 8608 */
487+
"extensionClass\0" /* 8626 */
488+
"trackValueDependency\0" /* 8641 */
489+
"file\0" /* 8662 */
490+
"trackFileDependency\0" /* 8667 */
491+
"directory\0" /* 8687 */
492+
"pattern\0" /* 8697 */
493+
"'*'\0" /* 8705 */
494+
"trackDirectoryDependency\0" /* 8709 */
495+
"trackClassDependency"; /* 8734 */
495496
inline constexpr reg::PackedArg args[] = {
496497
reg::packed(0, 0, 9), /* __construct $services */
497498
reg::packed(40, 0, 53), /* __construct $scopeFactory */
@@ -1004,20 +1005,21 @@ inline constexpr reg::PackedArg args[] = {
10041005
reg::packed(5138, 0, 1424), /* getIterableValueType $iteratee */
10051006
reg::packed(738, 0, 1424), /* getIterableValueType return */
10061007
reg::packed(738, 0, 8463), /* getPhpVersion return */
1008+
reg::packed(738, MAY_BE_NULL, 1424), /* getConfiguredPhpVersionRange return */
10071009
reg::packed(3874, 0, 1424), /* isOverallPhpVersionRange $type */
10081010
reg::packed(738, MAY_BE_BOOL), /* isOverallPhpVersionRange return */
1009-
reg::packed(1707, 0, 8526), /* invokeNodeCallback $node */
1011+
reg::packed(1707, 0, 8555), /* invokeNodeCallback $node */
10101012
reg::packed(738, MAY_BE_VOID), /* invokeNodeCallback return */
1011-
reg::packed(8560, MAY_BE_STRING), /* emitCollectedData $collectorType */
1012-
reg::packed(8574, MAY_BE_ANY), /* emitCollectedData $data */
1013+
reg::packed(8589, MAY_BE_STRING), /* emitCollectedData $collectorType */
1014+
reg::packed(8603, MAY_BE_ANY), /* emitCollectedData $data */
10131015
reg::packed(738, MAY_BE_VOID), /* emitCollectedData return */
1014-
reg::packed(8597, MAY_BE_STRING), /* trackValueDependency $extensionClass */
1016+
reg::packed(8626, MAY_BE_STRING), /* trackValueDependency $extensionClass */
10151017
reg::packed(2540, MAY_BE_STRING), /* trackValueDependency $key */
10161018
reg::packed(738, MAY_BE_VOID), /* trackValueDependency return */
1017-
reg::packed(8633, MAY_BE_STRING), /* trackFileDependency $file */
1019+
reg::packed(8662, MAY_BE_STRING), /* trackFileDependency $file */
10181020
reg::packed(738, MAY_BE_VOID), /* trackFileDependency return */
1019-
reg::packed(8658, MAY_BE_STRING), /* trackDirectoryDependency $directory */
1020-
reg::packed(8668, MAY_BE_STRING, reg::NoString, false, false, 8676), /* trackDirectoryDependency $pattern */
1021+
reg::packed(8687, MAY_BE_STRING), /* trackDirectoryDependency $directory */
1022+
reg::packed(8697, MAY_BE_STRING, reg::NoString, false, false, 8705), /* trackDirectoryDependency $pattern */
10211023
reg::packed(738, MAY_BE_VOID), /* trackDirectoryDependency return */
10221024
reg::packed(2977, MAY_BE_STRING), /* trackClassDependency $className */
10231025
reg::packed(738, MAY_BE_VOID), /* trackClassDependency return */
@@ -1218,13 +1220,14 @@ inline constexpr sigtab::Sig getNativeConstantTypes = { { 8400 /* getNativeConst
12181220
inline constexpr sigtab::Sig getIterableKeyType = { { 8423 /* getIterableKeyType */, 1, 506, 1, 507, ZEND_ACC_PUBLIC } };
12191221
inline constexpr sigtab::Sig getIterableValueType = { { 8442 /* getIterableValueType */, 1, 508, 1, 509, ZEND_ACC_PUBLIC } };
12201222
inline constexpr sigtab::Sig getPhpVersion = { { 8487 /* getPhpVersion */, 0, 510, 0, 510, ZEND_ACC_PUBLIC } };
1221-
inline constexpr sigtab::Sig isOverallPhpVersionRange = { { 8501 /* isOverallPhpVersionRange */, 1, 511, 1, 512, ZEND_ACC_PRIVATE } };
1222-
inline constexpr sigtab::Sig invokeNodeCallback = { { 8541 /* invokeNodeCallback */, 1, 513, 1, 514, ZEND_ACC_PUBLIC } };
1223-
inline constexpr sigtab::Sig emitCollectedData = { { 8579 /* emitCollectedData */, 2, 515, 2, 517, ZEND_ACC_PUBLIC } };
1224-
inline constexpr sigtab::Sig trackValueDependency = { { 8612 /* trackValueDependency */, 2, 518, 2, 520, ZEND_ACC_PUBLIC } };
1225-
inline constexpr sigtab::Sig trackFileDependency = { { 8638 /* trackFileDependency */, 1, 521, 1, 522, ZEND_ACC_PUBLIC } };
1226-
inline constexpr sigtab::Sig trackDirectoryDependency = { { 8680 /* trackDirectoryDependency */, 1, 523, 2, 525, ZEND_ACC_PUBLIC } };
1227-
inline constexpr sigtab::Sig trackClassDependency = { { 8705 /* trackClassDependency */, 1, 526, 1, 527, ZEND_ACC_PUBLIC } };
1223+
inline constexpr sigtab::Sig getConfiguredPhpVersionRange = { { 8501 /* getConfiguredPhpVersionRange */, 0, 511, 0, 511, ZEND_ACC_PRIVATE } };
1224+
inline constexpr sigtab::Sig isOverallPhpVersionRange = { { 8530 /* isOverallPhpVersionRange */, 1, 512, 1, 513, ZEND_ACC_PRIVATE } };
1225+
inline constexpr sigtab::Sig invokeNodeCallback = { { 8570 /* invokeNodeCallback */, 1, 514, 1, 515, ZEND_ACC_PUBLIC } };
1226+
inline constexpr sigtab::Sig emitCollectedData = { { 8608 /* emitCollectedData */, 2, 516, 2, 518, ZEND_ACC_PUBLIC } };
1227+
inline constexpr sigtab::Sig trackValueDependency = { { 8641 /* trackValueDependency */, 2, 519, 2, 521, ZEND_ACC_PUBLIC } };
1228+
inline constexpr sigtab::Sig trackFileDependency = { { 8667 /* trackFileDependency */, 1, 522, 1, 523, ZEND_ACC_PUBLIC } };
1229+
inline constexpr sigtab::Sig trackDirectoryDependency = { { 8709 /* trackDirectoryDependency */, 1, 524, 2, 526, ZEND_ACC_PUBLIC } };
1230+
inline constexpr sigtab::Sig trackClassDependency = { { 8734 /* trackClassDependency */, 1, 527, 1, 528, ZEND_ACC_PUBLIC } };
12281231
} // namespace sig
12291232

12301233
} // namespace ptdecl::MutatingScope

‎turbo-ext/tests/scope-family.php‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,35 @@ public function exprRef(Node\Expr $expr): mixed
643643

644644
return [$table, $table, []];
645645
};
646+
// PHP_VERSION_ID under a guard that only gives an upper bound (`PHP_VERSION_ID < X`),
647+
// with a single analysed version pinned to phpVersion: 80500 whatever PHP runs
648+
// this: getPhpVersion() raises the lower bound to the analysed version, or as
649+
// close to it as the guard allows - once with the analysed version above the
650+
// guard's bound and once below it
651+
$sfGuardedVersionScopes = [];
652+
foreach ([80399, 80599] as $sfGuardedMax) {
653+
$sfGuardedVersionFetch = new \PhpParser\Node\Expr\ConstFetch(new \PhpParser\Node\Name('PHP_VERSION_ID'));
654+
$sfGuardedVersionArgs = $sfHarness->constructorArgs(
655+
$sfScopeFactory->create(\PHPStan\Analyser\ScopeContext::create($sfFile))
656+
->assignExpression($sfGuardedVersionFetch, \PHPStan\Type\IntegerRangeType::fromInterval(\PHPStan\Analyser\ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID, $sfGuardedMax), new \PHPStan\Type\IntegerType()),
657+
);
658+
$sfGuardedVersionArgs['phpVersion'] = new \PHPStan\Php\PhpVersion(80500);
659+
$sfGuardedVersionArgs['configuredPhpVersionRangeHelper'] = new \PHPStan\Php\ConfiguredPhpVersionRangeHelper(80500, $scContainer->getByType(\PHPStan\Php\ComposerPhpVersionFactory::class));
660+
$sfGuardedVersionScope = new \PHPStan\Analyser\MutatingScope(...array_values($sfGuardedVersionArgs));
661+
$sfScopes[spl_object_id($sfGuardedVersionScope)] = [$sfGuardedVersionScope, [$sfGuardedVersionFetch], null];
662+
$sfGuardedVersionScopes[] = [$sfGuardedVersionScope, static function (string $side) use ($sfHarness, $sfGuardedVersionFetch, $sfGuardedMax): array {
663+
$native = $side === 'native';
664+
$range = $native
665+
? \PHPStanTurbo\IntegerRangeType::fromInterval(\PHPStan\Analyser\ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID, $sfGuardedMax)
666+
: \PHPStan\Type\IntegerRangeType::fromInterval(\PHPStan\Analyser\ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID, $sfGuardedMax);
667+
$holder = $native
668+
? new \PHPStanTurbo\ExpressionTypeHolder($sfGuardedVersionFetch, $range, \PHPStanTurbo\TrinaryLogic::createYes())
669+
: new \PHPStan\Analyser\ExpressionTypeHolder($sfGuardedVersionFetch, $range, \PHPStan\TrinaryLogic::createYes());
670+
$table = [$sfHarness->key($sfGuardedVersionFetch) => $holder];
671+
672+
return [$table, $table, []];
673+
}];
674+
}
646675
foreach ([80500, 70400] as $sfVersionId) {
647676
$sfVersionScope = $sfScopeFactory->create(\PHPStan\Analyser\ScopeContext::create($sfFile)->enterClass($sfReflectionProvider->getClass(\ScopeFamilyFixture\Holder::class)))
648677
->assignExpression(new \PhpParser\Node\Expr\ConstFetch(new \PhpParser\Node\Name('PHP_VERSION_ID')), new \PHPStan\Type\Constant\ConstantIntegerType($sfVersionId), new \PHPStan\Type\IntegerType());
@@ -759,6 +788,9 @@ public function exprRef(Node\Expr $expr): mixed
759788
// the scopes whose tables are built per side (each side's own Type classes)
760789
$sfScopes[spl_object_id($sfSyntheticScope)][3] = $sfSyntheticTables;
761790
$sfScopes[spl_object_id($sfOverallVersionScope)][3] = $sfOverallVersionTables;
791+
foreach ($sfGuardedVersionScopes as [$sfGuardedVersionScope, $sfGuardedVersionTables]) {
792+
$sfScopes[spl_object_id($sfGuardedVersionScope)][3] = $sfGuardedVersionTables;
793+
}
762794
$sfPromotedArgs = $sfHarness->constructorArgs($sfSyntheticScope);
763795
$sfPromotedArgs['nativeTypesPromoted'] = true;
764796
$sfPromotedScope = new \PHPStan\Analyser\MutatingScope(...array_values($sfPromotedArgs));

0 commit comments

Comments
 (0)