From 5602cc6c00527c8b99ebd8f78169569facacc999 Mon Sep 17 00:00:00 2001 From: VincentLanglet <9052536+VincentLanglet@users.noreply.github.com> Date: Wed, 4 Nov 2026 19:00:39 +0000 Subject: [PATCH 1/6] Narrow remembered `count()`/`sizeof()` of an array when the array expression is narrowed - MutatingScope::specifyExpressionTypeInPlace() now calls the new narrowRememberedCountsInPlace(): when the narrowed expression is an array and the scope remembers `count($expr)` / `sizeof($expr)` (Name or FullyQualified spelling), the remembered type is intersected with the new type's getArraySize(), for both PHPDoc and native tables. Intersections that would be never are skipped. - Previously a `count($arr)` entry left behind by merging branches (e.g. int<0, max> after `if (count($arr) > 2) {}`) shadowed the size implied by later narrowing of `$arr`. The same staleness affected every way of narrowing the array, not only `count($arr, COUNT_RECURSIVE)`: `$arr !== []`, `$arr === []`, truthiness, `!empty()`, `isset($arr[0])`, `array_key_exists()`, recursive `sizeof()`, and property fetches like `$this->prop`. All of these are covered by the regression test. - Ported to the native mirror in turbo-ext/src/MutatingScope.cpp, including the new private COUNT_FUNCTION_KEY_NAMES constant. --- src/Analyser/MutatingScope.php | 32 +++++++ tests/PHPStan/Analyser/nsrt/bug-13750.php | 107 ++++++++++++++++++++++ turbo-ext/src/MutatingScope.cpp | 68 ++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-13750.php diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 85867200a20..186667d34db 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -149,6 +149,9 @@ class MutatingScope implements Scope, NodeCallbackInvoker, CollectedDataEmitter, /** Magic methods that let the author decide which properties survive a serialize()/unserialize() round trip. */ private const CUSTOM_SERIALIZATION_METHODS = ['__sleep', '__serialize', '__unserialize']; + /** Names a remembered `count()` of an expression can be keyed under (Name and FullyQualified). */ + private const COUNT_FUNCTION_KEY_NAMES = ['count', 'sizeof', '\\count', '\\sizeof']; + /** * Expression keys the constant-fetch nodes of a global constant lookup print * as, by the name class, name and namespace they are built from. The nodes @@ -3682,6 +3685,7 @@ private function specifyExpressionTypeInPlace(Expr $expr, Type $type, Type $nati $exprString = $this->getNodeKey($expr); $this->expressionTypes[$exprString] = new ExpressionTypeHolder($expr, $type, $certainty); $this->nativeExpressionTypes[$exprString] = new ExpressionTypeHolder($expr, $nativeType, $certainty); + $this->narrowRememberedCountsInPlace($exprString, $type, $nativeType); if (!($expr instanceof AlwaysRememberedExpr)) { return; @@ -3690,6 +3694,34 @@ private function specifyExpressionTypeInPlace(Expr $expr, Type $type, Type $nati $this->specifyExpressionTypeInPlace($expr->expr, $type, $nativeType, $certainty); } + /** + * A remembered `count($expr)` (e.g. int<0, max> left over from merging + * branches) would otherwise shadow the size implied by the narrowed array. + */ + private function narrowRememberedCountsInPlace(string $exprString, Type $type, Type $nativeType): void + { + foreach (self::COUNT_FUNCTION_KEY_NAMES as $functionName) { + $countKey = $functionName . '(' . $exprString . ')'; + if (isset($this->expressionTypes[$countKey]) && $type->isArray()->yes()) { + $holder = $this->expressionTypes[$countKey]; + $narrowed = TypeCombinator::intersect($holder->getType(), $type->getArraySize()); + if (!$narrowed instanceof NeverType) { + $this->expressionTypes[$countKey] = new ExpressionTypeHolder($holder->getExpr(), $narrowed, $holder->getCertainty()); + } + } + if (!isset($this->nativeExpressionTypes[$countKey]) || !$nativeType->isArray()->yes()) { + continue; + } + + $nativeHolder = $this->nativeExpressionTypes[$countKey]; + $narrowedNative = TypeCombinator::intersect($nativeHolder->getType(), $nativeType->getArraySize()); + if ($narrowedNative instanceof NeverType) { + continue; + } + $this->nativeExpressionTypes[$countKey] = new ExpressionTypeHolder($nativeHolder->getExpr(), $narrowedNative, $nativeHolder->getCertainty()); + } + } + public function assignExpression(Expr $expr, Type $type, Type $nativeType): self { $scope = $this; diff --git a/tests/PHPStan/Analyser/nsrt/bug-13750.php b/tests/PHPStan/Analyser/nsrt/bug-13750.php new file mode 100644 index 00000000000..b279343aca6 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-13750.php @@ -0,0 +1,107 @@ + $arr */ + public function forgetCount(array $arr): void + { + if (count($arr) > 2) { + assertType('non-empty-array', $arr); + assertType('int<3, max>', count($arr)); + } + assertType('array', $arr); + assertType('int<0, max>', count($arr)); + if (count($arr, COUNT_RECURSIVE) > 2) { + assertType('non-empty-array', $arr); + assertType('int<1, max>', count($arr)); + } + } + +} + +class Analogous +{ + + /** @var array */ + private array $prop = []; + + /** @param array $arr */ + public function notIdentical(array $arr): void + { + if (count($arr) > 2) { + } + assertType('int<0, max>', count($arr)); + if ($arr !== []) { + assertType('int<1, max>', count($arr)); + assertType('int<1, max>', sizeof($arr)); + } else { + assertType('0', count($arr)); + } + } + + /** @param array $arr */ + public function truthy(array $arr): void + { + if (count($arr) > 2) { + } + if ($arr) { + assertType('int<1, max>', count($arr)); + } + if (!empty($arr)) { + assertType('int<1, max>', count($arr)); + } + } + + /** @param array $arr */ + public function offsetChecks(array $arr): void + { + if (count($arr) > 2) { + } + if (isset($arr[0])) { + assertType('int<1, max>', count($arr)); + } + if (array_key_exists('a', $arr)) { + assertType('int<1, max>', count($arr)); + } + } + + /** @param array $arr */ + public function sizeofRecursive(array $arr): void + { + if (sizeof($arr) > 2) { + } + assertType('int<0, max>', sizeof($arr)); + if (sizeof($arr, COUNT_RECURSIVE) > 2) { + assertType('int<1, max>', sizeof($arr)); + assertType('int<1, max>', count($arr)); + } + if (count($arr, COUNT_RECURSIVE) === 0) { + assertType('0', count($arr)); + } + } + + public function property(): void + { + if (count($this->prop) > 2) { + } + assertType('int<0, max>', count($this->prop)); + if ($this->prop !== []) { + assertType('int<1, max>', count($this->prop)); + } + } + + /** @param array $arr */ + public function keepsNarrowerCount(array $arr): void + { + if (count($arr) > 2) { + if ($arr !== []) { + assertType('int<3, max>', count($arr)); + } + } + } + +} diff --git a/turbo-ext/src/MutatingScope.cpp b/turbo-ext/src/MutatingScope.cpp index 1138b46ed9a..989d4a1c0bf 100644 --- a/turbo-ext/src/MutatingScope.cpp +++ b/turbo-ext/src/MutatingScope.cpp @@ -403,6 +403,15 @@ static const pt_superglobal_name pt_ms_custom_serialization_methods[] = { { PT_LC("__unserialize") }, }; +/* the twin's private COUNT_FUNCTION_KEY_NAMES (narrowRememberedCountsInPlace() + * walks the same names), a persistent list built once at module startup */ +static const pt_superglobal_name pt_ms_count_function_key_names[] = { + { PT_LC("count") }, + { PT_LC("sizeof") }, + { PT_LC("\\count") }, + { PT_LC("\\sizeof") }, +}; + /* PHPStan\Analyser\ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID and * PHPStan\Php\PhpVersionFactory::MAX_PHP_VERSION — the one place the native * code reads them from (getPhpVersion()) */ @@ -6737,6 +6746,7 @@ class MutatingScope || !writeScopeTable(self, PT_MS_PROP_NATIVE_EXPRESSION_TYPES, PT_LC("nativeExpressionTypes"), key.get(), ownedNativeHolder.raw()))) { return false; } + if (UNEXPECTED(!narrowRememberedCountsInPlace(key.get(), type, nativeType))) return false; bool isAlwaysRemembered; if (UNEXPECTED(!isInstance(zv::Ref(&exprZv), PT_CLASS_ALWAYS_REMEMBERED_EXPR, isAlwaysRemembered))) return false; @@ -6750,6 +6760,55 @@ class MutatingScope return specifyExpressionTypeInPlace(inner.deref().asObject(), type, nativeType, certainty); } + /* private narrowRememberedCountsInPlace() — intersects a remembered + * count($expr) / sizeof($expr) with the narrowed array's size; false = + * pending exception */ + [[nodiscard]] bool narrowRememberedCountsInPlace(zend_string *exprString, zval *type, zval *nativeType) + { + for (const pt_superglobal_name &functionName : pt_ms_count_function_key_names) { + zv::Str countKey = zv::Str::adopt(zend_strpprintf(0, "%s(%s)", functionName.name, ZSTR_VAL(exprString))); + if (UNEXPECTED(!narrowRememberedCountInTable(PT_MS_PROP_EXPRESSION_TYPES, PT_LC("expressionTypes"), countKey.get(), type)) + || UNEXPECTED(!narrowRememberedCountInTable(PT_MS_PROP_NATIVE_EXPRESSION_TYPES, PT_LC("nativeExpressionTypes"), countKey.get(), nativeType))) { + return false; + } + } + return true; + } + + /* one table's arm of narrowRememberedCountsInPlace(); false = pending exception */ + [[nodiscard]] bool narrowRememberedCountInTable(uint32_t tableSlot, const char *name, size_t len, zend_string *countKey, zval *arrayType) + { + if (UNEXPECTED(!requireSlot(tableSlot, name))) return false; + zval *holder = zend_symtable_find(Z_ARRVAL_P(slot(tableSlot).raw()), countKey); + if (holder == NULL) return true; + if (UNEXPECTED(Z_TYPE_P(arrayType) != IS_OBJECT)) { + zend_throw_error(NULL, "Call to a member function isArray() on %s", zend_zval_value_name(arrayType)); + return false; + } + zend_long isArray = pt_type_op_trinary(Z_OBJ_P(arrayType), PT_OP_IS_ARRAY, 0, NULL); + if (UNEXPECTED(isArray < 0)) return false; + if (isArray != PT_TRI_YES) return true; + + zv::Val holderZv = zv::Val::copyOf(zv::Ref(holder)); + zv::Val countType = holderType(holderZv.ref()); + if (UNEXPECTED(countType.isUndef())) return false; + zv::Val arraySize = pt_type_call(Z_OBJ_P(arrayType), PT_LC("getarraysize"), 0, NULL); + if (UNEXPECTED(arraySize.isUndef())) return false; + zv::Args args{countType.raw(), arraySize.raw()}; + zv::Val narrowed = pt_type_combinator_intersect(2, args); + if (UNEXPECTED(narrowed.isUndef())) return false; + if (Z_TYPE_P(narrowed.raw()) == IS_OBJECT && instanceof_function(Z_OBJCE_P(narrowed.raw()), pt_ce_never_type)) return true; + + zv::Val countExpr = holderRead(holderZv.ref(), PT_ETH_PROP_EXPR, PT_LC("getexpr")); + if (UNEXPECTED(countExpr.isUndef())) return false; + zend_long certainty = holderCertainty(holderZv.ref()); + if (UNEXPECTED(certainty < 0)) return false; + zval newHolder; + pt_holder_create(&newHolder, countExpr.raw(), narrowed.raw(), certainty); + zv::Val ownedHolder = zv::Val::adopt(newHolder); + return writeScopeTable(self, tableSlot, name, len, countKey, ownedHolder.raw()); + } + /* the ArrayDimFetch arm of specifyExpressionTypeInPlace(): the var's * offset-accessible narrowing, applied in place */ bool specifyArrayDimVarInPlace(zend_object *dim, zend_object *var, zval *type, zval *certainty) @@ -12268,6 +12327,13 @@ static void pt_ms_custom_serialization_methods_constant(zval *out) pt_persistent_list_into(out, pt_ms_custom_serialization_methods_list); } +static HashTable *pt_ms_count_function_key_names_list = nullptr; + +static void pt_ms_count_function_key_names_constant(zval *out) +{ + pt_persistent_list_into(out, pt_ms_count_function_key_names_list); +} + PT_MINIT_REGISTRATION(pt_register_mutating_scope) { using namespace pt_ms; @@ -12316,6 +12382,8 @@ PT_MINIT_REGISTRATION(pt_register_mutating_scope) cls.privateClassConstantLong("GLOBAL_CONSTANT_FETCH_KEYS_LIMIT", PT_MS_GLOBAL_CONSTANT_FETCH_KEYS_LIMIT); pt_ms_custom_serialization_methods_list = pt_persistent_string_list(pt_ms_custom_serialization_methods, sizeof(pt_ms_custom_serialization_methods) / sizeof(pt_ms_custom_serialization_methods[0])); cls.privateClassConstantValue("CUSTOM_SERIALIZATION_METHODS", pt_ms_custom_serialization_methods_constant); + pt_ms_count_function_key_names_list = pt_persistent_string_list(pt_ms_count_function_key_names, sizeof(pt_ms_count_function_key_names) / sizeof(pt_ms_count_function_key_names[0])); + cls.privateClassConstantValue("COUNT_FUNCTION_KEY_NAMES", pt_ms_count_function_key_names_constant); /* }}} */ cls.method("__construct", reg::Public, 3, { From 8e620e18f99c897276c455fd3c5ce4558708a75e Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Wed, 4 Nov 2026 20:04:11 +0000 Subject: [PATCH 2/6] Forget merged call expressions that are no more precise than the call itself Instead of narrowing a remembered count()/sizeof() whenever its array is narrowed, do not keep such an entry after merging scopes in the first place: merging count($a) of int<3, max> and int<0, 2> gives int<0, max>, which is exactly what count($a) resolves to on its own. The stale entry then shadowed what later narrowing of $a implied for the call. The merge now drops a remembered FuncCall/MethodCall/StaticCall whose merged type (PHPDoc and native) equals the type the call resolves to without the entry. This is not specific to count() - strlen($s) after `if (strlen($s) > 2) {}` had the same problem with `$s !== ''`. Co-Authored-By: Claude Opus 5.5 --- src/Analyser/MutatingScope.php | 113 +++++++++---- tests/PHPStan/Analyser/nsrt/bug-13750.php | 24 +++ turbo-ext/src/MutatingScope.cpp | 187 ++++++++++++++-------- 3 files changed, 219 insertions(+), 105 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 186667d34db..c8d5c9a1e1c 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -104,6 +104,7 @@ use Serializable; use Throwable; use function abs; +use function array_diff_key; use function array_filter; use function array_key_exists; use function array_keys; @@ -149,9 +150,6 @@ class MutatingScope implements Scope, NodeCallbackInvoker, CollectedDataEmitter, /** Magic methods that let the author decide which properties survive a serialize()/unserialize() round trip. */ private const CUSTOM_SERIALIZATION_METHODS = ['__sleep', '__serialize', '__unserialize']; - /** Names a remembered `count()` of an expression can be keyed under (Name and FullyQualified). */ - private const COUNT_FUNCTION_KEY_NAMES = ['count', 'sizeof', '\\count', '\\sizeof']; - /** * Expression keys the constant-fetch nodes of a global constant lookup print * as, by the name class, name and namespace they are built from. The nodes @@ -3685,7 +3683,6 @@ private function specifyExpressionTypeInPlace(Expr $expr, Type $type, Type $nati $exprString = $this->getNodeKey($expr); $this->expressionTypes[$exprString] = new ExpressionTypeHolder($expr, $type, $certainty); $this->nativeExpressionTypes[$exprString] = new ExpressionTypeHolder($expr, $nativeType, $certainty); - $this->narrowRememberedCountsInPlace($exprString, $type, $nativeType); if (!($expr instanceof AlwaysRememberedExpr)) { return; @@ -3694,34 +3691,6 @@ private function specifyExpressionTypeInPlace(Expr $expr, Type $type, Type $nati $this->specifyExpressionTypeInPlace($expr->expr, $type, $nativeType, $certainty); } - /** - * A remembered `count($expr)` (e.g. int<0, max> left over from merging - * branches) would otherwise shadow the size implied by the narrowed array. - */ - private function narrowRememberedCountsInPlace(string $exprString, Type $type, Type $nativeType): void - { - foreach (self::COUNT_FUNCTION_KEY_NAMES as $functionName) { - $countKey = $functionName . '(' . $exprString . ')'; - if (isset($this->expressionTypes[$countKey]) && $type->isArray()->yes()) { - $holder = $this->expressionTypes[$countKey]; - $narrowed = TypeCombinator::intersect($holder->getType(), $type->getArraySize()); - if (!$narrowed instanceof NeverType) { - $this->expressionTypes[$countKey] = new ExpressionTypeHolder($holder->getExpr(), $narrowed, $holder->getCertainty()); - } - } - if (!isset($this->nativeExpressionTypes[$countKey]) || !$nativeType->isArray()->yes()) { - continue; - } - - $nativeHolder = $this->nativeExpressionTypes[$countKey]; - $narrowedNative = TypeCombinator::intersect($nativeHolder->getType(), $nativeType->getArraySize()); - if ($narrowedNative instanceof NeverType) { - continue; - } - $this->nativeExpressionTypes[$countKey] = new ExpressionTypeHolder($nativeHolder->getExpr(), $narrowedNative, $nativeHolder->getCertainty()); - } - } - public function assignExpression(Expr $expr, Type $type, Type $nativeType): self { $scope = $this; @@ -4453,8 +4422,8 @@ private function mergeWithVariableState(?self $otherScope, bool $preserveVacuous $otherScope->nativeExpressionTypes, ); - /** @var static */ - return ScopeOps::scopeWith( + $afterExtractCall = $this->afterExtractCall && $otherScope->afterExtractCall; + $merged = ScopeOps::scopeWith( $this, $mergedExpressionTypes, $mergedNativeTypes, @@ -4463,10 +4432,84 @@ private function mergeWithVariableState(?self $otherScope, bool $preserveVacuous [], [], $this->inFirstLevelStatement, - $this->afterExtractCall && $otherScope->afterExtractCall, + $afterExtractCall, + ); + + $uninformativeKeys = $merged->findUninformativeRememberedCalls($differingExpressionKeys, $afterExtractCall); + if ($uninformativeKeys === []) { + /** @var static */ + return $merged; + } + + /** @var static */ + return ScopeOps::scopeWith( + $this, + array_diff_key($mergedExpressionTypes, $uninformativeKeys), + array_diff_key($mergedNativeTypes, $uninformativeKeys), + $conditionalExpressions, + [], + [], + [], + $this->inFirstLevelStatement, + $afterExtractCall, ); } + /** + * Keys of call expressions whose merged type is exactly what the call + * resolves to without the entry. Merging `count($a)` of int<3, max> and + * int<0, 2> gives int<0, max> - remembering that adds nothing, and the + * stale entry would shadow what later narrowing of `$a` implies for the call. + * + * @param array $differingExpressionKeys + * @return array + */ + private function findUninformativeRememberedCalls(array $differingExpressionKeys, bool $afterExtractCall): array + { + $candidates = []; + foreach (array_keys($differingExpressionKeys) as $exprString) { + $holder = $this->expressionTypes[$exprString] ?? null; + if ($holder === null || !$holder->getCertainty()->yes()) { + continue; + } + $expr = $holder->getExpr(); + if (!$expr instanceof FuncCall && !$expr instanceof MethodCall && !$expr instanceof Expr\StaticCall) { + continue; + } + $candidates[$exprString] = $holder; + } + if ($candidates === []) { + return []; + } + + $withoutCandidates = ScopeOps::scopeWith( + $this, + array_diff_key($this->expressionTypes, $candidates), + array_diff_key($this->nativeExpressionTypes, $candidates), + $this->conditionalExpressions, + [], + [], + [], + $this->inFirstLevelStatement, + $afterExtractCall, + ); + + $uninformativeKeys = []; + foreach ($candidates as $exprString => $holder) { + $expr = $holder->getExpr(); + if (!$holder->getType()->equals($withoutCandidates->getType($expr))) { + continue; + } + $nativeHolder = $this->nativeExpressionTypes[$exprString] ?? null; + if ($nativeHolder !== null && !$nativeHolder->getType()->equals($withoutCandidates->getNativeType($expr))) { + continue; + } + $uninformativeKeys[$exprString] = true; + } + + return $uninformativeKeys; + } + /** * Drops the keys of class-constant fetches that resolve to their declared value. * diff --git a/tests/PHPStan/Analyser/nsrt/bug-13750.php b/tests/PHPStan/Analyser/nsrt/bug-13750.php index b279343aca6..e0050153658 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-13750.php +++ b/tests/PHPStan/Analyser/nsrt/bug-13750.php @@ -94,6 +94,30 @@ public function property(): void } } + public function strlen(string $s): void + { + if (strlen($s) > 2) { + } + assertType('int<0, max>', strlen($s)); + if ($s !== '') { + assertType('int<1, max>', strlen($s)); + } + } + + /** @param array $arr */ + public function keepsInformativeMergedCount(array $arr): void + { + if (count($arr) === 0) { + return; + } + if (count($arr) > 5) { + } + assertType('int<1, max>', count($arr)); + if (count($arr) > 2) { + assertType('int<3, max>', count($arr)); + } + } + /** @param array $arr */ public function keepsNarrowerCount(array $arr): void { diff --git a/turbo-ext/src/MutatingScope.cpp b/turbo-ext/src/MutatingScope.cpp index 989d4a1c0bf..acc320a3d25 100644 --- a/turbo-ext/src/MutatingScope.cpp +++ b/turbo-ext/src/MutatingScope.cpp @@ -403,15 +403,6 @@ static const pt_superglobal_name pt_ms_custom_serialization_methods[] = { { PT_LC("__unserialize") }, }; -/* the twin's private COUNT_FUNCTION_KEY_NAMES (narrowRememberedCountsInPlace() - * walks the same names), a persistent list built once at module startup */ -static const pt_superglobal_name pt_ms_count_function_key_names[] = { - { PT_LC("count") }, - { PT_LC("sizeof") }, - { PT_LC("\\count") }, - { PT_LC("\\sizeof") }, -}; - /* PHPStan\Analyser\ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID and * PHPStan\Php\PhpVersionFactory::MAX_PHP_VERSION — the one place the native * code reads them from (getPhpVersion()) */ @@ -6746,7 +6737,6 @@ class MutatingScope || !writeScopeTable(self, PT_MS_PROP_NATIVE_EXPRESSION_TYPES, PT_LC("nativeExpressionTypes"), key.get(), ownedNativeHolder.raw()))) { return false; } - if (UNEXPECTED(!narrowRememberedCountsInPlace(key.get(), type, nativeType))) return false; bool isAlwaysRemembered; if (UNEXPECTED(!isInstance(zv::Ref(&exprZv), PT_CLASS_ALWAYS_REMEMBERED_EXPR, isAlwaysRemembered))) return false; @@ -6760,55 +6750,6 @@ class MutatingScope return specifyExpressionTypeInPlace(inner.deref().asObject(), type, nativeType, certainty); } - /* private narrowRememberedCountsInPlace() — intersects a remembered - * count($expr) / sizeof($expr) with the narrowed array's size; false = - * pending exception */ - [[nodiscard]] bool narrowRememberedCountsInPlace(zend_string *exprString, zval *type, zval *nativeType) - { - for (const pt_superglobal_name &functionName : pt_ms_count_function_key_names) { - zv::Str countKey = zv::Str::adopt(zend_strpprintf(0, "%s(%s)", functionName.name, ZSTR_VAL(exprString))); - if (UNEXPECTED(!narrowRememberedCountInTable(PT_MS_PROP_EXPRESSION_TYPES, PT_LC("expressionTypes"), countKey.get(), type)) - || UNEXPECTED(!narrowRememberedCountInTable(PT_MS_PROP_NATIVE_EXPRESSION_TYPES, PT_LC("nativeExpressionTypes"), countKey.get(), nativeType))) { - return false; - } - } - return true; - } - - /* one table's arm of narrowRememberedCountsInPlace(); false = pending exception */ - [[nodiscard]] bool narrowRememberedCountInTable(uint32_t tableSlot, const char *name, size_t len, zend_string *countKey, zval *arrayType) - { - if (UNEXPECTED(!requireSlot(tableSlot, name))) return false; - zval *holder = zend_symtable_find(Z_ARRVAL_P(slot(tableSlot).raw()), countKey); - if (holder == NULL) return true; - if (UNEXPECTED(Z_TYPE_P(arrayType) != IS_OBJECT)) { - zend_throw_error(NULL, "Call to a member function isArray() on %s", zend_zval_value_name(arrayType)); - return false; - } - zend_long isArray = pt_type_op_trinary(Z_OBJ_P(arrayType), PT_OP_IS_ARRAY, 0, NULL); - if (UNEXPECTED(isArray < 0)) return false; - if (isArray != PT_TRI_YES) return true; - - zv::Val holderZv = zv::Val::copyOf(zv::Ref(holder)); - zv::Val countType = holderType(holderZv.ref()); - if (UNEXPECTED(countType.isUndef())) return false; - zv::Val arraySize = pt_type_call(Z_OBJ_P(arrayType), PT_LC("getarraysize"), 0, NULL); - if (UNEXPECTED(arraySize.isUndef())) return false; - zv::Args args{countType.raw(), arraySize.raw()}; - zv::Val narrowed = pt_type_combinator_intersect(2, args); - if (UNEXPECTED(narrowed.isUndef())) return false; - if (Z_TYPE_P(narrowed.raw()) == IS_OBJECT && instanceof_function(Z_OBJCE_P(narrowed.raw()), pt_ce_never_type)) return true; - - zv::Val countExpr = holderRead(holderZv.ref(), PT_ETH_PROP_EXPR, PT_LC("getexpr")); - if (UNEXPECTED(countExpr.isUndef())) return false; - zend_long certainty = holderCertainty(holderZv.ref()); - if (UNEXPECTED(certainty < 0)) return false; - zval newHolder; - pt_holder_create(&newHolder, countExpr.raw(), narrowed.raw(), certainty); - zv::Val ownedHolder = zv::Val::adopt(newHolder); - return writeScopeTable(self, tableSlot, name, len, countKey, ownedHolder.raw()); - } - /* the ArrayDimFetch arm of specifyExpressionTypeInPlace(): the var's * offset-accessible narrowing, applied in place */ bool specifyArrayDimVarInPlace(zend_object *dim, zend_object *var, zval *type, zval *certainty) @@ -8470,7 +8411,8 @@ class MutatingScope return zv::Val(); } - return scopeWithTables( + bool afterExtractCall = slotBool(PT_MS_PROP_AFTER_EXTRACT_CALL) && theirAfterExtractCall; + zv::Val merged = scopeWithTables( Z_ARRVAL_P(mergedTypes), Z_ARRVAL_P(mergedNativeTypes), Z_ARRVAL_P(conditionalExpressions.raw()), @@ -8478,7 +8420,121 @@ class MutatingScope (HashTable *) &zend_empty_array, (HashTable *) &zend_empty_array, slotBool(PT_MS_PROP_IN_FIRST_LEVEL_STATEMENT), - slotBool(PT_MS_PROP_AFTER_EXTRACT_CALL) && theirAfterExtractCall); + afterExtractCall); + if (UNEXPECTED(merged.isUndef())) return zv::Val(); + + zv::Val uninformativeKeys = findUninformativeRememberedCalls(Z_ARRVAL_P(differing.raw()), Z_ARRVAL_P(mergedTypes), Z_ARRVAL_P(mergedNativeTypes), Z_ARRVAL_P(conditionalExpressions.raw()), afterExtractCall); + if (UNEXPECTED(uninformativeKeys.isUndef())) return zv::Val(); + if (zend_hash_num_elements(Z_ARRVAL_P(uninformativeKeys.raw())) == 0) return merged; + + zv::Arr prunedTypes = withoutKeys(Z_ARRVAL_P(mergedTypes), Z_ARRVAL_P(uninformativeKeys.raw())); + zv::Arr prunedNativeTypes = withoutKeys(Z_ARRVAL_P(mergedNativeTypes), Z_ARRVAL_P(uninformativeKeys.raw())); + return scopeWithTables( + prunedTypes.table(), + prunedNativeTypes.table(), + Z_ARRVAL_P(conditionalExpressions.raw()), + (HashTable *) &zend_empty_array, + (HashTable *) &zend_empty_array, + (HashTable *) &zend_empty_array, + slotBool(PT_MS_PROP_IN_FIRST_LEVEL_STATEMENT), + afterExtractCall); + } + + /* array_diff_key($table, $keys) */ + static zv::Arr withoutKeys(HashTable *table, HashTable *keys) + { + zv::Arr result = zv::Arr::copyOfTable(table); + result.separate(); + for (auto entry : zv::TableRef(keys)) { + zend_string *key = entry.stringKeyOrNull(); + if (key != NULL) { + zend_hash_del(result.table(), key); + } else { + zend_hash_index_del(result.table(), entry.indexKey()); + } + } + return result; + } + + /* private (twin findUninformativeRememberedCalls(), called on the merged + * scope - here on its tables): keys of call expressions whose merged + * type is exactly what the call resolves to without the entry */ + zv::Val findUninformativeRememberedCalls(HashTable *differingExpressionKeys, HashTable *expressionTypes, HashTable *nativeExpressionTypes, HashTable *conditionalExpressions, bool afterExtractCall) + { + static const int callClasses[] = { PT_CLASS_FUNC_CALL, PT_CLASS_METHOD_CALL, PT_CLASS_STATIC_CALL }; + zv::Arr candidates = zv::Arr::create(0); + for (auto entry : zv::TableRef(differingExpressionKeys)) { + zend_string *key = entry.stringKeyOrNull(); + zval *holder = key != NULL ? zend_hash_find(expressionTypes, key) : zend_hash_index_find(expressionTypes, entry.indexKey()); + if (holder == NULL) continue; + zend_long certainty = holderCertainty(zv::Ref(holder)); + if (UNEXPECTED(certainty < 0)) return zv::Val(); + if (certainty != PT_TRI_YES) continue; + zv::Val expr = holderExpr(zv::Ref(holder)); + if (UNEXPECTED(expr.isUndef())) return zv::Val(); + bool isCall = false; + for (int classIdx : callClasses) { + if (UNEXPECTED(!isInstance(expr.ref(), classIdx, isCall))) return zv::Val(); + if (isCall) break; + } + if (!isCall) continue; + candidates.separate(); + Z_TRY_ADDREF_P(holder); + if (key != NULL) { + zend_hash_add_new(candidates.table(), key, holder); + } else { + zend_hash_index_add_new(candidates.table(), entry.indexKey(), holder); + } + } + zv::Arr uninformativeKeys = zv::Arr::create(0); + if (zend_hash_num_elements(candidates.table()) == 0) return zv::Val(std::move(uninformativeKeys)); + + zv::Arr withoutTypes = withoutKeys(expressionTypes, candidates.table()); + zv::Arr withoutNativeTypes = withoutKeys(nativeExpressionTypes, candidates.table()); + zv::Val withoutCandidates = scopeWithTables( + withoutTypes.table(), + withoutNativeTypes.table(), + conditionalExpressions, + (HashTable *) &zend_empty_array, + (HashTable *) &zend_empty_array, + (HashTable *) &zend_empty_array, + slotBool(PT_MS_PROP_IN_FIRST_LEVEL_STATEMENT), + afterExtractCall); + if (UNEXPECTED(withoutCandidates.isUndef())) return zv::Val(); + zend_object *withoutCandidatesObject = requireObject(withoutCandidates, "getType"); + if (UNEXPECTED(withoutCandidatesObject == NULL)) return zv::Val(); + + for (auto entry : zv::TableRef(candidates.table())) { + zv::Val expr = holderExpr(entry.value()); + if (UNEXPECTED(expr.isUndef())) return zv::Val(); + zv::Val type = holderType(entry.value()); + if (UNEXPECTED(type.isUndef())) return zv::Val(); + zv::Val freshType = pt_mutating_scope_get_type(withoutCandidatesObject, expr.raw()); + if (UNEXPECTED(freshType.isUndef())) return zv::Val(); + bool equal = pt_call_type_equals(type.raw(), freshType.raw()); + if (UNEXPECTED(EG(exception) != NULL)) return zv::Val(); + if (!equal) continue; + zend_string *key = entry.stringKeyOrNull(); + zval *nativeHolder = key != NULL ? zend_hash_find(nativeExpressionTypes, key) : zend_hash_index_find(nativeExpressionTypes, entry.indexKey()); + if (nativeHolder != NULL) { + zv::Val nativeType = holderType(zv::Ref(nativeHolder)); + if (UNEXPECTED(nativeType.isUndef())) return zv::Val(); + zv::Val freshNativeType = pt_mutating_scope_get_native_type(withoutCandidatesObject, expr.raw()); + if (UNEXPECTED(freshNativeType.isUndef())) return zv::Val(); + bool nativeEqual = pt_call_type_equals(nativeType.raw(), freshNativeType.raw()); + if (UNEXPECTED(EG(exception) != NULL)) return zv::Val(); + if (!nativeEqual) continue; + } + uninformativeKeys.separate(); + zval trueZv; + ZVAL_TRUE(&trueZv); + if (key != NULL) { + zend_hash_add_new(uninformativeKeys.table(), key, &trueZv); + } else { + zend_hash_index_add_new(uninformativeKeys.table(), entry.indexKey(), &trueZv); + } + } + return zv::Val(std::move(uninformativeKeys)); } /* private (twin 4476) — drops the keys of class-constant fetches that @@ -12327,13 +12383,6 @@ static void pt_ms_custom_serialization_methods_constant(zval *out) pt_persistent_list_into(out, pt_ms_custom_serialization_methods_list); } -static HashTable *pt_ms_count_function_key_names_list = nullptr; - -static void pt_ms_count_function_key_names_constant(zval *out) -{ - pt_persistent_list_into(out, pt_ms_count_function_key_names_list); -} - PT_MINIT_REGISTRATION(pt_register_mutating_scope) { using namespace pt_ms; @@ -12382,8 +12431,6 @@ PT_MINIT_REGISTRATION(pt_register_mutating_scope) cls.privateClassConstantLong("GLOBAL_CONSTANT_FETCH_KEYS_LIMIT", PT_MS_GLOBAL_CONSTANT_FETCH_KEYS_LIMIT); pt_ms_custom_serialization_methods_list = pt_persistent_string_list(pt_ms_custom_serialization_methods, sizeof(pt_ms_custom_serialization_methods) / sizeof(pt_ms_custom_serialization_methods[0])); cls.privateClassConstantValue("CUSTOM_SERIALIZATION_METHODS", pt_ms_custom_serialization_methods_constant); - pt_ms_count_function_key_names_list = pt_persistent_string_list(pt_ms_count_function_key_names, sizeof(pt_ms_count_function_key_names) / sizeof(pt_ms_count_function_key_names[0])); - cls.privateClassConstantValue("COUNT_FUNCTION_KEY_NAMES", pt_ms_count_function_key_names_constant); /* }}} */ cls.method("__construct", reg::Public, 3, { From 5593926bf85fe73f2c3ef0d231a5b0adecaf496a Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Wed, 4 Nov 2026 21:59:36 +0000 Subject: [PATCH 3/6] Narrow a remembered count()/sizeof() by the narrowed array's size The merge-time pruning only drops remembered calls that are no more precise than the call itself. A partly informative count, like int<0, 2>|int<5, max> after returning on 3-4 elements, is kept, so a later `$arr !== []` left it untrimmed. Narrowing an array expression now intersects any remembered count($expr)/sizeof($expr) (Name and FullyQualified spellings) with the new type's getArraySize(), for the PHPDoc and native tables, skipping results that would be never. Ported to turbo-ext/src/MutatingScope.cpp. Co-Authored-By: Claude Opus 5.5 --- src/Analyser/MutatingScope.php | 33 +++++++++++ tests/PHPStan/Analyser/nsrt/bug-13750.php | 16 ++++++ turbo-ext/src/MutatingScope.cpp | 68 +++++++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index c8d5c9a1e1c..43d8bd18bda 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -150,6 +150,9 @@ class MutatingScope implements Scope, NodeCallbackInvoker, CollectedDataEmitter, /** Magic methods that let the author decide which properties survive a serialize()/unserialize() round trip. */ private const CUSTOM_SERIALIZATION_METHODS = ['__sleep', '__serialize', '__unserialize']; + /** Names a remembered `count()` of an expression can be keyed under (Name and FullyQualified). */ + private const COUNT_FUNCTION_KEY_NAMES = ['count', 'sizeof', '\\count', '\\sizeof']; + /** * Expression keys the constant-fetch nodes of a global constant lookup print * as, by the name class, name and namespace they are built from. The nodes @@ -3683,6 +3686,7 @@ private function specifyExpressionTypeInPlace(Expr $expr, Type $type, Type $nati $exprString = $this->getNodeKey($expr); $this->expressionTypes[$exprString] = new ExpressionTypeHolder($expr, $type, $certainty); $this->nativeExpressionTypes[$exprString] = new ExpressionTypeHolder($expr, $nativeType, $certainty); + $this->narrowRememberedCountsInPlace($exprString, $type, $nativeType); if (!($expr instanceof AlwaysRememberedExpr)) { return; @@ -3691,6 +3695,35 @@ private function specifyExpressionTypeInPlace(Expr $expr, Type $type, Type $nati $this->specifyExpressionTypeInPlace($expr->expr, $type, $nativeType, $certainty); } + /** + * A remembered `count($expr)` that is still partly informative after a + * merge (e.g. int<0, 2>|int<5, max>, so findUninformativeRememberedCalls() + * keeps it) would otherwise shadow the size implied by the narrowed array. + */ + private function narrowRememberedCountsInPlace(string $exprString, Type $type, Type $nativeType): void + { + foreach (self::COUNT_FUNCTION_KEY_NAMES as $functionName) { + $countKey = $functionName . '(' . $exprString . ')'; + if (isset($this->expressionTypes[$countKey]) && $type->isArray()->yes()) { + $holder = $this->expressionTypes[$countKey]; + $narrowed = TypeCombinator::intersect($holder->getType(), $type->getArraySize()); + if (!$narrowed instanceof NeverType) { + $this->expressionTypes[$countKey] = new ExpressionTypeHolder($holder->getExpr(), $narrowed, $holder->getCertainty()); + } + } + if (!isset($this->nativeExpressionTypes[$countKey]) || !$nativeType->isArray()->yes()) { + continue; + } + + $nativeHolder = $this->nativeExpressionTypes[$countKey]; + $narrowedNative = TypeCombinator::intersect($nativeHolder->getType(), $nativeType->getArraySize()); + if ($narrowedNative instanceof NeverType) { + continue; + } + $this->nativeExpressionTypes[$countKey] = new ExpressionTypeHolder($nativeHolder->getExpr(), $narrowedNative, $nativeHolder->getCertainty()); + } + } + public function assignExpression(Expr $expr, Type $type, Type $nativeType): self { $scope = $this; diff --git a/tests/PHPStan/Analyser/nsrt/bug-13750.php b/tests/PHPStan/Analyser/nsrt/bug-13750.php index e0050153658..b817cf63a9f 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-13750.php +++ b/tests/PHPStan/Analyser/nsrt/bug-13750.php @@ -118,6 +118,22 @@ public function keepsInformativeMergedCount(array $arr): void } } + /** @param array $arr */ + public function partlyInformativeMergedCount(array $arr): void + { + if (count($arr) > 2) { + if (count($arr) < 5) { + return; + } + } + assertType('int<0, 2>|int<5, max>', count($arr)); + if ($arr !== []) { + assertType('int<1, 2>|int<5, max>', count($arr)); + } else { + assertType('0', count($arr)); + } + } + /** @param array $arr */ public function keepsNarrowerCount(array $arr): void { diff --git a/turbo-ext/src/MutatingScope.cpp b/turbo-ext/src/MutatingScope.cpp index acc320a3d25..a95a27ab484 100644 --- a/turbo-ext/src/MutatingScope.cpp +++ b/turbo-ext/src/MutatingScope.cpp @@ -403,6 +403,15 @@ static const pt_superglobal_name pt_ms_custom_serialization_methods[] = { { PT_LC("__unserialize") }, }; +/* the twin's private COUNT_FUNCTION_KEY_NAMES (narrowRememberedCountsInPlace() + * walks the same names), a persistent list built once at module startup */ +static const pt_superglobal_name pt_ms_count_function_key_names[] = { + { PT_LC("count") }, + { PT_LC("sizeof") }, + { PT_LC("\\count") }, + { PT_LC("\\sizeof") }, +}; + /* PHPStan\Analyser\ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID and * PHPStan\Php\PhpVersionFactory::MAX_PHP_VERSION — the one place the native * code reads them from (getPhpVersion()) */ @@ -6737,6 +6746,7 @@ class MutatingScope || !writeScopeTable(self, PT_MS_PROP_NATIVE_EXPRESSION_TYPES, PT_LC("nativeExpressionTypes"), key.get(), ownedNativeHolder.raw()))) { return false; } + if (UNEXPECTED(!narrowRememberedCountsInPlace(key.get(), type, nativeType))) return false; bool isAlwaysRemembered; if (UNEXPECTED(!isInstance(zv::Ref(&exprZv), PT_CLASS_ALWAYS_REMEMBERED_EXPR, isAlwaysRemembered))) return false; @@ -6750,6 +6760,55 @@ class MutatingScope return specifyExpressionTypeInPlace(inner.deref().asObject(), type, nativeType, certainty); } + /* private narrowRememberedCountsInPlace() — intersects a remembered + * count($expr) / sizeof($expr) with the narrowed array's size; false = + * pending exception */ + [[nodiscard]] bool narrowRememberedCountsInPlace(zend_string *exprString, zval *type, zval *nativeType) + { + for (const pt_superglobal_name &functionName : pt_ms_count_function_key_names) { + zv::Str countKey = zv::Str::adopt(zend_strpprintf(0, "%s(%s)", functionName.name, ZSTR_VAL(exprString))); + if (UNEXPECTED(!narrowRememberedCountInTable(PT_MS_PROP_EXPRESSION_TYPES, PT_LC("expressionTypes"), countKey.get(), type)) + || UNEXPECTED(!narrowRememberedCountInTable(PT_MS_PROP_NATIVE_EXPRESSION_TYPES, PT_LC("nativeExpressionTypes"), countKey.get(), nativeType))) { + return false; + } + } + return true; + } + + /* one table's arm of narrowRememberedCountsInPlace(); false = pending exception */ + [[nodiscard]] bool narrowRememberedCountInTable(uint32_t tableSlot, const char *name, size_t len, zend_string *countKey, zval *arrayType) + { + if (UNEXPECTED(!requireSlot(tableSlot, name))) return false; + zval *holder = zend_symtable_find(Z_ARRVAL_P(slot(tableSlot).raw()), countKey); + if (holder == NULL) return true; + if (UNEXPECTED(Z_TYPE_P(arrayType) != IS_OBJECT)) { + zend_throw_error(NULL, "Call to a member function isArray() on %s", zend_zval_value_name(arrayType)); + return false; + } + zend_long isArray = pt_type_op_trinary(Z_OBJ_P(arrayType), PT_OP_IS_ARRAY, 0, NULL); + if (UNEXPECTED(isArray < 0)) return false; + if (isArray != PT_TRI_YES) return true; + + zv::Val holderZv = zv::Val::copyOf(zv::Ref(holder)); + zv::Val countType = holderType(holderZv.ref()); + if (UNEXPECTED(countType.isUndef())) return false; + zv::Val arraySize = pt_type_call(Z_OBJ_P(arrayType), PT_LC("getarraysize"), 0, NULL); + if (UNEXPECTED(arraySize.isUndef())) return false; + zv::Args args{countType.raw(), arraySize.raw()}; + zv::Val narrowed = pt_type_combinator_intersect(2, args); + if (UNEXPECTED(narrowed.isUndef())) return false; + if (Z_TYPE_P(narrowed.raw()) == IS_OBJECT && instanceof_function(Z_OBJCE_P(narrowed.raw()), pt_ce_never_type)) return true; + + zv::Val countExpr = holderRead(holderZv.ref(), PT_ETH_PROP_EXPR, PT_LC("getexpr")); + if (UNEXPECTED(countExpr.isUndef())) return false; + zend_long certainty = holderCertainty(holderZv.ref()); + if (UNEXPECTED(certainty < 0)) return false; + zval newHolder; + pt_holder_create(&newHolder, countExpr.raw(), narrowed.raw(), certainty); + zv::Val ownedHolder = zv::Val::adopt(newHolder); + return writeScopeTable(self, tableSlot, name, len, countKey, ownedHolder.raw()); + } + /* the ArrayDimFetch arm of specifyExpressionTypeInPlace(): the var's * offset-accessible narrowing, applied in place */ bool specifyArrayDimVarInPlace(zend_object *dim, zend_object *var, zval *type, zval *certainty) @@ -12383,6 +12442,13 @@ static void pt_ms_custom_serialization_methods_constant(zval *out) pt_persistent_list_into(out, pt_ms_custom_serialization_methods_list); } +static HashTable *pt_ms_count_function_key_names_list = nullptr; + +static void pt_ms_count_function_key_names_constant(zval *out) +{ + pt_persistent_list_into(out, pt_ms_count_function_key_names_list); +} + PT_MINIT_REGISTRATION(pt_register_mutating_scope) { using namespace pt_ms; @@ -12431,6 +12497,8 @@ PT_MINIT_REGISTRATION(pt_register_mutating_scope) cls.privateClassConstantLong("GLOBAL_CONSTANT_FETCH_KEYS_LIMIT", PT_MS_GLOBAL_CONSTANT_FETCH_KEYS_LIMIT); pt_ms_custom_serialization_methods_list = pt_persistent_string_list(pt_ms_custom_serialization_methods, sizeof(pt_ms_custom_serialization_methods) / sizeof(pt_ms_custom_serialization_methods[0])); cls.privateClassConstantValue("CUSTOM_SERIALIZATION_METHODS", pt_ms_custom_serialization_methods_constant); + pt_ms_count_function_key_names_list = pt_persistent_string_list(pt_ms_count_function_key_names, sizeof(pt_ms_count_function_key_names) / sizeof(pt_ms_count_function_key_names[0])); + cls.privateClassConstantValue("COUNT_FUNCTION_KEY_NAMES", pt_ms_count_function_key_names_constant); /* }}} */ cls.method("__construct", reg::Public, 3, { From 76478b893b2b0e346a9ffa1f40b011b51e170ef0 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Thu, 5 Nov 2026 08:51:34 +0000 Subject: [PATCH 4/6] Leave uninformative merged calls out of the merged scope instead of pruning it afterwards mergeWithVariableState() used to build the full merged scope, then a second scope without the candidate calls to resolve them, and a third scope without the uninformative ones. Now the candidates are picked from the merged tables before any scope exists, the merged scope is built without them, and the calls are resolved on that scope. Only when some candidate is still informative is a scope with those entries written; in the common cases (no candidates, or all of them uninformative) a single scope is built. Co-Authored-By: Claude Opus 5.5 --- src/Analyser/MutatingScope.php | 67 ++++++++++++++++------------- turbo-ext/src/MutatingScope.cpp | 76 +++++++++++++++++++-------------- 2 files changed, 82 insertions(+), 61 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 43d8bd18bda..23b5bcb2f6a 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4456,10 +4456,17 @@ private function mergeWithVariableState(?self $otherScope, bool $preserveVacuous ); $afterExtractCall = $this->afterExtractCall && $otherScope->afterExtractCall; - $merged = ScopeOps::scopeWith( + + // Remembered calls whose type differs between the branches may merge into + // exactly what the call resolves to on its own - e.g. count($a) of + // int<3, max> and int<0, 2> merges into int<0, max>. Such an entry adds + // nothing and would shadow what later narrowing of $a implies for the call, + // so it is not written into the merged scope at all. + $callCandidates = self::findMergedCallCandidates($differingExpressionKeys, $mergedExpressionTypes); + $scope = ScopeOps::scopeWith( $this, - $mergedExpressionTypes, - $mergedNativeTypes, + $callCandidates === [] ? $mergedExpressionTypes : array_diff_key($mergedExpressionTypes, $callCandidates), + $callCandidates === [] ? $mergedNativeTypes : array_diff_key($mergedNativeTypes, $callCandidates), $conditionalExpressions, [], [], @@ -4467,11 +4474,15 @@ private function mergeWithVariableState(?self $otherScope, bool $preserveVacuous $this->inFirstLevelStatement, $afterExtractCall, ); + if ($callCandidates === []) { + /** @var static */ + return $scope; + } - $uninformativeKeys = $merged->findUninformativeRememberedCalls($differingExpressionKeys, $afterExtractCall); - if ($uninformativeKeys === []) { + $uninformativeKeys = $scope->findUninformativeMergedCalls($callCandidates, $mergedNativeTypes); + if (count($uninformativeKeys) === count($callCandidates)) { /** @var static */ - return $merged; + return $scope; } /** @var static */ @@ -4489,19 +4500,17 @@ private function mergeWithVariableState(?self $otherScope, bool $preserveVacuous } /** - * Keys of call expressions whose merged type is exactly what the call - * resolves to without the entry. Merging `count($a)` of int<3, max> and - * int<0, 2> gives int<0, max> - remembering that adds nothing, and the - * stale entry would shadow what later narrowing of `$a` implies for the call. + * Certain merged call expressions whose type differs between the merged branches. * * @param array $differingExpressionKeys - * @return array + * @param array $mergedExpressionTypes + * @return array */ - private function findUninformativeRememberedCalls(array $differingExpressionKeys, bool $afterExtractCall): array + private static function findMergedCallCandidates(array $differingExpressionKeys, array $mergedExpressionTypes): array { $candidates = []; foreach (array_keys($differingExpressionKeys) as $exprString) { - $holder = $this->expressionTypes[$exprString] ?? null; + $holder = $mergedExpressionTypes[$exprString] ?? null; if ($holder === null || !$holder->getCertainty()->yes()) { continue; } @@ -4511,30 +4520,28 @@ private function findUninformativeRememberedCalls(array $differingExpressionKeys } $candidates[$exprString] = $holder; } - if ($candidates === []) { - return []; - } - $withoutCandidates = ScopeOps::scopeWith( - $this, - array_diff_key($this->expressionTypes, $candidates), - array_diff_key($this->nativeExpressionTypes, $candidates), - $this->conditionalExpressions, - [], - [], - [], - $this->inFirstLevelStatement, - $afterExtractCall, - ); + return $candidates; + } + /** + * Called on the merged scope without the candidates: keys of candidates whose + * merged type (and native type) is exactly what the call resolves to here. + * + * @param array $candidates + * @param array $mergedNativeTypes + * @return array + */ + private function findUninformativeMergedCalls(array $candidates, array $mergedNativeTypes): array + { $uninformativeKeys = []; foreach ($candidates as $exprString => $holder) { $expr = $holder->getExpr(); - if (!$holder->getType()->equals($withoutCandidates->getType($expr))) { + if (!$holder->getType()->equals($this->getType($expr))) { continue; } - $nativeHolder = $this->nativeExpressionTypes[$exprString] ?? null; - if ($nativeHolder !== null && !$nativeHolder->getType()->equals($withoutCandidates->getNativeType($expr))) { + $nativeHolder = $mergedNativeTypes[$exprString] ?? null; + if ($nativeHolder !== null && !$nativeHolder->getType()->equals($this->getNativeType($expr))) { continue; } $uninformativeKeys[$exprString] = true; diff --git a/turbo-ext/src/MutatingScope.cpp b/turbo-ext/src/MutatingScope.cpp index a95a27ab484..f42ee8c1eb8 100644 --- a/turbo-ext/src/MutatingScope.cpp +++ b/turbo-ext/src/MutatingScope.cpp @@ -8471,20 +8471,41 @@ class MutatingScope } bool afterExtractCall = slotBool(PT_MS_PROP_AFTER_EXTRACT_CALL) && theirAfterExtractCall; - zv::Val merged = scopeWithTables( - Z_ARRVAL_P(mergedTypes), - Z_ARRVAL_P(mergedNativeTypes), + + /* merged calls no more precise than the call itself are never written + * into the merged scope */ + zv::Val callCandidates = findMergedCallCandidates(Z_ARRVAL_P(differing.raw()), Z_ARRVAL_P(mergedTypes)); + if (UNEXPECTED(callCandidates.isUndef())) return zv::Val(); + HashTable *candidateTable = Z_ARRVAL_P(callCandidates.raw()); + uint32_t candidateCount = zend_hash_num_elements(candidateTable); + if (candidateCount == 0) { + return scopeWithTables( + Z_ARRVAL_P(mergedTypes), + Z_ARRVAL_P(mergedNativeTypes), + Z_ARRVAL_P(conditionalExpressions.raw()), + (HashTable *) &zend_empty_array, + (HashTable *) &zend_empty_array, + (HashTable *) &zend_empty_array, + slotBool(PT_MS_PROP_IN_FIRST_LEVEL_STATEMENT), + afterExtractCall); + } + + zv::Arr withoutTypes = withoutKeys(Z_ARRVAL_P(mergedTypes), candidateTable); + zv::Arr withoutNativeTypes = withoutKeys(Z_ARRVAL_P(mergedNativeTypes), candidateTable); + zv::Val scope = scopeWithTables( + withoutTypes.table(), + withoutNativeTypes.table(), Z_ARRVAL_P(conditionalExpressions.raw()), (HashTable *) &zend_empty_array, (HashTable *) &zend_empty_array, (HashTable *) &zend_empty_array, slotBool(PT_MS_PROP_IN_FIRST_LEVEL_STATEMENT), afterExtractCall); - if (UNEXPECTED(merged.isUndef())) return zv::Val(); + if (UNEXPECTED(scope.isUndef())) return zv::Val(); - zv::Val uninformativeKeys = findUninformativeRememberedCalls(Z_ARRVAL_P(differing.raw()), Z_ARRVAL_P(mergedTypes), Z_ARRVAL_P(mergedNativeTypes), Z_ARRVAL_P(conditionalExpressions.raw()), afterExtractCall); + zv::Val uninformativeKeys = findUninformativeMergedCalls(scope, candidateTable, Z_ARRVAL_P(mergedNativeTypes)); if (UNEXPECTED(uninformativeKeys.isUndef())) return zv::Val(); - if (zend_hash_num_elements(Z_ARRVAL_P(uninformativeKeys.raw())) == 0) return merged; + if (zend_hash_num_elements(Z_ARRVAL_P(uninformativeKeys.raw())) == candidateCount) return scope; zv::Arr prunedTypes = withoutKeys(Z_ARRVAL_P(mergedTypes), Z_ARRVAL_P(uninformativeKeys.raw())); zv::Arr prunedNativeTypes = withoutKeys(Z_ARRVAL_P(mergedNativeTypes), Z_ARRVAL_P(uninformativeKeys.raw())); @@ -8515,16 +8536,15 @@ class MutatingScope return result; } - /* private (twin findUninformativeRememberedCalls(), called on the merged - * scope - here on its tables): keys of call expressions whose merged - * type is exactly what the call resolves to without the entry */ - zv::Val findUninformativeRememberedCalls(HashTable *differingExpressionKeys, HashTable *expressionTypes, HashTable *nativeExpressionTypes, HashTable *conditionalExpressions, bool afterExtractCall) + /* private static (twin findMergedCallCandidates()): certain merged call + * expressions whose type differs between the merged branches */ + static zv::Val findMergedCallCandidates(HashTable *differingExpressionKeys, HashTable *mergedExpressionTypes) { static const int callClasses[] = { PT_CLASS_FUNC_CALL, PT_CLASS_METHOD_CALL, PT_CLASS_STATIC_CALL }; zv::Arr candidates = zv::Arr::create(0); for (auto entry : zv::TableRef(differingExpressionKeys)) { zend_string *key = entry.stringKeyOrNull(); - zval *holder = key != NULL ? zend_hash_find(expressionTypes, key) : zend_hash_index_find(expressionTypes, entry.indexKey()); + zval *holder = key != NULL ? zend_hash_find(mergedExpressionTypes, key) : zend_hash_index_find(mergedExpressionTypes, entry.indexKey()); if (holder == NULL) continue; zend_long certainty = holderCertainty(zv::Ref(holder)); if (UNEXPECTED(certainty < 0)) return zv::Val(); @@ -8545,40 +8565,34 @@ class MutatingScope zend_hash_index_add_new(candidates.table(), entry.indexKey(), holder); } } - zv::Arr uninformativeKeys = zv::Arr::create(0); - if (zend_hash_num_elements(candidates.table()) == 0) return zv::Val(std::move(uninformativeKeys)); + return zv::Val(std::move(candidates)); + } - zv::Arr withoutTypes = withoutKeys(expressionTypes, candidates.table()); - zv::Arr withoutNativeTypes = withoutKeys(nativeExpressionTypes, candidates.table()); - zv::Val withoutCandidates = scopeWithTables( - withoutTypes.table(), - withoutNativeTypes.table(), - conditionalExpressions, - (HashTable *) &zend_empty_array, - (HashTable *) &zend_empty_array, - (HashTable *) &zend_empty_array, - slotBool(PT_MS_PROP_IN_FIRST_LEVEL_STATEMENT), - afterExtractCall); - if (UNEXPECTED(withoutCandidates.isUndef())) return zv::Val(); - zend_object *withoutCandidatesObject = requireObject(withoutCandidates, "getType"); - if (UNEXPECTED(withoutCandidatesObject == NULL)) return zv::Val(); + /* private (twin findUninformativeMergedCalls(), called on the merged scope + * without the candidates): keys of candidates whose merged type (and + * native type) is exactly what the call resolves to there */ + static zv::Val findUninformativeMergedCalls(zv::Val &scope, HashTable *candidates, HashTable *mergedNativeTypes) + { + zend_object *scopeObject = requireObject(scope, "getType"); + if (UNEXPECTED(scopeObject == NULL)) return zv::Val(); - for (auto entry : zv::TableRef(candidates.table())) { + zv::Arr uninformativeKeys = zv::Arr::create(0); + for (auto entry : zv::TableRef(candidates)) { zv::Val expr = holderExpr(entry.value()); if (UNEXPECTED(expr.isUndef())) return zv::Val(); zv::Val type = holderType(entry.value()); if (UNEXPECTED(type.isUndef())) return zv::Val(); - zv::Val freshType = pt_mutating_scope_get_type(withoutCandidatesObject, expr.raw()); + zv::Val freshType = pt_mutating_scope_get_type(scopeObject, expr.raw()); if (UNEXPECTED(freshType.isUndef())) return zv::Val(); bool equal = pt_call_type_equals(type.raw(), freshType.raw()); if (UNEXPECTED(EG(exception) != NULL)) return zv::Val(); if (!equal) continue; zend_string *key = entry.stringKeyOrNull(); - zval *nativeHolder = key != NULL ? zend_hash_find(nativeExpressionTypes, key) : zend_hash_index_find(nativeExpressionTypes, entry.indexKey()); + zval *nativeHolder = key != NULL ? zend_hash_find(mergedNativeTypes, key) : zend_hash_index_find(mergedNativeTypes, entry.indexKey()); if (nativeHolder != NULL) { zv::Val nativeType = holderType(zv::Ref(nativeHolder)); if (UNEXPECTED(nativeType.isUndef())) return zv::Val(); - zv::Val freshNativeType = pt_mutating_scope_get_native_type(withoutCandidatesObject, expr.raw()); + zv::Val freshNativeType = pt_mutating_scope_get_native_type(scopeObject, expr.raw()); if (UNEXPECTED(freshNativeType.isUndef())) return zv::Val(); bool nativeEqual = pt_call_type_equals(nativeType.raw(), freshNativeType.raw()); if (UNEXPECTED(EG(exception) != NULL)) return zv::Val(); From 0539ff6f099c94412401a7b5201fdd5d6e796dcd Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Thu, 5 Nov 2026 09:09:17 +0000 Subject: [PATCH 5/6] Drop the count()-only narrowing of remembered counts The general merge-time pruning of uninformative remembered calls already fixes the reported case for every function. The count()/sizeof()-specific intersection on narrowing only covered partly informative merged counts and is not worth a special case for a single function. Co-Authored-By: Claude Opus 5.5 --- src/Analyser/MutatingScope.php | 33 ----------- tests/PHPStan/Analyser/nsrt/bug-13750.php | 16 ------ turbo-ext/src/MutatingScope.cpp | 68 ----------------------- 3 files changed, 117 deletions(-) diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 23b5bcb2f6a..2f7f4681837 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -150,9 +150,6 @@ class MutatingScope implements Scope, NodeCallbackInvoker, CollectedDataEmitter, /** Magic methods that let the author decide which properties survive a serialize()/unserialize() round trip. */ private const CUSTOM_SERIALIZATION_METHODS = ['__sleep', '__serialize', '__unserialize']; - /** Names a remembered `count()` of an expression can be keyed under (Name and FullyQualified). */ - private const COUNT_FUNCTION_KEY_NAMES = ['count', 'sizeof', '\\count', '\\sizeof']; - /** * Expression keys the constant-fetch nodes of a global constant lookup print * as, by the name class, name and namespace they are built from. The nodes @@ -3686,7 +3683,6 @@ private function specifyExpressionTypeInPlace(Expr $expr, Type $type, Type $nati $exprString = $this->getNodeKey($expr); $this->expressionTypes[$exprString] = new ExpressionTypeHolder($expr, $type, $certainty); $this->nativeExpressionTypes[$exprString] = new ExpressionTypeHolder($expr, $nativeType, $certainty); - $this->narrowRememberedCountsInPlace($exprString, $type, $nativeType); if (!($expr instanceof AlwaysRememberedExpr)) { return; @@ -3695,35 +3691,6 @@ private function specifyExpressionTypeInPlace(Expr $expr, Type $type, Type $nati $this->specifyExpressionTypeInPlace($expr->expr, $type, $nativeType, $certainty); } - /** - * A remembered `count($expr)` that is still partly informative after a - * merge (e.g. int<0, 2>|int<5, max>, so findUninformativeRememberedCalls() - * keeps it) would otherwise shadow the size implied by the narrowed array. - */ - private function narrowRememberedCountsInPlace(string $exprString, Type $type, Type $nativeType): void - { - foreach (self::COUNT_FUNCTION_KEY_NAMES as $functionName) { - $countKey = $functionName . '(' . $exprString . ')'; - if (isset($this->expressionTypes[$countKey]) && $type->isArray()->yes()) { - $holder = $this->expressionTypes[$countKey]; - $narrowed = TypeCombinator::intersect($holder->getType(), $type->getArraySize()); - if (!$narrowed instanceof NeverType) { - $this->expressionTypes[$countKey] = new ExpressionTypeHolder($holder->getExpr(), $narrowed, $holder->getCertainty()); - } - } - if (!isset($this->nativeExpressionTypes[$countKey]) || !$nativeType->isArray()->yes()) { - continue; - } - - $nativeHolder = $this->nativeExpressionTypes[$countKey]; - $narrowedNative = TypeCombinator::intersect($nativeHolder->getType(), $nativeType->getArraySize()); - if ($narrowedNative instanceof NeverType) { - continue; - } - $this->nativeExpressionTypes[$countKey] = new ExpressionTypeHolder($nativeHolder->getExpr(), $narrowedNative, $nativeHolder->getCertainty()); - } - } - public function assignExpression(Expr $expr, Type $type, Type $nativeType): self { $scope = $this; diff --git a/tests/PHPStan/Analyser/nsrt/bug-13750.php b/tests/PHPStan/Analyser/nsrt/bug-13750.php index b817cf63a9f..e0050153658 100644 --- a/tests/PHPStan/Analyser/nsrt/bug-13750.php +++ b/tests/PHPStan/Analyser/nsrt/bug-13750.php @@ -118,22 +118,6 @@ public function keepsInformativeMergedCount(array $arr): void } } - /** @param array $arr */ - public function partlyInformativeMergedCount(array $arr): void - { - if (count($arr) > 2) { - if (count($arr) < 5) { - return; - } - } - assertType('int<0, 2>|int<5, max>', count($arr)); - if ($arr !== []) { - assertType('int<1, 2>|int<5, max>', count($arr)); - } else { - assertType('0', count($arr)); - } - } - /** @param array $arr */ public function keepsNarrowerCount(array $arr): void { diff --git a/turbo-ext/src/MutatingScope.cpp b/turbo-ext/src/MutatingScope.cpp index f42ee8c1eb8..c1f2806aefd 100644 --- a/turbo-ext/src/MutatingScope.cpp +++ b/turbo-ext/src/MutatingScope.cpp @@ -403,15 +403,6 @@ static const pt_superglobal_name pt_ms_custom_serialization_methods[] = { { PT_LC("__unserialize") }, }; -/* the twin's private COUNT_FUNCTION_KEY_NAMES (narrowRememberedCountsInPlace() - * walks the same names), a persistent list built once at module startup */ -static const pt_superglobal_name pt_ms_count_function_key_names[] = { - { PT_LC("count") }, - { PT_LC("sizeof") }, - { PT_LC("\\count") }, - { PT_LC("\\sizeof") }, -}; - /* PHPStan\Analyser\ConstantResolver::PHP_MIN_ANALYZABLE_VERSION_ID and * PHPStan\Php\PhpVersionFactory::MAX_PHP_VERSION — the one place the native * code reads them from (getPhpVersion()) */ @@ -6746,7 +6737,6 @@ class MutatingScope || !writeScopeTable(self, PT_MS_PROP_NATIVE_EXPRESSION_TYPES, PT_LC("nativeExpressionTypes"), key.get(), ownedNativeHolder.raw()))) { return false; } - if (UNEXPECTED(!narrowRememberedCountsInPlace(key.get(), type, nativeType))) return false; bool isAlwaysRemembered; if (UNEXPECTED(!isInstance(zv::Ref(&exprZv), PT_CLASS_ALWAYS_REMEMBERED_EXPR, isAlwaysRemembered))) return false; @@ -6760,55 +6750,6 @@ class MutatingScope return specifyExpressionTypeInPlace(inner.deref().asObject(), type, nativeType, certainty); } - /* private narrowRememberedCountsInPlace() — intersects a remembered - * count($expr) / sizeof($expr) with the narrowed array's size; false = - * pending exception */ - [[nodiscard]] bool narrowRememberedCountsInPlace(zend_string *exprString, zval *type, zval *nativeType) - { - for (const pt_superglobal_name &functionName : pt_ms_count_function_key_names) { - zv::Str countKey = zv::Str::adopt(zend_strpprintf(0, "%s(%s)", functionName.name, ZSTR_VAL(exprString))); - if (UNEXPECTED(!narrowRememberedCountInTable(PT_MS_PROP_EXPRESSION_TYPES, PT_LC("expressionTypes"), countKey.get(), type)) - || UNEXPECTED(!narrowRememberedCountInTable(PT_MS_PROP_NATIVE_EXPRESSION_TYPES, PT_LC("nativeExpressionTypes"), countKey.get(), nativeType))) { - return false; - } - } - return true; - } - - /* one table's arm of narrowRememberedCountsInPlace(); false = pending exception */ - [[nodiscard]] bool narrowRememberedCountInTable(uint32_t tableSlot, const char *name, size_t len, zend_string *countKey, zval *arrayType) - { - if (UNEXPECTED(!requireSlot(tableSlot, name))) return false; - zval *holder = zend_symtable_find(Z_ARRVAL_P(slot(tableSlot).raw()), countKey); - if (holder == NULL) return true; - if (UNEXPECTED(Z_TYPE_P(arrayType) != IS_OBJECT)) { - zend_throw_error(NULL, "Call to a member function isArray() on %s", zend_zval_value_name(arrayType)); - return false; - } - zend_long isArray = pt_type_op_trinary(Z_OBJ_P(arrayType), PT_OP_IS_ARRAY, 0, NULL); - if (UNEXPECTED(isArray < 0)) return false; - if (isArray != PT_TRI_YES) return true; - - zv::Val holderZv = zv::Val::copyOf(zv::Ref(holder)); - zv::Val countType = holderType(holderZv.ref()); - if (UNEXPECTED(countType.isUndef())) return false; - zv::Val arraySize = pt_type_call(Z_OBJ_P(arrayType), PT_LC("getarraysize"), 0, NULL); - if (UNEXPECTED(arraySize.isUndef())) return false; - zv::Args args{countType.raw(), arraySize.raw()}; - zv::Val narrowed = pt_type_combinator_intersect(2, args); - if (UNEXPECTED(narrowed.isUndef())) return false; - if (Z_TYPE_P(narrowed.raw()) == IS_OBJECT && instanceof_function(Z_OBJCE_P(narrowed.raw()), pt_ce_never_type)) return true; - - zv::Val countExpr = holderRead(holderZv.ref(), PT_ETH_PROP_EXPR, PT_LC("getexpr")); - if (UNEXPECTED(countExpr.isUndef())) return false; - zend_long certainty = holderCertainty(holderZv.ref()); - if (UNEXPECTED(certainty < 0)) return false; - zval newHolder; - pt_holder_create(&newHolder, countExpr.raw(), narrowed.raw(), certainty); - zv::Val ownedHolder = zv::Val::adopt(newHolder); - return writeScopeTable(self, tableSlot, name, len, countKey, ownedHolder.raw()); - } - /* the ArrayDimFetch arm of specifyExpressionTypeInPlace(): the var's * offset-accessible narrowing, applied in place */ bool specifyArrayDimVarInPlace(zend_object *dim, zend_object *var, zval *type, zval *certainty) @@ -12456,13 +12397,6 @@ static void pt_ms_custom_serialization_methods_constant(zval *out) pt_persistent_list_into(out, pt_ms_custom_serialization_methods_list); } -static HashTable *pt_ms_count_function_key_names_list = nullptr; - -static void pt_ms_count_function_key_names_constant(zval *out) -{ - pt_persistent_list_into(out, pt_ms_count_function_key_names_list); -} - PT_MINIT_REGISTRATION(pt_register_mutating_scope) { using namespace pt_ms; @@ -12511,8 +12445,6 @@ PT_MINIT_REGISTRATION(pt_register_mutating_scope) cls.privateClassConstantLong("GLOBAL_CONSTANT_FETCH_KEYS_LIMIT", PT_MS_GLOBAL_CONSTANT_FETCH_KEYS_LIMIT); pt_ms_custom_serialization_methods_list = pt_persistent_string_list(pt_ms_custom_serialization_methods, sizeof(pt_ms_custom_serialization_methods) / sizeof(pt_ms_custom_serialization_methods[0])); cls.privateClassConstantValue("CUSTOM_SERIALIZATION_METHODS", pt_ms_custom_serialization_methods_constant); - pt_ms_count_function_key_names_list = pt_persistent_string_list(pt_ms_count_function_key_names, sizeof(pt_ms_count_function_key_names) / sizeof(pt_ms_count_function_key_names[0])); - cls.privateClassConstantValue("COUNT_FUNCTION_KEY_NAMES", pt_ms_count_function_key_names_constant); /* }}} */ cls.method("__construct", reg::Public, 3, { From 3856e7f49bb4d3607f3f28a908c35206e3e7a1b3 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Fri, 6 Nov 2026 14:17:57 +0000 Subject: [PATCH 6/6] Regenerate the MutatingScope turbo declarations header Co-Authored-By: Claude Opus 5.5 --- turbo-ext/src/generated/MutatingScope.h | 439 ++++++++++++------------ 1 file changed, 226 insertions(+), 213 deletions(-) diff --git a/turbo-ext/src/generated/MutatingScope.h b/turbo-ext/src/generated/MutatingScope.h index e01ebcdf60c..185b41a21e1 100644 --- a/turbo-ext/src/generated/MutatingScope.h +++ b/turbo-ext/src/generated/MutatingScope.h @@ -393,106 +393,111 @@ inline constexpr char strings[] = "collectAbsorbedClosures\0" /* 6540 */ "mergeWithVariableState\0" /* 6564 */ "differingExpressionKeys\0" /* 6587 */ - "ourExpressionTypes\0" /* 6611 */ - "theirExpressionTypes\0" /* 6630 */ - "withoutPreciseClassConstantFetches\0" /* 6651 */ - "currentConditionalExpressions\0" /* 6686 */ - "sourceConditionalExpressions\0" /* 6716 */ - "otherExpressionTypes\0" /* 6745 */ - "preserveVacuousConditionalExpressions\0" /* 6766 */ - "ourConditionalExpressions\0" /* 6804 */ - "theirConditionalExpressions\0" /* 6830 */ - "mergeSameGuardConditionalExpressions\0" /* 6858 */ - "newConditionalExpressions\0" /* 6895 */ - "existingConditionalExpressions\0" /* 6921 */ - "mergeConditionalExpressions\0" /* 6952 */ - "calledMethodScope\0" /* 6980 */ - "mergeInitializedProperties\0" /* 6998 */ - "finallyScope\0" /* 7025 */ - "originalFinallyScope\0" /* 7038 */ - "processFinallyScope\0" /* 7059 */ - "ourVariableTypeHolders\0" /* 7079 */ - "finallyVariableTypeHolders\0" /* 7102 */ - "originalVariableTypeHolders\0" /* 7129 */ - "processFinallyScopeVariableTypeHolders\0" /* 7157 */ - "closureScope\0" /* 7196 */ - "prevScope\0" /* 7209 */ - "byRefUses\0" /* 7219 */ - "processClosureScope\0" /* 7229 */ - "finalScope\0" /* 7249 */ - "processAlwaysIterableForeachScopeWithoutPollute\0" /* 7260 */ - "writableVariableNames\0" /* 7308 */ - "generalizeWith\0" /* 7330 */ - "generalizeWithVariableState\0" /* 7345 */ - "variableTypeHolders\0" /* 7373 */ - "otherVariableTypeHolders\0" /* 7393 */ - "generalizeVariableTypeHolders\0" /* 7418 */ - "flattenUnionForGeneralization\0" /* 7448 */ - "a\0" /* 7478 */ - "b\0" /* 7480 */ - "depth\0" /* 7482 */ - "generalizeType\0" /* 7488 */ - "getArrayDepth\0" /* 7503 */ - "equals\0" /* 7517 */ - "otherConditionalExpressions\0" /* 7524 */ - "compareConditionalExpressions\0" /* 7552 */ - "compareVariableTypeHolders\0" /* 7582 */ - "propertyReflection\0" /* 7609 */ - "PHPStan\\Reflection\\PropertyReflection\0" /* 7628 */ - "canAccessProperty\0" /* 7666 */ - "PHPStan\\Reflection\\ExtendedPropertyReflection\0" /* 7684 */ - "canReadProperty\0" /* 7730 */ - "canWriteProperty\0" /* 7746 */ - "methodReflection\0" /* 7763 */ - "PHPStan\\Reflection\\MethodReflection\0" /* 7780 */ - "canCallMethod\0" /* 7816 */ - "constantReflection\0" /* 7830 */ - "PHPStan\\Reflection\\ClassConstantReflection\0" /* 7849 */ - "canAccessConstant\0" /* 7892 */ - "classMemberReflection\0" /* 7910 */ - "PHPStan\\Reflection\\ClassMemberReflection\0" /* 7932 */ - "canAccessClassMember\0" /* 7973 */ - "debug\0" /* 7994 */ - "typeWithMethod\0" /* 8000 */ - "methodName\0" /* 8015 */ - "filterTypeWithMethod\0" /* 8026 */ - "PHPStan\\Reflection\\ExtendedMethodReflection\0" /* 8047 */ - "getMethodReflection\0" /* 8091 */ - "getNakedMethod\0" /* 8111 */ - "typeWithProperty\0" /* 8126 */ - "getPropertyReflection\0" /* 8143 */ - "getInstancePropertyReflection\0" /* 8165 */ - "getStaticPropertyReflection\0" /* 8195 */ - "typeWithConstant\0" /* 8223 */ - "constantName\0" /* 8240 */ - "getConstantReflection\0" /* 8253 */ - "constantType\0" /* 8275 */ - "getConstantExplicitTypeFromConfig\0" /* 8288 */ - "getConstantTypes\0" /* 8322 */ - "getGlobalConstantType\0" /* 8339 */ - "nameString\0" /* 8361 */ - "createGlobalConstantFetches\0" /* 8372 */ - "getNativeConstantTypes\0" /* 8400 */ - "getIterableKeyType\0" /* 8423 */ - "getIterableValueType\0" /* 8442 */ - "PHPStan\\Php\\PhpVersions\0" /* 8463 */ - "getPhpVersion\0" /* 8487 */ - "getConfiguredPhpVersionRange\0" /* 8501 */ - "isOverallPhpVersionRange\0" /* 8530 */ - "PhpParser\\Node\0" /* 8555 */ - "invokeNodeCallback\0" /* 8570 */ - "collectorType\0" /* 8589 */ - "data\0" /* 8603 */ - "emitCollectedData\0" /* 8608 */ - "extensionClass\0" /* 8626 */ - "trackValueDependency\0" /* 8641 */ - "file\0" /* 8662 */ - "trackFileDependency\0" /* 8667 */ - "directory\0" /* 8687 */ - "pattern\0" /* 8697 */ - "'*'\0" /* 8705 */ - "trackDirectoryDependency\0" /* 8709 */ - "trackClassDependency"; /* 8734 */ + "mergedExpressionTypes\0" /* 6611 */ + "findMergedCallCandidates\0" /* 6633 */ + "candidates\0" /* 6658 */ + "mergedNativeTypes\0" /* 6669 */ + "findUninformativeMergedCalls\0" /* 6687 */ + "ourExpressionTypes\0" /* 6716 */ + "theirExpressionTypes\0" /* 6735 */ + "withoutPreciseClassConstantFetches\0" /* 6756 */ + "currentConditionalExpressions\0" /* 6791 */ + "sourceConditionalExpressions\0" /* 6821 */ + "otherExpressionTypes\0" /* 6850 */ + "preserveVacuousConditionalExpressions\0" /* 6871 */ + "ourConditionalExpressions\0" /* 6909 */ + "theirConditionalExpressions\0" /* 6935 */ + "mergeSameGuardConditionalExpressions\0" /* 6963 */ + "newConditionalExpressions\0" /* 7000 */ + "existingConditionalExpressions\0" /* 7026 */ + "mergeConditionalExpressions\0" /* 7057 */ + "calledMethodScope\0" /* 7085 */ + "mergeInitializedProperties\0" /* 7103 */ + "finallyScope\0" /* 7130 */ + "originalFinallyScope\0" /* 7143 */ + "processFinallyScope\0" /* 7164 */ + "ourVariableTypeHolders\0" /* 7184 */ + "finallyVariableTypeHolders\0" /* 7207 */ + "originalVariableTypeHolders\0" /* 7234 */ + "processFinallyScopeVariableTypeHolders\0" /* 7262 */ + "closureScope\0" /* 7301 */ + "prevScope\0" /* 7314 */ + "byRefUses\0" /* 7324 */ + "processClosureScope\0" /* 7334 */ + "finalScope\0" /* 7354 */ + "processAlwaysIterableForeachScopeWithoutPollute\0" /* 7365 */ + "writableVariableNames\0" /* 7413 */ + "generalizeWith\0" /* 7435 */ + "generalizeWithVariableState\0" /* 7450 */ + "variableTypeHolders\0" /* 7478 */ + "otherVariableTypeHolders\0" /* 7498 */ + "generalizeVariableTypeHolders\0" /* 7523 */ + "flattenUnionForGeneralization\0" /* 7553 */ + "a\0" /* 7583 */ + "b\0" /* 7585 */ + "depth\0" /* 7587 */ + "generalizeType\0" /* 7593 */ + "getArrayDepth\0" /* 7608 */ + "equals\0" /* 7622 */ + "otherConditionalExpressions\0" /* 7629 */ + "compareConditionalExpressions\0" /* 7657 */ + "compareVariableTypeHolders\0" /* 7687 */ + "propertyReflection\0" /* 7714 */ + "PHPStan\\Reflection\\PropertyReflection\0" /* 7733 */ + "canAccessProperty\0" /* 7771 */ + "PHPStan\\Reflection\\ExtendedPropertyReflection\0" /* 7789 */ + "canReadProperty\0" /* 7835 */ + "canWriteProperty\0" /* 7851 */ + "methodReflection\0" /* 7868 */ + "PHPStan\\Reflection\\MethodReflection\0" /* 7885 */ + "canCallMethod\0" /* 7921 */ + "constantReflection\0" /* 7935 */ + "PHPStan\\Reflection\\ClassConstantReflection\0" /* 7954 */ + "canAccessConstant\0" /* 7997 */ + "classMemberReflection\0" /* 8015 */ + "PHPStan\\Reflection\\ClassMemberReflection\0" /* 8037 */ + "canAccessClassMember\0" /* 8078 */ + "debug\0" /* 8099 */ + "typeWithMethod\0" /* 8105 */ + "methodName\0" /* 8120 */ + "filterTypeWithMethod\0" /* 8131 */ + "PHPStan\\Reflection\\ExtendedMethodReflection\0" /* 8152 */ + "getMethodReflection\0" /* 8196 */ + "getNakedMethod\0" /* 8216 */ + "typeWithProperty\0" /* 8231 */ + "getPropertyReflection\0" /* 8248 */ + "getInstancePropertyReflection\0" /* 8270 */ + "getStaticPropertyReflection\0" /* 8300 */ + "typeWithConstant\0" /* 8328 */ + "constantName\0" /* 8345 */ + "getConstantReflection\0" /* 8358 */ + "constantType\0" /* 8380 */ + "getConstantExplicitTypeFromConfig\0" /* 8393 */ + "getConstantTypes\0" /* 8427 */ + "getGlobalConstantType\0" /* 8444 */ + "nameString\0" /* 8466 */ + "createGlobalConstantFetches\0" /* 8477 */ + "getNativeConstantTypes\0" /* 8505 */ + "getIterableKeyType\0" /* 8528 */ + "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 */ inline constexpr reg::PackedArg args[] = { reg::packed(0, 0, 9), /* __construct $services */ reg::packed(40, 0, 53), /* __construct $scopeFactory */ @@ -899,104 +904,110 @@ inline constexpr reg::PackedArg args[] = { reg::packed(6479, MAY_BE_NULL, 523), /* mergeWithVariableState $otherScope */ reg::packed(6490, MAY_BE_BOOL, reg::NoString, false, false, 148), /* mergeWithVariableState $preserveVacuousConditionals */ reg::packed(738, 0, 523), /* mergeWithVariableState return */ + reg::packed(6587, MAY_BE_ARRAY), /* findMergedCallCandidates $differingExpressionKeys */ + reg::packed(6611, MAY_BE_ARRAY), /* findMergedCallCandidates $mergedExpressionTypes */ + reg::packed(738, MAY_BE_ARRAY), /* findMergedCallCandidates return */ + reg::packed(6658, MAY_BE_ARRAY), /* findUninformativeMergedCalls $candidates */ + reg::packed(6669, MAY_BE_ARRAY), /* findUninformativeMergedCalls $mergedNativeTypes */ + reg::packed(738, MAY_BE_ARRAY), /* findUninformativeMergedCalls return */ reg::packed(6587, MAY_BE_ARRAY), /* withoutPreciseClassConstantFetches $differingExpressionKeys */ - reg::packed(6611, MAY_BE_ARRAY), /* withoutPreciseClassConstantFetches $ourExpressionTypes */ - reg::packed(6630, MAY_BE_ARRAY), /* withoutPreciseClassConstantFetches $theirExpressionTypes */ + reg::packed(6716, MAY_BE_ARRAY), /* withoutPreciseClassConstantFetches $ourExpressionTypes */ + reg::packed(6735, MAY_BE_ARRAY), /* withoutPreciseClassConstantFetches $theirExpressionTypes */ reg::packed(738, MAY_BE_ARRAY), /* withoutPreciseClassConstantFetches return */ - reg::packed(6686, MAY_BE_ARRAY), /* preserveVacuousConditionalExpressions $currentConditionalExpressions */ - reg::packed(6716, MAY_BE_ARRAY), /* preserveVacuousConditionalExpressions $sourceConditionalExpressions */ - reg::packed(6745, MAY_BE_ARRAY), /* preserveVacuousConditionalExpressions $otherExpressionTypes */ + reg::packed(6791, MAY_BE_ARRAY), /* preserveVacuousConditionalExpressions $currentConditionalExpressions */ + reg::packed(6821, MAY_BE_ARRAY), /* preserveVacuousConditionalExpressions $sourceConditionalExpressions */ + reg::packed(6850, MAY_BE_ARRAY), /* preserveVacuousConditionalExpressions $otherExpressionTypes */ reg::packed(738, MAY_BE_ARRAY), /* preserveVacuousConditionalExpressions return */ - reg::packed(6686, MAY_BE_ARRAY), /* mergeSameGuardConditionalExpressions $currentConditionalExpressions */ - reg::packed(6804, MAY_BE_ARRAY), /* mergeSameGuardConditionalExpressions $ourConditionalExpressions */ - reg::packed(6830, MAY_BE_ARRAY), /* mergeSameGuardConditionalExpressions $theirConditionalExpressions */ + reg::packed(6791, MAY_BE_ARRAY), /* mergeSameGuardConditionalExpressions $currentConditionalExpressions */ + reg::packed(6909, MAY_BE_ARRAY), /* mergeSameGuardConditionalExpressions $ourConditionalExpressions */ + reg::packed(6935, MAY_BE_ARRAY), /* mergeSameGuardConditionalExpressions $theirConditionalExpressions */ reg::packed(738, MAY_BE_ARRAY), /* mergeSameGuardConditionalExpressions return */ - reg::packed(6895, MAY_BE_ARRAY), /* mergeConditionalExpressions $newConditionalExpressions */ - reg::packed(6921, MAY_BE_ARRAY), /* mergeConditionalExpressions $existingConditionalExpressions */ + reg::packed(7000, MAY_BE_ARRAY), /* mergeConditionalExpressions $newConditionalExpressions */ + reg::packed(7026, MAY_BE_ARRAY), /* mergeConditionalExpressions $existingConditionalExpressions */ reg::packed(738, MAY_BE_ARRAY), /* mergeConditionalExpressions return */ - reg::packed(6980, 0, 523), /* mergeInitializedProperties $calledMethodScope */ + reg::packed(7085, 0, 523), /* mergeInitializedProperties $calledMethodScope */ reg::packed(738, 0, 523), /* mergeInitializedProperties return */ - reg::packed(7025, 0, 523), /* processFinallyScope $finallyScope */ - reg::packed(7038, 0, 523), /* processFinallyScope $originalFinallyScope */ + reg::packed(7130, 0, 523), /* processFinallyScope $finallyScope */ + reg::packed(7143, 0, 523), /* processFinallyScope $originalFinallyScope */ reg::packed(738, 0, 523), /* processFinallyScope return */ - reg::packed(7079, MAY_BE_ARRAY), /* processFinallyScopeVariableTypeHolders $ourVariableTypeHolders */ - reg::packed(7102, MAY_BE_ARRAY), /* processFinallyScopeVariableTypeHolders $finallyVariableTypeHolders */ - reg::packed(7129, MAY_BE_ARRAY), /* processFinallyScopeVariableTypeHolders $originalVariableTypeHolders */ + reg::packed(7184, MAY_BE_ARRAY), /* processFinallyScopeVariableTypeHolders $ourVariableTypeHolders */ + reg::packed(7207, MAY_BE_ARRAY), /* processFinallyScopeVariableTypeHolders $finallyVariableTypeHolders */ + reg::packed(7234, MAY_BE_ARRAY), /* processFinallyScopeVariableTypeHolders $originalVariableTypeHolders */ reg::packed(738, MAY_BE_ARRAY), /* processFinallyScopeVariableTypeHolders return */ - reg::packed(7196, 0, 523), /* processClosureScope $closureScope */ - reg::packed(7209, MAY_BE_NULL, 523), /* processClosureScope $prevScope */ - reg::packed(7219, MAY_BE_ARRAY), /* processClosureScope $byRefUses */ + reg::packed(7301, 0, 523), /* processClosureScope $closureScope */ + reg::packed(7314, MAY_BE_NULL, 523), /* processClosureScope $prevScope */ + reg::packed(7324, MAY_BE_ARRAY), /* processClosureScope $byRefUses */ reg::packed(738, 0, 523), /* processClosureScope return */ - reg::packed(7249, 0, 523), /* processAlwaysIterableForeachScopeWithoutPollute $finalScope */ + reg::packed(7354, 0, 523), /* processAlwaysIterableForeachScopeWithoutPollute $finalScope */ reg::packed(738, 0, 523), /* processAlwaysIterableForeachScopeWithoutPollute return */ reg::packed(6479, 0, 523), /* generalizeWith $otherScope */ - reg::packed(7308, MAY_BE_NULL | MAY_BE_ARRAY, reg::NoString, false, false, 222), /* generalizeWith $writableVariableNames */ + reg::packed(7413, MAY_BE_NULL | MAY_BE_ARRAY, reg::NoString, false, false, 222), /* generalizeWith $writableVariableNames */ reg::packed(738, 0, 523), /* generalizeWith return */ reg::packed(6479, 0, 523), /* generalizeWithVariableState $otherScope */ - reg::packed(7308, MAY_BE_NULL | MAY_BE_ARRAY), /* generalizeWithVariableState $writableVariableNames */ + reg::packed(7413, MAY_BE_NULL | MAY_BE_ARRAY), /* generalizeWithVariableState $writableVariableNames */ reg::packed(738, 0, 523), /* generalizeWithVariableState return */ - reg::packed(7373, MAY_BE_ARRAY), /* generalizeVariableTypeHolders $variableTypeHolders */ - reg::packed(7393, MAY_BE_ARRAY), /* generalizeVariableTypeHolders $otherVariableTypeHolders */ - reg::packed(7308, MAY_BE_NULL | MAY_BE_ARRAY), /* generalizeVariableTypeHolders $writableVariableNames */ + reg::packed(7478, MAY_BE_ARRAY), /* generalizeVariableTypeHolders $variableTypeHolders */ + reg::packed(7498, MAY_BE_ARRAY), /* generalizeVariableTypeHolders $otherVariableTypeHolders */ + reg::packed(7413, MAY_BE_NULL | MAY_BE_ARRAY), /* generalizeVariableTypeHolders $writableVariableNames */ reg::packed(738, MAY_BE_ARRAY), /* generalizeVariableTypeHolders return */ reg::packed(3874, 0, 1424), /* flattenUnionForGeneralization $type */ reg::packed(738, MAY_BE_ARRAY), /* flattenUnionForGeneralization return */ - reg::packed(7478, 0, 1424), /* generalizeType $a */ - reg::packed(7480, 0, 1424), /* generalizeType $b */ - reg::packed(7482, MAY_BE_LONG), /* generalizeType $depth */ + reg::packed(7583, 0, 1424), /* generalizeType $a */ + reg::packed(7585, 0, 1424), /* generalizeType $b */ + reg::packed(7587, MAY_BE_LONG), /* generalizeType $depth */ reg::packed(738, 0, 1424), /* generalizeType return */ reg::packed(3874, 0, 1424), /* getArrayDepth $type */ reg::packed(738, MAY_BE_LONG), /* getArrayDepth return */ reg::packed(6479, 0, 523), /* equals $otherScope */ reg::packed(738, MAY_BE_BOOL), /* equals return */ reg::packed(278, MAY_BE_ARRAY), /* compareConditionalExpressions $conditionalExpressions */ - reg::packed(7524, MAY_BE_ARRAY), /* compareConditionalExpressions $otherConditionalExpressions */ + reg::packed(7629, MAY_BE_ARRAY), /* compareConditionalExpressions $otherConditionalExpressions */ reg::packed(738, MAY_BE_BOOL), /* compareConditionalExpressions return */ - reg::packed(7373, MAY_BE_ARRAY), /* compareVariableTypeHolders $variableTypeHolders */ - reg::packed(7393, MAY_BE_ARRAY), /* compareVariableTypeHolders $otherVariableTypeHolders */ + reg::packed(7478, MAY_BE_ARRAY), /* compareVariableTypeHolders $variableTypeHolders */ + reg::packed(7498, MAY_BE_ARRAY), /* compareVariableTypeHolders $otherVariableTypeHolders */ reg::packed(738, MAY_BE_BOOL), /* compareVariableTypeHolders return */ - reg::packed(7609, 0, 7628), /* canAccessProperty $propertyReflection */ + reg::packed(7714, 0, 7733), /* canAccessProperty $propertyReflection */ reg::packed(738, MAY_BE_BOOL), /* canAccessProperty return */ - reg::packed(7609, 0, 7684), /* canReadProperty $propertyReflection */ + reg::packed(7714, 0, 7789), /* canReadProperty $propertyReflection */ reg::packed(738, MAY_BE_BOOL), /* canReadProperty return */ - reg::packed(7609, 0, 7684), /* canWriteProperty $propertyReflection */ + reg::packed(7714, 0, 7789), /* canWriteProperty $propertyReflection */ reg::packed(738, MAY_BE_BOOL), /* canWriteProperty return */ - reg::packed(7763, 0, 7780), /* canCallMethod $methodReflection */ + reg::packed(7868, 0, 7885), /* canCallMethod $methodReflection */ reg::packed(738, MAY_BE_BOOL), /* canCallMethod return */ - reg::packed(7830, 0, 7849), /* canAccessConstant $constantReflection */ + reg::packed(7935, 0, 7954), /* canAccessConstant $constantReflection */ reg::packed(738, MAY_BE_BOOL), /* canAccessConstant return */ - reg::packed(7910, 0, 7932), /* canAccessClassMember $classMemberReflection */ + reg::packed(8015, 0, 8037), /* canAccessClassMember $classMemberReflection */ reg::packed(738, MAY_BE_BOOL), /* canAccessClassMember return */ reg::packed(738, MAY_BE_ARRAY), /* debug return */ - reg::packed(8000, 0, 1424), /* filterTypeWithMethod $typeWithMethod */ - reg::packed(8015, MAY_BE_STRING), /* filterTypeWithMethod $methodName */ + reg::packed(8105, 0, 1424), /* filterTypeWithMethod $typeWithMethod */ + reg::packed(8120, MAY_BE_STRING), /* filterTypeWithMethod $methodName */ reg::packed(738, MAY_BE_NULL, 1424), /* filterTypeWithMethod return */ - reg::packed(8000, 0, 1424), /* getMethodReflection $typeWithMethod */ - reg::packed(8015, MAY_BE_STRING), /* getMethodReflection $methodName */ - reg::packed(738, MAY_BE_NULL, 8047), /* getMethodReflection return */ - reg::packed(8000, 0, 1424), /* getNakedMethod $typeWithMethod */ - reg::packed(8015, MAY_BE_STRING), /* getNakedMethod $methodName */ - reg::packed(738, MAY_BE_NULL, 8047), /* getNakedMethod return */ - reg::packed(8126, 0, 1424), /* getPropertyReflection $typeWithProperty */ + reg::packed(8105, 0, 1424), /* getMethodReflection $typeWithMethod */ + reg::packed(8120, MAY_BE_STRING), /* getMethodReflection $methodName */ + reg::packed(738, MAY_BE_NULL, 8152), /* getMethodReflection return */ + reg::packed(8105, 0, 1424), /* getNakedMethod $typeWithMethod */ + reg::packed(8120, MAY_BE_STRING), /* getNakedMethod $methodName */ + reg::packed(738, MAY_BE_NULL, 8152), /* getNakedMethod return */ + reg::packed(8231, 0, 1424), /* getPropertyReflection $typeWithProperty */ reg::packed(3728, MAY_BE_STRING), /* getPropertyReflection $propertyName */ - reg::packed(738, MAY_BE_NULL, 7684), /* getPropertyReflection return */ - reg::packed(8126, 0, 1424), /* getInstancePropertyReflection $typeWithProperty */ + reg::packed(738, MAY_BE_NULL, 7789), /* getPropertyReflection return */ + reg::packed(8231, 0, 1424), /* getInstancePropertyReflection $typeWithProperty */ reg::packed(3728, MAY_BE_STRING), /* getInstancePropertyReflection $propertyName */ - reg::packed(738, MAY_BE_NULL, 7684), /* getInstancePropertyReflection return */ - reg::packed(8126, 0, 1424), /* getStaticPropertyReflection $typeWithProperty */ + reg::packed(738, MAY_BE_NULL, 7789), /* getInstancePropertyReflection return */ + reg::packed(8231, 0, 1424), /* getStaticPropertyReflection $typeWithProperty */ reg::packed(3728, MAY_BE_STRING), /* getStaticPropertyReflection $propertyName */ - reg::packed(738, MAY_BE_NULL, 7684), /* getStaticPropertyReflection return */ - reg::packed(8223, 0, 1424), /* getConstantReflection $typeWithConstant */ - reg::packed(8240, MAY_BE_STRING), /* getConstantReflection $constantName */ - reg::packed(738, MAY_BE_NULL, 7849), /* getConstantReflection return */ - reg::packed(8240, MAY_BE_STRING), /* getConstantExplicitTypeFromConfig $constantName */ - reg::packed(8275, 0, 1424), /* getConstantExplicitTypeFromConfig $constantType */ + reg::packed(738, MAY_BE_NULL, 7789), /* getStaticPropertyReflection return */ + reg::packed(8328, 0, 1424), /* getConstantReflection $typeWithConstant */ + reg::packed(8345, MAY_BE_STRING), /* getConstantReflection $constantName */ + reg::packed(738, MAY_BE_NULL, 7954), /* getConstantReflection return */ + reg::packed(8345, MAY_BE_STRING), /* getConstantExplicitTypeFromConfig $constantName */ + reg::packed(8380, 0, 1424), /* getConstantExplicitTypeFromConfig $constantType */ reg::packed(738, 0, 1424), /* getConstantExplicitTypeFromConfig return */ reg::packed(738, MAY_BE_ARRAY), /* getConstantTypes return */ reg::packed(1520, 0, 1525), /* getGlobalConstantType $name */ reg::packed(738, MAY_BE_NULL, 1424), /* getGlobalConstantType return */ reg::packed(1520, 0, 1525), /* createGlobalConstantFetches $name */ - reg::packed(8361, MAY_BE_STRING), /* createGlobalConstantFetches $nameString */ + reg::packed(8466, MAY_BE_STRING), /* createGlobalConstantFetches $nameString */ reg::packed(227, MAY_BE_NULL | MAY_BE_STRING), /* createGlobalConstantFetches $namespace */ reg::packed(738, MAY_BE_ARRAY), /* createGlobalConstantFetches return */ reg::packed(738, MAY_BE_ARRAY), /* getNativeConstantTypes return */ @@ -1004,22 +1015,22 @@ inline constexpr reg::PackedArg args[] = { reg::packed(738, 0, 1424), /* getIterableKeyType return */ reg::packed(5138, 0, 1424), /* getIterableValueType $iteratee */ reg::packed(738, 0, 1424), /* getIterableValueType return */ - reg::packed(738, 0, 8463), /* getPhpVersion return */ + reg::packed(738, 0, 8568), /* getPhpVersion 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, 8555), /* invokeNodeCallback $node */ + reg::packed(1707, 0, 8660), /* invokeNodeCallback $node */ reg::packed(738, MAY_BE_VOID), /* invokeNodeCallback return */ - reg::packed(8589, MAY_BE_STRING), /* emitCollectedData $collectorType */ - reg::packed(8603, MAY_BE_ANY), /* emitCollectedData $data */ + reg::packed(8694, MAY_BE_STRING), /* emitCollectedData $collectorType */ + reg::packed(8708, MAY_BE_ANY), /* emitCollectedData $data */ reg::packed(738, MAY_BE_VOID), /* emitCollectedData return */ - reg::packed(8626, MAY_BE_STRING), /* trackValueDependency $extensionClass */ + reg::packed(8731, MAY_BE_STRING), /* trackValueDependency $extensionClass */ reg::packed(2540, MAY_BE_STRING), /* trackValueDependency $key */ reg::packed(738, MAY_BE_VOID), /* trackValueDependency return */ - reg::packed(8662, MAY_BE_STRING), /* trackFileDependency $file */ + reg::packed(8767, MAY_BE_STRING), /* trackFileDependency $file */ reg::packed(738, MAY_BE_VOID), /* trackFileDependency return */ - reg::packed(8687, MAY_BE_STRING), /* trackDirectoryDependency $directory */ - reg::packed(8697, MAY_BE_STRING, reg::NoString, false, false, 8705), /* trackDirectoryDependency $pattern */ + reg::packed(8792, MAY_BE_STRING), /* trackDirectoryDependency $directory */ + reg::packed(8802, MAY_BE_STRING, reg::NoString, false, false, 8810), /* 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 */ @@ -1180,54 +1191,56 @@ inline constexpr sigtab::Sig isInFirstLevelStatement = { { 6455 /* isInFirstLeve inline constexpr sigtab::Sig mergeWith = { { 6518 /* mergeWith */, 1, 396, 2, 398, ZEND_ACC_PUBLIC } }; inline constexpr sigtab::Sig collectAbsorbedClosures = { { 6540 /* collectAbsorbedClosures */, 2, 399, 2, 401, ZEND_ACC_PRIVATE } }; inline constexpr sigtab::Sig mergeWithVariableState = { { 6564 /* mergeWithVariableState */, 1, 402, 2, 404, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig withoutPreciseClassConstantFetches = { { 6651 /* withoutPreciseClassConstantFetches */, 3, 405, 3, 408, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig preserveVacuousConditionalExpressions = { { 6766 /* preserveVacuousConditionalExpressions */, 3, 409, 3, 412, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig mergeSameGuardConditionalExpressions = { { 6858 /* mergeSameGuardConditionalExpressions */, 3, 413, 3, 416, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig mergeConditionalExpressions = { { 6952 /* mergeConditionalExpressions */, 2, 417, 2, 419, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig mergeInitializedProperties = { { 6998 /* mergeInitializedProperties */, 1, 420, 1, 421, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processFinallyScope = { { 7059 /* processFinallyScope */, 2, 422, 2, 424, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processFinallyScopeVariableTypeHolders = { { 7157 /* processFinallyScopeVariableTypeHolders */, 3, 425, 3, 428, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig processClosureScope = { { 7229 /* processClosureScope */, 3, 429, 3, 432, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig processAlwaysIterableForeachScopeWithoutPollute = { { 7260 /* processAlwaysIterableForeachScopeWithoutPollute */, 1, 433, 1, 434, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig generalizeWith = { { 7330 /* generalizeWith */, 1, 435, 2, 437, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig generalizeWithVariableState = { { 7345 /* generalizeWithVariableState */, 2, 438, 2, 440, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig generalizeVariableTypeHolders = { { 7418 /* generalizeVariableTypeHolders */, 3, 441, 3, 444, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig flattenUnionForGeneralization = { { 7448 /* flattenUnionForGeneralization */, 1, 445, 1, 446, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig generalizeType = { { 7488 /* generalizeType */, 3, 447, 3, 450, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getArrayDepth = { { 7503 /* getArrayDepth */, 1, 451, 1, 452, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig equals = { { 7517 /* equals */, 1, 453, 1, 454, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig compareConditionalExpressions = { { 7552 /* compareConditionalExpressions */, 2, 455, 2, 457, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig compareVariableTypeHolders = { { 7582 /* compareVariableTypeHolders */, 2, 458, 2, 460, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig canAccessProperty = { { 7666 /* canAccessProperty */, 1, 461, 1, 462, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig canReadProperty = { { 7730 /* canReadProperty */, 1, 463, 1, 464, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig canWriteProperty = { { 7746 /* canWriteProperty */, 1, 465, 1, 466, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig canCallMethod = { { 7816 /* canCallMethod */, 1, 467, 1, 468, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig canAccessConstant = { { 7892 /* canAccessConstant */, 1, 469, 1, 470, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig canAccessClassMember = { { 7973 /* canAccessClassMember */, 1, 471, 1, 472, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig debug = { { 7994 /* debug */, 0, 473, 0, 473, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig filterTypeWithMethod = { { 8026 /* filterTypeWithMethod */, 2, 474, 2, 476, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getMethodReflection = { { 8091 /* getMethodReflection */, 2, 477, 2, 479, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getNakedMethod = { { 8111 /* getNakedMethod */, 2, 480, 2, 482, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getPropertyReflection = { { 8143 /* getPropertyReflection */, 2, 483, 2, 485, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getInstancePropertyReflection = { { 8165 /* getInstancePropertyReflection */, 2, 486, 2, 488, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getStaticPropertyReflection = { { 8195 /* getStaticPropertyReflection */, 2, 489, 2, 491, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getConstantReflection = { { 8253 /* getConstantReflection */, 2, 492, 2, 494, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getConstantExplicitTypeFromConfig = { { 8288 /* getConstantExplicitTypeFromConfig */, 2, 495, 2, 497, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getConstantTypes = { { 8322 /* getConstantTypes */, 0, 498, 0, 498, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getGlobalConstantType = { { 8339 /* getGlobalConstantType */, 1, 499, 1, 500, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig createGlobalConstantFetches = { { 8372 /* createGlobalConstantFetches */, 3, 501, 3, 504, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; -inline constexpr sigtab::Sig getNativeConstantTypes = { { 8400 /* getNativeConstantTypes */, 0, 505, 0, 505, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig getIterableKeyType = { { 8423 /* getIterableKeyType */, 1, 506, 1, 507, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getIterableValueType = { { 8442 /* getIterableValueType */, 1, 508, 1, 509, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getPhpVersion = { { 8487 /* getPhpVersion */, 0, 510, 0, 510, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig getConfiguredPhpVersionRange = { { 8501 /* getConfiguredPhpVersionRange */, 0, 511, 0, 511, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig isOverallPhpVersionRange = { { 8530 /* isOverallPhpVersionRange */, 1, 512, 1, 513, ZEND_ACC_PRIVATE } }; -inline constexpr sigtab::Sig invokeNodeCallback = { { 8570 /* invokeNodeCallback */, 1, 514, 1, 515, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig emitCollectedData = { { 8608 /* emitCollectedData */, 2, 516, 2, 518, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig trackValueDependency = { { 8641 /* trackValueDependency */, 2, 519, 2, 521, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig trackFileDependency = { { 8667 /* trackFileDependency */, 1, 522, 1, 523, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig trackDirectoryDependency = { { 8709 /* trackDirectoryDependency */, 1, 524, 2, 526, ZEND_ACC_PUBLIC } }; -inline constexpr sigtab::Sig trackClassDependency = { { 8734 /* trackClassDependency */, 1, 527, 1, 528, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig findMergedCallCandidates = { { 6633 /* findMergedCallCandidates */, 2, 405, 2, 407, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig findUninformativeMergedCalls = { { 6687 /* findUninformativeMergedCalls */, 2, 408, 2, 410, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig withoutPreciseClassConstantFetches = { { 6756 /* withoutPreciseClassConstantFetches */, 3, 411, 3, 414, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig preserveVacuousConditionalExpressions = { { 6871 /* preserveVacuousConditionalExpressions */, 3, 415, 3, 418, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mergeSameGuardConditionalExpressions = { { 6963 /* mergeSameGuardConditionalExpressions */, 3, 419, 3, 422, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mergeConditionalExpressions = { { 7057 /* mergeConditionalExpressions */, 2, 423, 2, 425, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig mergeInitializedProperties = { { 7103 /* mergeInitializedProperties */, 1, 426, 1, 427, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processFinallyScope = { { 7164 /* processFinallyScope */, 2, 428, 2, 430, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processFinallyScopeVariableTypeHolders = { { 7262 /* processFinallyScopeVariableTypeHolders */, 3, 431, 3, 434, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig processClosureScope = { { 7334 /* processClosureScope */, 3, 435, 3, 438, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig processAlwaysIterableForeachScopeWithoutPollute = { { 7365 /* processAlwaysIterableForeachScopeWithoutPollute */, 1, 439, 1, 440, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig generalizeWith = { { 7435 /* generalizeWith */, 1, 441, 2, 443, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig generalizeWithVariableState = { { 7450 /* generalizeWithVariableState */, 2, 444, 2, 446, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig generalizeVariableTypeHolders = { { 7523 /* generalizeVariableTypeHolders */, 3, 447, 3, 450, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig flattenUnionForGeneralization = { { 7553 /* flattenUnionForGeneralization */, 1, 451, 1, 452, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig generalizeType = { { 7593 /* generalizeType */, 3, 453, 3, 456, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getArrayDepth = { { 7608 /* getArrayDepth */, 1, 457, 1, 458, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig equals = { { 7622 /* equals */, 1, 459, 1, 460, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig compareConditionalExpressions = { { 7657 /* compareConditionalExpressions */, 2, 461, 2, 463, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig compareVariableTypeHolders = { { 7687 /* compareVariableTypeHolders */, 2, 464, 2, 466, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig canAccessProperty = { { 7771 /* canAccessProperty */, 1, 467, 1, 468, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig canReadProperty = { { 7835 /* canReadProperty */, 1, 469, 1, 470, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig canWriteProperty = { { 7851 /* canWriteProperty */, 1, 471, 1, 472, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig canCallMethod = { { 7921 /* canCallMethod */, 1, 473, 1, 474, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig canAccessConstant = { { 7997 /* canAccessConstant */, 1, 475, 1, 476, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig canAccessClassMember = { { 8078 /* canAccessClassMember */, 1, 477, 1, 478, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig debug = { { 8099 /* debug */, 0, 479, 0, 479, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig filterTypeWithMethod = { { 8131 /* filterTypeWithMethod */, 2, 480, 2, 482, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getMethodReflection = { { 8196 /* getMethodReflection */, 2, 483, 2, 485, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getNakedMethod = { { 8216 /* getNakedMethod */, 2, 486, 2, 488, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getPropertyReflection = { { 8248 /* getPropertyReflection */, 2, 489, 2, 491, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getInstancePropertyReflection = { { 8270 /* getInstancePropertyReflection */, 2, 492, 2, 494, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getStaticPropertyReflection = { { 8300 /* getStaticPropertyReflection */, 2, 495, 2, 497, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConstantReflection = { { 8358 /* getConstantReflection */, 2, 498, 2, 500, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConstantExplicitTypeFromConfig = { { 8393 /* getConstantExplicitTypeFromConfig */, 2, 501, 2, 503, ZEND_ACC_PUBLIC } }; +inline constexpr sigtab::Sig getConstantTypes = { { 8427 /* getConstantTypes */, 0, 504, 0, 504, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig getGlobalConstantType = { { 8444 /* getGlobalConstantType */, 1, 505, 1, 506, ZEND_ACC_PRIVATE } }; +inline constexpr sigtab::Sig createGlobalConstantFetches = { { 8477 /* createGlobalConstantFetches */, 3, 507, 3, 510, ZEND_ACC_PRIVATE | ZEND_ACC_STATIC } }; +inline constexpr sigtab::Sig getNativeConstantTypes = { { 8505 /* getNativeConstantTypes */, 0, 511, 0, 511, ZEND_ACC_PRIVATE } }; +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 } }; } // namespace sig } // namespace ptdecl::MutatingScope