diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index c7319e2bb99..babb2d54c02 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -177,6 +177,12 @@ class MutatingScope implements Scope, NodeCallbackInvoker, CollectedDataEmitter, private ?self $scopeWithPromotedNativeTypes = null; + /** What getPhpVersion() answered for the expression types in $phpVersionExpressionTypes. */ + private ?PhpVersions $phpVersion = null; + + /** @var array */ + private array $phpVersionExpressionTypes = []; + /** * @param array $expressionTypes * @param array $conditionalExpressions @@ -5953,6 +5959,21 @@ public function getIterableValueType(Type $iteratee): Type } public function getPhpVersion(): PhpVersions + { + // The same scope is asked over and over - on every property fetch, among others - and the + // answer depends only on its expression types. While they are untouched, both properties + // hold the same array and the comparison is a pointer check; a write in place separates + // them and the answer is computed again. + if ($this->phpVersion !== null && $this->phpVersionExpressionTypes === $this->expressionTypes) { + return $this->phpVersion; + } + + $this->phpVersionExpressionTypes = $this->expressionTypes; + + return $this->phpVersion = $this->resolvePhpVersion(); + } + + private function resolvePhpVersion(): PhpVersions { $constType = $this->getGlobalConstantType(new Name('PHP_VERSION_ID')); if ($constType !== null && !$this->isOverallPhpVersionRange($constType)) { diff --git a/tests/PHPStan/Analyser/ScopePhpVersionTest.php b/tests/PHPStan/Analyser/ScopePhpVersionTest.php index 0c68a2bc87b..31016881348 100644 --- a/tests/PHPStan/Analyser/ScopePhpVersionTest.php +++ b/tests/PHPStan/Analyser/ScopePhpVersionTest.php @@ -3,8 +3,12 @@ namespace PHPStan\Analyser; use PhpParser\Node; +use PhpParser\Node\Expr\ConstFetch; use PhpParser\Node\Expr\Exit_; +use PhpParser\Node\Name; use PHPStan\Testing\TypeInferenceTestCase; +use PHPStan\Type\IntegerRangeType; +use PHPStan\Type\IntegerType; use PHPStan\Type\VerbosityLevel; use PHPUnit\Framework\Attributes\DataProvider; @@ -39,4 +43,20 @@ public function testPhpVersion(string $expected, string $file): void }); } + public function testPhpVersionFollowsExpressionTypesWrittenInPlace(): void + { + $scope = self::getContainer()->getByType(ScopeFactory::class)->create(ScopeContext::create(__FILE__)); + $unnarrowed = $scope->getPhpVersion()->getType()->describe(VerbosityLevel::precise()); + + $narrowed = $scope->assignExpression( + new ConstFetch(new Name('PHP_VERSION_ID')), + IntegerRangeType::fromInterval(80100, 80199), + new IntegerType(), + ); + $this->assertNotSame('int<80100, 80199>', $unnarrowed); + + $scope->expressionTypes = $narrowed->expressionTypes; + $this->assertSame('int<80100, 80199>', $scope->getPhpVersion()->getType()->describe(VerbosityLevel::precise())); + } + } diff --git a/turbo-ext/src/MutatingScope.cpp b/turbo-ext/src/MutatingScope.cpp index dfc21fc70bf..7ed45e5e3d0 100644 --- a/turbo-ext/src/MutatingScope.cpp +++ b/turbo-ext/src/MutatingScope.cpp @@ -15,9 +15,9 @@ * methods must be declared here — linking checks that. * * Layout. The twin's properties are declared typed property slots in the - * twin's declaration order — the five class-body properties first + * twin's declaration order — the seven class-body properties first * (resolvedTypes, nodeCallbackScope, namespace, scopeOutOfFirstLevelStatement, - * scopeWithPromotedNativeTypes), then the 32 promoted constructor + * scopeWithPromotedNativeTypes, phpVersion, phpVersionExpressionTypes), then the 32 promoted constructor * properties in parameter order ($namespace is the one constructor * parameter that is not promoted) — so the std object handlers do * GC/clone/free and a PHP subclass's own properties follow them. Promoted @@ -300,6 +300,8 @@ enum : uint32_t PT_MS_PROP_NAMESPACE, PT_MS_PROP_SCOPE_OUT_OF_FIRST_LEVEL_STATEMENT, PT_MS_PROP_SCOPE_WITH_PROMOTED_NATIVE_TYPES, + PT_MS_PROP_PHP_VERSION, + PT_MS_PROP_PHP_VERSION_EXPRESSION_TYPES, PT_MS_PROP_SERVICES, PT_MS_PROP_SCOPE_FACTORY, PT_MS_PROP_CONTEXT, @@ -3666,6 +3668,32 @@ class MutatingScope * getFunctionType() (2881) */ zv::Val getPhpVersion() + { + /* the twin's memo: the answer for the expression types it was + * computed from - the same array while they are untouched (the + * pointer check `===` starts with), a separated one after a write in + * place */ + if (UNEXPECTED(!requireSlot(PT_MS_PROP_EXPRESSION_TYPES, "expressionTypes"))) return zv::Val(); + zval *expressionTypes = OBJ_PROP_NUM(self, PT_MS_PROP_EXPRESSION_TYPES); + zval *memoTypes = OBJ_PROP_NUM(self, PT_MS_PROP_PHP_VERSION_EXPRESSION_TYPES); + zv::Ref memo = slot(PT_MS_PROP_PHP_VERSION); + if ( + memo.isObject() + && Z_TYPE_P(memoTypes) == IS_ARRAY + && (Z_ARR_P(memoTypes) == Z_ARR_P(expressionTypes) || zend_is_identical(memoTypes, expressionTypes)) + ) { + return zv::Val::copyOf(memo); + } + + writeSlot(PT_MS_PROP_PHP_VERSION_EXPRESSION_TYPES, zv::Val::copyOf(zv::Ref(expressionTypes))); + zv::Val phpVersion = resolvePhpVersion(); + if (UNEXPECTED(phpVersion.isUndef())) return zv::Val(); + writeSlot(PT_MS_PROP_PHP_VERSION, zv::Val::copyOf(phpVersion.ref())); + return phpVersion; + } + + /* private */ + zv::Val resolvePhpVersion() { zv::Val constantName = zv::Val::string(PT_LC("PHP_VERSION_ID")); zv::Val nameNode = pt_name_node_new(PT_CLASS_NAME, constantName.raw()); @@ -12411,6 +12439,8 @@ PT_MINIT_REGISTRATION(pt_register_mutating_scope) cls.property("namespace", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_STRING | MAY_BE_NULL); cls.property("scopeOutOfFirstLevelStatement", ZEND_ACC_PRIVATE, reg::PropertyKind::TypedNull, MAY_BE_NULL, "self"); cls.property("scopeWithPromotedNativeTypes", ZEND_ACC_PRIVATE, reg::PropertyKind::TypedNull, MAY_BE_NULL, "self"); + cls.property("phpVersion", ZEND_ACC_PRIVATE, reg::PropertyKind::TypedNull, MAY_BE_NULL, phpVersions); + cls.property("phpVersionExpressionTypes", ZEND_ACC_PRIVATE, reg::PropertyKind::TypedEmptyArray, MAY_BE_ARRAY); cls.property("services", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, scopeServices); cls.property("scopeFactory", ZEND_ACC_PROTECTED, reg::PropertyKind::Typed, 0, internalScopeFactory); cls.property("context", ZEND_ACC_PROTECTED, reg::PropertyKind::Typed, 0, scopeContext); diff --git a/turbo-ext/src/generated/MutatingScope.h b/turbo-ext/src/generated/MutatingScope.h index 185b41a21e1..80a6cf5067a 100644 --- a/turbo-ext/src/generated/MutatingScope.h +++ b/turbo-ext/src/generated/MutatingScope.h @@ -15,25 +15,27 @@ inline constexpr uint32_t nodeCallbackScope = 1; inline constexpr uint32_t namespace_ = 2; inline constexpr uint32_t scopeOutOfFirstLevelStatement = 3; inline constexpr uint32_t scopeWithPromotedNativeTypes = 4; -inline constexpr uint32_t services = 5; -inline constexpr uint32_t scopeFactory = 6; -inline constexpr uint32_t context = 7; -inline constexpr uint32_t declareStrictTypes = 8; -inline constexpr uint32_t function = 9; -inline constexpr uint32_t expressionTypes = 10; -inline constexpr uint32_t nativeExpressionTypes = 11; -inline constexpr uint32_t conditionalExpressions = 12; -inline constexpr uint32_t inClosureBindScopeClasses = 13; -inline constexpr uint32_t anonymousFunctionReflection = 14; -inline constexpr uint32_t inFirstLevelStatement = 15; -inline constexpr uint32_t currentlyAssignedExpressions = 16; -inline constexpr uint32_t currentlyAllowedUndefinedExpressions = 17; -inline constexpr uint32_t inFunctionCallsStack = 18; -inline constexpr uint32_t afterExtractCall = 19; -inline constexpr uint32_t parentScope = 20; -inline constexpr uint32_t nativeTypesPromoted = 21; -inline constexpr uint32_t templateArgumentFrame = 22; -inline constexpr uint32_t templateArgumentConstraints = 23; +inline constexpr uint32_t phpVersion = 5; +inline constexpr uint32_t phpVersionExpressionTypes = 6; +inline constexpr uint32_t services = 7; +inline constexpr uint32_t scopeFactory = 8; +inline constexpr uint32_t context = 9; +inline constexpr uint32_t declareStrictTypes = 10; +inline constexpr uint32_t function = 11; +inline constexpr uint32_t expressionTypes = 12; +inline constexpr uint32_t nativeExpressionTypes = 13; +inline constexpr uint32_t conditionalExpressions = 14; +inline constexpr uint32_t inClosureBindScopeClasses = 15; +inline constexpr uint32_t anonymousFunctionReflection = 16; +inline constexpr uint32_t inFirstLevelStatement = 17; +inline constexpr uint32_t currentlyAssignedExpressions = 18; +inline constexpr uint32_t currentlyAllowedUndefinedExpressions = 19; +inline constexpr uint32_t inFunctionCallsStack = 20; +inline constexpr uint32_t afterExtractCall = 21; +inline constexpr uint32_t parentScope = 22; +inline constexpr uint32_t nativeTypesPromoted = 23; +inline constexpr uint32_t templateArgumentFrame = 24; +inline constexpr uint32_t templateArgumentConstraints = 25; } // namespace slot inline void declareClass(reg::Class &cls) @@ -50,6 +52,8 @@ inline void declareProperties(reg::Class &cls) cls.property("namespace", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, MAY_BE_NULL | MAY_BE_STRING); cls.property("scopeOutOfFirstLevelStatement", ZEND_ACC_PRIVATE, reg::PropertyKind::TypedNull, MAY_BE_NULL, "PHPStan\\Analyser\\MutatingScope"); cls.property("scopeWithPromotedNativeTypes", ZEND_ACC_PRIVATE, reg::PropertyKind::TypedNull, MAY_BE_NULL, "PHPStan\\Analyser\\MutatingScope"); + cls.property("phpVersion", ZEND_ACC_PRIVATE, reg::PropertyKind::TypedNull, MAY_BE_NULL, "PHPStan\\Php\\PhpVersions"); + cls.property("phpVersionExpressionTypes", ZEND_ACC_PRIVATE, reg::PropertyKind::TypedEmptyArray, MAY_BE_ARRAY); cls.property("services", ZEND_ACC_PRIVATE, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\ScopeServices"); cls.property("scopeFactory", ZEND_ACC_PROTECTED, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\InternalScopeFactory"); cls.property("context", ZEND_ACC_PROTECTED, reg::PropertyKind::Typed, 0, "PHPStan\\Analyser\\ScopeContext"); @@ -482,22 +486,23 @@ inline constexpr char strings[] = "getIterableValueType\0" /* 8547 */ "PHPStan\\Php\\PhpVersions\0" /* 8568 */ "getPhpVersion\0" /* 8592 */ - "getConfiguredPhpVersionRange\0" /* 8606 */ - "isOverallPhpVersionRange\0" /* 8635 */ - "PhpParser\\Node\0" /* 8660 */ - "invokeNodeCallback\0" /* 8675 */ - "collectorType\0" /* 8694 */ - "data\0" /* 8708 */ - "emitCollectedData\0" /* 8713 */ - "extensionClass\0" /* 8731 */ - "trackValueDependency\0" /* 8746 */ - "file\0" /* 8767 */ - "trackFileDependency\0" /* 8772 */ - "directory\0" /* 8792 */ - "pattern\0" /* 8802 */ - "'*'\0" /* 8810 */ - "trackDirectoryDependency\0" /* 8814 */ - "trackClassDependency"; /* 8839 */ + "resolvePhpVersion\0" /* 8606 */ + "getConfiguredPhpVersionRange\0" /* 8624 */ + "isOverallPhpVersionRange\0" /* 8653 */ + "PhpParser\\Node\0" /* 8678 */ + "invokeNodeCallback\0" /* 8693 */ + "collectorType\0" /* 8712 */ + "data\0" /* 8726 */ + "emitCollectedData\0" /* 8731 */ + "extensionClass\0" /* 8749 */ + "trackValueDependency\0" /* 8764 */ + "file\0" /* 8785 */ + "trackFileDependency\0" /* 8790 */ + "directory\0" /* 8810 */ + "pattern\0" /* 8820 */ + "'*'\0" /* 8828 */ + "trackDirectoryDependency\0" /* 8832 */ + "trackClassDependency"; /* 8857 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 9), /* __construct $services */ reg::packed(40, 0, 53), /* __construct $scopeFactory */ @@ -1016,21 +1021,22 @@ inline constexpr reg::PackedArg args[] = { reg::packed(5138, 0, 1424), /* getIterableValueType $iteratee */ reg::packed(738, 0, 1424), /* getIterableValueType return */ reg::packed(738, 0, 8568), /* getPhpVersion return */ + reg::packed(738, 0, 8568), /* resolvePhpVersion return */ reg::packed(738, MAY_BE_NULL, 1424), /* getConfiguredPhpVersionRange return */ reg::packed(3874, 0, 1424), /* isOverallPhpVersionRange $type */ reg::packed(738, MAY_BE_BOOL), /* isOverallPhpVersionRange return */ - reg::packed(1707, 0, 8660), /* invokeNodeCallback $node */ + reg::packed(1707, 0, 8678), /* invokeNodeCallback $node */ reg::packed(738, MAY_BE_VOID), /* invokeNodeCallback return */ - reg::packed(8694, MAY_BE_STRING), /* emitCollectedData $collectorType */ - reg::packed(8708, MAY_BE_ANY), /* emitCollectedData $data */ + reg::packed(8712, MAY_BE_STRING), /* emitCollectedData $collectorType */ + reg::packed(8726, MAY_BE_ANY), /* emitCollectedData $data */ reg::packed(738, MAY_BE_VOID), /* emitCollectedData return */ - reg::packed(8731, MAY_BE_STRING), /* trackValueDependency $extensionClass */ + reg::packed(8749, MAY_BE_STRING), /* trackValueDependency $extensionClass */ reg::packed(2540, MAY_BE_STRING), /* trackValueDependency $key */ reg::packed(738, MAY_BE_VOID), /* trackValueDependency return */ - reg::packed(8767, MAY_BE_STRING), /* trackFileDependency $file */ + reg::packed(8785, MAY_BE_STRING), /* trackFileDependency $file */ reg::packed(738, MAY_BE_VOID), /* trackFileDependency return */ - reg::packed(8792, MAY_BE_STRING), /* trackDirectoryDependency $directory */ - reg::packed(8802, MAY_BE_STRING, reg::NoString, false, false, 8810), /* trackDirectoryDependency $pattern */ + reg::packed(8810, MAY_BE_STRING), /* trackDirectoryDependency $directory */ + reg::packed(8820, MAY_BE_STRING, reg::NoString, false, false, 8828), /* trackDirectoryDependency $pattern */ reg::packed(738, MAY_BE_VOID), /* trackDirectoryDependency return */ reg::packed(2977, MAY_BE_STRING), /* trackClassDependency $className */ reg::packed(738, MAY_BE_VOID), /* trackClassDependency return */ @@ -1233,14 +1239,15 @@ inline constexpr sigtab::Sig getNativeConstantTypes = { { 8505 /* getNativeConst inline constexpr sigtab::Sig getIterableKeyType = { { 8528 /* getIterableKeyType */, 1, 512, 1, 513, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getIterableValueType = { { 8547 /* getIterableValueType */, 1, 514, 1, 515, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig getPhpVersion = { { 8592 /* getPhpVersion */, 0, 516, 0, 516, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getConfiguredPhpVersionRange = { { 8606 /* getConfiguredPhpVersionRange */, 0, 517, 0, 517, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isOverallPhpVersionRange = { { 8635 /* isOverallPhpVersionRange */, 1, 518, 1, 519, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig invokeNodeCallback = { { 8675 /* invokeNodeCallback */, 1, 520, 1, 521, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig emitCollectedData = { { 8713 /* emitCollectedData */, 2, 522, 2, 524, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig trackValueDependency = { { 8746 /* trackValueDependency */, 2, 525, 2, 527, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig trackFileDependency = { { 8772 /* trackFileDependency */, 1, 528, 1, 529, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig trackDirectoryDependency = { { 8814 /* trackDirectoryDependency */, 1, 530, 2, 532, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig trackClassDependency = { { 8839 /* trackClassDependency */, 1, 533, 1, 534, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig resolvePhpVersion = { { 8606 /* resolvePhpVersion */, 0, 517, 0, 517, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getConfiguredPhpVersionRange = { { 8624 /* getConfiguredPhpVersionRange */, 0, 518, 0, 518, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig isOverallPhpVersionRange = { { 8653 /* isOverallPhpVersionRange */, 1, 519, 1, 520, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig invokeNodeCallback = { { 8693 /* invokeNodeCallback */, 1, 521, 1, 522, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig emitCollectedData = { { 8731 /* emitCollectedData */, 2, 523, 2, 525, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig trackValueDependency = { { 8764 /* trackValueDependency */, 2, 526, 2, 528, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig trackFileDependency = { { 8790 /* trackFileDependency */, 1, 529, 1, 530, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig trackDirectoryDependency = { { 8832 /* trackDirectoryDependency */, 1, 531, 2, 533, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig trackClassDependency = { { 8857 /* trackClassDependency */, 1, 534, 1, 535, ZEND_ACC_PUBLIC } }; } // namespace sig } // namespace ptdecl::MutatingScope diff --git a/turbo-ext/src/generated/NodeCallbackScope.h b/turbo-ext/src/generated/NodeCallbackScope.h index 5a8723a79d7..251332f8002 100644 --- a/turbo-ext/src/generated/NodeCallbackScope.h +++ b/turbo-ext/src/generated/NodeCallbackScope.h @@ -10,12 +10,12 @@ namespace ptdecl::NodeCallbackScope { /* the OBJ_PROP_NUM slots of the instance properties the class declares (the inherited ones come first) */ namespace slot { -inline constexpr uint32_t truthyValueExprs = 24; -inline constexpr uint32_t falseyValueExprs = 25; -inline constexpr uint32_t walkScope = 26; -inline constexpr uint32_t seededWalkScope = 27; -inline constexpr uint32_t askedTypes = 28; -inline constexpr uint32_t askedNativeTypes = 29; +inline constexpr uint32_t truthyValueExprs = 26; +inline constexpr uint32_t falseyValueExprs = 27; +inline constexpr uint32_t walkScope = 28; +inline constexpr uint32_t seededWalkScope = 29; +inline constexpr uint32_t askedTypes = 30; +inline constexpr uint32_t askedNativeTypes = 31; } // namespace slot inline void declareClass(reg::Class &cls)