diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index b31ccecf985..ba9572e4fa5 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -136,6 +136,7 @@ use function is_string; use function ltrim; use function md5; +use function min; use function preg_match; use function preg_match_all; use function spl_object_id; @@ -6020,25 +6021,56 @@ public function getPhpVersion(): PhpVersions { $constType = $this->getGlobalConstantType(new Name('PHP_VERSION_ID')); if ($constType !== null && !$this->isOverallPhpVersionRange($constType)) { + if ( + $constType instanceof IntegerRangeType + && $constType->getMin() === ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID + && $constType->getMax() !== null + && $this->getConfiguredPhpVersionRange() === null + ) { + // A guard like `PHP_VERSION_ID < 80400` only gives an upper bound. Without + // a configured version range, the lower bound is merely the lowest version + // PHPStan knows, while outside the guard the single analysed PHP version + // applies. Keep the lower bound at that version, or as close to it as + // the guard allows. + $maxVersionId = $constType->getMax(); + return new PhpVersions(IntegerRangeType::fromInterval( + min($this->phpVersion->getVersionId(), $maxVersionId), + $maxVersionId, + )); + } + return new PhpVersions($constType); } - // The analysed PHP version range comes either from the NEON phpVersion min/max - // config or from the composer.json "require.php" constraint - the very same - // source ConstantResolver narrows PHP_VERSION_ID with, so that - // Scope::getPhpVersion() never contradicts the PHP_VERSION_ID constant. + $configuredRange = $this->getConfiguredPhpVersionRange(); + if ($configuredRange !== null) { + return new PhpVersions($configuredRange); + } + + return new PhpVersions(new ConstantIntegerType($this->phpVersion->getVersionId())); + } + + /** + * The analysed PHP version range comes either from the NEON phpVersion min/max + * config or from the composer.json "require.php" constraint - the very same + * source ConstantResolver narrows PHP_VERSION_ID with, so that + * Scope::getPhpVersion() never contradicts the PHP_VERSION_ID constant. + * Null when neither is set and a single PHP version is analysed. + */ + private function getConfiguredPhpVersionRange(): ?Type + { [$minPhpVersion, $maxPhpVersion] = $this->configuredPhpVersionRangeHelper->getVersionRange(); if ( - $minPhpVersion !== null - || ($maxPhpVersion !== null && $maxPhpVersion->getVersionId() !== PhpVersionFactory::MAX_PHP_VERSION) + $minPhpVersion === null + && ($maxPhpVersion === null || $maxPhpVersion->getVersionId() === PhpVersionFactory::MAX_PHP_VERSION) ) { - return new PhpVersions(IntegerRangeType::fromInterval( - $minPhpVersion !== null ? $minPhpVersion->getVersionId() : ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID, - $maxPhpVersion !== null ? $maxPhpVersion->getVersionId() : null, - )); + return null; } - return new PhpVersions(new ConstantIntegerType($this->phpVersion->getVersionId())); + return IntegerRangeType::fromInterval( + $minPhpVersion !== null ? $minPhpVersion->getVersionId() : ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID, + $maxPhpVersion !== null ? $maxPhpVersion->getVersionId() : null, + ); } /** diff --git a/src/Turbo/TurboExtensionEnabler.php b/src/Turbo/TurboExtensionEnabler.php index 374d29043e6..b05b57cc783 100644 --- a/src/Turbo/TurboExtensionEnabler.php +++ b/src/Turbo/TurboExtensionEnabler.php @@ -33,7 +33,7 @@ final class TurboExtensionEnabler { - public const EXPECTED_EXTENSION_VERSION = 'a0f7086'; + public const EXPECTED_EXTENSION_VERSION = 'b1d51b1'; private static bool $active = false; diff --git a/tests/PHPStan/Rules/Classes/InvalidPromotedPropertiesRuleTest.php b/tests/PHPStan/Rules/Classes/InvalidPromotedPropertiesRuleTest.php index e856be21354..4c109e94659 100644 --- a/tests/PHPStan/Rules/Classes/InvalidPromotedPropertiesRuleTest.php +++ b/tests/PHPStan/Rules/Classes/InvalidPromotedPropertiesRuleTest.php @@ -141,4 +141,10 @@ public function testConditionallyDeclaredClass(): void $this->analyse([__DIR__ . '/data/promoted-properties-php-versions.php'], $errors); } + #[RequiresPhp('>= 8.0.0')] + public function testBug15425(): void + { + $this->analyse([__DIR__ . '/data/bug-15425.php'], []); + } + } diff --git a/tests/PHPStan/Rules/Classes/data/bug-15425.php b/tests/PHPStan/Rules/Classes/data/bug-15425.php new file mode 100644 index 00000000000..80a74c039bc --- /dev/null +++ b/tests/PHPStan/Rules/Classes/data/bug-15425.php @@ -0,0 +1,16 @@ += 8.0 + +declare(strict_types = 1); + +namespace Bug15425; + +if (PHP_VERSION_ID < 80400) { + \PHPStan\dumpType(PHP_VERSION_ID); + + final class Foo + { + public function __construct(public ?string $message = null) + { + } + } +} diff --git a/turbo-ext/src/MutatingScope.cpp b/turbo-ext/src/MutatingScope.cpp index 66e26133896..4a810496c9f 100644 --- a/turbo-ext/src/MutatingScope.cpp +++ b/turbo-ext/src/MutatingScope.cpp @@ -3908,11 +3908,14 @@ class MutatingScope if (UNEXPECTED(constType.isUndef())) return zv::Val(); bool isOverallPhpVersionRange = false; + bool hasMinAnalyzableVersion = false; + zv::Val max; if (constType.ref().isObject() && constType.ref().instanceOf(pt_ce_integer_range_type)) { zv::Val min = pt_type_call(constType.ref().asObject(), PT_LC("getmin"), 0, NULL); if (UNEXPECTED(min.isUndef())) return zv::Val(); if (min.ref().isLong() && min.ref().asLong() == PT_MS_PHP_MIN_ANALYZABLE_VERSION_ID) { - zv::Val max = pt_type_call(constType.ref().asObject(), PT_LC("getmax"), 0, NULL); + hasMinAnalyzableVersion = true; + max = pt_type_call(constType.ref().asObject(), PT_LC("getmax"), 0, NULL); if (UNEXPECTED(max.isUndef())) return zv::Val(); if (max.isNull() || (max.ref().isLong() && max.ref().asLong() == PT_MS_MAX_PHP_VERSION)) { isOverallPhpVersionRange = true; @@ -3920,12 +3923,56 @@ class MutatingScope } } - if (!constType.isNull() && !isOverallPhpVersionRange) return pt_type_new(PT_CLASS_PHP_VERSIONS, 1, constType.raw()); + if (!constType.isNull() && !isOverallPhpVersionRange) { + if (hasMinAnalyzableVersion && max.ref().isLong()) { + zv::Val configuredRange = getConfiguredPhpVersionRange(); + if (UNEXPECTED(configuredRange.isUndef())) return zv::Val(); + if (configuredRange.isNull()) { + // A guard like `PHP_VERSION_ID < 80400` only gives an upper bound. Without + // a configured version range, the lower bound is merely the lowest version + // PHPStan knows, while outside the guard the single analysed PHP version + // applies. Keep the lower bound at that version, or as close to it as + // the guard allows. + zv::Ref phpVersion = slot(PT_MS_PROP_PHP_VERSION); + if (UNEXPECTED(!phpVersion.isObject())) return uninitializedProperty("phpVersion"); + zv::Val versionId = pt_php_version_get_version_id(phpVersion.raw()); + if (UNEXPECTED(versionId.isUndef())) return zv::Val(); + zend_long maxVersionId = max.ref().asLong(); + zend_long minVersionId = zval_get_long(versionId.raw()); + zval interval[2]; + ZVAL_LONG(&interval[0], minVersionId < maxVersionId ? minVersionId : maxVersionId); + ZVAL_LONG(&interval[1], maxVersionId); + zv::Val versionRange = pt_type_call_static_ce(pt_ce_integer_range_type, PT_LC("frominterval"), 2, interval); + if (UNEXPECTED(versionRange.isUndef())) return zv::Val(); + return pt_type_new(PT_CLASS_PHP_VERSIONS, 1, versionRange.raw()); + } + } + + return pt_type_new(PT_CLASS_PHP_VERSIONS, 1, constType.raw()); + } + + zv::Val configuredRange = getConfiguredPhpVersionRange(); + if (UNEXPECTED(configuredRange.isUndef())) return zv::Val(); + if (!configuredRange.isNull()) return pt_type_new(PT_CLASS_PHP_VERSIONS, 1, configuredRange.raw()); + + zv::Ref phpVersion = slot(PT_MS_PROP_PHP_VERSION); + if (UNEXPECTED(!phpVersion.isObject())) return uninitializedProperty("phpVersion"); + zv::Val versionId = pt_php_version_get_version_id(phpVersion.raw()); + if (UNEXPECTED(versionId.isUndef())) return zv::Val(); + zval constantInteger; + if (UNEXPECTED(!pt_constant_integer_type_new(&constantInteger, zval_get_long(versionId.raw())))) return zv::Val(); + zv::Val constantIntegerType = zv::Val::adopt(constantInteger); + return pt_type_new(PT_CLASS_PHP_VERSIONS, 1, constantIntegerType.raw()); + } - // The analysed PHP version range comes either from the NEON phpVersion min/max - // config or from the composer.json "require.php" constraint - the very same - // source ConstantResolver narrows PHP_VERSION_ID with, so that - // Scope::getPhpVersion() never contradicts the PHP_VERSION_ID constant. + /* The analysed PHP version range comes either from the NEON phpVersion min/max + * config or from the composer.json "require.php" constraint - the very same + * source ConstantResolver narrows PHP_VERSION_ID with, so that + * Scope::getPhpVersion() never contradicts the PHP_VERSION_ID constant. + * NULL when neither is set and a single PHP version is analysed; UNDEF = + * pending exception */ + zv::Val getConfiguredPhpVersionRange() + { if (UNEXPECTED(!requireSlot(PT_MS_PROP_CONFIGURED_PHP_VERSION_RANGE_HELPER, "configuredPhpVersionRangeHelper"))) return zv::Val(); zv::Ref rangeHelper = slot(PT_MS_PROP_CONFIGURED_PHP_VERSION_RANGE_HELPER); zv::Val range = pt_type_call(rangeHelper.asObject(), PT_LC("getversionrange"), 0, NULL); @@ -3952,36 +3999,25 @@ class MutatingScope if (UNEXPECTED(maxVersionId.isUndef())) return zv::Val(); narrowed = !(maxVersionId.ref().isLong() && maxVersionId.ref().asLong() == PT_MS_MAX_PHP_VERSION); } - if (narrowed) { - zval interval[2]; - zv::Val minVersionId, maxVersionId; - if (minPhpVersion != NULL) { - minVersionId = pt_php_version_get_version_id(minPhpVersion); - if (UNEXPECTED(minVersionId.isUndef())) return zv::Val(); - ZVAL_COPY_VALUE(&interval[0], minVersionId.raw()); - } else { - ZVAL_LONG(&interval[0], PT_MS_PHP_MIN_ANALYZABLE_VERSION_ID); - } - if (maxPhpVersion != NULL) { - maxVersionId = pt_php_version_get_version_id(maxPhpVersion); - if (UNEXPECTED(maxVersionId.isUndef())) return zv::Val(); - ZVAL_COPY_VALUE(&interval[1], maxVersionId.raw()); - } else { - ZVAL_NULL(&interval[1]); - } - zv::Val versionRange = pt_type_call_static_ce(pt_ce_integer_range_type, PT_LC("frominterval"), 2, interval); - if (UNEXPECTED(versionRange.isUndef())) return zv::Val(); - return pt_type_new(PT_CLASS_PHP_VERSIONS, 1, versionRange.raw()); - } + if (!narrowed) return zv::Val::null(); - zv::Ref phpVersion = slot(PT_MS_PROP_PHP_VERSION); - if (UNEXPECTED(!phpVersion.isObject())) return uninitializedProperty("phpVersion"); - zv::Val versionId = pt_php_version_get_version_id(phpVersion.raw()); - if (UNEXPECTED(versionId.isUndef())) return zv::Val(); - zval constantInteger; - if (UNEXPECTED(!pt_constant_integer_type_new(&constantInteger, zval_get_long(versionId.raw())))) return zv::Val(); - zv::Val constantIntegerType = zv::Val::adopt(constantInteger); - return pt_type_new(PT_CLASS_PHP_VERSIONS, 1, constantIntegerType.raw()); + zval interval[2]; + zv::Val minVersionId, maxVersionId; + if (minPhpVersion != NULL) { + minVersionId = pt_php_version_get_version_id(minPhpVersion); + if (UNEXPECTED(minVersionId.isUndef())) return zv::Val(); + ZVAL_COPY_VALUE(&interval[0], minVersionId.raw()); + } else { + ZVAL_LONG(&interval[0], PT_MS_PHP_MIN_ANALYZABLE_VERSION_ID); + } + if (maxPhpVersion != NULL) { + maxVersionId = pt_php_version_get_version_id(maxPhpVersion); + if (UNEXPECTED(maxVersionId.isUndef())) return zv::Val(); + ZVAL_COPY_VALUE(&interval[1], maxVersionId.raw()); + } else { + ZVAL_NULL(&interval[1]); + } + return pt_type_call_static_ce(pt_ce_integer_range_type, PT_LC("frominterval"), 2, interval); } /* $this->getPhpVersion()->supportsNamedArguments()->no() negated — the diff --git a/turbo-ext/src/generated/MutatingScope.h b/turbo-ext/src/generated/MutatingScope.h index 832eb9a9226..4e3ae75c523 100644 --- a/turbo-ext/src/generated/MutatingScope.h +++ b/turbo-ext/src/generated/MutatingScope.h @@ -528,21 +528,22 @@ inline constexpr char strings[] = "getIterableValueType\0" /* 9182 */ "PHPStan\\Php\\PhpVersions\0" /* 9203 */ "getPhpVersion\0" /* 9227 */ - "isOverallPhpVersionRange\0" /* 9241 */ - "PhpParser\\Node\0" /* 9266 */ - "invokeNodeCallback\0" /* 9281 */ - "collectorType\0" /* 9300 */ - "data\0" /* 9314 */ - "emitCollectedData\0" /* 9319 */ - "extensionClass\0" /* 9337 */ - "trackValueDependency\0" /* 9352 */ - "file\0" /* 9373 */ - "trackFileDependency\0" /* 9378 */ - "directory\0" /* 9398 */ - "pattern\0" /* 9408 */ - "'*'\0" /* 9416 */ - "trackDirectoryDependency\0" /* 9420 */ - "trackClassDependency"; /* 9445 */ + "getConfiguredPhpVersionRange\0" /* 9241 */ + "isOverallPhpVersionRange\0" /* 9270 */ + "PhpParser\\Node\0" /* 9295 */ + "invokeNodeCallback\0" /* 9310 */ + "collectorType\0" /* 9329 */ + "data\0" /* 9343 */ + "emitCollectedData\0" /* 9348 */ + "extensionClass\0" /* 9366 */ + "trackValueDependency\0" /* 9381 */ + "file\0" /* 9402 */ + "trackFileDependency\0" /* 9407 */ + "directory\0" /* 9427 */ + "pattern\0" /* 9437 */ + "'*'\0" /* 9445 */ + "trackDirectoryDependency\0" /* 9449 */ + "trackClassDependency"; /* 9474 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 10), /* __construct $container */ reg::packed(48, 0, 61), /* __construct $scopeFactory */ @@ -1070,20 +1071,21 @@ inline constexpr reg::PackedArg args[] = { reg::packed(5878, 0, 2162), /* getIterableValueType $iteratee */ reg::packed(1476, 0, 2162), /* getIterableValueType return */ reg::packed(1476, 0, 9203), /* getPhpVersion return */ + reg::packed(1476, MAY_BE_NULL, 2162), /* getConfiguredPhpVersionRange return */ reg::packed(4614, 0, 2162), /* isOverallPhpVersionRange $type */ reg::packed(1476, MAY_BE_BOOL), /* isOverallPhpVersionRange return */ - reg::packed(2500, 0, 9266), /* invokeNodeCallback $node */ + reg::packed(2500, 0, 9295), /* invokeNodeCallback $node */ reg::packed(1476, MAY_BE_VOID), /* invokeNodeCallback return */ - reg::packed(9300, MAY_BE_STRING), /* emitCollectedData $collectorType */ - reg::packed(9314, MAY_BE_ANY), /* emitCollectedData $data */ + reg::packed(9329, MAY_BE_STRING), /* emitCollectedData $collectorType */ + reg::packed(9343, MAY_BE_ANY), /* emitCollectedData $data */ reg::packed(1476, MAY_BE_VOID), /* emitCollectedData return */ - reg::packed(9337, MAY_BE_STRING), /* trackValueDependency $extensionClass */ + reg::packed(9366, MAY_BE_STRING), /* trackValueDependency $extensionClass */ reg::packed(3280, MAY_BE_STRING), /* trackValueDependency $key */ reg::packed(1476, MAY_BE_VOID), /* trackValueDependency return */ - reg::packed(9373, MAY_BE_STRING), /* trackFileDependency $file */ + reg::packed(9402, MAY_BE_STRING), /* trackFileDependency $file */ reg::packed(1476, MAY_BE_VOID), /* trackFileDependency return */ - reg::packed(9398, MAY_BE_STRING), /* trackDirectoryDependency $directory */ - reg::packed(9408, MAY_BE_STRING, reg::NoString, false, false, 9416), /* trackDirectoryDependency $pattern */ + reg::packed(9427, MAY_BE_STRING), /* trackDirectoryDependency $directory */ + reg::packed(9437, MAY_BE_STRING, reg::NoString, false, false, 9445), /* trackDirectoryDependency $pattern */ reg::packed(1476, MAY_BE_VOID), /* trackDirectoryDependency return */ reg::packed(3717, MAY_BE_STRING), /* trackClassDependency $className */ reg::packed(1476, MAY_BE_VOID), /* trackClassDependency return */ @@ -1285,13 +1287,14 @@ inline constexpr sigtab::Sig getNativeConstantTypes = { { 9140 /* getNativeConst inline constexpr sigtab::Sig getIterableKeyType = { { 9163 /* getIterableKeyType */, 1, 521, 1, 522, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getIterableValueType = { { 9182 /* getIterableValueType */, 1, 523, 1, 524, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getPhpVersion = { { 9227 /* getPhpVersion */, 0, 525, 0, 525, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig isOverallPhpVersionRange = { { 9241 /* isOverallPhpVersionRange */, 1, 526, 1, 527, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig invokeNodeCallback = { { 9281 /* invokeNodeCallback */, 1, 528, 1, 529, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig emitCollectedData = { { 9319 /* emitCollectedData */, 2, 530, 2, 532, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig trackValueDependency = { { 9352 /* trackValueDependency */, 2, 533, 2, 535, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig trackFileDependency = { { 9378 /* trackFileDependency */, 1, 536, 1, 537, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig trackDirectoryDependency = { { 9420 /* trackDirectoryDependency */, 1, 538, 2, 540, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig trackClassDependency = { { 9445 /* trackClassDependency */, 1, 541, 1, 542, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConfiguredPhpVersionRange = { { 9241 /* getConfiguredPhpVersionRange */, 0, 526, 0, 526, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOverallPhpVersionRange = { { 9270 /* isOverallPhpVersionRange */, 1, 527, 1, 528, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig invokeNodeCallback = { { 9310 /* invokeNodeCallback */, 1, 529, 1, 530, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig emitCollectedData = { { 9348 /* emitCollectedData */, 2, 531, 2, 533, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig trackValueDependency = { { 9381 /* trackValueDependency */, 2, 534, 2, 536, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig trackFileDependency = { { 9407 /* trackFileDependency */, 1, 537, 1, 538, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig trackDirectoryDependency = { { 9449 /* trackDirectoryDependency */, 1, 539, 2, 541, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig trackClassDependency = { { 9474 /* trackClassDependency */, 1, 542, 1, 543, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::MutatingScope diff --git a/turbo-ext/tests/scope-family.php b/turbo-ext/tests/scope-family.php index a0fb591f0d5..4d085699dcc 100644 --- a/turbo-ext/tests/scope-family.php +++ b/turbo-ext/tests/scope-family.php @@ -643,6 +643,35 @@ public function exprRef(Node\Expr $expr): mixed return [$table, $table, []]; }; +// PHP_VERSION_ID under a guard that only gives an upper bound (`PHP_VERSION_ID < X`), +// with a single analysed version pinned to phpVersion: 80500 whatever PHP runs +// this: getPhpVersion() raises the lower bound to the analysed version, or as +// close to it as the guard allows - once with the analysed version above the +// guard's bound and once below it +$sfGuardedVersionScopes = []; +foreach ([80399, 80599] as $sfGuardedMax) { + $sfGuardedVersionFetch = new \PhpParser\Node\Expr\ConstFetch(new \PhpParser\Node\Name('PHP_VERSION_ID')); + $sfGuardedVersionArgs = $sfHarness->constructorArgs( + $sfScopeFactory->create(\PHPStan\Analyser\ScopeContext::create($sfFile)) + ->assignExpression($sfGuardedVersionFetch, \PHPStan\Type\IntegerRangeType::fromInterval(\PHPStan\Analyser\ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID, $sfGuardedMax), new \PHPStan\Type\IntegerType()), + ); + $sfGuardedVersionArgs['phpVersion'] = new \PHPStan\Php\PhpVersion(80500); + $sfGuardedVersionArgs['configuredPhpVersionRangeHelper'] = new \PHPStan\Php\ConfiguredPhpVersionRangeHelper(80500, $scContainer->getByType(\PHPStan\Php\ComposerPhpVersionFactory::class)); + $sfGuardedVersionScope = new \PHPStan\Analyser\MutatingScope(...array_values($sfGuardedVersionArgs)); + $sfScopes[spl_object_id($sfGuardedVersionScope)] = [$sfGuardedVersionScope, [$sfGuardedVersionFetch], null]; + $sfGuardedVersionScopes[] = [$sfGuardedVersionScope, static function (string $side) use ($sfHarness, $sfGuardedVersionFetch, $sfGuardedMax): array { + $native = $side === 'native'; + $range = $native + ? \PHPStanTurbo\IntegerRangeType::fromInterval(\PHPStan\Analyser\ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID, $sfGuardedMax) + : \PHPStan\Type\IntegerRangeType::fromInterval(\PHPStan\Analyser\ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID, $sfGuardedMax); + $holder = $native + ? new \PHPStanTurbo\ExpressionTypeHolder($sfGuardedVersionFetch, $range, \PHPStanTurbo\TrinaryLogic::createYes()) + : new \PHPStan\Analyser\ExpressionTypeHolder($sfGuardedVersionFetch, $range, \PHPStan\TrinaryLogic::createYes()); + $table = [$sfHarness->key($sfGuardedVersionFetch) => $holder]; + + return [$table, $table, []]; + }]; +} foreach ([80500, 70400] as $sfVersionId) { $sfVersionScope = $sfScopeFactory->create(\PHPStan\Analyser\ScopeContext::create($sfFile)->enterClass($sfReflectionProvider->getClass(\ScopeFamilyFixture\Holder::class))) ->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 // the scopes whose tables are built per side (each side's own Type classes) $sfScopes[spl_object_id($sfSyntheticScope)][3] = $sfSyntheticTables; $sfScopes[spl_object_id($sfOverallVersionScope)][3] = $sfOverallVersionTables; +foreach ($sfGuardedVersionScopes as [$sfGuardedVersionScope, $sfGuardedVersionTables]) { + $sfScopes[spl_object_id($sfGuardedVersionScope)][3] = $sfGuardedVersionTables; +} $sfPromotedArgs = $sfHarness->constructorArgs($sfSyntheticScope); $sfPromotedArgs['nativeTypesPromoted'] = true; $sfPromotedScope = new \PHPStan\Analyser\MutatingScope(...array_values($sfPromotedArgs));