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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, ExpressionTypeHolder> */
private array $phpVersionExpressionTypes = [];

/**
* @param array<string, ExpressionTypeHolder> $expressionTypes
* @param array<string, ConditionalExpressionHolder[]> $conditionalExpressions
Expand Down Expand Up @@ -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)) {
Expand Down
20 changes: 20 additions & 0 deletions tests/PHPStan/Analyser/ScopePhpVersionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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()));
}

}
34 changes: 32 additions & 2 deletions turbo-ext/src/MutatingScope.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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);
Expand Down
107 changes: 57 additions & 50 deletions turbo-ext/src/generated/MutatingScope.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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");
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions turbo-ext/src/generated/NodeCallbackScope.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading